1. 项目概述:基于深度学习的COVID-19阳性率预测实战
最近在复现李宏毅教授机器学习课程中的作业时,我完成了一个用深度学习预测美国各州COVID-19阳性率的项目。这个案例非常适合机器学习初学者练手,它涵盖了数据预处理、模型构建、训练优化到结果预测的完整流程。不同于常见的MNIST或CIFAR-10这类标准数据集,这个项目使用的是真实的疫情数据,预测目标是根据各州的人口统计、防疫政策等特征,预估未来可能的新冠检测阳性率。
这个项目的技术栈主要基于PyTorch框架,模型采用了一个三层的全连接神经网络。整个实现过程涉及几个关键环节:首先是数据准备阶段,需要正确加载和划分训练集、验证集;然后是特征工程,这里可以选择使用全部特征或手动筛选部分特征;接着是模型设计与训练,包括网络结构定义、损失函数选择、优化器配置等;最后是预测结果的生成与保存。下面我会详细拆解每个环节的实现细节和注意事项。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心代码解析与实现细节
2.1 环境配置与数据准备
首先来看基础环境配置部分。我们需要确保所有必要的库都已安装,特别是PyTorch及其相关工具包。这里我推荐使用conda创建独立的Python环境,避免与其他项目的依赖冲突:
python复制import math
import numpy as np
import pandas as pd
import os
import csv
from tqdm import tqdm # 进度条显示
import torch
import torch.nn as nn
from torch.utils.data import DataLoader, Dataset, random_split
from torch.utils.tensorboard import SummaryWriter # 训练可视化
数据准备阶段有几个关键点需要注意:
- 数据路径要正确,建议使用相对路径并检查文件是否存在
- 数据划分比例要合理,通常验证集占20%左右
- 随机种子的设置对结果复现至关重要
python复制def same_seed(seed):
"""固定随机种子保证结果可复现"""
torch.backends.cudnn.deterministic = True
torch.backends.cudnn.benchmark = False
np.random.seed(seed)
torch.manual_seed(seed)
if torch.cuda.is_available():
torch.cuda.manual_seed_all(seed)
def train_valid_split(data_set, valid_radio, seed):
"""划分训练集和验证集"""
valid_data_size = int(len(data_set) * valid_radio)
train_data_size = len(data_set) - valid_data_size
train_data, valid_data = random_split(
data_set, [train_data_size, valid_data_size],
generator=torch.Generator().manual_seed(seed))
return np.array(train_data), np.array(valid_data)
注意:在实际项目中,建议添加对数据完整性的检查,比如检查是否有缺失值、异常值等。对于疫情数据,还需要注意数据的时间连续性,确保训练集和验证集的时间分布合理。
2.2 特征工程与数据加载
特征选择是影响模型性能的关键因素之一。在这个项目中,我们可以选择使用全部特征或手动筛选部分特征:
python复制def select_feat(train_data, valid_data, test_data, select_all=True):
"""特征选择函数"""
y_train = train_data[:, -1] # 最后一列是标签
y_valid = valid_data[:, -1]
raw_x_train = train_data[:, :-1] # 特征列
raw_x_valid = valid_data[:, :-1]
raw_x_test = test_data
if select_all:
feat_idx = list(range(raw_x_train.shape[1]))
else:
feat_idx = [0, 1, 2, 3, 4] # 示例:手动选择前5个特征
return raw_x_train[:, feat_idx], raw_x_valid[:, feat_idx], raw_x_test[:, feat_idx], y_train, y_valid
数据加载部分我们自定义了Dataset类,这是PyTorch的标准做法:
python复制class COVID19Dataset(Dataset):
"""自定义数据集类"""
def __init__(self, features, targets=None):
if targets is None:
self.targets = None
else:
self.targets = torch.FloatTensor(targets)
self.features = torch.FloatTensor(features)
def __getitem__(self, idx):
if self.targets is None:
return self.features[idx]
else:
return self.features[idx], self.targets[idx]
def __len__(self):
return len(self.features)
提示:对于数值型数据,特别是不同特征量纲差异较大时,建议添加数据标准化步骤。可以在Dataset类中添加自动标准化功能,或者在数据加载后进行统一处理。
3. 模型设计与实现
3.1 神经网络结构设计
本项目采用了一个相对简单的全连接网络结构,包含三个线性层和ReLU激活函数:
python复制class My_Model(nn.Module):
"""自定义神经网络模型"""
def __init__(self, input_dim):
super(My_Model, self).__init__()
self.layers = nn.Sequential(
nn.Linear(input_dim, 16), # 输入层到隐藏层
nn.ReLU(), # 激活函数
nn.Linear(16, 8), # 隐藏层到隐藏层
nn.ReLU(),
nn.Linear(8, 1) # 输出层
)
def forward(self, x):
x = self.layers(x)
x = x.squeeze(1) # 去掉多余的维度
return x
这个结构的设计考虑了几个因素:
- 输入维度由特征数量决定,自动适配
- 隐藏层维度逐步减小(16→8→1),形成"漏斗"结构
- 使用ReLU激活函数避免梯度消失问题
- 最终输出为单个数值(阳性率预测值)
3.2 模型配置参数
合理的参数配置对训练效果至关重要,以下是本项目的主要配置:
python复制device = 'cuda' if torch.cuda.is_available() else 'cpu' # 自动检测GPU
config = {
'seed': 1122408, # 随机种子
'select_all': True, # 是否使用全部特征
'valid_radio': 0.2, # 验证集比例
'n_epochs': 3000, # 训练轮数
'batch_size': 256, # 批大小
'learning_rate': 1e-5, # 学习率
'early_stop': 400, # 早停轮数
'save_path': './models/model.ckpt' # 模型保存路径
}
经验分享:学习率设置是调参中最关键的环节之一。对于这种回归任务,通常需要设置较小的学习率(如1e-5到1e-4)。如果发现训练loss波动很大,可能是学习率过高;如果loss下降很慢,则可能是学习率过低。
4. 训练过程与优化技巧
4.1 训练循环实现
训练过程的核心代码包含了以下几个关键部分:
python复制def trainer(train_loader, valid_loader, model, config, device):
"""模型训练函数"""
criterion = nn.MSELoss(reduction='mean') # 使用均方误差损失
optimizer = torch.optim.SGD(model.parameters(), lr=config['learning_rate'], momentum=0.9)
writer = SummaryWriter() # TensorBoard可视化
# 创建模型保存目录
if not os.path.isdir('./models'):
os.mkdir('./models')
n_epochs = config['n_epochs']
best_loss = math.inf
step = 0
early_stop_count = 0
for epoch in range(n_epochs):
model.train()
loss_record = []
train_pbar = tqdm(train_loader, position=0, leave=True)
# 训练循环
for x, y in train_pbar:
optimizer.zero_grad()
x, y = x.to(device), y.to(device)
pred = model(x)
loss = criterion(pred, y)
loss.backward()
optimizer.step()
step += 1
loss_record.append(loss.detach().item())
# 更新进度条显示
train_pbar.set_description(f'Epoch [{epoch+1}/{n_epochs}]')
train_pbar.set_postfix({'loss': loss.detach().item()})
mean_train_loss = sum(loss_record) / len(loss_record)
writer.add_scalar('Loss/train', mean_train_loss, step)
# 验证循环
model.eval()
loss_record = []
for x, y in valid_loader:
x, y = x.to(device), y.to(device)
with torch.no_grad():
pred = model(x)
loss = criterion(pred, y)
loss_record.append(loss.detach().item())
mean_valid_loss = sum(loss_record) / len(loss_record)
print(f'Epoch [{epoch+1}/{n_epochs}]: Train loss: {mean_train_loss:.4f}, Valid loss: {mean_valid_loss:.4f}')
writer.add_scalar('Loss/valid', mean_valid_loss, step)
# 早停机制
if mean_valid_loss < best_loss:
best_loss = mean_valid_loss
torch.save(model.state_dict(), config['save_path'])
print('Saving model with loss {:.3f}.'.format(best_loss))
early_stop_count = 0
else:
early_stop_count += 1
if early_stop_count >= config['early_stop']:
print('\nModel is not improving, so we halt train session.')
return
4.2 关键训练技巧
-
学习率策略:虽然这里使用了固定学习率,但对于更复杂的任务,建议使用学习率调度器(如ReduceLROnPlateau)
-
优化器选择:SGD配合momentum(0.9)是经典配置,也可以尝试Adam优化器
-
早停机制:防止过拟合的关键,当验证集loss连续多轮不下降时停止训练
-
损失函数:回归任务通常使用MSE(均方误差),对于异常值较多的数据可以考虑MAE(平均绝对误差)
-
可视化监控:使用TensorBoard可以实时观察训练过程,便于调参
避坑指南:在训练深度学习模型时,一定要同时监控训练集和验证集的loss。如果训练集loss下降但验证集loss上升,很可能出现了过拟合。这时可以尝试减小模型复杂度、增加正则化或使用更多的训练数据。
5. 模型预测与结果分析
5.1 预测流程实现
训练完成后,我们需要用训练好的模型对新数据进行预测:
python复制def predict(test_loader, model, device):
"""模型预测函数"""
model.eval()
preds = []
for x in tqdm(test_loader):
x = x.to(device)
with torch.no_grad():
pred = model(x)
preds.append(pred.detach().cpu())
preds = torch.cat(preds, dim=0).numpy()
return preds
def save_pred(preds, file):
"""保存预测结果"""
with open(file, 'w') as fp:
writer = csv.writer(fp)
writer.writerow(['id', 'tested_positive'])
for i, p in enumerate(preds):
writer.writerow([i, p])
# 加载最佳模型并进行预测
model = My_Model(input_dim=x_train.shape[1]).to(device)
model.load_state_dict(torch.load(config['save_path']))
preds = predict(test_loader, model, device)
save_pred(preds, 'pred.csv')
5.2 结果分析与改进方向
得到预测结果后,我们需要评估模型性能并考虑可能的改进方向:
-
评估指标:除了MSE,还可以计算MAE、R²等指标全面评估模型
-
误差分析:检查哪些样本预测误差较大,分析可能的原因
-
特征重要性:可以通过permutation importance等方法分析各特征的重要性
-
模型改进:
- 尝试更复杂的网络结构
- 添加Batch Normalization层
- 使用更先进的优化器
- 引入正则化技术防止过拟合
-
数据增强:对训练数据进行合理的变换或合成,增加数据多样性
实战心得:在实际项目中,模型性能的提升往往更多来自数据质量的改进和特征工程,而不是模型结构的调整。建议先把重点放在数据清洗、特征选择和特征工程上,等这些方面做到位后再考虑使用更复杂的模型。
