新冠病毒预测项目实战

一.导入包

import torch
import matplotlib.pyplot as plt #画图
import matplotlib
matplotlib.use("TkAgg")
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


注意:

若电脑显卡配置高则额外添加以下包:

import matplotlib
matplotlib.use("TkAgg")

且若用到

model = torch.load(model_path).to(device)

改为

model = torch.load(model_path, weights_only=False).to(device) 

二.核心代码

1.处理数据

# 定义数据集类 - 就像创建一个专门处理COVID数据的"数据处理器"
class CovidDataset(Dataset):    # 继承PyTorch的Dataset类
    def __init__(self, file_path, mode):
        # 打开CSV文件
        with open(file_path, "r") as f:
            # 读取CSV文件的所有内容,变成Python列表格式
            ori_data = list(csv.reader(f))
            # 将数据转为numpy数组,去掉第一行标题和第一列ID,只保留数值数据
            # 这就像我们看数据时跳过表头,只看实际的数字部分
            csv_data = np.array(ori_data)[1:, 1:].astype(float)        
            
            # 划分训练集和验证集:每5个数据取4个作为训练,1个作为验证
            # 这就像把一叠卡片每5张分成一组,每组取4张练习,留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))]  # 测试集用全部数据
            
            # 提取特征数据(前93列)
            X = torch.tensor(csv_data[indices, :93])
            
            # 如果不是测试集,提取标签数据(最后一列),这是我们要预测的值
            if mode != "test":
                self.Y = torch.tensor(csv_data[indices, -1])
            
            # 对特征数据进行标准化处理:让每个特征都变成均值为0,标准差为1
            # 这就像把所有的数据都调整到统一的尺度上比较
            self.X = (X - X.mean(dim=0, keepdim=True)) / X.std(dim=0, keepdim=True)
            self.mode = mode  # 记住当前是训练、验证还是测试模式
    def __getitem__(self, item):
        # 根据索引获取一个数据样本
        # 如果是测试模式,只返回特征X;否则返回特征X和标签Y
        # 像从一堆卡片中抽出一张来看
        if self.mode == "test":
            return self.X[item].float()
        else:
            return self.X[item].float(), self.Y[item].float()

    def __len__(self):
        # 返回数据集的大小,像数一数有多少张卡片
        return len(self.X)

要继承PyTorch的Dataset类获取所需数据X和Y

init函数作为Python类的初始化方法,会在创建对象时自动调用,它接收两个参数,file_path是数据的路径(CSV文件),mode是训练模式"train"/“val”/“test”(训练/验证/测试)

直接在左侧复制绝对路径,类似下图

逢五取一(太简单一般不用)的方式将covid_train.csv划分为训练集和验证集,val(验证集)模式下逢五取一,train(训练集)模式下非五就取,用indices列表,存放序号,再用indices取出csv_data里的数据

2.构造模型

myModel继承torch.nn.Module,用于设计神经网络的模型框架,这里设计的是2层全连接网络

# 定义神经网络模型
class myModel(nn.Module):      # 继承PyTorch的Module类
    def __init__(self, inDim):
        super(myModel, self).__init__()  # 调用父类初始化方法
        
        # 第一层全连接层:把93个输入特征变成128个隐藏特征
        # 这就像把93个观察指标转化成128个更抽象的特征
        self.fc1 = nn.Linear(inDim, 128)
        
        # ReLU激活函数:给神经网络增加非线性,让它能学习复杂模式
        self.relu1 = nn.ReLU()
        
        # 第二层全连接层:把128个隐藏特征变成1个输出值
        # 这就像从128个抽象特征中计算出最终的预测结果
        self.fc2 = nn.Linear(128, 1)

    def forward(self, x):    # 数据向前传播
        # 数据通过第一层,就像信息通过第一道处理工序
        x = self.fc1(x)
        # 经过ReLU激活,让负值归零,正值保留
        x = self.relu1(x)
        # 数据通过第二层,得到最终的输出
        x = self.fc2(x)

        # 如果输出的维度大于1,去掉多余的维度(确保输出形状正确)
        # 这就像整理文件,去掉不必要的包装
        if len(x.size()) > 1:
            x = x.squeeze(1)       
        return x

forward函数定义了前向传播的过程。在按批次输入参数x时,x的形状为(batch_size, inDim),而在输出时,形状为(batch_size, 1),这不是我们想要的,我们想要的形状是(batch_size),要去掉多余的维度,因为我们做的是回归预测,预测值通常是一维的连续数值,所以要按行合并。

3.训练流程与超参数

(1)训练流程

def train_val(model, train_loader, val_loader, lr, optimizer, device, epochs, save_path):
    model = model.to(device)    # 把模型放到指定的设备(GPU或CPU)上
    
    # 记录训练和验证的损失,用于画图观察学习过程
    plt_train_loss = []  # 训练损失记录
    plt_val_loss = []    # 验证损失记录
    min_val_loss = 999999999999999999.9  # 初始化一个很大的最小损失值

    # 开始训练,每一轮叫一个epoch
    for epoch in range(epochs):          
        model.train()  # 告诉模型现在是训练模式,可以更新参数
        start_time = time.time()  # 记录本轮开始时间
        train_loss = 0.0  # 初始化本轮训练损失
        
        # 遍历训练数据,每次取一小批(batch)数据
        for x, y in train_loader:
            x, y = x.to(device), y.to(device)  # 把数据移到指定设备
            y_pred = model(x)  # 让模型做预测
            bat_loss = loss(y_pred, y, model)  # 计算这批数据的损失
            bat_loss.backward()  # 反向传播,计算梯度
            optimizer.step()  # 更新模型参数
            optimizer.zero_grad()  # 清空梯度,为下一批数据做准备,不清空梯度会累计
            train_loss += bat_loss.cpu().item()  # 累计损失

        # 计算平均训练损失,并记录下来
        plt_train_loss.append(train_loss/train_loader.__len__())#等同len(train_loader)

        # 切换到验证模式
        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)  # 计算验证损失
                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)

        # 打印本轮训练信息:第几轮、耗时、训练损失、验证损失
        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]))

    # 训练结束后,画出损失变化图,看看学习过程
    plt.plot(plt_train_loss)
    plt.plot(plt_val_loss)
    plt.title("loss")
    plt.legend(["train", "val"])
    plt.show()

在每一轮epoch中,会首先将模型切换至训练模式。然后写一个for循环,用x和y从事先做好的可迭代对象train_loader中按batch_size接收数据,然后将x放入已经实例化的模型model完成前向传播,然后给函数调用loss传参计算损失,然后调用bat_loss.backward()完成反向传播,然后用优化器进行参数更新和梯度清零,最后累加到本批次的总损失上。for循环结束后,记录该损失的算术平均值。

而在验证模式中,流程基本一致,只是验证过程不涉及训练模型,所以在前向传播中不能计算梯度,前向传播的过程记录了梯度的计算式,但是没有loss所以更新不了,反向传播后有了loss才能进行更新,所以这里是让前向不去计算式子,不更新张量网

(2)超参数

val_loader: 验证数据的数据加载器,用于批量提供验证数据。
train_loader: 训练数据的数据加载器,用于批量提供训练数据。
model: 需要训练的模型,通常是nn.Module的子类。
lr: 学习率(learning rate),用于优化器更新参数的步长。
optimizer: 优化器,如SGD、Adam等,用于更新模型参数。
device: 设备(如'cuda'或'cpu'),模型和数据将被移动到的设备。
epochs: 训练的总轮数。
save_path: 模型保存的路径。

train_file = r"C:\Users\Administrator\Desktop\李哥深度学习项目班\第三节,回归实战代码\regression\covid\covid.train.csv"
test_file = r"C:\Users\Administrator\Desktop\李哥深度学习项目班\第三节,回归实战代码\regression\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_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)


def mseLoss(pred, target, model):
    loss = nn.MSELoss(reduction='mean')    #平方差损失
    ''' 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             # 返回损失。

loss = mseLoss
epochs = 20   #运行轮次
lr = 0.001     #学习率
device = "cuda" if torch.cuda.is_available() else "cpu"
print(device)

data_dim = 93
model = myModel(data_dim).to(device)
save_path = "model_save/best_model.pth"
rel_path = "pred.csv"
optimizer = optim.SGD(params=model.parameters(), lr=lr, momentum=0.9)


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


注意最后一个批次的数据量可能不是 batch_size

测试集不需要分批次,也不需要打乱,到时候就按顺序一个一个预测得到结果就好

  • mseLoss

使用mseLoss函数进行优化,mseLoss函数会计算损失,计算公式为:

当前批次损失 = MSE(当前批次预测值, 当前批次真实值) + 0.00075 × Σ(模型所有参数²)

在本案例中,损失是唯一衡量训练好坏的参考

注:loss = mseLoss 是将函数对象赋值给变量 loss,那里不是调用函数,所以不传递参数

  • optimizer

优化器类型: SGD:随机梯度下降优化器(Stochastic Gradient Descent),随机的意思就是每次更新只使用一个批次的数据计算梯度,而非全部数据(普通的梯度下降使用全部数据)

PyTorch 不会提供默认优化器,必须显式定义

0.9 是基于经验的选择,适用于大多数情况,通常在 0.5-0.99 之间

(3)预测结果

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())  # 保存结果
    
    # 把预测结果保存到CSV文件中
    with open(rel_path, "w", newline="") as f:
        csv_writer = csv.writer(f)
        csv_writer.writerow(["id", "tested_positive"])  # 写表头
        for i, pred in enumerate(rel):     # 逐行写入预测结果
            csv_writer.writerow([str(i), str(pred)])
        print("结果保存到了"+rel_path)

#提交

evaluate(save_path, test_loader, rel_path, device)

使用保存过的最优模型来预测,并保存到rel_path中,并按照题目要求提交,完成。

Logo

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

更多推荐