引言今天带大家完成一次kaggle上的一个比赛通过本次学习可以掌握深度学习回归项目的通用流程。任务描述新冠每日病例预测训练数据2700个样本检测数据893个样本评估指标均方根误差RMSE数据收集与预处理数据我们在这个竞赛里面的Data中看到一共有三个CSV文件。直接下载下来。covid.train.csv: 训练集。我们使用这里面提供的数据来训练模型。covid.test.csv: 测试集。将训练好的模型通过这个测试集来检验我们训练的质量。sampleSubmission.csv: 我们要将最后得到的结果填入这个csv文件里面然后提交。covid.train.csv我们先打开covid.train.csv看看数据。可以看到数据前面这些列是一些编码。编码实际上是美国各州的邮政缩写代码。它采用了(One-Hot)独热编码的方式。什么是独热编码举个例子想象你在填写一份表格其中有一个问题是您所在的地区是选项华北、华东、华南、西部如果你的回答是华东在独热编码中它会变成华北: 0 华东: 1 ← 只有这里是1表示是 华南: 0 西部: 0也就是说只有其中一个地区为1其余地区都用0来表示。我们在看后面的数据代表的是影响感染的各种因素。可以当作我们要输入的x。 我们要输出的y是tested_positive检测阳性比例。并且我们可以看到这一组变量出现了三次说明可能是不同时间段进行测量的。总结从这个数据中我们可以看到这是一个面板数据Panel Data包含维度1时间/批次- 每组变量重复3次应该是3个不同的时间点或调查批次维度2地区- 40个美国州从AL到WI维度3指标- 18个调查指标症状、行为、心理等covid.test.csv我们打开covid.test.csv后可以看到和covid.train.csv差不多除了最后一列少了一列tested_positive。说明这个是让我们训练好模型计算出来的。模型训练基本流程一共分为四个部分数据模型超参训练流程接下来我们一个一个来完成。首先引入我们所需要的包import torch import matplotlib.pyplot as plt import matplotlib import numpy as np import csv import pandas from torch.utils.data import Dataset, DataLoader import torch.nn as nn from torch import optim import time数据第一个数据部分我们需要按照固定流程来写。Datasetinitgetitemlen我们先定义一个名为CovidDataset的类继承自PyTorch的Dataset基类。这意味着这个类必须实现__len__和__getitem__方法。class CovidDataset(Dataset):1.__init__方法#需要接收两个参数一共我们要读取的文件的路径一个是数据模式train/val/test def __init__(self, file_path, mode): #使用with语句以只读模式打开文件。with可以确保文件使用后自动关闭。 with open(file_path, r) as f: #用csv.reader逐行读取文件,然后转成列表形式 ori_data list(csv.reader(f)) #将列表转换为numpy数组 #不要第一行和第一列第一行是类别名第一列是计数的id我们只要取数据 #因为使用csv.reader()读取CSV文件时所有数据默认都是字符串类型。所以将所有数据转换为浮点数类型 csv_data np.array(ori_data)[1:, 1:].astype(float) # 逢5取1 这里只是简单分 不推荐 将数据分成训练集 验证集 测试集 if mode train: indices [i for i in range(len(csv_data)) if i % 5 ! 0] elif mode val: indices [i for i in range(len(csv_data)) if i % 5 0] elif mode test: indices [i for i in range(len(csv_data))] X torch.tensor(csv_data[indices, :93]) # 切片左闭右开 if mode ! test: self.Y torch.tensor(csv_data[indices, -1]) # 标准化操作将数值变为0附近的数值可以消除量纲可能存在的影响 self.X (X - X.mean(dim0, keepdimTrue)) / X.std(dim0, keepdimTrue) self.mode mode为什么要做标准化我们可以看到在数据集中有的数据是0.0几有的数据是大几十。但是这些数据又同时重要所以我们要消除这种差异。将数值变为0附近的数值可以消除量纲可能存在的影响。2.__getitem__方法# 固定的写法 def __getitem__(self, item): if self.mode test: return self.X[item].float() # 64位变成32位 else: return self.X[item].float(), self.Y[item].float()3.__len__方法def __len__(self): return len(self.X)总结class CovidDataset(Dataset): def __init__(self, file_path, mode): with open(file_path, r) as f: ori_data list(csv.reader(f)) csv_data np.array(ori_data)[1:, 1:].astype(float) # 不要第一行和第一列 # 逢5取1 不推荐 if mode train: indices [i for i in range(len(csv_data)) if i % 5 ! 0] elif mode val: indices [i for i in range(len(csv_data)) if i % 5 0] elif mode test: indices [i for i in range(len(csv_data))] X torch.tensor(csv_data[indices, :93]) # 切片左闭右开 if mode ! test: self.Y torch.tensor(csv_data[indices, -1]) self.X (X - X.mean(dim0, keepdimTrue)) / X.std(dim0, keepdimTrue) # 标准化操作将数值变为0附近的数值可以消除量纲可能存在的影响 self.mode mode # 固定的写法 def __getitem__(self, item): if self.mode test: return self.X[item].float() # 64位变成32位 else: return self.X[item].float(), self.Y[item].float() def __len__(self): return len(self.X)模型模型写起来比较较为固定我们把全连接写进去就可以了。inDim是输入特征的维度这里是93因为数据集是93列。全连接层 fc1 (93,128)激活函数 relu1全连接层 fc2 (128,1)前向传播 forwadclass myModel(nn.Module): def __init__(self, inDim): super(myModel, self).__init__() self.fc1 nn.Linear(inDim, 128) self.relu1 nn.ReLU() self.fc2 nn.Linear(128, 1) def forward(self, x): x self.fc1(x) x self.relu1(x) x self.fc2(x) if len(x.size()) 1: x x.squeeze(1) # 如果维度大于1 就去掉第二个维度 return x超参def train_val(model, train_loader, val_loader, optimizer, device, epochs, save_path): # 训练验证函数接收模型、数据加载器、优化器、设备、训练轮数和保存路径 model model.to(device) # 将模型移动到指定设备CPU/GPU plt_train_loss [] # 创建列表存储训练损失值用于绘图 plt_val_loss [] # 创建列表存储验证损失值用于绘图 min_val_loss 9999999999999999.9 # 初始化最小验证损失为一个很大的数 for epoch in range(epochs): # 开始epoch循环 model.train() # 设置模型为训练模式启用Dropout、BatchNorm等 stat_time time.time() # 记录开始时间 train_loss 0.0 # 初始化训练损失 for x, y in train_loader: # 遍历训练数据批次 x, y x.to(device), y.to(device) # 将数据和标签移动到指定设备 y_pred model(x) # 前向传播模型预测 bat_loss loss(y_pred, y) # 计算损失调用自定义的mseLoss函数 bat_loss.backward() # 反向传播计算梯度 optimizer.step() # 更新模型参数 optimizer.zero_grad() # 清空梯度防止累积 train_loss bat_loss.cpu().item() # 累加批次损失移到CPU并转为Python数值 plt_train_loss.append(train_loss / train_loader.__len__()) # 计算平均训练损失并存储 model.eval() # 设置模型为评估模式 val_loss 0.0 # 初始化验证损失 with torch.no_grad(): # 禁用梯度计算节省内存和计算 for val_x, val_y in val_loader: # 遍历验证数据批次 val_x, val_y val_x.to(device), val_y.to(device) # 移到指定设备 val_pred_y model(val_x) # 前向传播 val_bat_loss loss(val_pred_y, val_y, model) # 计算验证损失注意第三个参数model用于正则化 val_loss val_bat_loss.cpu().item() # 累加验证损失 plt_val_loss.append(val_loss / val_loader.__len__()) # 计算平均验证损失并存储 # 保存最佳模型 if val_loss min_val_loss: # 如果当前验证损失比之前的最小值还小 min_val_loss val_loss # 更新最小验证损失 torch.save(model, save_path) # 保存整个模型到指定路径 # 打印训练信息epoch、总轮数、耗时、训练损失、验证损失 print([%03d/%03d] %2.2f sec(s) train_loss: %.6f val_loss:%.6f % \ (epoch, epochs, time.time() - stat_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(model_path, test_loader, rel_path, device): # 评估函数加载训练好的模型并对测试集进行预测 model torch.load(model_path).to(device) # 加载保存的模型并移到指定设备 rel [] # 创建列表存储预测结果 model.eval() # 设置评估模式 with torch.no_grad(): # 禁用梯度 for x in test_loader: # 遍历测试数据 x x.to(device) # 移到指定设备 pred model(x) # 预测 rel.append(pred.cpu().item()) # 将预测结果加入列表移到CPU并转为Python数值 with open(rel_path, w, newline) as f: # 以写入模式打开结果文件 csv_writer csv.writer(f) # 创建CSV写入器 csv_writer.writerow([id, tested_positive]) # 写入表头 for i, pred in enumerate(rel): # 遍历所有预测结果 csv_writer.writerow([str(i), str(pred)]) # 逐行写入预测结果id和预测值 print(结果保存到 rel_path) # 打印保存信息 train_file rE:\DL\环境安装\covid\covid.train.csv # 训练数据文件路径 test_file rE:\DL\环境安装\covid\covid.test.csv # 测试数据文件路径 # for x, y in train_set: # pred_y model(x) # print(pred_y) # 被注释掉的测试代码用于检查数据加载是否正确 batch_size 16 # 定义批次大小 # 创建数据集实例 train_set CovidDataset(train_file, train) # 创建训练集 val_set CovidDataset(train_file, val) # 创建验证集 test_set CovidDataset(test_file, test) # 创建测试集 # 创建数据加载器 train_loader DataLoader(train_set, batch_sizebatch_size, shuffleTrue) # 训练数据加载器批次16打乱顺序 val_loader DataLoader(val_set, batch_sizebatch_size, shuffleTrue) # 验证数据加载器批次16打乱顺序 test_loader DataLoader(test_set, batch_size1, shuffleFalse) # 测试数据加载器批次大小1不打乱逐个预测 def mseLoss(pred, target, model): # 自定义损失函数MSE损失 L2正则化 loss nn.MSELoss(reductionmean) # 创建MSE损失函数返回平均值 Calculate loss regularization_loss 0 # 初始化正则化损失为0 for param in model.parameters(): # 遍历模型所有参数 # TODO: you may implement L1/L2 regularization here # 使用L2正则项 # regularization_loss torch.sum(abs(param)) # L1正则化被注释掉 regularization_loss torch.sum(param ** 2) # L2正则化计算所有参数的平方和 return loss(pred, target) 0.00075 * regularization_loss # 返回MSE损失 正则化项系数0.00075 loss mseLoss # 将自定义损失函数赋值给loss变量 #loss nn.MSELoss() # 被注释掉的简单MSE损失 epochs 20 # 设置训练轮数为20 lr 0.001 # 设置学习率为0.001 device cuda if torch.cuda.is_available() else cpu # 检测可用设备如果有GPU就用cuda否则用cpu print(device) # 打印使用的设备 data_dim 93 # 输入特征维度为93 model myModel(data_dim).to(device) # 创建模型并移到指定设备 save_path model_save/best_model_pth # 定义模型保存路径 rel_path pred.csv # 定义预测结果输出路径 optimizer optim.SGD(paramsmodel.parameters(), lrlr, momentum0.9) # 创建SGD优化器添加动量0.9 # train_val(model, train_loader, val_loader, optimizer, device, epochs, save_path) # 被注释掉的训练调用 # 提交 evaluate(save_path, test_loader, rel_path, device) # 执行评估需要先有训练好的模型def train_val(model, train_loader, val_loader, optimizer, device, epochs, save_path): # 训练验证函数接收模型、数据加载器、优化器、设备、训练轮数和保存路径 model model.to(device) # 将模型移动到指定设备CPU/GPU plt_train_loss [] # 创建列表存储训练损失值用于绘图 plt_val_loss [] # 创建列表存储验证损失值用于绘图 min_val_loss 9999999999999999.9 # 初始化最小验证损失为一个很大的数 for epoch in range(epochs): # 开始epoch循环 model.train() # 设置模型为训练模式启用Dropout、BatchNorm等 stat_time time.time() # 记录开始时间 train_loss 0.0 # 初始化训练损失 for x, y in train_loader: # 遍历训练数据批次 x, y x.to(device), y.to(device) # 将数据和标签移动到指定设备 y_pred model(x) # 前向传播模型预测 bat_loss loss(y_pred, y) # 计算损失调用自定义的mseLoss函数 bat_loss.backward() # 反向传播计算梯度 optimizer.step() # 更新模型参数 optimizer.zero_grad() # 清空梯度防止累积 train_loss bat_loss.cpu().item() # 累加批次损失移到CPU并转为Python数值 plt_train_loss.append(train_loss / train_loader.__len__()) # 计算平均训练损失并存储 model.eval() # 设置模型为评估模式 val_loss 0.0 # 初始化验证损失 with torch.no_grad(): # 禁用梯度计算节省内存和计算 for val_x, val_y in val_loader: # 遍历验证数据批次 val_x, val_y val_x.to(device), val_y.to(device) # 移到指定设备 val_pred_y model(val_x) # 前向传播 val_bat_loss loss(val_pred_y, val_y, model) # 计算验证损失注意第三个参数model用于正则化 val_loss val_bat_loss.cpu().item() # 累加验证损失 plt_val_loss.append(val_loss / val_loader.__len__()) # 计算平均验证损失并存储 # 保存最佳模型 if val_loss min_val_loss: # 如果当前验证损失比之前的最小值还小 min_val_loss val_loss # 更新最小验证损失 torch.save(model, save_path) # 保存整个模型到指定路径 # 打印训练信息epoch、总轮数、耗时、训练损失、验证损失 print([%03d/%03d] %2.2f sec(s) train_loss: %.6f val_loss:%.6f % \ (epoch, epochs, time.time() - stat_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(model_path, test_loader, rel_path, device): # 评估函数加载训练好的模型并对测试集进行预测 model torch.load(model_path).to(device) # 加载保存的模型并移到指定设备 rel [] # 创建列表存储预测结果 model.eval() # 设置评估模式 with torch.no_grad(): # 禁用梯度 for x in test_loader: # 遍历测试数据 x x.to(device) # 移到指定设备 pred model(x) # 预测 rel.append(pred.cpu().item()) # 将预测结果加入列表移到CPU并转为Python数值 with open(rel_path, w, newline) as f: # 以写入模式打开结果文件 csv_writer csv.writer(f) # 创建CSV写入器 csv_writer.writerow([id, tested_positive]) # 写入表头 for i, pred in enumerate(rel): # 遍历所有预测结果 csv_writer.writerow([str(i), str(pred)]) # 逐行写入预测结果id和预测值 print(结果保存到 rel_path) # 打印保存信息 train_file rE:\DL\环境安装\covid\covid.train.csv # 训练数据文件路径 test_file rE:\DL\环境安装\covid\covid.test.csv # 测试数据文件路径 # for x, y in train_set: # pred_y model(x) # print(pred_y) # 被注释掉的测试代码用于检查数据加载是否正确 batch_size 16 # 定义批次大小 # 创建数据集实例 train_set CovidDataset(train_file, train) # 创建训练集 val_set CovidDataset(train_file, val) # 创建验证集 test_set CovidDataset(test_file, test) # 创建测试集 # 创建数据加载器 train_loader DataLoader(train_set, batch_sizebatch_size, shuffleTrue) # 训练数据加载器批次16打乱顺序 val_loader DataLoader(val_set, batch_sizebatch_size, shuffleTrue) # 验证数据加载器批次16打乱顺序 test_loader DataLoader(test_set, batch_size1, shuffleFalse) # 测试数据加载器批次大小1不打乱逐个预测 def mseLoss(pred, target, model): # 自定义损失函数MSE损失 L2正则化 loss nn.MSELoss(reductionmean) # 创建MSE损失函数返回平均值 Calculate loss regularization_loss 0 # 初始化正则化损失为0 for param in model.parameters(): # 遍历模型所有参数 # TODO: you may implement L1/L2 regularization here # 使用L2正则项 # regularization_loss torch.sum(abs(param)) # L1正则化被注释掉 regularization_loss torch.sum(param ** 2) # L2正则化计算所有参数的平方和 return loss(pred, target) 0.00075 * regularization_loss # 返回MSE损失 正则化项系数0.00075 loss mseLoss # 将自定义损失函数赋值给loss变量 #loss nn.MSELoss() # 被注释掉的简单MSE损失 epochs 20 # 设置训练轮数为20 lr 0.001 # 设置学习率为0.001 device cuda if torch.cuda.is_available() else cpu # 检测可用设备如果有GPU就用cuda否则用cpu print(device) # 打印使用的设备 data_dim 93 # 输入特征维度为93 model myModel(data_dim).to(device) # 创建模型并移到指定设备 save_path model_save/best_model_pth # 定义模型保存路径 rel_path pred.csv # 定义预测结果输出路径 optimizer optim.SGD(paramsmodel.parameters(), lrlr, momentum0.9) # 创建SGD优化器添加动量0.9 # train_val(model, train_loader, val_loader, optimizer, device, epochs, save_path) # 被注释掉的训练调用 # 提交 evaluate(save_path, test_loader, rel_path, device) # 执行评估需要先有训练好的模型