首页 > 其他分享 >d2l-ai深度学习日记(三)-多层感知机

d2l-ai深度学习日记(三)-多层感知机

时间:2024-09-28 15:54:40浏览次数:12  
标签:num features nn ai 感知机 train d2l test net

 前言:

这个博客《d2l-ai深度学习日记》将记录我在深度学习领域的学习与探索,特别是基于《动手学深度学习》这本经典教材的学习过程。在这个过程中,我不仅希望总结所学,还希望通过分享心得,与志同道合的朋友一起交流成长。这不仅是对知识的沉淀,也是我备战研究生考试、追逐学术进阶之路的一部分。

过去学习日志:

d2l-ai深度学习日记(二)-CSDN博客

d2l-ai深度学习日记(一)-CSDN博客

d2l-ai深度学习日记之预备知识(二)-CSDN博客

d2l-ai深度学习日记之预备知识(一)-CSDN博客

目录

 前言:

一.从零开始实现暂退法

1.dropout_layer 函数

2.定义模型参数

3.定义模型

二.暂退法的简介实现

1.定义模型和参数

2.训练和验证

三.实战Kaggle⽐赛:预测房价(初步实现)

1.准备工作

2.数据预处理

3.初始化网络以及参数

4.训练

四.实战Kaggle⽐赛:预测房价(优化)

1.优化训练次数和学习率

2.优化权重衰减参数

3.优化网络层

4.加入激活函数

5.优化预处理

6.动态学习率

五.小结


一.从零开始实现暂退法

在上一次学习中,知道了过拟合这种概念,暂退法是缓解过拟合的一种方法.我们期待“好”的预测模型能在未知 的数据上有很好的表现

在训练过程中,在计算后续层之前向⽹络的每⼀层注⼊噪声。因为当训练⼀个有多层 的深层⽹络时,注⼊噪声只会在输⼊-输出映射上增强平滑性。这个想法被称为暂退法

看起来非常反直觉,虽然这会导致模型在训练集上面的效果没有以前好,但是可以使得模型在测试集(未见过的数据)上面表现更好,从而是的预测模型更"好"

1.dropout_layer 函数

bc2d1233f03d4a15b3f911bd3500bfa7.png

简单地理解暂退法,就算如上图所示,随机丢弃隐藏层里面的特征:

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)

e2c37ac080954012bf6819d6f8615292.png

二.暂退法的简介实现

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)

836edf5901b24ec19780e42efe6bdb76.png

三.实战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'))

下载数据集的代码,直接粘贴复制就好,不用理解

下载完成之后是这样:

e6054df1a8ae405e9bbe6ed73283c153.png

打印出数据集

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.数据预处理

首先处理缺失值,将所有缺失的值替换为相应特征的平均值.

然后,为了将所有特征放在⼀个共同的尺度上,我们通过将特征重新缩 放到零均值和单位⽅差来标准化数据

eq?x%5Cleftarrow%20%5Cfrac%7Bx-%5Cmu%20%7D%7B%5Csigma%20%7D

# 若⽆法获得测试数据,则可根据训练数据计算均值和标准差 
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

构造官方评分的指标函数,均⽅根误差:

eq?%5Csqrt%7B%5Cfrac%7B1%7D%7Bn%7D%5Csum_%7Bi%3D1%7D%5E%7Bn%7D%28log%28y_%7Bi%7D%29-log%28%5Chat%7By_%7Bi%7D%7D%29%29%5E%7B2%7D%7D

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份,每份含有eq?%5Cfrac%7BN%7D%7BK%7D个样本。选择其中一份作为验证集,另外K − 1份作为训练集,验证集集就有K种情况。

在这里,我们将数据集分成了五份.

num_epochs是训练次数,我们训练100次

lr是学习率,我们的学习率为5

权重衰退设置为0,暂时不考虑权重衰退

batch理解为一次喂给神经网络的数据大小量,这里我们每次的数据输入64个.

结果:

922a683c9a3645cd9cc3bc88dac2ee1c.png

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)

结果:

b49f9a1bb2a0441ca53da4f2c7edc79f.png

5c007d170c854614821c12b9f7c2a41e.png

现在我们就得到了可以提交的文件,打开Kaggle官网并且跳转到该问题:House Prices - Advanced Regression Techniques | Kaggle

95689518443f4c5294bcded35a9da266.png

登录后,点击参加比赛.这里需要翻墙加谷歌账号.参加之后,再提交作品

54cd9c35a34648ec99525021318df33e.png

值得注意的是,只能提交csv格式文件,xlxs格式是不支持的

515badecc4b040d0b5010b2a1b9379a4.png

可以看到我第一次的成绩是8.52724

四.实战Kaggle⽐赛:预测房价(优化)

首先我已经能够成功提交了,下一步就算进行优化

点击LeaderBoard查看排行榜

4e0eddaafb4b4bd09b113fda4296ef66.png

可以看到,这个分数其实就算我的log_rmse函数输出结果,是损失函数输出.这个分数越小越好,第一名的损失为0,可以看到它已经实现了完美预测.

d6a80b0c3c7d418d978547c37afebe69.png

而我们的分数只能排在几千名开外

1.优化训练次数和学习率

作为神经网络最基本的几个超参,先从训练epoch和lr下手,无疑是最简单的.

由于训练时间非常短,我其他都先不修改,先将epoch设置为500,看看训练结果有什么变化

2f6f77f31083449ba3626fccf5d7f8e0.png

可以看到,有明显的优化,valid数据集上面的rmse从100epoch的8到了7.

提交试试

00747d9b690947e09bf2edd93882a045.png

依然是四千名开外.

从上面的增大epoch来看,虽然模型在训练集上面的损失明显小于测试.但是随着epoch增加,模型的损失还在远远不断地减小,我认为可能还是未拟合状态.

为了节省时间,于是我直接将学习率从5提高到10

3e617324107643be81f94fe159e92fc3.png

还是呈下降趋势,将学习率提高到20

a12f346f768047a0b89e176ad553eebe.png

可以看到,模型开始有过拟合趋势,在430epoch地时候,刚好达到拟合.提交试试

b12af353e9ff4a46871438fa48c6935d.png

可以看到,仅仅修改训练次数和学习率,损失函数就下降了如此之多,这说明了超参的重要性.

2.优化权重衰减参数

可以看到,模型有明显的过拟合,于是我选择调整权重衰减参数,来缓解过拟合.简单来说:权重衰减是深度学习中的一种正则化技术,通常用于防止模型的过拟合。它通过在损失函数中增加一个与模型参数相关的项来控制模型的复杂度,从而约束模型参数的大小

在查阅资料之后,我首先在上一步的基础上,取weight_decay为0.0001.

76d5e5cc29a043288bc7e68253fdc5bf.png不行,没什么变化,之后我再取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的原因是,网络变得复杂之后,训练速度明显变慢

结果:

257b87945025466aa747e1e0e77f0189.png

模型更复杂了,结果可能更差了,于是我查阅资料,发现可能是学习率太高了,于是我调整参数:

k, num_epochs, lr, weight_decay, batch_size,dropout1,dropout2 = 5, 100, 0.001, 0, 64 ,0.2,0.5

72a60eff8c384beabd45bfd42f0c34bf.png

训练集效果不错,但是验证机却不尽人意,还是存在过拟合问题

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

99c4db3d10a241fba556bfa27a971161.png

有明显的好转,但是还是过拟合严重,现在修改衰减权重:

k, num_epochs, lr, weight_decay, batch_size,dropout1,dropout2 = 5, 200, 0.001, 0.001, 64 ,0.5,0.6

2b8ecebf038b40a29fff1cb524cc1426.png

相差不大,没有明显进步.

之后我尝试修改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近邻插补基于其他数据样本的相似性来填充缺失值,比简单的中位数填充更智能。并使用 PowerTransformerYeo-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

相关文章

  • WPF FlowDocument List ListItem Paragraph BlockUIContainer Table TableRowGr
    <Windowx:Class="WpfApp419.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft......
  • AI工衣工服智能识别系统
    AI工衣工服智能识别系统AI工衣工服智能识别系统通过深度学习技术,对工地/工厂现场作业区域进行实时不间断监测,AI工衣工服智能识别系统监测到现场作业人员未按要求穿工作服时,立即抓拍告警同步及时提醒后台人员进行制止。AI工衣工服智能识别系统24小时实时对施工现场进行自动识别分析......
  • 流量新密码?AI宠物定制写真在小红书爆火,有人搞了10W+
    大家好,我是灵魂画师向阳不知道大家发现没?消费者对于情感价值的需求猛增,宠物服务行业衍生出越来越多的“拟人化”新业态。宠物摄影和写真成为宠物经济中的新兴行业,吸引了越来越多的摄影师和养宠人的关注。一些摄影师和摄影机构单独开辟了这个业务,通过给宠物穿上服装、拍摄......
  • Stable Diffusion绘画 | AnimateDiff:用AI制作动画(附插件安装包)
    使用AnimateDiff插件,我们只需要按时间节点,输入不同的提示词,就可以非常轻松地生成一系列丝滑的动画。AnimateDiff的底层技术框架,是由上海人工智能实验室&香港中文大学&斯坦福大学联合研发的,它能很好地结合SD的各种模型,以及配合ControlNet和Upscale来生成出......
  • 【AI绘画】Flux 出图背景太模糊?一招解决!
    大家好,我是写编程的木木。小伙伴们在使用Flux出图的时候,应该也遇到过大多数情况下背景都是比较模糊的,虽然大多数时候没啥影响,毕竟我们很多时候都只是看主体嘛。但是也有些场景,我们希望整体的构图中背景也可以高清一些,这样在看整张图片的时候也舒服一些。今天我们要分享......
  • AI绘画SD和MJ零基础入门到精通教程,这些技巧让你的画作更惊艳!这些指令快速激发你的创作
    ‍......
  • AIGC基础工具-用于数据分析和数据处理的核心库Pandas介绍
    文章目录1.Pandas的核心数据结构1.1Series创建`Series`Series重要属性示例1.2DataFrame创建`DataFrame`DataFrame重要属性示例2.Pandas数据的导入与导出2.1读取CSV文件2.2读取Excel文件2.3写入CSV文件2.4读取JSON文件3.Pandas的数据操作3.1......
  • AIGC基础工具-科学计算和数据处理的重要库NumPy(Numerical Python)简介
    文章目录1.NumPy的核心概念1.1`ndarray`:多维数组对象示例代码2.NumPy的数据类型(`dtype`)示例代码3.NumPy的数组创建方法3.1使用`array()`创建数组3.2使用`zeros()`和`ones()`3.3使用`arange()`和`linspace()`3.4使用`random`模块生成随机数......
  • 【AI绘画】再谈大模型工作流技术之——ComfyUI框架
    “工作流,一种根据配置执行固定操作的流程”而最近在做一款AIGC产品的过程中,主要负责的就是Comfyui工作流这块;本着要知其然,也要知其所以然的心态,因此向公司的炼丹师请教了一下ComfyUI工作流的工作原理以及工作流程。ComfyUI的工作原理以及流程首先要声明一个误区,ComfyUI......
  • AI大模型算法工程师就业宝典—— 高薪入职攻略与转行秘籍!
    从ChatGPT到新近的GPT-4,GPT模型的发展表明,AI正在向着“类⼈化”⽅向迅速发展。GPT-4具备深度阅读和识图能⼒,能够出⾊地通过专业考试并完成复杂指令,向⼈类引以为傲的“创造⼒”发起挑战。现有的就业结构即将发⽣重⼤变化,社会⽣产⼒的快速提升将催⽣新的⾏业和岗位机会。如......