李哥深度学习回归代码
一、引进包
import matplotlib.pyplot as plt
import torch
import numpy as np
import csv
import pandas as pd
from torch.utils.data import DataLoader, Dataset
import torch.nn as nn
from torch import optim
import time
from sklearn.feature_selection import SelectKBest, chi2
import os
import matplotlib
matplotlib.use('TkAgg')
os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE"
由于pytorch版本问题,要修改代码为
model = torch.load(sava_path,weights_only=False).to(device)
二、代码部分
数据类
class CovidDataset(Dataset):
def __init__(self, file_path, mode="train", all_feature=False, feature_dim=6):
with open(file_path, "r") as f: #文件打开函数,以读的形式并把打开的文件对象赋值给f
ori_data = list(csv.reader(f)) #逐步读取csv文件并用逗号分开,最后转为列表赋给ori_data
column = ori_data[0] #读取csv文件第一行
csv_data = np.array(ori_data[1:])[:, 1:].astype(float) #将csv文件从第二行第二列开始转为数组并从字符串转为float
feature = np.array(ori_data[1:])[:, 1:-1] #从第二行开始,第二列到倒数第二列,除去倒数第一列
label_data = np.array(ori_data[1:])[:, -1] #从第二行开始,列为倒数第一列
if all_feature:
col = np.array([i for i in range(0, 93)])
else:
_, col = get_feature_importance(feature, label_data, feature_dim, column)
col = col.tolist()
if mode == "train": #逢5取1.
indices = [i for i in range(len(csv_data)) if i % 5 != 0]
data = torch.tensor(csv_data[indices, :-1]) #不包括最后一列 第一行,0->-2,第二行,0->-2......
self.y = torch.tensor(csv_data[indices, -1]) #csv_data[1、2、3、4、6][-1]
elif mode == "val":
indices = [i for i in range(len(csv_data)) if i % 5 == 0]
data = torch.tensor(csv_data[indices, :-1])
self.y = torch.tensor(csv_data[indices, -1])
else:
indices = [i for i in range(len(csv_data))]
data = torch.tensor(csv_data[indices])
data = data[:, col]
self.data = (data- data.mean(dim=0, keepdim=True))/data.std(dim=0, keepdim=True)
# data为100*2 data.mean(dim=0, keepdim=True)为shape[1,2] 且值为列的均值 相减时,利用广播机制1*2->100*2值不变
#如果不加keepdim 为shape[2]
#a = torch.tensor([1.0, 2.0]) # shape [2]
#b = torch.tensor([[1.0, 2.0]]) # shape [1, 2]
#例如检测新冠病毒 (年龄,每月检测次数)有样本 5 100000 和 10 100500相差为5 和 500 如果直接接入神经网络,会被认为次数比年龄更重要,所以归一,使量级相差不大
self.mode = mode #保存为实例属性,对__getitem__很重要
def __getitem__(self, idx):
if self.mode != "test":
return self.data[idx].float(), self.y[idx].float()
else:
return self.data[idx].float()
def __len__(self):
return len(self.data)
CovidDataset用来获取X和Y,用以训练、测试、验证
初始化函数init,file_path为数据文件路径(covid.train.csv),mode为模式(训练/测试/验证),并且默认值为训练,all_feature用来表达是否使用全部原始数据,feature_dim是使用重要数据的维度(“列数”,既几列重要数据),默认为6.
csv_data为数据文件中除去第一行和第一类的有效数据(第一行为数据的分类,第一类为每行数据的序号)
feature和label_data用于验证,feature是用于预测的数据,label_data是用于特征选择,选出哪些列重要。
col为数据的列号,用于模型,可以通过all_feature选择是否全部数据还是部分重要数据。_是由于get_feature_importance会返回X_new, indices[0:k],但col只要indices,不要X_new,所以利用_来接受第一个返回值。
tolist将Numpy数组转为列表
训练时,取除了下标为5的倍数的列作为训练集,indices为其下标的列表,data为下标对应数据的张量。self.y设置实例属性y,用作与预测值比较
验证时,取下标为5的倍数的列数据为测试集,区分于训练集。
测试时,不与真实值比较,而是得到预测值作为结果,可以得到模型的好坏
self.data为输入的张量,(data- data.mean(dim=0, keepdim=True))/data.std(dim=0, keepdim=True)是为了数据归一化,使不同列的数据处于一个量级,防止量级低的数据被认为不重要,量级高的认为重要(有些计算重要性的方法依赖于数值的大小,基于梯度或距离模型可能会忽略数值变化小的,因为变化对损失函数的影响微乎其微),dim=0,沿0维压缩,按列求和,keepdim=True,保留第0维。
self.mode = mode 保存为实例属性mode
__getitem__训练、测试用来返回输入值和预测值,验证值返回输入值
模型类
class MyModel(nn.Module):
def __init__(self, inDim): #定义模型结构,创建三个实例属性,初始化w和b但没有任何数据
super(MyModel, self).__init__()
self.fc1 = nn.Linear(inDim, 64)
self.relu1 = nn.ReLU()
self.fc2 = nn.Linear(64, 1)
def forward(self, x): #模型前向过程
x = self.fc1(x)
x = self.relu1(x)
x = self.fc2(x)
if len(x.size()) > 1:
return x.squeeze(1) #删除第一维将[N,1]->[N]如tensor{[1],[2]....}->tensor{[1,2......]}
return x
MyModel继承nn.Module,用来得到预测值。
__init__初始化函数,inDim是输入数据的维度,super()用来调用父类构造方法的写法,以完成父类的初始化。self.fc1、self.relu1、self.fc2是创建三个实例属性,初始化w和b但没有任何数据,self.fc1 =Linear(inDim,64)将维度inDim线性变换为64维,只是定义,self.relu1=nn.ReLU()是用来定义ReLU激活函数模块,
forward函数,是通过实际输入的数据,两次线性变换和一次激活函数得到预测值。预测值应该是一维数据,所以用x.queeze去除多余维度,将[N,1]->[N]如tensor{[1],[2]....}->tensor{[1,2......]}
训练+验证
def train_val(model, train_loader, val_loader, device, epochs, optimizer, loss, save_path):
#MyModel实例化模型,train_loader是训练数据加载器,val_loader是测试数据加载器,device设备,epochs轮数,optimizer优化器,loss损失,save_path最佳模型保存位置
model = model.to(device) #将模型移动到指定设备
plt_train_loss = [] #记录所有轮次的loss
plt_val_loss = []
min_val_loss = 9999999999999
for epoch in range(epochs): #冲锋的号角
train_loss = 0.0
val_loss = 0.0
start_time = time.time()
model.train() #模型调为训练模式
for batch_x, batch_y in train_loader:
x, target = batch_x.to(device), batch_y.to(device) #将输入特征和真实y移到设备,并用清晰的变量名接收
pred = model(x) #相当于model.forward(x)
train_bat_loss = loss(pred, target,model)
optimizer.zero_grad() # 梯度清零
train_bat_loss.backward() #反向传播
optimizer.step() #更新模型的作用,如w,b
train_loss += train_bat_loss.cpu().item() #累计总损失值
plt_train_loss.append(train_loss / train_loader.__len__()) #计算平均值,train_loader.__len__()是data的长度
model.eval()#将模型切换到测试模式
with torch.no_grad():#不进行梯度计算
for batch_x, batch_y in val_loader: #遍历得到输入数据和真实的y
x, target = batch_x.to(device), batch_y.to(device)#将输入特征和真实y移到设备,并用清晰的变量名接收
pred = model(x) #得到预测值
val_bat_loss = loss(pred, target,model) #得到损失
val_loss += val_bat_loss.cpu().item() #统计预测值
plt_val_loss.append(val_loss/ val_loader.__len__()) #得到平均预测值
if val_loss < min_val_loss: #保存最佳模型
torch.save(model, save_path)
min_val_loss = val_loss
print("[%03d/%03d] %2.2f sec(s) Trainloss: %.6f |Valloss: %.6f"% \
(epoch, epochs, time.time()-start_time, plt_train_loss[-1], plt_val_loss[-1]))
plt.plot(plt_train_loss) #绘制训练曲线
plt.plot(plt_val_loss) #绘制测试曲线
plt.title("loss")
plt.legend(["train", "val"])
plt.show()
model = model.to(device) #将模型移动到指定设备
训练
model.train() #模型调为训练模式
for batch_x, batch_y in train_loader:
x, target = batch_x.to(device), batch_y.to(device) #将输入特征和真实y移到设备,并用清晰的变量名接收
pred = model(x) #相当于model.forward(x)
train_bat_loss = loss(pred, target,model)
optimizer.zero_grad() # 梯度清零
train_bat_loss.backward() #反向传播
optimizer.step() #更新模型的作用,如w,b
train_loss += train_bat_loss.cpu().item() #累计总损失值
plt_train_loss.append(train_loss / train_loader.__len__()) #计算平均值,train_loader.__len__()是data的长度
plt_train_loss记录训练总损失
model.train()将模型切换为训练模式。
batch_x和batch_y是每次从train_loader中取一组张量用做训练,我的train_loader.data格式为tensor:(2160,6)。batch_x,batch_y格式为tensor:(16,6)。
model(x)输入数据到模型之中,得到预测值pred。
loss(pred, target,model)计算损失
optimizer.zero_grad()因为梯度是累加的,不让上一批数据影响后一批,所以要梯度清零。train_bat_loss.backward() #反向传播,更新各参数梯度,第一组数据使参数θ0->θ1,第二组发现模型更新使用参数θ1,后使θ1->θ2。
optimizer.step() #更新模型的参数,如w,b,为了更好的拟合
train_loss += train_bat_loss.cpu().item() 累计总损失值
plt_train_loss.append(train_loss / train_loader.__len__()) 一轮损失计算平均值,train_loader.__len__()是data的长度
验证
model.eval()#将模型切换到测试模式
with torch.no_grad():#不进行梯度计算
for batch_x, batch_y in val_loader: #遍历得到输入数据和真实的y
x, target = batch_x.to(device), batch_y.to(device)#将输入特征和真实y移到设备,并用清晰的变量名接收
pred = model(x) #得到预测值
val_bat_loss = loss(pred, target,model) #得到损失
val_loss += val_bat_loss.cpu().item() #统计预测值
plt_val_loss.append(val_loss/ val_loader.__len__()) #得到平均预测值
if val_loss < min_val_loss: #保存最佳模型
torch.save(model, save_path)
min_val_loss = val_loss
与训练模型不同,不进行梯度更新。在epochs轮中,得到最佳模型,并保存到save_path。
print("[%03d/%03d] %2.2f sec(s) Trainloss: %.6f |Valloss: %.6f"% \
(epoch, epochs, time.time()-start_time, plt_train_loss[-1],plt_val_loss[-1]))
每一轮输出运行时长、训练损失和测试损失,用来看模型拟合程度。
plt.plot(plt_train_loss) #绘制训练曲线
plt.plot(plt_val_loss) #绘制测试曲线
plt.title("loss")
plt.legend(["train", "val"])
plt.show()
最后绘制出训练和测试的折线图。
测试
def evaluate(sava_path, test_loader,device,rel_path ): #得出测试结果文件
model = torch.load(sava_path,weights_only=False).to(device)
rel = []
with torch.no_grad():
for x in test_loader: #便利得到输入数据
pred = model(x.to(device)) #得到预测值
rel.append(pred.cpu().item()) #记录数据
print(rel)
with open(rel_path, "w",newline='') as f: #newline=‘’让打印不换行 以写的形式将打开函数赋给f
csvWriter = csv.writer(f) #创建一个CSV写入器对象,它会自动处理字段中的逗号、引号等字符,按CSV格式写入
csvWriter.writerow(["id","tested_positive"])#写入CSV文件的表头(第一行),包括两个字段名
for i, value in enumerate(rel): #enumerate用于在遍历可迭代对象(如列表、元组、字符串等)时,同时获取元素的索引(index)和值(value)。
csvWriter.writerow([str(i), str(value)]) #将每一行写为 [id, tested_positive] 的形式
print("文件已经保存到"+rel_path)
model = torch.load(sava_path,weights_only=False).to(device)加载最佳模型,weights_only=False允许加载任意Python对象。
rel用来记录预测值。
csvWriter是一个CSV写入器对象,用来操作rel_path,后再将预测值按序号写入rel_path。
其余代码
def get_feature_importance(feature_data, label_data, k =4,column = None): #相关系数,取重要的四列
"""
此处省略 feature_data, label_data 的生成代码。
如果是 CSV 文件,可通过 read_csv() 函数获得特征和标签。
这个函数的目的是, 找到所有的特征种, 比较有用的k个特征, 并打印这些列的名字。
"""
model = SelectKBest(chi2, k=k) #定义一个选择k个最佳特征的函数
feature_data = np.array(feature_data, dtype=np.float64)
# label_data = np.array(label_data, dtype=np.float64)
X_new = model.fit_transform(feature_data, label_data) #用这个函数选择k个最佳特征
#feature_data是特征数据,label_data是标签数据,该函数可以选择出k个特征
print('x_new', X_new)
scores = model.scores_ # scores即每一列与结果的相关性
# 按重要性排序,选出最重要的 k 个
indices = np.argsort(scores)[::-1] #[::-1]表示反转一个列表或者矩阵。
# argsort这个函数, 可以矩阵排序后的下标。 比如 indices[0]表示的是,scores中最小值的下标。
if column: # 如果需要打印选中的列
k_best_features = [column[i+1] for i in indices[0:k].tolist()] # 选中这些列 打印
print('k best features are: ',k_best_features)
return X_new, indices[0:k] # 返回选中列的特征和他们的下标。
all_feature = False
if all_feature: #设置特征维度
feature_dim = 93
else:
feature_dim = 6
train_file = "covid.train.csv"
test_file = "covid.test.csv"
#创建用于训练、测试、验证的 CovidDataset 数据集实例
train_dataset = CovidDataset(train_file, "train",all_feature=all_feature, feature_dim=feature_dim)
val_dataset = CovidDataset(train_file, "val",all_feature=all_feature, feature_dim=feature_dim)
test_dataset = CovidDataset(test_file, "test",all_feature=all_feature, feature_dim=feature_dim)
batch_size = 16 #步长
#将自定义的数据集 train_dataset 封装成可迭代的数据加载器,并且shuffle表示是否要打乱数据
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
val_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=1, shuffle=False)
# for batch_x, batch_y in train_loader:
# print(batch_x, batch_y)
#
# predy = model(batch_x)
#
# file = pd.read_csv(train_file)
# print(file.head())
device = "cuda" if torch.cuda.is_available() else "cpu"
print(device)
config = {
"lr": 0.001,
"epochs": 20,
"momentum": 0.9,
"save_path": "model_save/best_model.pth",
"rel_path": "pred.csv"
}#字典是一个典型的超参数与路径配置管理结构
def mseLoss_with_reg(pred, target, model): #正则化的回归损失函数
#防止过拟合 若Loss=Loss+w平方,就是靠求导后更新w时,权值的系数会因为正则项而减小,从而降低过大权值的影响
#然后正常数据权值因为不大且相互之间相差不大,受正则函数影响小,所以无碍
#回传时w=w-? 这个问号中因为正则化会变大
loss = nn.MSELoss(reduction='mean')#创建一个MSELoss对象
''' Calculate loss '''
regularization_loss = 0 # 正则项
for param in model.parameters(): #遍历参数
# TODO: you may implement L1/L2 regularization here
# 使用L2正则项
# regularization_loss += torch.sum(abs(param))
regularization_loss += torch.sum(param ** 2) # 计算所有参数平方
return loss(pred, target) + 0.00075 * regularization_loss # 返回损失。
model = MyModel(inDim=feature_dim).to(device)
loss = mseLoss_with_reg
optimizer = optim.SGD(model.parameters(), lr=config["lr"], momentum=config["momentum"])
#更新参数时使用的优化器,动量帮助跳出局部极小值,极小值和最小值的区别
#动量让优化器“记得自己是从哪来的”,并利用这股“冲劲”跨越障碍。 不是靠“增大步长”,而是靠“保持运动趋势”来逃离局部陷阱
#惯即使当前梯度≈0,历史速度仍驱动参数更新(惯性作用)
train_val(model, train_loader, val_loader, device, config["epochs"], optimizer, loss, config["save_path"])
evaluate(config["save_path"], test_loader, device, config["rel_path"])
运用正则化,防止过拟合 若Loss=Loss+w平方,就是靠求导后更新w时,权值的系数会因为正则项而减小,从而降低过大权值的影响,然后正常数据权值因为不大且相互之间相差不大,受正则函数影响小,所以无碍 。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐

所有评论(0)