本文基于 PyTorch 框架实现了一套完整的新冠相关数据回归预测解决方案,涵盖从数据预处理到模型部署的全流程。核心内容包括:

  1. 数据处理层:自定义CovidDataset类继承Dataset抽象类,实现 CSV 数据读取、按比例划分训练 / 验证集、特征标准化(Z-score),并区分训练 / 验证集(带标签)与测试集(无标签),解决了数据量纲不一致、数据集划分的核心问题;
  2. 模型层:构建两层全连接神经网络(输入 93 维特征→128 维隐藏层→1 维回归输出),通过 ReLU 激活函数引入非线性,适配回归任务的预测需求;
  3. 训练与优化层:实现带 L2 正则化的 MSE 损失函数(防止过拟合),基于 SGD 优化器(动量 0.9)完成模型训练,训练过程中实时监控训练 / 验证损失、保存验证损失最小的最佳模型,并绘制损失曲线可视化训练趋势;
  4. 预测与部署层:加载最佳模型对测试集进行无梯度预测,将结果整理为指定格式的 CSV 文件,完成整个回归任务的闭环。
import torch
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt #画图
import numpy as np  #矩阵相关
import csv     #csv文件
import pandas  #csv文件
from torch.utils.data import Dataset, DataLoader
import torch.nn as nn
from torch import optim
import time

# 这个类继承自 PyTorch 的 Dataset 抽象类(必须实现特定方法才能被 DataLoader 调用),核心作用是:
# 1、从指定 CSV 文件读取新冠相关数据;
# 2、根据 mode(train/val/test)划分数据集;
# 3、对特征数据 X 做标准化处理;
# 4、区分训练 / 验证集(有标签 Y)和测试集(无标签 Y)。
class CovidDataset(Dataset):
    def __init__(self, file_path, mode):    # 相当于C++的构造函数
        with open(file_path, "r") as f:
            ori_data = list(csv.reader(f))  #从用 Python 内置的 csv 模块的 reader 函数读取文件内容,再转换成列表,赋值给 ori_data;数据格式:ori_data 是二维列表,每一行对应 CSV 文件的一行,每一列对应一个单元格。
            csv_data = np.array(ori_data)[1:, 1:].astype(float)        #把二维列表 ori_data 转换成 numpy 数组(方便后续切片和数值计算);不要第一行和第一列

            #逢五取1,不推荐
            if mode == "train": #如果是 “训练集(train)” 模式,生成训练集的索引列表;比如 [1,2,3,4,6,7,8,9,...]。(约 80% 数据)
                indices = [i for i in range(len(csv_data)) if i % 5 != 0]

            elif mode == "val": #如果是 “验证集(val)” 模式,生成验证集的索引列表;[0,5,10...](约 20% 数据);
                indices = [i for i in range(len(csv_data)) if i % 5 == 0]

            elif mode == "test": #如果是 “测试集(test)” 模式,生成包含所有行索引的列表;
                indices = [i for i in range(len(csv_data))]

            X = torch.tensor(csv_data[indices, :93]) #按之前的索引筛选行,且只取前 93 列(这 93 列是模型的输入特征);把 numpy 数组转换成 PyTorch 张量(PyTorch 模型只能处理张量数据)。
            if mode != "test":  #训练集没有最后一列,只为训练 / 验证集加载标签数据 Y;
                self.Y = torch.tensor(csv_data[indices, -1])  #按索引筛选行,取最后一列(这一列是模型的标签,比如确诊数、死亡率等);
            # 对特征数据 X 做标准化(Z-score 归一化),并保存为对象属性;
            # 为什么标准化:特征的量纲不同(比如A列是 “人数”,B列是 “百分比”),标准化后能让模型更快收敛、精度更高。
            self.X = (X - X.mean(dim=0, keepdim=True)) / X.std(dim=0, keepdim=True)
            self.mode = mode #把传入的 mode 保存为对象属性,后续可以通过 dataset.mode 查看当前数据集的模式(方便后续扩展,比如根据 mode 做不同的预处理)。
    # 总结:核心流程:读取CSV数据 → 清洗(去表头 / 首列、转数值)→ 按mode划分数据集 → 提取特征X和标签Y → 标准化X → 保存为对象属性;

    def __getitem__(self, item):    # 根据传入的 item(索引),返回对应位置的特征 X 和标签 Y。
        if self.mode == "test":         # 如果当前数据集是 “测试模式”,只返回特征 X;
            return self.X[item].float()
        else:  #如果是训练 / 验证模式(train/val),返回 “特征 X + 标签 Y” 的元组;
            return self.X[item].float(), self.Y[item].float()

    def __len__(self):      # 返回数据集的总条数
        return len(self.X)


# Model模型 相当于 y=f(x)里的函数f
# 这个模型是一个两层全连接的回归模型:
# 输入:维度为 inDim 的特征向量(对应你之前数据集里的 93 维(列)特征);
# 隐藏层:128 维,搭配 ReLU 激活函数引入非线性;
# 输出:1 个数值(比如预测新冠相关的某个指标,如确诊数、死亡率等);
class myModel(nn.Module):
    # 作用:模型的初始化方法,接收一个参数inDim;
    # 参数说明:inDim是模型输入特征的维度(对应你之前数据集里的93维,所以创建模型时要传inDim = 93)。
    def __init__(self, inDim):
        super(myModel, self).__init__()
        self.fc1 = nn.Linear(inDim, 128)    # 定义第一个全连接层(Linear Layer,也叫稠密层),并赋值为模型的属性 self.fc1。128:输出维度(隐藏层的神经元数量,可根据任务调整);
        self.relu1 = nn.ReLU()              # 定义 ReLU 激活函数层,赋值为 self.relu1;为什么用 ReLU:全连接层是线性变换,激活函数引入非线性,让模型能拟合复杂的非线性关系(如果没有激活函数,多层全连接等价于单层,失去深度的意义);
        self.fc2 = nn.Linear(128, 1)        # 定义第二个全连接层,作为输出层;输出维度:1(回归任务的典型输出,只预测一个数值);

    # 定义模型的前向传播方法,这是 PyTorch 模型的核心方法;
    def forward(self, x):           # x 是输入的张量(比如批量特征,形状为 [batch_size, 93]);
        x = self.fc1(x)             # 将输入 x 传入第一个全连接层 fc1,得到 128 维的中间特征;假设输入 x 是 [32, 93](32 个样本,每个 93 维),经过 fc1 后变成 [32, 128]。
        x = self.relu1(x)           # 将 fc1 的输出传入 ReLU 激活函数;维度不变,还是 [32, 128],但数值被非线性变换(负数置 0)。
        x = self.fc2(x)             # 第二个全连接层 fc2。从 [32, 128] 变成 [32, 1](32 个样本,每个样本输出 1 个值)。

        if len(x.size()) > 1:
            x = x.squeeze(1)       #如果维度大于1, 就去掉第二个维度
        return x


# 当数据集、模型、超参都写好后,开始写训练流程
# 这个 train_val 函数实现了深度学习模型的全流程训练与验证:
# 1、按指定轮次(epochs)循环,每一轮先训练模型(更新参数),再验证模型(不更新参数);
# 2、记录每一轮的训练损失和验证损失;
# 3、保存验证损失最小的 “最佳模型”;
# 4、打印每一轮的训练耗时、训练损失、验证损失,方便监控训练过程。
def train_val(model, train_loader, val_loader, lr, optimizer, device, epochs, save_path):
    model = model.to(device)    # 再次将模型部署到指定设备(CPU/GPU)
    plt_train_loss = []  # 初始化两个空列表,分别存储每一轮的平均训练损失和平均验证损失,后续可用于绘制损失曲线
    plt_val_loss = []
    min_val_loss = 999999999999999999.9     #loss越小,说明这个模型越好

    for epoch in range(epochs):             # 训练的外层循环,控制训练总轮次(比如epochs=100就循环 100 次),每一轮包含 “训练” 和 “验证” 两个阶段。

        model.train()                               # 变为train模式。train() 是 nn.Module 内置的方法(函数),训练模式下,模型中的Dropout(随机失活)、BatchNorm(批归一化)等层会按训练逻辑工作(比如 Dropout 会随机关掉部分神经元);验证 / 预测时需要切换为eval()模式。
        start_time = time.time()                    # 记录当前轮次训练 + 验证的开始时间,后续用于计算本轮耗时(time.time()返回当前时间戳,单位秒)。
        train_loss = 0.0                            # 准备一个计数器,初始为 0,用来累加 这一轮 所有数据批次的 loss
        for x, y in train_loader:                   # 训练的内层循环,遍历训练数据加载器train_loader,每次取出一个 批次 的输入x(特征)和标签y(真实值)。
            x, y = x.to(device), y.to(device)       # 将当前批次的输入和标签部署到和模型相同的设备上(必须!否则模型在 GPU、数据在 CPU 会报错)。
            y_pred = model(x)                       # 将输入x传入模型,得到模型的预测值y_pred
            bat_loss = loss(y_pred, y, model)       # 调用自定义的loss函数,计算预测值y_pred和真实值y之间的损失(误差)。
            bat_loss.backward()                     # 反向传播,计算损失loss对模型所有可训练参数的梯度(即 “参数该怎么调整才能减小loss”)。
            optimizer.step()                        # 调用优化器的step()方法,根据反向传播得到的梯度,更新模型的参数(即 “按反思的结果调整参数”)。
            optimizer.zero_grad()                   # 清空优化器中所有参数的梯度。PyTorch 的梯度会累加,如果不清零,下一个批次的梯度会和当前批次叠加,导致参数更新错误。
            train_loss += bat_loss.cpu().item()     # 将当前批次的损失值(从 GPU 移到 CPU,转为普通浮点数)累加到train_loss中,得到 本轮 训练的总损失。
            #以上是一轮的训练,train_loss是一轮训练得出的loss

        plt_train_loss.append(train_loss/train_loader.__len__())  # 计算 本轮 训练的平均损失(总损失 ÷ 训练批次数量),并添加到plt_train_loss列表中。


        #训练完成后,切换为验证模式validation
        model.eval()
        val_loss = 0.0                              # 准备一个新的计数器,初始为 0,用来累加 这一轮 验证所有批次的 loss
        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)
                val_loss += val_bat_loss.cpu().item()   # 计算本轮验证的平均损失,添加到plt_val_loss列表中。
        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)            # 将当前的最佳模型完整保存到指定路径save_path。

        print("[%03d/%03d] %2.2f sec(s)  train_loss: %.6f val_loss:%.6f" % \
              (epoch, epochs, time.time()-start_time, plt_train_loss[-1], plt_val_loss[-1]))  #打印当前训练轮次的关键信息,方便监控训练过程。输出当前轮次,总轮次,训练时间,本轮训练loss,本轮验证loss

    #画图
    plt.plot(plt_train_loss)
    plt.plot(plt_val_loss)
    plt.title("loss")               #标题“loss”
    plt.legend(["train", "val"])    #图例
    plt.show()


# 这个 evaluate 函数的核心作用是:
# 1、加载之前保存的最佳模型文件;
# 2、在测试数据集上进行无梯度的预测(避免参数更新);
# 3、将所有预测结果整理成指定格式的 CSV 文件(包含 ID 和预测值),并保存到指定路径。
def evaluate(model_path, test_loader, rel_path, device):
    model = torch.load(model_path).to(device)       # 加载保存的模型文件,并将模型部署到指定设备上。

    rel = []                                        # 初始化一个空列表,用于存储所有测试样本的预测值
    model.eval()                                    # 将模型切换为评估 / 预测模式
    with torch.no_grad():                           # 开启 “无梯度计算” 上下文管理器,是预测阶段的核心优化。预测时只需要模型输出结果,不需要反向传播计算梯度(梯度是训练时更新参数用的);关闭梯度计算能大幅节省 GPU/CPU 内存,提升预测速度。
        for x in test_loader:                       # 遍历测试数据加载器,每次取出 一个批次 的测试输入x(注意:测试集通常只有输入,没有标签y)。
            x = x.to(device)                        # 将当前批次的测试输入x移到和模型相同的设备上
            pred = model(x)                         # 将测试输入x传入模型,得到模型的预测结果pred(张量类型)。
            rel.append(pred.cpu().item())           # 将当前批次的预测值转换为 Python 原生数值,并添加到rel列表中。
    with open(rel_path, "w", newline="") as f:      # 以 “写入模式” 打开指定路径的文件,准备写入预测结果。
        csv_writer = csv.writer(f)
        csv_writer.writerow(["id", "tested_positive"])  # 写入 CSV 文件的表头行,定义两列的名称:id(样本序号)、tested_positive(预测值)。
        for i, pred in enumerate(rel):                  # 同时得到 第几个 和第几个的结果 enumrate
            csv_writer.writerow([str(i), str(pred)])    # 将样本序号和预测值按行写入 CSV 文件,且都转换为字符串(CSV 文件存储的是文本)。
        print("结果保存到了"+rel_path)

train_file = r"D:\桌面\深度学习\第三节,回归实战代码\regression\covid\covid.train.csv"
test_file = r"D:\桌面\深度学习\第三节,回归实战代码\regression\covid\covid.test.csv"

batch_size = 16     # 一批取16个数据

# train_set实例化训练集对象,传入训练数据文件路径 train_file,指定模式为 "train"(会按 i%5 != 0 筛选数据,且包含 X 和 Y);
train_set = CovidDataset(train_file, "train")
val_set = CovidDataset(train_file, "val")
test_set = CovidDataset(test_file, "test")

#创建训练集的数据加载器;每次读取 16 条数据;DataLoader 会自动调用 CovidDataset 的 __len__ 和 __getitem__ 方法,批量读取数据。
train_loader = DataLoader(train_set, batch_size=batch_size, shuffle=True)
val_loader = DataLoader(val_set, batch_size=batch_size, shuffle=True)       #验证集
test_loader = DataLoader(test_set, batch_size=1, shuffle=False)             #测试集


# 这个函数是在标准均方误差(MSE)损失的基础上,加入了 L2 正则化(权重衰减),用于防止模型过拟合。
# 这个 mseLoss 函数是带 L2 正则化的均方误差损失函数:
# 1、计算预测值和真实值之间的标准 MSE 损失(回归任务的基础损失);
# 2、计算模型所有参数的 L2 正则项(惩罚过大的参数,防止过拟合);
# 3、将基础 MSE 损失和正则项加权求和,得到最终的损失值。
# 正则化(公式: loss = loss + W * W , W为参数)
def mseLoss(pred, target, model):
    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)                  # 计算所有参数平方 加到正则项上。L2 正则化的核心逻辑:惩罚参数的平方和 —— 参数值越大,惩罚越重,迫使模型参数尽可能小,从而降低模型复杂度,防止过拟合。
    return loss(pred, target) + 0.00075 * regularization_loss             # 返回损失。

# hyperPare除模型外的超参(一般包含:学习率,优化器,损失函数等)
loss = mseLoss      # 在 Python 中,函数是 “一等对象”—— 可以像普通变量一样赋值、传递。loss = mseLoss 表示把 mseLoss 这个函数的 “引用” 赋值给变量 loss,此时 loss 和 mseLoss 指向同一个函数,二者完全等价。
epochs = 20         #运行轮次
lr = 0.001          #学习率,是优化器的关键超参数,控制参数更新的步长
device = "cuda" if torch.cuda.is_available() else "cpu"
print(device)

data_dim = 93                               # data_dim是传入模型的参数,通常表示输入数据的维度
model = myModel(data_dim).to(device)        # 实例化你的深度学习模型,并将模型部署到指定的计算设备(CPU/GPU)上。
save_path = "model_save/best_model.pth"     # 最佳模型的保存路径
rel_path = "pred.csv"                       # 最终预测结果的保存路径
optimizer = optim.SGD(params=model.parameters(), lr=lr, momentum=0.9)   # 初始化随机梯度下降(SGD)优化器,用于更新模型的参数(权重w、偏置b),最小化损失函数。momentum=0.9:动量参数,是 SGD 的改进项,用于加速收敛、减少震荡。


train_val(model, train_loader, val_loader, lr, optimizer, device, epochs, save_path)


#提交
#save_path保存了最好的模型,让测试数据test_loader通过此模型,结果保存到rel_path中,
evaluate(save_path, test_loader, rel_path, device)

运行结果:

cuda
[000/020] 4.74 sec(s)  train_loss: 27.745412 val_loss:2.953082
[001/020] 0.58 sec(s)  train_loss: 2.401397 val_loss:1.821427
[002/020] 0.56 sec(s)  train_loss: 1.921102 val_loss:1.864542
[003/020] 0.55 sec(s)  train_loss: 1.680141 val_loss:1.539485
[004/020] 0.60 sec(s)  train_loss: 1.553827 val_loss:1.466002
[005/020] 0.72 sec(s)  train_loss: 1.569113 val_loss:1.648107
[006/020] 0.57 sec(s)  train_loss: 1.425005 val_loss:1.493009
[007/020] 0.62 sec(s)  train_loss: 1.306934 val_loss:1.264574
[008/020] 0.70 sec(s)  train_loss: 1.255836 val_loss:1.300525
[009/020] 0.75 sec(s)  train_loss: 1.247716 val_loss:1.423543
[010/020] 0.71 sec(s)  train_loss: 1.204495 val_loss:1.592692
[011/020] 0.78 sec(s)  train_loss: 1.282885 val_loss:1.420568
[012/020] 1.28 sec(s)  train_loss: 1.276087 val_loss:1.229359
[013/020] 1.05 sec(s)  train_loss: 1.236784 val_loss:1.062106
[014/020] 0.74 sec(s)  train_loss: 1.137461 val_loss:1.263682
[015/020] 0.85 sec(s)  train_loss: 1.168308 val_loss:1.076750
[016/020] 0.79 sec(s)  train_loss: 1.091511 val_loss:1.202587
[017/020] 0.61 sec(s)  train_loss: 1.124766 val_loss:1.059878
[018/020] 0.69 sec(s)  train_loss: 1.050860 val_loss:1.227396
[019/020] 0.67 sec(s)  train_loss: 1.049143 val_loss:1.006752
结果保存到了pred.csv

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐