实战Kaggle比赛:预测房价

这是你第一次学习:实战Kaggle比赛:预测房价
总代码在文末有!

分为数据集预处理、模型训练及准备、函数调用

思路:加载数据、数据预处理、定义模型、损失函数、优化器、训练模型、参数验证(寻找超参数)、数据训练

以下是数据下载和解压压缩包代码,因为要从网上下载房价预测的数据集,但一般是去kaggle上手动下载:

def download(name, cache_dir=os.path.join('..', 'data')):  #@save
    """下载一个DATA_HUB中的文件,返回本地文件名"""
    assert name in DATA_HUB, f"{name} 不存在于 {DATA_HUB}"
    #断言( assert)语句,如果name  不在 DATA_HUB字典中则会抛出异常。其中包含了一个 f - string 表达式,用于将变量name和DATA_HUB    的值组合成一个字符串。
    url, sha1_hash = DATA_HUB[name]
# 从字典中回去对应名称的url地址和sha-1哈希值,并使用解构操作将它们分别赋值
    os.makedirs(cache_dir, exist_ok=True)
    fname = os.path.join(cache_dir, url.split('/')[-1])#从url中提取文件名字,使用ps。path。join方法将与cache_dir路径拼接为完整路径
    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)
    # 获取fname的父目录路径,赋值给电编base_dir
    data_dir, ext = os.path.splitext(fname)
    # 获取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)

1.查看数据类型?是否需要数据标准化

下载和解压完数据后根据遍历出的数据可以看出,其实数据值域差比很大,很多房价差距也很大,所以应该标准化化为同一个维度,并且数据集除了数值型数据还有许多字符型数据。


# 读取数据代码:
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:]))

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

# print("22",all_features[numeric_features])
# 把除去训练数据的价格之后的训练数据拼接测试数据,两个数据进行合并!
# 该语句通常用于将训练集和测试集的数据进行合并,以便进行特征工程或模型预测等操作。

# “Dummy_na=True”将“na”(缺失值)视为有效的特征值,并为其创建指示符特征
all_features = pd.get_dummies(all_features, dummy_na=True)

n_train = train_data.shape[0]
print(train_data.shape)
# 数据集预处理完
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)
# 这句话看不懂,就是把他拉成一列咯 ?
print(train_labels)
# 以上都是数据集处理,包含下载、解压、数据集获取、数据集合并(用于标准化)、标准化数据集、重新分开训练数据和测试数据

对于数值型数据,查看他们的值域是否差距过大,过大需要化为同一纬度好训练模型,这里使用的是化为均方数值
对于字符型,则根据值变为列,将字符型数据变为数值型数据

部分代码解析:

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

代码解析:

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()))

将所有数值变量标准化,减均值并除于标准差

  lambda x: (x - x.mean()) / (x.std()))

定义了一个 lambda 函数,它的输入参数是一个数组 x。函数的作用是将数组 x 中的每个元素减去 x 的均值,然后除以 x 的标准差,最终返回处理后的结果。

换句话说,这段代码实现了对数组 x 进行标准化处理的操作,使数据均值为0,标准差为1。

all_features[numeric_features] = all_features[numeric_features].fillna(0)

将数据集all_features中指定列numeric_features中的缺失值(NaN)用0进行填充。其中,fillna()函数是pandas库提供的函数,用于填充缺失值。因为在某些情况下,机器学习算法不能处理带有缺失值的数据,所以需要在训练前将缺失值进行处理。实际上,将缺失值替换为0并不总是最佳选择,具体应该根据业务需求和数据分析结果进行决定。

思考2:为什么会有缺失值?

不清楚,可能是用来预防有缺失值,更严谨的操作,但归一化后,就可以无脑填0了,否者要先算出均值再填

将特征重新缩放到零均值和单位方差原理:
在这里插入图片描述
即将每个特征的值减去该特征的平均值,然后再除以该特征的标准差。

非字符型数据的处理:

“Dummy_na=True”将“na”(缺失值)视为有效的特征值,并为其创建指示符特征,比如Size有’S’,‘M’和’L’,dummy_na为True,会生成四个属性Size_S、Size_M、Size_L,Size_nan,仅一个为1,剩余为0,为False,会少一个Size_nan

all_features = pd.get_dummies(all_features, dummy_na=True)
all_features.shape

注意:这里使用的是所有的标签,并且dummy_na为True就是指nan也会被记录

n_train = train_data.shape[0]
print(train_data.shape)
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_data.SalePrice.values.reshape(-1, 1)这句话细讲一下:

这段代码的作用是将 train_data 中的 SalePrice 列转化为一个二维的 NumPy 数组。其中使用 .values.reshape(-1, 1) 将其转化为一个只有一列的二维 NumPy 数组。这样就能够将 SalePrice 列与其他特征组成的 2D 数组拼接起来,构成完整的数据集

   clipped_preds = torch.clamp(net(features), 1, float('inf'))#这段代码把值设置到哪些数区间

所有小于 1 的值都被设置为 1。
使数据都在【1,正无穷大】区间

2.定义模型

# 以下都是训练:
loss = nn.MSELoss()
in_features = train_features.shape[1]
def get_net():
    net = nn.Sequential(nn.Linear(in_features,1))
    return net

3.根据数据和任务要求寻找损失函数

# 更关心相对误差,这里就用来处理相对误差inf代表无穷大(infinity的缩写),用价格预测的对数来衡量
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()

4.优化器

这里使用的是现成的优化器

5.模型训练函数

一共有两次训练模型

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

6.K折验证,为后面最后的测试数据训练找超参数!

k折交叉验证基本分为两部分:1.训练数据的不同k值下的区分和返回2.再训练集和验证集在每个epoch上的损失函数平均值,同时还可以减少模型对数据的过度拟合
train训练函数会使用优化器和损失函数将模型进行……并且返回训练误差和则测试误差

在这里插入代码片# 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

以上代码是服务于k折验证,是为了k折验证每个周期内数据的分配,不懂得我觉得可以去看b站李沐讲的这个视频:


# k折交叉验证

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]
        print(train_ls[-1],"222",train_ls)
        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

分析:这段代码实现了K折交叉验证的过程。具体来说,它接收输入参数包括:k-折数、训练数据X_train、训练标签y_train、训练轮数num_epochs、学习率learning_rate、权重衰减项weight_decay和批量大小batch_size。然后,对于每一折,它将训练数据划分成训练集和验证集,使用get_net()函数获取一个神经网络模型,调用train()函数对模型进行训练,并记录训练集和验证集上的损失值。最后,将所有折的训练集和验证集上的损失值加起来,求平均损失并返回。其中,在第一次迭代时,还会绘制训练集和验证集的RMSE随轮数变化曲线。

8.数据训练,保存scv文件

接下来开始训练模型:

模型选择和超参数赋值:
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}')
将训练好的神经网络模型应用于测试集组合:
def train_and_pred(train_features, test_features, train_labels, test_data,
                   num_epochs, lr, weight_decay, batch_size):
    net = get_net()
    #get_net()创建一个新的神经网络模型。然后使用函数train()对该模型进行训练并返回训练误差train_ls和测试误差
    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}')
    plt.show()
    # 将网络应用于测试集。
    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)

train_and_pred(train_features, test_features, train_labels, test_data,
               num_epochs, lr, weight_decay, batch_size)

plt.show()

———————————思考笔记———————————

思考1:为什么需要将所有特征放在一个共同的尺度上, 我们通过将特征重新缩放到零均值和单位方差来标准化数据:

chat的回答:

将所有特征放在一个共同的尺度上有以下几个好处:

  1. 避免某些特征因为其数值范围较大而对模型产生不良影响。如果某些特征的值域非常大,那么它们会在模型中占据主导地位,使得其他特征的作用被忽略。通过标准化数据,可以确保每个特征对模型的贡献相当。
  2. 加速模型训练过程。如果特征没有被标准化,则优化算法需要花费更长的时间才能找到最佳的权重和偏置值,因为不同的特征需要采用不同的学习率进行更新。将特征标准化后,可以避免这种情况。
  3. 一些机器学习算法(例如KNN和聚类算法)需要计算样本之间的距离或相似度。如果特征没有被标准化,则某些特征的值域较大会掩盖其他特征的作用,从而导致结果失真。

标准化数据的一种常见方法是将特征重新缩放到零均值和单位方差。这种方法使得数据的平均值为0,方差为1,并使得所有特征具有相同的尺度。这样就可以确保所有特征对模型的贡献相当,并避免了上述问题。

思考2:为什么会有上述问题?

不标准化数据可能会导致一些特征的重要性被掩盖,而另一些特征的重要性被夸大,从而影响结果的准确性。
例如,如果一个数据集包含两个特征:A和B,其中A的值在1到100之间,B的值在0到1之间。如果这些特征没有经过标准化处理,那么模型可能更多地关注A,因为它的值比B更大。在这种情况下,B的重要性可能被低估,导致模型对其产生的影响也被低估。另外,如果使用的算法基于距离度量,如K近邻算法,那么如果不标准化数据,特征A将占据较大的权重,而特征B则会失去影响力,因为它的值域很小。

标准化数据可以解决这个问题,它使得所有特征都具有相同的尺度,这使得模型能够平等地对待每一个特征,并且不会夸大或低估任何一个特征的影响力。这样可以更好地保持特征之间的相对权重,并提高模型的准确性。

思考3:但这样数据不会损失精度嘛

我们仅对原始数据进行线性变换,即将每个特征的值减去该特征的平均值,然后再除以该特征的标准差。这种变换并不改变数据的原始信息,只是改变了数据的比例尺度。

思考1-3的总结:

不同数据值域不同,单纯用数据大小进行衡量不公平,因此需要标准化数据使得数据占比更公平,对模型训练来说,这个操作可以确保特征对模型的贡献相当,部分机器学习算法甚至会因为某特征值域较大从而掩盖其他特征的作用导致结果失真

思考4:训练模型是如何与k折交叉联系在一起的:

与上述代码的关系是,上述代码通常在k折交叉验证中被作为子函数调用,在单个训练迭代中对训练数据进行处理、计算梯度并更新模型参数。通过对k折交叉验证的多轮训练和验证过程进行循环迭代,我们可以得到模型在不同验证集上的性能指标,并对模型进行进一步优化。

思考5:将训练好的神经网络模型应用于测试集,并将预测结果保存为一个csv文件

1.调用函数get_net()创建一个新的神经网络模型。
2.将模型传入函数train()对该模型进行训练,并返回训练误差train_ls和测试误差,但由于测试数据没有传入,因此用下划线表示。

3.接着,使用函数d2l.plot()将训练误差随时间变化的曲线图绘制出来。其中np.arange(1, num_epochs + 1)生成了从1到num_epochs的整数序列,作为x轴,[train_ls]是列表形式的训练误差,作为y轴,xlabelylabel分别是x轴和y轴的标签,xlim=[1, num_epochs]指定x轴的范围为1到num_epochs,yscale='log'指定y轴的比例尺为对数尺度。

接着输出训练误差的最后一个值,即最终训练误差。

然后,将测试数据输入到训练好的模型中,得到预测结果preds。由于preds是一个tensor,需要使用.detach().numpy()将其转换为numpy数组。接着,将预测结果重塑为一行多列的numpy数组,并将其转换为pandas的Series对象。然后将其添加到测试数据的DataFrame中,并通过pd.concat()函数将id和预测结果合并为一个DataFrame。最后,使用submission.to_csv()将结果保存为csv文件,参数index=False表示不包含行索引。

思考6:为什么感觉一直在调用train训练函数,所以一共要训练多少次,训练场景有哪些:

1.在k折验证中使用train函数进行验证来得出训练集和验证集的平均误差和(就是总的误差的均值),这一步得到误差总和最终目的其实是选择合适的超参数,超参数对于最终的算法模型调用十分重要
2.然后就是在最后调用时使用训练函数进行测试集的训练,

总代码:


import hashlib
import os
import tarfile
import zipfile
import requests
# 如果没有安装pandas,请取消下一行的注释
import matplotlib.pyplot as plt
import sys
sys.path.append('d:\_python\lib\site-packages')
from d2l import torch as d2l


import numpy as np
import pandas as pd
import torch
from torch import nn
from d2l import torch as d2l
#@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}"
    #断言( assert)语句,如果name  不在 DATA_HUB字典中则会抛出异常。其中包含了一个 f - string 表达式,用于将变量name和DATA_HUB    的值组合成一个字符串。
    url, sha1_hash = DATA_HUB[name]
# 从字典中回去对应名称的url地址和sha-1哈希值,并使用解构操作将它们分别赋值
    os.makedirs(cache_dir, exist_ok=True)
    fname = os.path.join(cache_dir, url.split('/')[-1])#从url中提取文件名字,使用ps。path。join方法将与cache_dir路径拼接为完整路径
    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)
    # 获取fname的父目录路径,赋值给电编base_dir
    data_dir, ext = os.path.splitext(fname)
    # 获取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:]))

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

# print("22",all_features[numeric_features])
# 把除去训练数据的价格之后的训练数据拼接测试数据,两个数据进行合并!
# 该语句通常用于将训练集和测试集的数据进行合并,以便进行特征工程或模型预测等操作。

# “Dummy_na=True”将“na”(缺失值)视为有效的特征值,并为其创建指示符特征
all_features = pd.get_dummies(all_features, dummy_na=True)

n_train = train_data.shape[0]
print(train_data.shape)
# 数据集预处理完
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)
# 这句话看不懂,就是把他拉成一列咯 ?
print(train_labels)
# 以上都是数据集处理,包含下载、解压、数据集获取、数据集合并(用于标准化)、标准化数据集、重新分开训练数据和测试数据

# 以下都是训练:
loss = nn.MSELoss()
in_features = train_features.shape[1]
def get_net():
    net = nn.Sequential(nn.Linear(in_features,1))
    return net

# 更关心相对误差,这里就用来处理相对误差inf代表无穷大(infinity的缩写),用价格预测的对数来衡量
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

# 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


# k折交叉验证

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]
        print(train_ls[-1],"222",train_ls)
        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

# 模型选择
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}')

# 将训练好的神经网络模型应用于测试集组合:
def train_and_pred(train_features, test_features, train_labels, test_data,
                   num_epochs, lr, weight_decay, batch_size):
    net = get_net()
    #get_net()创建一个新的神经网络模型。然后使用函数train()对该模型进行训练并返回训练误差train_ls和测试误差
    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}')
    plt.show()
    # 将网络应用于测试集。
    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)

train_and_pred(train_features, test_features, train_labels, test_data,
               num_epochs, lr, weight_decay, batch_size)

plt.show()

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐