- 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
- 🍖 原作者:K同学啊
一、实验目的:
探索本案例是否还有进一步优化的空间
二、实验环境:
- 语言环境:python 3.8
- 编译器:Jupyter notebook
- 深度学习环境:Pytorch
- torch==2.4.0+cu124
- torchvision==0.19.0+cu124
三、数据集标准化处理
对比R6的代码,本案例的改进部分主要是取消了两行代码的注释# 数据集标准化处理
from sklearn.preprocessing import StandardScaler
# '高密度脂蛋白胆固醇'字段与糖尿病负相关,故而在 X 中去掉该字段
X = DataFrame.drop(['是否糖尿病','高密度脂蛋白胆固醇'],axis=1)
y = DataFrame['是否糖尿病']
# 数据集标准化处理
sc_X = StandardScaler()
X = sc_X.fit_transform(X)
X = torch.tensor(np.array(X), dtype=torch.float32)
y = torch.tensor(np.array(y), dtype=torch.int64)
train_X, test_X, train_y, test_y = train_test_split(X, y,
test_size=0.2,
random_state=1)
train_X.shape, train_y.shape
代码输出结果如下:
对比R6的结果可以看到测试集准确率有了明显的提高,而原因我也有在上一次总结中提及。结果差异大的可能原因分析如下:
数据处理差异
- 特征选择不同
- 在R6中,除了删除
'高密度脂蛋白胆固醇'
和'是否糖尿病'
,还删除了'卡号'
。这意味着R6代码使用的特征集比R7少了一个额外的特征。不同的特征集会对模型的学习和预测能力产生影响。'卡号'
这个特征本身包含了一些与糖尿病相关的潜在信息(不同卡号对应的人群有不同的糖尿病发病倾向等),那么去掉它会改变模型的行为。
- 在R6中,除了删除
- 标准化处理的有无
- R7代码对数据进行了标准化处理(
sc_X.fit_transform(X)
),而R6没有。标准化可以使数据的特征具有相似的尺度,这对于一些基于距离或梯度的算法(如神经网络中的梯度下降)非常重要。如果数据没有标准化,可能会导致某些特征在模型训练过程中对损失函数的影响过大或过小,从而影响模型的收敛速度和性能。例如,在神经网络中,如果某个特征的值域很大,而另一个特征的值域很小,那么在计算梯度时,可能会使模型过度关注值域大的特征,而忽略值域小的特征。
- R7代码对数据进行了标准化处理(
模型训练相关因素
- 初始条件差异
- 模型的初始参数(如神经网络的权重初始化)可能会因为数据的不同而导致不同的训练轨迹。不同的数据分布(由于数据处理的不同)可能会使模型在相同的初始化策略下朝着不同的方向优化。
- 训练动态变化
- 由于数据的改变,模型在训练过程中的梯度更新情况也会不同。R6代码中没有标准化的数据可能会导致梯度在某些方向上变化剧烈,影响模型收敛。而在R7中,标准化后的数据可能使梯度更新更加稳定和合理,导致训练和测试准确率的变化趋势不同。而且,不同的数据特征可能在不同的训练阶段对模型的贡献不同,进而影响模型在每个 epoch 的准确率和损失值。
四、总结
标准化处理对模型训练的具体影响:
-
加快收敛速度
- 原理:在未进行标准化处理时,不同特征的取值范围可能差异巨大。例如,一个特征的取值范围是0 - 1,另一个特征的取值范围是0 - 1000。在基于梯度下降的优化算法中,如随机梯度下降(SGD),损失函数的梯度更新会受到特征尺度的影响。对于取值范围大的特征,其梯度更新步长可能会过大,导致模型难以收敛到最优解;而对于取值范围小的特征,其梯度更新步长可能过小,模型学习这些特征的速度会很慢。标准化将数据变换到均值为0、标准差为1的分布,使得不同特征具有相似的尺度。这样,在梯度更新时,各个特征能够以相对平衡的步长进行学习,从而加快模型收敛速度。
- 示例:假设我们有一个简单的线性回归模型 y = w 1 x 1 + w 2 x 2 + b y = w_1x_1+w_2x_2 + b y=w1x1+w2x2+b,其中 x 1 x_1 x1的取值范围是[0, 1], x 2 x_2 x2的取值范围是[0, 100]。如果不进行标准化,当更新 w 1 w_1 w1和 w 2 w_2 w2的梯度时,由于 x 2 x_2 x2的取值范围大, w 2 w_2 w2的更新步长会比 w 1 w_1 w1大很多。经过标准化后, x 1 x_1 x1和 x 2 x_2 x2的尺度变得相似, w 1 w_1 w1和 w 2 w_2 w2可以以更合理的步长进行更新,模型能够更快地找到合适的权重组合。
-
提高模型精度
- 原理:标准化可以减少数据中异常值和离群点对模型训练的影响。在一些机器学习算法中,如支持向量机(SVM)和神经网络,异常值可能会导致模型过度拟合这些异常数据点,从而降低模型在其他正常数据点上的泛化能力。标准化通过将数据压缩到一个相对稳定的范围内,降低了异常值的影响,使模型能够更好地学习数据的整体分布规律,进而提高模型的精度。
- 示例:考虑一个K - 近邻(K - NN)分类器,它基于数据点之间的距离进行分类。如果存在一个特征有很大的取值范围,并且有一些离群点,那么这些离群点会在距离计算中占据主导地位,导致分类错误。通过标准化,所有特征的取值范围变得相对均匀,离群点的影响被减弱,K - NN分类器能够更准确地根据数据的真实分布进行分类。
-
增强模型稳定性和鲁棒性
- 原理:在模型训练过程中,数据的微小变化可能会导致模型性能的大幅波动。标准化后的数据在一定程度上减少了这种波动,因为它使得数据的分布更加稳定。对于神经网络等复杂模型,这种稳定性尤为重要,因为它们通常具有大量的参数,容易受到数据变化的影响。标准化可以帮助模型在不同的数据集(如训练集和测试集)上保持相对一致的性能,提高模型的鲁棒性。
- 示例:在深度学习中,当使用小批量梯度下降(Mini - Batch Gradient Descent)训练模型时,每个小批量数据的分布可能会有所不同。如果没有标准化,模型可能会因为小批量数据的差异而产生较大的性能波动。而标准化后,小批量数据的分布更加稳定,模型的训练过程更加平稳,对数据变化的敏感度降低,从而增强了模型的鲁棒性。
除了标准化处理,还有下述数据预处理方法:
- 数据清洗
- 缺失值处理
- 删除法:当数据集中的缺失值占比较小,且缺失是完全随机的情况下,可以直接删除含有缺失值的行或列。例如,在一个包含1000条记录的客户信息数据集中,如果只有少数几条记录的“联系电话”字段缺失,且这些缺失看起来是随机发生的,那么可以考虑删除这些记录。
- 插补法:
- 均值/中位数/众数插补:对于数值型特征的缺失值,可以使用该特征的均值、中位数来填充。比如,在一个学生成绩数据集中,如果某个学生的数学成绩缺失,可以用全班数学成绩的均值来填充。对于分类型特征,则可以使用众数填充,例如,在一个调查问卷数据集中,“性别”字段有缺失值,就可以用出现频率最高的性别(众数)来填充。
- 回归插补:通过建立一个回归模型,利用数据集中其他相关特征来预测缺失值。假设在一个房屋价格数据集中,“房屋面积”字段有缺失值,可以建立一个以房屋价格、房间数量等其他特征为自变量,房屋面积为因变量的回归模型,然后利用该模型预测缺失的房屋面积。
- 异常值处理
- 盖帽法:将超出一定范围(如大于某个分位数加上几倍的四分位距,或小于某个分位数减去几倍的四分位距)的异常值替换为该范围的边界值。例如,在一个员工工资数据集中,若发现工资数据有异常高的值,可以将这些异常高的值替换为上四分位数加上1.5倍四分位距的值。
- 删除法:当异常值对模型训练有严重干扰,且其出现是由于数据录入错误等原因时,可以直接删除异常值。例如,在一个体温测量数据集中,如果出现了明显不符合人体正常体温范围(如50℃)的异常值,且确定是测量错误导致的,就可以删除。
- 缺失值处理
- 数据编码
- 独热编码(One - Hot Encoding):主要用于处理分类型数据。对于一个具有 n n n个类别(如颜色有红、绿、蓝三种)的分类变量,会创建 n n n个新的二进制变量(0或1)来表示。例如,对于“颜色”这个分类变量,会创建“红色”、“绿色”、“蓝色”三个新变量,当原始数据为红色时,“红色”变量为1,其他两个为0。这种编码方式可以避免模型将分类变量的类别顺序错误地理解为数值大小关系。
- 标签编码(Label Encoding):将分类变量的类别转换为整数。例如,对于一个包含“低”、“中”、“高”三个类别的变量“风险等级”,可以将“低”编码为0,“中”编码为1,“高”编码为2。不过这种编码方式可能会让模型误解类别之间的顺序关系,所以在一些对类别顺序不敏感的模型(如决策树)中可以使用,而在对顺序敏感的模型(如线性回归)中可能需要谨慎使用。
- 数据变换
- 对数变换:对于一些具有正偏态分布(数据大部分集中在左侧,右侧有较长的尾巴)的数据,如收入数据、人口数据等,可以使用对数变换将其转换为近似正态分布。例如,对一组公司营业收入数据进行对数变换后,数据的分布会更加对称,这样更符合一些模型(如基于正态分布假设的线性回归)的假设,有助于提高模型性能。
- 平方根变换:和对数变换类似,主要用于改善数据的分布形态。当数据的偏态程度不是特别严重时,平方根变换可能是一种有效的方法。例如,对于一些生物实验中的细胞计数数据,其分布可能存在一定的偏态,通过平方根变换可以使其分布更加合理,方便后续模型的训练。
五、模型优化探索
1. 调整模型结构
-
增加更多的层:可以考虑增加
LSTM
层的数量。更多的层可能能够捕捉到更复杂的数据模式。例如,可以增加到3层或4层的LSTM
,但要注意防止过拟合,可以结合正则化方法。 -
添加Dropout层:在
LSTM
层之间或者在全连接层之前添加Dropout
层。Dropout
在训练过程中随机忽略一些神经元,有助于减少过拟合。比如,可以在每个LSTM
层之后添加Dropout
层,设置Dropout
概率为0.2 - 0.5之间。
class model_lstm(nn.Module):
def __init__(self):
super(model_lstm, self).__init__()
self.lstm0 = nn.LSTM(input_size=13, hidden_size=200, num_layers=1, batch_first=True)
self.dropout1 = nn.Dropout(0.2) # 添加Dropout层,概率为0.2
self.lstm1 = nn.LSTM(input_size=200, hidden_size=200, num_layers=1, batch_first=True)
self.dropout2 = nn.Dropout(0.2) # 添加另一个Dropout层
self.fc0 = nn.Linear(200, 2)
def forward(self, x):
out, hidden1 = self.lstm0(x)
out = self.dropout1(out) # 使用Dropout
out, _ = self.lstm1(out, hidden1)
out = self.dropout2(out) # 使用Dropout
out = self.fc0(out)
return out
2. 优化超参数
- 调整隐藏层大小:当前
LSTM
的隐藏层大小是200,可以尝试不同的值,如128、256、300等。通过交叉验证等方法来确定最佳的隐藏层大小,以提高模型的性能。 - 调整学习率:使用不同的学习率策略,如学习率衰减。可以从一个相对较大的学习率开始,随着训练的进行逐渐降低学习率。例如,使用Adam优化器,并设置初始学习率为0.001,然后每几个
epoch
按照一定比例(如0.9)降低学习率。
from torch.optim.lr_scheduler import StepLR
loss_fn = nn.CrossEntropyLoss() # 创建损失函数
learn_rate = 1e-3 # 初始学习率增大为1e - 3
opt = torch.optim.Adam(model.parameters(), lr=learn_rate)
scheduler = StepLR(opt, step_size=10, gamma=0.1) # 添加学习率调度器
epochs = 30
train_loss = []
train_acc = []
test_loss = []
test_acc = []
for epoch in range(epochs):
model.train()
epoch_train_acc, epoch_train_loss = train(train_dl, model, loss_fn, opt)
model.eval()
epoch_test_acc, epoch_test_loss = test(test_dl, model, loss_fn)
train_acc.append(epoch_train_acc)
train_loss.append(epoch_train_loss)
test_acc.append(epoch_test_acc)
test_loss.append(epoch_test_loss)
# 获取当前的学习率
lr = opt.state_dict()['param_groups'][0]['lr']
scheduler.step() # 更新学习率
template = ('Epoch:{:2d}, Train_acc:{:.1f}%, Train_loss:{:.3f}, Test_acc:{:.1f}%, Test_loss:{:.3f}, Lr:{:.2E}')
print(template.format(epoch + 1, epoch_train_acc * 100, epoch_train_loss,
epoch_test_acc * 100, epoch_test_loss, lr))
print("=" * 20, 'Done', "=" * 20)
3. 改进输入数据处理
- 特征工程:进一步分析输入的13个特征,看是否可以通过特征组合、特征变换等方式提高数据的质量。例如,如果某些特征之间存在非线性关系,可以尝试添加一些新的特征来表示这种关系。
- 数据增强(如果适用):如果数据具有一定的可变性,可以考虑数据增强方法。例如,如果数据是时间序列数据,可以通过对数据进行小幅度的时间平移、缩放等操作来增加数据量和多样性。
4. 模型集成
- 使用多个模型进行集成:训练多个不同初始化或者不同超参数的
LSTM
模型,然后将它们的预测结果进行集成,如简单平均或者加权平均。这样可以利用不同模型的优势,提高整体的预测准确性。
经过上述调整模型结构、优化超参数后(特征工程效果不明显),效果有所提升,结果输出如下:
5. 改进空间
-
进一步调整模型结构
- 增加更多层或调整层大小:虽然已经添加了一些改进,但可以继续尝试增加
LSTM
的层数或者调整隐藏层大小。例如,可以尝试将LSTM
的层数增加到3层,同时适当调整每层的隐藏单元数量,如将隐藏单元数量从200增加到256或300等,然后观察模型性能的变化。 - 尝试不同类型的层:可以考虑在模型中添加其他类型的神经网络层,如卷积层(如果数据结构适合)。对于时间序列数据或具有局部相关性的数据,卷积层可以提取数据中的局部特征,与
LSTM
层结合可能会提高模型性能。
- 增加更多层或调整层大小:虽然已经添加了一些改进,但可以继续尝试增加
-
优化超参数
- 更精细的学习率调整:目前使用了简单的学习率衰减策略,可以尝试更复杂的学习率调整方法,如余弦退火学习率(Cosine Annealing Learning Rate)。这种方法可以使学习率在训练过程中按照余弦函数的规律变化,在前期可以有较大的学习率快速收敛,后期学习率逐渐减小以更精细地调整模型参数。
- 调整Dropout概率:当前设置的
Dropout
概率为0.2,可以尝试不同的值,如0.3、0.4等,观察对模型过拟合情况的影响。不同的Dropout
概率可能会改变模型的复杂度和泛化能力。
-
改进特征工程
- 深入分析数据相关性:对新添加的特征进行更深入的分析,检查它们与目标变量的相关性。如果某些新特征与目标变量相关性较低,可以考虑删除或进一步修改这些特征的构建方式。同时,可以探索更多的特征组合和变换方法,以挖掘数据中更有价值的信息。
- 添加领域相关特征(如果可能):根据数据的领域知识,添加更多有意义的特征。例如,如果是医疗数据,可以考虑添加一些与疾病相关的衍生特征,如不同指标之间的比率等。
-
模型集成
- 使用多种模型集成:除了单一的
LSTM
模型,可以尝试将其与其他类型的模型(如决策树、支持向量机等)进行集成。例如,可以使用堆叠(Stacking)或混合(Blending)等集成方法,将不同模型的预测结果进行组合,以提高模型的泛化能力和稳定性。 - 训练多个不同初始化的LSTM模型并集成:训练多个不同初始化参数的
LSTM
模型,然后通过平均或加权平均等方式将它们的预测结果集成在一起。这种方法可以利用不同初始化模型的多样性,降低模型的方差,提高预测性能。
- 使用多种模型集成:除了单一的