作业背景
Covid-19病例预测。
数据格式为40个州(one-hot形式)+前三天的其他数据(每天18个),总计93个feature。预测的目标是第三天的病例数据。
作业来源:作业Slide
目标:
1. 初步学习运用深度神经网络解决回归问题。
2. 了解深度神经网络的步骤。
3. 了解Pytorch的使用方法。
Simple Baseline
运行给定的代码就能够达到Simple Baseline。
核心的步骤包括:
1. 加载numpy,torch,matplotlib等库。注意需要指定模型的随机种子来保证模型的可恢复性。
2. 数据预处理类Covid19Dataset。根据不同的模式加载不同的数据。例如训练模式下需要将数据按照k折交叉验证法分成训练数据和验证数据。在测试模式下,加载特定的测试数据。之后需要用统一的Pytorch加载器包装待处理数据。
3. 深度神经网络的构造。构造包含两层全连接层的神经网络模型。使用MSE作为回归问题的损失函数。
4. 训练阶段创建模型的实例并进行迭代时训练。注意每次迭代都需要保存模型。训练结束后,对模型Loss随迭代次数的变化进行可视化,用于选取更合适的迭代次数。
5. 在验证集上进行模型准确率的分析验证。
6. 输出保存测试集的结果,并在Kaggle上查看模型得分。
Medium Baseline
Medium Baseline要求选取更加有效的特征。具体为选择40个州以及前两天的阳性病例特征用于训练。
class COVID19Dataset(Dataset):
''' Dataset for loading and preprocessing the COVID19 dataset '''
if not target_only:
feats = list(range(93))
else:
# Using 40 states & 2 tested_positive features (indices = 57 & 75)
feats = list(range(40)) + [57, 75]
pass
Strong Baseline
- 特征选择
这里选择与Covid-19关系密切的几个特征。也可以尝试使用斯皮尔曼系数计算不同特征与预测目标的相关性,选取相关性大的那些特征。
特征 | 含义 | 是否选取 |
---|---|---|
0-39 | 州 | ✔ |
cli | 类Covid-19病例 | ✔ |
ili | 类Covid-19病例 | ✔ |
hh_cmnty_cli | 类Covid-19病例 | ✔ |
nohh_cmnty_cli | 类Covid-19病例 | ✔ |
wearing_mask | Covid-19相关行为 | |
travel_outside_state | Covid-19相关行为 | |
work_outside_home | Covid-19相关行为 | |
shop | Covid-19相关行为 | |
restaurant | Covid-19相关行为 | |
spent_time | Covid-19相关行为 | |
large_event | Covid-19相关行为 | |
public_transit | Covid-19相关行为 | |
anxious | 调查者情绪状况 | |
public_transit | 调查者情绪状况 | |
depressed | 调查者情绪状况 | |
felt_isolated | 调查者情绪状况 | |
worried_become_ill | 调查者情绪状况 | |
worried_finances | 调查者情绪状况 | |
tested_positive | 预测目标 | ✔ |
- 模型参数的选取
激活函数以及损失函数的选取建议:
问题类型 | 最后一层激活函数 | 损失函数 |
---|---|---|
二分类问题 | sigmoid | binary_crossentropy |
多分类、单标签问题 | softmax | categorical_crossentropy |
多分类、多标签问题 | sigmoid | binary_crossentropy |
回归到任意值 | 无 | MSE/RMSE |
回归到 0~1 范围内的值 | sigmoid | mse 或 binary_crossentropy |
这里选择RMSE作为损失函数,因为最终Kaggle上的分数就是通过RMSE计算得出。模型最终的参数为:
class NeuralNet(nn.Module):
''' A simple fully-connected deep neural network '''
def __init__(self, input_dim):
super(NeuralNet, self).__init__()
self.net = nn.Sequential(
nn.Linear(input_dim, 256),
nn.ReLU(),
nn.Linear(256, 128),
nn.LeakyReLU(),
nn.Linear(128, 1)
)
# Mean squared error loss
self.criterion = nn.MSELoss(reduction='mean')
def forward(self, x):
return self.net(x).squeeze(1)
# L2正则项
def regularization(self, C):
regular = 0
for param in self.net.parameters():
regular += torch.norm(param, 2)
return C * regular
def cal_loss(self, pred, target):
# RMSE + L1/L2 regularization
return torch.sqrt(self.criterion(pred, target)) + self.regularization(0.15)
-
L1/L2 Regularization
L1/L2正则化都是防止过拟合的方式。正则化通过给损失增加模型惩罚项使模型的结构化风险最小。这里以回归问题为例子,其中C为正则项系数(一般可以选择-
):
- 正常的MSE
- L1正则化
- L2正则化
在Optimizer中,参数weight_decay表示使用L2正则化的系数C。
总结,L1正则化就是在loss function后边所加正则项为L1范数,加上L1范数容易得到稀疏解(0比较多)。L2正则化就是loss function后边所加正则项为L2范数的平方,加上L2正则相比于L1正则来说,得到的解比较平滑(不是稀疏),但是同样能够保证解中接近于0(但不是等于0,所以相对平滑)的维度比较多,降低模型的复杂度。
- 正常的MSE
-
Dropout
目的: Dropout可以比较有效的缓解过拟合的发生,在一定程度上达到正则化的效果。
操作:在前向传播的时候,Dropout让某个神经元的激活值以一定的概率p停止工作,这样可以使模型泛化性更强,因为它不会太依赖某些局部的特征。
具体流程:
- 训练阶段:
让某个神经元以概率p停止工作,换句话说是让它的激活函数值以概率p变为0。实现上就是以概率p的方式生成长度与权重数量相同的01数组从而隐藏部分权重。 - 猜测阶段:
预测模型的时候,每一个神经单元的权重参数要乘以概率p。目的是保持训练和测试数据的一致性。
当前Dropout被大量利用于全连接网络,而且一般认为设置为0.5或者0.3,而在卷积网络隐藏层中由于卷积自身的稀疏化以及稀疏化的ReLu函数的大量使用等原因,Dropout策略在卷积网络隐藏层中使用较少。总体而言,Dropout是一个超参,需要根据具体的网络、具体的应用领域进行尝试。
- 训练阶段:
-
数据归一化问题
进行数据归一化的原因:
原则:样本的所有特征,在特征空间中,对样本的距离产生的影响是同级的;
问题:特征数字化后,由于取值大小不同,造成特征空间中样本点的距离会被个别特征值所主导,而受其它特征的影响比较小;归一化的方法:
- 最值归一化
- 标准化
这里需要注意的是对于测试集的标准化应当选取训练集的平均数与方差,原因是测试集可能很小,平均值和方差不能反映大量数据的特征。这也是代码示例中提到的问题:
- 最值归一化
# Normalize features (you may remove this part to see what will happen)
self.data[:, 40:] = (self.data[:, 40:] - self.data[:, 40:].\
mean(dim=0, keepdim=True)) / self.data[:, 40:].std(dim=0, keepdim=True)
# 正确的处理方法为,train_mean和train_std为全局变量
if mode == 'train':
train_mean = self.data[:, 40:].mean(dim=0, keepdim=True)
train_std = self.data[:, 40:].std(dim=0, keepdim=True)
self.data[:, 40:] = (self.data[:, 40:] - train_mean) / train_std
-
mini-batch
mini-batch主要解决的问题是:实际应用时的训练数据往往都太大了,一次加载到电脑里可能内存不够,其次运行速度也很慢。mini-batch的思想就是将数据分批输入神经网络中,降低网络的负载量,也能加快网络的收敛速度。
具体来说,将所有输入数据分成固定大小的小批数据,每次训练只输入小批数据。同时,数据可以进行打乱操作增加数据的随机性提升网络的学习能力。
-
Optimizer
优化器是优化梯度下降的特定算法,这边简单介绍一下主流的优化器算法。
优化器 | 作用 |
---|---|
BGD | 原始的梯度下降算法,直接根据Loss更新权重。缺点是容易受到局部最优点的影响 |
SGD | 采用随机的小样本更新权重,增加更新次数,加快收敛速度。带动量的SGD考虑了上一次更新的权重,保证了每一次更新的梯度不为0从而逃离鞍点 |
AdaGrad | 每次迭代不断减小更新幅度 |
RMSprop | 与AdaGrad不同,使用了衰减率更新的梯度 |
Adam | Adam使用momentum和Bias correction更新梯度,涉及的参数包括alpha和beta。Adam的下降速度比较快,应用广泛 |
- 最终实现
模型配置信息:
config = {
'n_epochs': 3000,
'batch_size': 100,
'optimizer': 'SGD',
'optim_hparas': {
'lr': 0.001,
'momentum': 0.9
},
'early_stop': 200,
'save_path': 'models/model.pth'
}
Kaggle上最终结果:
Private 数据集没有达到Strong baseline,模型方面可以继续调优。
参考文章