一、前言
在深度学习的任务中,回归预测是一类常见的场景,主要用于预测连续型的数值结果。本文是我在学习新冠阳性人数回归预测后的总结,用于今后的复盘、回顾。如有问题,还望指教。
本项目实现的核心技术栈:
①框架:PyTorch
②数据处理:NumPy、CSV模块
③特征选择:SelectKBest 、卡方检验
④可视化:Matplotlib
二、实现步骤模块
项目的完整流程可概括为:①数据预处理与特征选择 → ②自定义数据集类 → ③构建神经网络模型 → ④训练与验证 → ⑤测试评估 → ⑥保存结果,下面将分模块详细讲解
2.1导入模块
# 数据可视化
import matplotlib.pyplot as plt
# 张量计算、神经网络、自动求导
import torch
# 数值计算与矩阵运算
import numpy as np
# CSV文件操作
import csv
# 数据加载核心类
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
# 解决Windows系统下libiomp5md.dll库冲突问题
os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"
2.2特征选择
在提供的数据集中,不是每一列都重要,部分特征对预期结果贡献很低。甚至是纯噪音,导致数据出现过拟合。于是相关系数的考虑就非常重要。
本项目采用 SelectKBest 结合卡方检验(chi2),筛选出与标签(新冠阳性人数)相关性最高的 k 个特征,提升模型训练效率和预测精度。
def get_feature_importance(feature_data, label_data, k=4, column=None):
"""
筛选与标签相关性最高的k个特征,返回筛选后的数据和特征下标
"""
# 初始化特征选择器,采用卡方检验评分
model = SelectKBest(chi2, k=k)
# 转换数据类型,满足卡方检验要求
feature_data = np.array(feature_data, dtype=np.float64)
# 拟合数据并完成特征选择
X_new = model.fit_transform(feature_data, label_data)
# 获取特征得分并降序排序,提取最优特征下标
scores = model.scores_
indices = np.argsort(scores)[::-1]
# 打印选中的特征名称(若传入列名)
if column:
k_best_features = [column[i+1] for i in indices[0:k].tolist()]
print('最优k个特征:', k_best_features)
return X_new, indices[0:k]
2.3数据集类
PyTorch提供了Dataset类,用于封装自定义的数据集,我们需要进程该类,实现我们最核心的三个方法:init 、getitem 、len,并实现数据读取、预处理、数据集划分、归一化等操作。
class CovidDataset(Dataset):
def __init__(self, file_path, mode="train", all_feature=False, feature_dim=6):
# 读取CSV文件
with open(file_path, "r") as f:
ori_data = list(csv.reader(f))
column = ori_data[0]
csv_data = np.array(ori_data[1:])[:, 1:].astype(float)
# 提取特征与标签数据
feature = np.array(ori_data[1:])[:, 1:-1]
label_data = np.array(ori_data[1:])[:, -1]
# 筛选特征(全部特征/最优k个特征)
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()
csv_data = csv_data.astype(np.float32)
# 划分训练/验证/测试集
if mode == "train":
indices = [i for i in range(len(csv_data)) if i % 5 != 0] # 80%训练集
data = torch.tensor(csv_data[indices, :-1])
self.Y = torch.tensor(csv_data[indices, -1])
elif mode == "val":
indices = [i for i in range(len(csv_data)) if i % 5 == 0] # 20%验证集
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)
self.mode = mode
# 获取单条数据(DataLoader调用)
def __getitem__(self, idx):
if self.mode != "test":
return self.data[idx].float(), self.Y[idx]
else:
return self.data[idx].float()
# 获取数据集总长度(DataLoader调用)
def __len__(self):
return len(self.data)
2.4神经网络模型
本项目构建的是一个非常简单的两层全连接神经网络,激活函数采用ReLu。
神经网络定义已经前项传播步骤如下:
class MyModel(nn.Module):
def __init__(self, inDim):
super(MyModel, self).__init__()
# 定义网络层:两层全连接、ReLU激活函数
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)
return x
2.5训练与验证
2.5.1定义带L2 正则化的MSE损失函数
正则化的作用是:在模型的原始确实函数中加上一个正则化项,对模型复杂程度进行约束和惩罚,迫使模型放弃过于复杂的拟合方式,缓解过拟合现象,让模拟更加平滑。
def mseLoss_with_reg(pred, target, model):
"""
带L2正则化的均方误差损失函数,防止过拟合
"""
loss = nn.MSELoss(reduction='mean')
regularization_loss = 0
# 计算L2正则项(惩罚过大的模型参数)
for param in model.parameters():
regularization_loss += torch.sum(param ** 2)
# 总损失=基础MSE损失+正则项损失
return loss(pred, target.float()) + 0.00075 * regularization_loss
2.5.2训练与验证核心函数
主要目的是进行多轮训练,在通过验证,来保存下最优的模型。(注意:在验证时,需要关闭梯度计算)
同时记录下各批次训练的缺失的平均值,并通过可视化展现出来。
def train_val(model, train_loader, val_loader, device, epochs, optimizer, loss, save_path):
# 将模型移到指定的设备(GPU/CPU)上,后续计算都在该设备上进行
# 如果有 GPU(cuda可用),模型和数据都移到 GPU,训练速度会大幅提升
model = model.to(device)
plt_train_loss = []
plt_val_loss = []
min_val_loss = 99999 # 初始化最小验证损失
# 创建模型保存文件夹
os.makedirs(os.path.dirname(save_path), exist_ok=True)
# 轮次训练循环
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)
# 前向传播
pred = model(x)
train_bat_loss = loss(pred, target, model)
# 反向传播与参数更新
train_bat_loss.backward()
optimizer.step()
optimizer.zero_grad()
# 累加批次损失
train_loss += train_bat_loss.cpu().item()
# 计算训练平均损失
plt_train_loss.append(train_loss / len(train_loader))
# 验证模式:关闭梯度计算
model.eval()
with torch.no_grad():
for batch_x, batch_y in val_loader:
x, target = batch_x.to(device), batch_y.to(device)
pred = model(x)
val_bat_loss = loss(pred, target, model)
val_loss += val_bat_loss.cpu().item()
# 计算验证平均损失并保存最优模型
avg_val_loss = val_loss / len(val_loader)
plt_val_loss.append(avg_val_loss)
if avg_val_loss < min_val_loss:
torch.save(model, save_path)
min_val_loss = avg_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("Training & Validation Loss")
plt.legend(["train", "val"])
plt.show()
2.6测试评估
测试函数,将我们保存的最优模型对测试集进行操作,并将结果保存到文件,以便后续对比、使用。
def evaluate(save_path, test_loader, device, rel_path):
# 加载最优模型
model = torch.load(save_path).to(device)
rel = []
# 测试集预测(关闭梯度计算)
with torch.no_grad():
for x in test_loader:
pred = model(x.to(device))
rel.append(pred.cpu().item())
# 保存预测结果到CSV文件
with open(rel_path, "w", newline='') as f:
csvWriter = csv.writer(f)
csvWriter.writerow(["id", "tested_positive"])
for i, value in enumerate(rel):
csvWriter.writerow([str(i), str(value)])
print("预测结果已保存至:" + rel_path)
三、串联所有模块
以下代码主要负责定义各种参数、创建数据集和数据加载器、初始化模型、初始化优化器等,最后,通过训练验证和测试评估函数,来完成整个项目流程。
if __name__ == "__main__":
# 1. 特征配置
all_feature = False
feature_dim = 93 if all_feature else 6
# 2. 文件路径配置
train_file = "covid.train.csv"
test_file = "covid.test.csv"
# 3. 创建数据集
train_dataset = CovidDataset(train_file, "train", all_feature, feature_dim)
val_dataset = CovidDataset(train_file, "val", all_feature, feature_dim)
test_dataset = CovidDataset(test_file, "test", all_feature, feature_dim)
# 4. 创建数据加载器
batch_size = 16
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)
# 5. 自动选择训练设备(GPU/CPU)
device = "cuda" if torch.cuda.is_available() else "cpu"
print(f"当前训练设备:{device}")
# 6. 超参数配置
config = {
"lr": 0.001,
"epochs": 20,
"momentum": 0.9,
"save_path": "model_save/best_model.pth",
"rel_path": "pred.csv"
}
# 7. 初始化模型、损失函数与优化器
model = MyModel(inDim=feature_dim).to(device)
loss = mseLoss_with_reg
optimizer = optim.SGD(model.parameters(), lr=config["lr"], momentum=config["momentum"])
# 8. 启动训练与验证
train_val(model, train_loader, val_loader, device, config["epochs"], optimizer, loss, config["save_path"])
# 9. 启动测试与结果保存
evaluate(config["save_path"], test_loader, device, config["rel_path"])
后续会逐渐完善本篇博客 使过程更加详细完善
四、总结
在本次项目的完成中,我曾遇到过诸多问题,例如:PyTorch版本配置错误、学习率一开始过高、模型参数类型不一致等等,通过查询博客等方式一步一步对该项目进行纠错、复盘与总结。后续会更多的学习相关知识,不断进步。