1. 项目概述与背景
新冠疫情爆发以来,准确预测感染人数成为公共卫生决策的重要依据。这个Kaggle竞赛项目提供了一个绝佳的实践机会,让我们能够运用深度学习技术构建回归模型来预测新冠感染人数。不同于一般的分类任务,回归问题要求模型能够输出连续数值,这对特征工程和模型设计都提出了特殊要求。
我在实际开发中发现,处理这类时间序列相关的回归问题时,有几个关键点需要特别注意:
- 特征标准化对模型收敛至关重要
- 合理的正则化策略能有效防止过拟合
- 损失函数的选择直接影响预测精度
这个项目虽然基于Kaggle竞赛,但我更关注的是完整实现一个端到端的深度学习解决方案,而非单纯追求竞赛排名。下面我将详细拆解整个实现过程,包括数据预处理、模型构建、训练优化等关键环节。
2. 数据准备与预处理
2.1 数据集结构与特点
原始数据以CSV格式存储,包含以下关键特征:
- 前93列为各种流行病学指标和人口统计特征
- 最后一列为待预测的阳性检测人数
- 训练集约27000条记录,测试集约9000条记录
注意:实际处理时发现某些特征存在量纲差异大的问题,比如人口数量与温度值的数值范围相差几个数量级,必须进行标准化处理。
2.2 自定义数据集类实现
PyTorch的Dataset类让我们能够灵活地封装数据加载逻辑。以下是核心实现要点:
python复制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)
# 数据集划分策略
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]
# 特征标准化
X = torch.tensor(csv_data[indices, :93])
self.X = (X - X.mean(dim=0, keepdim=True)) / X.std(dim=0, keepdim=True)
# 标签处理
if mode != "test":
self.Y = torch.tensor(csv_data[indices, -1])
关键处理步骤解析:
- 数据清洗:跳过标题行和索引列,将字符串转为浮点数
- 数据集划分:采用简单的按5分位划分,实际项目中建议使用sklearn的train_test_split
- 特征标准化:按列进行Z-score标准化,公式为 (x - μ)/σ
- 内存优化:使用float32而非默认的float64减少内存占用
2.3 数据加载器配置
python复制train_loader = DataLoader(train_set, batch_size=16, shuffle=True)
val_loader = DataLoader(val_set, batch_size=16, shuffle=True)
test_loader = DataLoader(test_set, batch_size=1, shuffle=False)
批次大小设置为16的考虑:
- 较小批次能更好利用GPU并行计算
- 避免单个批次内存占用过大
- 是2的幂次方,某些GPU架构对此有优化
3. 模型架构设计
3.1 全连接网络结构
采用两层全连接网络作为基础架构:
python复制class myModel(nn.Module):
def __init__(self, inDim):
super().__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)
return x.squeeze(1) if len(x.size()) > 1 else x
设计选择背后的考量:
- 输入维度:93对应原始特征数量
- 隐藏层大小:128个神经元在实验中获得较好效果
- 激活函数:ReLU相比Sigmoid能缓解梯度消失问题
- 输出处理:squeeze操作确保输出形状与标签匹配
3.2 正则化策略
自定义带L2正则化的MSE损失函数:
python复制def mseLoss(pred, target, model):
base_loss = nn.MSELoss(reduction='mean')
reg_loss = 0
for param in model.parameters():
reg_loss += torch.sum(param**2)
return base_loss(pred, target) + 0.00075 * reg_loss
正则化系数0.00075的确定:
- 初始尝试常用值0.001
- 通过网格搜索在0.0001到0.001之间微调
- 最终选择验证集表现最好的参数
4. 模型训练与优化
4.1 训练流程实现
python复制def train_val(model, train_loader, val_loader, lr, optimizer, device, epochs, save_path):
model = model.to(device)
train_losses, val_losses = [], []
min_val_loss = float('inf')
for epoch in range(epochs):
# 训练阶段
model.train()
train_loss = 0
for x, y in train_loader:
x, y = x.to(device), y.to(device)
optimizer.zero_grad()
pred = model(x)
loss = mseLoss(pred, y, model)
loss.backward()
optimizer.step()
train_loss += loss.item()
# 验证阶段
model.eval()
val_loss = 0
with torch.no_grad():
for x, y in val_loader:
x, y = x.to(device), y.to(device)
pred = model(x)
val_loss += mseLoss(pred, y, model).item()
# 记录与保存
avg_train_loss = train_loss/len(train_loader)
avg_val_loss = val_loss/len(val_loader)
if avg_val_loss < min_val_loss:
min_val_loss = avg_val_loss
torch.save(model, save_path)
关键训练技巧:
- 混合精度训练:可考虑使用torch.cuda.amp加速训练
- 学习率调度:添加ReduceLROnPlateau动态调整学习率
- 早停机制:当验证损失连续多轮不下降时终止训练
4.2 优化器选择
使用带动量的SGD优化器:
python复制optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9)
相比Adam优化器的优势:
- 更适合小规模数据集
- 参数更新更稳定
- 最终收敛效果更好
5. 模型评估与结果分析
5.1 测试集预测实现
python复制def evaluate(model_path, test_loader, rel_path, device):
model = torch.load(model_path).to(device)
model.eval()
predictions = []
with torch.no_grad():
for x in test_loader:
x = x.to(device)
pred = model(x)
predictions.append(pred.cpu().item())
# 结果保存
with open(rel_path, "w", newline="") as f:
writer = csv.writer(f)
writer.writerow(["id", "tested_positive"])
for i, pred in enumerate(predictions):
writer.writerow([i, f"{pred:.4f}"])
输出处理细节:
- 保留4位小数提高结果精度
- 确保ID与预测值严格对应
- 采用无BOM的UTF-8编码避免乱码
5.2 性能评估指标
除基础的MSE外,还可考虑:
- MAE:对异常值不敏感
python复制
nn.L1Loss()(predictions, targets) - R²分数:解释方差比例
python复制from sklearn.metrics import r2_score - 相对误差:评估预测偏离程度
5.3 训练过程可视化
绘制训练/验证损失曲线:
python复制plt.plot(train_losses, label='Train')
plt.plot(val_losses, label='Validation')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()
plt.title('Training Progress')
plt.show()
典型曲线分析:
- 健康收敛:两条曲线同步下降后趋于平稳
- 过拟合:训练损失持续下降但验证损失上升
- 欠拟合:两条曲线都处于较高位置
6. 实战经验与优化建议
6.1 常见问题排查
-
梯度爆炸:
- 现象:损失值变为NaN
- 解决:添加梯度裁剪
torch.nn.utils.clip_grad_norm_
-
模型不收敛:
- 检查数据标准化是否正确
- 尝试更小的学习率
- 验证网络结构是否合理
-
过拟合:
- 增加Dropout层
- 加强L2正则化
- 获取更多训练数据
6.2 进阶优化方向
-
特征工程:
- 尝试多项式特征扩展
- 添加滞后特征捕捉时间依赖性
- 使用PCA降维
-
模型架构:
- 引入注意力机制
- 尝试LSTM处理时序特征
- 使用残差连接加深网络
-
集成方法:
- 训练多个模型取平均
- 使用Stacking融合不同模型
- 实现模型快照集成
6.3 部署注意事项
- 模型量化:
python复制
torch.quantization.quantize_dynamic(model, dtype=torch.qint8) - ONNX导出:
python复制torch.onnx.export(model, dummy_input, "model.onnx") - API封装:
- 使用Flask/FastAPI创建预测接口
- 添加输入数据验证
- 实现批处理预测支持
在实际部署到生产环境时,建议添加完整的监控和日志系统,记录预测请求的响应时间、输入数据分布和预测结果统计,这对后续模型迭代非常重要。
