前言:
这个博客《d2l-ai深度学习日记》将记录我在深度学习领域的学习与探索,特别是基于《动手学深度学习》这本经典教材的学习过程。在这个过程中,我不仅希望总结所学,还希望通过分享心得,与志同道合的朋友一起交流成长。这不仅是对知识的沉淀,也是我备战研究生考试、追逐学术进阶之路的一部分。
过去学习日志:
目录
一.从零开始实现暂退法
在上一次学习中,知道了过拟合这种概念,暂退法是缓解过拟合的一种方法.我们期待“好”的预测模型能在未知 的数据上有很好的表现
在训练过程中,在计算后续层之前向⽹络的每⼀层注⼊噪声。因为当训练⼀个有多层 的深层⽹络时,注⼊噪声只会在输⼊-输出映射上增强平滑性。这个想法被称为暂退法
看起来非常反直觉,虽然这会导致模型在训练集上面的效果没有以前好,但是可以使得模型在测试集(未见过的数据)上面表现更好,从而是的预测模型更"好"
1.dropout_layer 函数
简单地理解暂退法,就算如上图所示,随机丢弃隐藏层里面的特征:
def dropout_layer(X, dropout):
assert 0 <= dropout <= 1
# 在本情况中,所有元素都被丢弃
if dropout == 1:
return torch.zeros_like(X) # 在本情况中,所有元素都被保留
if dropout == 0:
return X
mask = (torch.rand(X.shape) > dropout).float()
return mask * X / (1.0 - dropout)
dropout是丢弃特征的概率
通过下⾯⼏个例⼦来测试dropout_layer函数:
X= torch.arange(16, dtype = torch.float32).reshape((2, 8))
print(X)
print(dropout_layer(X, 0.))
print(dropout_layer(X, 0.5))
print(dropout_layer(X, 1.))
输出:
tensor([[ 0., 1., 2., 3., 4., 5., 6., 7.],
[ 8., 9., 10., 11., 12., 13., 14., 15.]])
tensor([[ 0., 1., 2., 3., 4., 5., 6., 7.],
[ 8., 9., 10., 11., 12., 13., 14., 15.]])
tensor([[ 0., 2., 0., 0., 8., 10., 12., 14.],
[16., 0., 20., 22., 24., 26., 28., 0.]])
tensor([[0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0.]])
2.定义模型参数
num_inputs, num_outputs, num_hiddens1, num_hiddens2 = 784, 10, 256, 256
这些参数在之前的学习中,使用了很多遍了,不再赘述
3.定义模型
dropout1, dropout2 = 0.2, 0.5
class Net(nn.Module):
def __init__(self, num_inputs, num_outputs, num_hiddens1, num_hiddens2,is_training = True):
super(Net, self).__init__()
self.num_inputs = num_inputs
self.training = is_training
self.lin1 = nn.Linear(num_inputs, num_hiddens1)
self.lin2 = nn.Linear(num_hiddens1, num_hiddens2)
self.lin3 = nn.Linear(num_hiddens2, num_outputs)
self.relu = nn.ReLU()
def forward(self, X):
H1 = self.relu(self.lin1(X.reshape((-1, self.num_inputs)))) # 只有在训练模型时才使⽤dropout
if self.training == True:
# 在第⼀个全连接层之后添加⼀个dropout层
H1 = dropout_layer(H1, dropout1)
H2 = self.relu(self.lin2(H1))
if self.training == True:
# 在第⼆个全连接层之后添加⼀个dropout层
H2 = dropout_layer(H2, dropout2)
out = self.lin3(H2)
return out
net = Net(num_inputs, num_outputs, num_hiddens1, num_hiddens2)
此时的模型就比较复杂了,一共有四层:输入层,隐藏层1,隐藏层2,输出层.
经过输入层后,会经过激活函数:Relu,在经过隐藏层1前,会调用dropout_layer函数,dropout概率为0.2,即以0.2的概率丢弃隐藏层1的元素,同样地,在经过隐藏层2前,会调用dropout_layer函数,dropout概率为0.5,即以0.5的概率丢弃隐藏层1的元素.最后经过输出层.
4.训练和测试
这类似于前⾯描述的多层感知机训练和测试,依然使用mnist数据集
num_epochs, lr, batch_size = 10, 0.5, 256
loss = nn.CrossEntropyLoss(reduction='none')
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
trainer = torch.optim.SGD(net.parameters(), lr=lr)
train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)
二.暂退法的简介实现
1.定义模型和参数
net = nn.Sequential(nn.Flatten(),nn.Linear(784, 256),nn.ReLU(), nn.Dropout(dropout1),nn.Linear(256, 256), nn.ReLU(),nn.Dropout(dropout2),nn.Linear(256, 10))
# 在第⼀个全连接层之后添加⼀个dropout层
# 在第⼆个全连接层之后添加⼀个dropout层
def init_weights(m):
if type(m) == nn.Linear:
nn.init.normal_(m.weight, std=0.01)
net.apply(init_weights);
一行就解决了网络定义
2.训练和验证
trainer = torch.optim.SGD(net.parameters(), lr=lr)
train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)
三.实战Kaggle⽐赛:预测房价(初步实现)
在进行实践之前,我必须说明,在书<<动手学深度学习>>的多层感知器一章中,还有许多重要的章节:前向传播,后向传播,计算图,数值稳定性和模型初始化,环境和分布偏移.这些内容对于理解深度学习是非常重要的,但是这些章节代码几乎没有,取而代之的有很多复杂的数学公式,以我的水平难以讲清楚,我建议可以直接阅读书本.
1.准备工作
#@save
DATA_HUB = dict()
DATA_URL = 'http://d2l-data.s3-accelerate.amazonaws.com/'
def download(name, cache_dir=os.path.join('..', 'data')): #@save
# """下载⼀个DATA_HUB中的⽂件,返回本地⽂件名"""
assert name in DATA_HUB, f"{name} 不存在于 {DATA_HUB}"
url, sha1_hash = DATA_HUB[name]
os.makedirs(cache_dir, exist_ok=True)
fname = os.path.join(cache_dir, url.split('/')[-1])
if os.path.exists(fname):
sha1 = hashlib.sha1()
with open(fname, 'rb') as f:
while True:
data = f.read(1048576)
if not data:
break
sha1.update(data)
if sha1.hexdigest() == sha1_hash:
return fname # 命中缓存
print(f'正在从{url}下载{fname}...')
r = requests.get(url, stream=True, verify=True)
with open(fname, 'wb') as f:
f.write(r.content)
return fname
def download_extract(name, folder=None): #@save
# """下载并解压zip/tar⽂件"""
fname = download(name)
base_dir = os.path.dirname(fname)
data_dir, ext = os.path.splitext(fname)
if ext == '.zip':
fp = zipfile.ZipFile(fname, 'r')
elif ext in ('.tar', '.gz'):
fp = tarfile.open(fname, 'r')
else:
assert False, '只有zip/tar⽂件可以被解压缩'
fp.extractall(base_dir)
return os.path.join(base_dir, folder) if folder else data_dir
def download_all(): #@save
# """下载DATA_HUB中的所有⽂件"""
for name in DATA_HUB:
download(name)
DATA_HUB['kaggle_house_train'] = ( #@save
DATA_URL + 'kaggle_house_pred_train.csv', '585e9cc93e70b39160e7921475f9bcd7d31219ce')
DATA_HUB['kaggle_house_test'] = ( #@save
DATA_URL + 'kaggle_house_pred_test.csv', 'fa19780a7b011d9b009e8bff8e99922a8ee2eb90')
train_data = pd.read_csv(download('kaggle_house_train'))
test_data = pd.read_csv(download('kaggle_house_test'))
下载数据集的代码,直接粘贴复制就好,不用理解
下载完成之后是这样:
打印出数据集
print(train_data.shape)
print(test_data.shape)
print(train_data.iloc[0:4, [0, 1, 2, 3, -3, -2, -1]])
all_features = pd.concat((train_data.iloc[:, 1:-1], test_data.iloc[:, 1:]))
(1460, 81)
(1459, 80)
Id MSSubClass MSZoning LotFrontage SaleType SaleCondition SalePrice
0 1 60 RL 65.0 WD Normal 208500
1 2 20 RL 80.0 WD Normal 181500
2 3 60 RL 68.0 WD Normal 223500
3 4 70 RL 60.0 WD Abnorml 140000
2.数据预处理
首先处理缺失值,将所有缺失的值替换为相应特征的平均值.
然后,为了将所有特征放在⼀个共同的尺度上,我们通过将特征重新缩 放到零均值和单位⽅差来标准化数据
# 若⽆法获得测试数据,则可根据训练数据计算均值和标准差
numeric_features = all_features.dtypes[all_features.dtypes != 'object'].index
all_features[numeric_features] = all_features[numeric_features].apply(lambda x: (x - x.mean()) / (x.std()))
# 在标准化数据之后,所有均值消失,因此我们可以将缺失值设置为0
all_features[numeric_features] = all_features[numeric_features].fillna(0)
# “Dummy_na=True”将“na”(缺失值)视为有效的特征值,并为其创建指⽰符特征
all_features = pd.get_dummies(all_features, dummy_na=True)
all_features.shape
最后,对于字符串元素,我们使用独热编码(在d2l-ai深度学习日记之预备知识(一)-CSDN博客提到过,这里不做赘述)
# “Dummy_na=True”将“na”(缺失值)视为有效的特征值,并为其创建指⽰符特征
all_features = pd.get_dummies(all_features, dummy_na=True)
3.初始化网络以及参数
定义训练集特征和标签,训练集标签:
n_train = train_data.shape[0]
train_features = torch.tensor(all_features[:n_train].values, dtype=torch.float32)
test_features = torch.tensor(all_features[n_train:].values, dtype=torch.float32)
train_labels = torch.tensor(train_data.SalePrice.values.reshape(-1, 1), dtype=torch.float32)
定义损失函数和线性模型网络:
loss = nn.MSELoss()
in_features = train_features.shape[1]
def get_net():
net = nn.Sequential(nn.Linear(in_features,1))
return net
构造官方评分的指标函数,均⽅根误差:
def log_rmse(net, features, labels):
# 为了在取对数时进⼀步稳定该值,将⼩于1的值设置为1
clipped_preds = torch.clamp(net(features), 1, float('inf'))
rmse = torch.sqrt(loss(torch.log(clipped_preds),torch.log(labels)))
return rmse.item()
构建训练函数
def train(net, train_features, train_labels, test_features, test_labels,num_epochs, learning_rate, weight_decay, batch_size):
train_ls, test_ls = [], []
train_iter = d2l.load_array((train_features, train_labels), batch_size) # 这⾥使⽤的是Adam优化算法
optimizer = torch.optim.Adam(net.parameters(),lr = learning_rate,weight_decay = weight_decay)
for epoch in range(num_epochs):
for X, y in train_iter:
optimizer.zero_grad()
l = loss(net(X), y)
l.backward()
optimizer.step()
train_ls.append(log_rmse(net, train_features, train_labels))
if test_labels is not None:
test_ls.append(log_rmse(net, test_features, test_labels))
return train_ls, test_ls
这里使用的Adm优化算法不用去管,只需要知道dam优化 器的主要吸引⼒在于它对初始学习率不那么敏感就行.
构建计算模型精度的函数,这里使用K折交叉验证
def get_k_fold_data(k, i, X, y):
assert k > 1
fold_size = X.shape[0] // k
X_train, y_train = None, None
for j in range(k):
idx = slice(j * fold_size, (j + 1) * fold_size)
X_part, y_part = X[idx, :], y[idx]
if j == i:
X_valid, y_valid = X_part, y_part
elif X_train is None:
X_train, y_train = X_part, y_part
else:
X_train = torch.cat([X_train, X_part], 0)
y_train = torch.cat([y_train, y_part], 0)
return X_train, y_train, X_valid, y_valid
def k_fold(k, X_train, y_train, num_epochs, learning_rate, weight_decay,batch_size):
train_l_sum, valid_l_sum = 0, 0
for i in range(k):
data = get_k_fold_data(k, i, X_train, y_train)
net = get_net()
train_ls, valid_ls = train(net, *data, num_epochs, learning_rate,weight_decay, batch_size)
train_l_sum += train_ls[-1]
valid_l_sum += valid_ls[-1]
if i == 0:
d2l.plot(list(range(1, num_epochs + 1)), [train_ls, valid_ls],xlabel='epoch', ylabel='rmse', xlim=[1, num_epochs],legend=['train', 'valid'], yscale='log')
print(f'折{i + 1},训练log rmse{float(train_ls[-1]):f}, 'f'验证log rmse{float(valid_ls[-1]):f}')
return train_l_sum / k, valid_l_sum / k
4.训练
k, num_epochs, lr, weight_decay, batch_size = 5, 100, 5, 0, 64
train_l, valid_l = k_fold(k, train_features, train_labels, num_epochs, lr,weight_decay, batch_size)
print(f'{k}-折验证: 平均训练log rmse: {float(train_l):f}, 'f'平均验证log rmse: {float(valid_l):f}')
详细解释一下这里的超参数:
这里我们的数据并不是特别大,所以我们使用K折交叉验证,简单来说就是
将含有N个样本的数据集,分成K份,每份含有个样本。选择其中一份作为验证集,另外K − 1份作为训练集,验证集集就有K种情况。
在这里,我们将数据集分成了五份.
num_epochs是训练次数,我们训练100次
lr是学习率,我们的学习率为5
权重衰退设置为0,暂时不考虑权重衰退
batch理解为一次喂给神经网络的数据大小量,这里我们每次的数据输入64个.
结果:
5.生成预测结果并提交
def train_and_pred(train_features, test_features, train_labels, test_data,num_epochs, lr, weight_decay, batch_size):
net = get_net()
train_ls, _ = train(net, train_features, train_labels, None, None,num_epochs, lr, weight_decay, batch_size)
d2l.plot(np.arange(1, num_epochs + 1), [train_ls], xlabel='epoch',
ylabel='log rmse', xlim=[1, num_epochs], yscale='log')
print(f'训练log rmse:{float(train_ls[-1]):f}') # 将⽹络应⽤于测试集。
preds = net(test_features).detach().numpy() # 将其重新格式化以导出到Kaggle
test_data['SalePrice'] = pd.Series(preds.reshape(1, -1)[0])
submission = pd.concat([test_data['Id'], test_data['SalePrice']], axis=1)
submission.to_csv('submission.csv', index=False)
这个函数不用去细看,知道它是生成我的能够提交的submission.csv文件就行.
train_and_pred(train_features, test_features, train_labels, test_data,num_epochs, lr, weight_decay, batch_size)
结果:
现在我们就得到了可以提交的文件,打开Kaggle官网并且跳转到该问题:House Prices - Advanced Regression Techniques | Kaggle
登录后,点击参加比赛.这里需要翻墙加谷歌账号.参加之后,再提交作品
值得注意的是,只能提交csv格式文件,xlxs格式是不支持的
可以看到我第一次的成绩是8.52724
四.实战Kaggle⽐赛:预测房价(优化)
首先我已经能够成功提交了,下一步就算进行优化
点击LeaderBoard查看排行榜
可以看到,这个分数其实就算我的log_rmse函数输出结果,是损失函数输出.这个分数越小越好,第一名的损失为0,可以看到它已经实现了完美预测.
而我们的分数只能排在几千名开外
1.优化训练次数和学习率
作为神经网络最基本的几个超参,先从训练epoch和lr下手,无疑是最简单的.
由于训练时间非常短,我其他都先不修改,先将epoch设置为500,看看训练结果有什么变化
可以看到,有明显的优化,valid数据集上面的rmse从100epoch的8到了7.
提交试试
依然是四千名开外.
从上面的增大epoch来看,虽然模型在训练集上面的损失明显小于测试.但是随着epoch增加,模型的损失还在远远不断地减小,我认为可能还是未拟合状态.
为了节省时间,于是我直接将学习率从5提高到10
还是呈下降趋势,将学习率提高到20
可以看到,模型开始有过拟合趋势,在430epoch地时候,刚好达到拟合.提交试试
可以看到,仅仅修改训练次数和学习率,损失函数就下降了如此之多,这说明了超参的重要性.
2.优化权重衰减参数
可以看到,模型有明显的过拟合,于是我选择调整权重衰减参数,来缓解过拟合.简单来说:权重衰减是深度学习中的一种正则化技术,通常用于防止模型的过拟合。它通过在损失函数中增加一个与模型参数相关的项来控制模型的复杂度,从而约束模型参数的大小。
在查阅资料之后,我首先在上一步的基础上,取weight_decay为0.0001.
不行,没什么变化,之后我再取weight_decay为0.001,0.01,0.1也没什么变化
3.优化网络层
我现在的网络是单层线性网络,查询之后后发现,可能是模型结太过简单.
于是我打算扩充网络层,将我的网络改为实现暂退法的多层网络:
# 修改后的网络结构,加入两个隐藏层和dropout
def get_net(dropout1,dropout2):
net = nn.Sequential(
nn.Linear(in_features, 256), # 第一隐藏层,256个单元
nn.Dropout(dropout1), # 第一个Dropout层
nn.Linear(256, 128), # 第二隐藏层,128个单元
nn.Dropout(dropout2), # 第二个Dropout层
nn.Linear(128, 1) # 输出层
)
return net
修改k_fold以传入参数:
def k_fold(k, X_train, y_train, num_epochs, learning_rate, weight_decay, batch_size, dropout1, dropout2):
train_l_sum, valid_l_sum = 0, 0
for i in range(k):
print(f'--- 开始第 {i + 1} 折训练 ---') # 打印开始第几折训练
data = get_k_fold_data(k, i, X_train, y_train)
net = get_net(dropout1, dropout2)
train_ls, valid_ls = train(net, *data, num_epochs, learning_rate, weight_decay, batch_size)
train_l_sum += train_ls[-1]
valid_l_sum += valid_ls[-1]
print(f'折 {i + 1},最后一轮训练损失: {train_ls[-1]:.4f},验证损失: {valid_ls[-1]:.4f}')
if i == 0:
d2l.plot(list(range(1, num_epochs + 1)), [train_ls, valid_ls], xlabel='epoch', ylabel='rmse', xlim=[1, num_epochs], legend=['train', 'valid'], yscale='log')
print(f'折 {i + 1},训练log rmse {float(train_ls[-1]):f}, 验证log rmse {float(valid_ls[-1]):f}')
avg_train_loss = train_l_sum / k
avg_valid_loss = valid_l_sum / k
print(f'--- 训练完成,平均训练log rmse: {avg_train_loss:.4f},平均验证log rmse: {avg_valid_loss:.4f} ---') # 最终打印平均损失
return avg_train_loss, avg_valid_loss
训练:
k, num_epochs, lr, weight_decay, batch_size,dropout1,dropout2 = 5, 100, 5, 0, 64 ,0.2,0.5
train_l, valid_l = k_fold(k, train_features, train_labels, num_epochs, lr,weight_decay, batch_size,dropout1,dropout2 )
print(f'{k}-折验证: 平均训练log rmse: {float(train_l):f}, 'f'平均验证log rmse: {float(valid_l):f}')
这里修改epoch的原因是,网络变得复杂之后,训练速度明显变慢
结果:
模型更复杂了,结果可能更差了,于是我查阅资料,发现可能是学习率太高了,于是我调整参数:
k, num_epochs, lr, weight_decay, batch_size,dropout1,dropout2 = 5, 100, 0.001, 0, 64 ,0.2,0.5
训练集效果不错,但是验证机却不尽人意,还是存在过拟合问题
4.加入激活函数
# 修改后的网络结构,加入两个隐藏层和dropout
def get_net(dropout1,dropout2):
net = nn.Sequential(
nn.Linear(in_features, 256), # 第一隐藏层,256个单元
nn.ReLU(), # 激活函数
nn.Dropout(dropout1), # 第一个Dropout层
nn.Linear(256, 128), # 第二隐藏层,128个单元
nn.ReLU(), # 激活函数
nn.Dropout(dropout2), # 第二个Dropout层
nn.Linear(128, 1) # 输出层
)
return net
训练参数:
k, num_epochs, lr, weight_decay, batch_size,dropout1,dropout2 = 5, 200, 0.001, 0, 64 ,0.5,0.6
有明显的好转,但是还是过拟合严重,现在修改衰减权重:
k, num_epochs, lr, weight_decay, batch_size,dropout1,dropout2 = 5, 200, 0.001, 0.001, 64 ,0.5,0.6
相差不大,没有明显进步.
之后我尝试修改dropout参数也没有明显的进步
5.优化预处理
# 合并训练集和测试集以保证预处理一致性
n_train = train_data.shape[0]
all_features = pd.concat((train_data.iloc[:, :-1], test_data))
# 数值型特征:使用中位数填充缺失值,并进行标准化
numeric_features = all_features.select_dtypes(include=['float64', 'int64']).columns
all_features[numeric_features] = all_features[numeric_features].fillna(all_features[numeric_features].median())
# 类别型特征:使用 'Missing' 或众数填充
categorical_features = all_features.select_dtypes(include=['object']).columns
missing_fill_features = ['PoolQC', 'MiscFeature', 'Alley', 'Fence', 'FireplaceQu', 'GarageType', 'GarageFinish', 'GarageQual', 'GarageCond']
existing_missing_fill_features = [col for col in missing_fill_features if col in all_features.columns]
all_features[existing_missing_fill_features] = all_features[existing_missing_fill_features].fillna('Missing')
# 使用众数填充剩余类别特征的缺失值
for col in categorical_features:
if col not in existing_missing_fill_features:
all_features[col].fillna(all_features[col].mode()[0], inplace=True)
# 标准化数值特征
scaler = StandardScaler()
all_features[numeric_features] = scaler.fit_transform(all_features[numeric_features])
# 对类别特征进行独热编码
all_features = pd.get_dummies(all_features, columns=categorical_features, dummy_na=True)
# 将 bool 类型转换为 int 类型
all_features = all_features.astype(float)
# 检查数据类型是否有问题
print("Data types after converting bool to int:")
print(all_features.dtypes.value_counts())
# 检查是否存在 NaN 值并处理它们
if all_features.isnull().sum().sum() > 0:
print("Warning: NaN values detected after converting to numeric. Filling NaN with 0.")
all_features = all_features.fillna(0) # 用0填充所有NaN值
# 将训练集和测试集分开
train_features = torch.tensor(all_features[:n_train].values, dtype=torch.float32)
test_features = torch.tensor(all_features[n_train:].values, dtype=torch.float32)
train_labels = torch.tensor(train_data.SalePrice.values.reshape(-1, 1), dtype=torch.float32)
# 现在可以继续使用 train_features, test_features 和 train_labels 进行模型训练
之前是直接将所有数值特征的缺失值填充为 0,这明显是不合理的.
对于数值型特征,使用众数先填充缺失值,再进行标准化.
类别型特征:使用 'Missing' 或众数填充.
再进行独热编码
结果:
优化得非常好
又尝试使用更高级的预处理:
# 合并训练集和测试集以保证预处理一致性
n_train = train_data.shape[0]
all_features = pd.concat((train_data.iloc[:, :-1], test_data))
# 数值型特征:使用 K近邻插补处理缺失值
numeric_features = all_features.select_dtypes(include=['float64', 'int64']).columns
# 使用K近邻插补填充缺失值
imputer = KNNImputer(n_neighbors=5)
all_features[numeric_features] = imputer.fit_transform(all_features[numeric_features])
# 类别型特征:使用 'Missing' 或众数填充,并进行独热编码
categorical_features = all_features.select_dtypes(include=['object']).columns
missing_fill_features = ['PoolQC', 'MiscFeature', 'Alley', 'Fence', 'FireplaceQu', 'GarageType', 'GarageFinish', 'GarageQual', 'GarageCond']
existing_missing_fill_features = [col for col in missing_fill_features if col in all_features.columns]
all_features[existing_missing_fill_features] = all_features[existing_missing_fill_features].fillna('Missing')
# 使用众数填充剩余类别特征的缺失值
for col in categorical_features:
if col not in existing_missing_fill_features:
all_features[col].fillna(all_features[col].mode()[0], inplace=True)
# 对类别特征进行独热编码
all_features = pd.get_dummies(all_features, columns=categorical_features, dummy_na=True)
# 处理溢出问题:使用 clip 限制数据范围,防止对数变换时出现极端值
all_features[numeric_features] = all_features[numeric_features].clip(lower=1e-5, upper=1e5)
# 对数变换:对数值特征进行对数变换以使数据更符合正态分布
log_transformer = PowerTransformer(method='yeo-johnson')
all_features[numeric_features] = log_transformer.fit_transform(all_features[numeric_features])
# 标准化数值特征
scaler = StandardScaler()
all_features[numeric_features] = scaler.fit_transform(all_features[numeric_features])
# 检查是否存在 NaN 值并处理它们
if all_features.isnull().sum().sum() > 0:
print("Warning: NaN values detected after processing. Filling NaN with 0.")
all_features = all_features.fillna(0) # 用0填充所有NaN值
# 将数据类型转换为 float,确保不会出现 object 类型列
all_features = all_features.astype(float)
# 将训练集和测试集分开
train_features = torch.tensor(all_features[:n_train].values, dtype=torch.float32)
test_features = torch.tensor(all_features[n_train:].values, dtype=torch.float32)
train_labels = torch.tensor(train_data.SalePrice.values.reshape(-1, 1), dtype=torch.float32)
# 现在可以继续使用 train_features, test_features 和 train_labels 进行模型训练
把之前的使用众数填充改为了使用K近邻插补填充缺失值,使用 KNNImputer
来填充数值特征的缺失值。K近邻插补基于其他数据样本的相似性来填充缺失值,比简单的中位数填充更智能。并使用 PowerTransformer
(Yeo-Johnson
变换)来对数值特征进行对数变换。它支持正数和负数,能使数据更符合正态分布。
并且过拟合开始不那么严重,我修改参数:
k, num_epochs, lr, weight_decay, batch_size,dropout1,dropout2 = 5, 200, 0.0001, 0.0001, 64 ,0.5,0.8
6.动态学习率
将神经网络改为
# 修改后的网络结构,使用 Leaky ReLU 激活函数
def get_net(dropout1, dropout2):
net = nn.Sequential(
nn.Linear(in_features, 512), # 第一隐藏层,512个单元
nn.LeakyReLU(0.1), # 使用 Leaky ReLU 激活函数
nn.Dropout(dropout1), # 第一个Dropout层
nn.Linear(512, 256), # 第二隐藏层,256个单元
nn.LeakyReLU(0.1), # 使用 Leaky ReLU 激活函数
nn.Dropout(dropout2), # 第二个Dropout层
nn.Linear(256, 1) # 输出层
)
return net
将神经网络的单元增大,使得其更复杂,不过效果式微,所以我更换了激活函数.
然后修改参数:
k, num_epochs, lr, weight_decay, batch_size,dropout1,dropout2 = 5, 2000, 0.0005, 1e-6,256 ,0.2,0.5
发现rmse一直到2000个epoch还在缓慢下降,这说明在后面的epoch中,我们的学习率需要调整,需要使其下降得更快,于是我查阅资料,引入了动态学习率
def train(net, train_features, train_labels, test_features, test_labels, num_epochs, learning_rate, weight_decay, batch_size):
train_ls, test_ls = [], []
train_iter = d2l.load_array((train_features, train_labels), batch_size) # 使用Adam优化算法
optimizer = torch.optim.Adam(net.parameters(), lr=learning_rate, weight_decay=weight_decay)
# 引入动态学习率调度器,根据训练损失调整学习率
scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', factor=0.5, patience=10, verbose=True)
for epoch in range(num_epochs):
for X, y in train_iter:
optimizer.zero_grad()
l = loss(net(X), y)
l.backward()
optimizer.step()
# 记录训练损失并使用调度器
train_loss = log_rmse(net, train_features, train_labels) # 记录训练损失
train_ls.append(train_loss)
print(f'Epoch {epoch + 1}/{num_epochs},训练log rmse: {train_loss:.4f}') # 打印每一轮的训练损失
# 使用当前的训练损失来调整学习率
scheduler.step(train_loss)
# 如果有测试集,则计算并记录测试损失
if test_labels is not None:
test_ls.append(log_rmse(net, test_features, test_labels))
return train_ls, test_ls
学习率调度器:torch.optim.lr_scheduler.ReduceLROnPlateau
会在训练过程中检测损失变化,并在模型收敛速度减缓时降低学习率。你可以根据损失的变化动态调整学习率,使模型在训练后期更加平滑地找到全局最优点。
factor=0.5
表示每次调整时,将学习率缩小为之前的 50%。patience=10
表示如果 10 个 epoch 内损失没有显著下降,则调整学习率。
后续难以进步
五.小结
在这次学习中,初次接触过拟合这个概率,并且学习了过拟合的方法.并且开始进行实战,实战效果很不错,让我收获很多.尤其是领悟到了超参数:学习率等参数的重要性.
标签:num,features,nn,ai,感知机,train,d2l,test,net From: https://blog.csdn.net/Wyh666a/article/details/142505304