深度学习--------------Kaggle房价预测

下载和缓存数据集

import hashlib
import os
import tarfile
import zipfile
import requests
 

# download传递的参数分别是数据集的名称、缓存文件夹的路径
def download(name, cache_dir=os.path.join('..', 'data')):  # @save
    """下载一个DATA_HUB中的文件,返回本地文件名"""
    # 检查变量name是否存在于名为DATA_HUB的字典中,如果是异常则给出消息提示
    assert name in DATA_HUB, f"{name} 不存在于 {DATA_HUB}"
    # 检查与name相对应的元组并解包赋值给url和sha1_hash
    url, sha1_hash = DATA_HUB[name]
    os.makedirs(cache_dir, exist_ok=True)
    # 缓存的路径和目标目录,[-1]:url指向文件名
    fname = os.path.join(cache_dir, url.split('/')[-1])
    if os.path.exists(fname): # 检查文件是否存在
        sha1 = hashlib.sha1() # 计算文件的哈希值,初始化哈希对象
        # with确保文件在使用后正确关闭
        with open(fname, 'rb') as f: # 读取文件内容
            while True: # 循环读取文件内容并更新哈希对象
                data = f.read(1048576) # 读取1MB到data
                if not data: # 为真跳出
                    break
                # 将读取到的数据块更新到哈希对象中
                sha1.update(data)
        # 检查计算出的哈希值是否与预期的哈希值匹配
        if sha1.hexdigest() == sha1_hash:
            return fname  # 命中缓存
    print(f'正在从{url}下载{fname}...')
    # 发送get请求并设置stream=True支持流式传输
    r = requests.get(url, stream=True, verify=True)
    with open(fname, 'wb') as f: # 写入模式
        f.write(r.content)
    return fname


# folder=None是指解压后数据应存放的文件名,如未指定则默认使用去除扩展名后的文件名作为文件夹名
def download_extract(name, folder=None):  # @save
    """下载并解压zip/tar文件"""
    # 下载文件,并返回下载的完整路径fname
    fname = download(name)
    # 获取下载文件所在的基本目录
    base_dir = os.path.dirname(fname)
    # 从文件名中分离出扩展名exe和数据目录
    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文件可以被解压缩'
    # 使用extractall方法将文件解压到基本目录,如果指定了folder参数,则返回base_dir和folder组合的路径
    # 否则返回去除扩展名后的文件名作为路径
    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)

# @save
DATA_HUB = dict() # 这是一个字典,用来存储数据集的信息。
DATA_URL = 'http://d2l-data.s3-accelerate.amazonaws.com/' # 指向一个存储了多个数据集文件的服务器




访问和读取数据集

import numpy as np
import pandas as pd
import torch
from torch import nn
from d2l import torch as d2l

下载并缓存Kaggle房屋数据集

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

使用pandas分别加载包含训练数据和测试数据的两个CSV文件

train_data = pd.read_csv(download('kaggle_house_train'))
test_data = pd.read_csv(download('kaggle_house_test'))

输出:

在这里插入图片描述


训练数据集包括1460个样本,每个样本80个特征和1个标签, 而测试数据集包含1459个样本,每个样本80个特征。
print(train_data.shape)
print(test_data.shape)

输出:
在这里插入图片描述


查看前四个最后两个特征,以及相应标签(房价)。

print(train_data.iloc[0:4, [0, 1, 2, 3, -3, -2, -1]])

输出:
在这里插入图片描述

在每个样本中,第一个特征是ID, 这有助于模型识别每个训练样本。 虽然这很方便,但它不携带任何用于预测的信息。 因此,在将数据提供给模型之前,(我们将其从数据集中删除)。

# 删除train_data的第一列ID和最后一列房价;删除test_data的第一列索引
all_features = pd.concat((train_data.iloc[:, 1:-1], test_data.iloc[:, 1:]))
# 查看一下
print(all_features)

输出:

在这里插入图片描述




总代码

import hashlib
import os
import tarfile
import zipfile
import requests
import pandas as pd

# download传递的参数分别是数据集的名称、缓存文件夹的路径
def download(name, cache_dir=os.path.join('..', 'data')):  # @save
    """下载一个DATA_HUB中的文件,返回本地文件名"""
    # 检查变量name是否存在于名为DATA_HUB的字典中,如果是异常则给出消息提示
    assert name in DATA_HUB, f"{name} 不存在于 {DATA_HUB}"
    # 检查与name相对应的元组并解包赋值给url和sha1_hash
    url, sha1_hash = DATA_HUB[name]
    os.makedirs(cache_dir, exist_ok=True)
    # 缓存的路径和目标目录,[-1]:url指向文件名
    fname = os.path.join(cache_dir, url.split('/')[-1])
    if os.path.exists(fname): # 检查文件是否存在
        sha1 = hashlib.sha1() # 计算文件的哈希值,初始化哈希对象
        # with确保文件在使用后正确关闭
        with open(fname, 'rb') as f: # 读取文件内容
            while True: # 循环读取文件内容并更新哈希对象
                data = f.read(1048576) # 读取1MB到data
                if not data: # 为真跳出
                    break
                # 将读取到的数据块更新到哈希对象中
                sha1.update(data)
        # 检查计算出的哈希值是否与预期的哈希值匹配
        if sha1.hexdigest() == sha1_hash:
            return fname  # 命中缓存
    print(f'正在从{url}下载{fname}...')
    # 发送get请求并设置stream=True支持流式传输
    r = requests.get(url, stream=True, verify=True)
    with open(fname, 'wb') as f: # 写入模式
        f.write(r.content)
    return fname


# folder=None是指解压后数据应存放的文件名,如未指定则默认使用去除扩展名后的文件名作为文件夹名
def download_extract(name, folder=None):  # @save
    """下载并解压zip/tar文件"""
    # 下载文件,并返回下载的完整路径fname
    fname = download(name)
    # 获取下载文件所在的基本目录
    base_dir = os.path.dirname(fname)
    # 从文件名中分离出扩展名exe和数据目录
    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文件可以被解压缩'
    # 使用extractall方法将文件解压到基本目录,如果指定了folder参数,则返回base_dir和folder组合的路径
    # 否则返回去除扩展名后的文件名作为路径
    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)


# @save
DATA_HUB = dict() # 这是一个字典,用来存储数据集的信息。
DATA_URL = 'http://d2l-data.s3-accelerate.amazonaws.com/' # 指向一个存储了多个数据集文件的服务器
# 第二个参数:表示数据集文件的哈希值,用哈希值验证文件的完整性
DATA_HUB['kaggle_house_train'] = (DATA_URL + 'kaggle_house_pred_train.csv','585e9cc9370b9160e7921475fbcd7d31219ce')
DATA_HUB['kaggle_house_test'] = (DATA_URL + 'kaggle_house_pred_test.csv', 'fal9780a7b011d9b009e8bff8e99922a8ee2eb90')
# 读取路径指向的csv文件
train_data = pd.read_csv(download('kaggle_house_train'))
test_data = pd.read_csv(download('kaggle_house_test'))

print(train_data.shape) # 1460个样本,80个特征,1个标号label
print(test_data.shape) # 测试样本没有标号label
print(train_data.iloc[0:4,[0,1,2,3,-3,-2,-1]]) # 前面四行的某些列特征

输出:
在这里插入图片描述




数据预处理

如上所述,我们有各种各样的数据类型。 在开始建模之前,我们需要对数据进行预处理。 首先,我们将所有缺失的值替换为相应特征的平均值。然后,为了将所有特征放在一个共同的尺度上, 我们通过将特征重新缩放到零均值和单位方差来标准化数据在这里插入图片描述其中 𝜇 和 𝜎 分别表示均值和标准差。 现在,这些特征具有零均值和单位方差,即 在这里插入图片描述在这里插入图片描述
直观地说,我们标准化数据有两个原因: 首先,它方便优化。 其次,因为我们不知道哪些特征是相关的, 所以我们不想让惩罚分配给一个特征的系数比分配给其他任何特征的系数更大。

# 若无法获得测试数据,则可根据训练数据计算均值和标准差
# all_features.dtypes 获取每个特征的数据类型
# all_features.dtypes != 'object' 返回一个布尔值的 Series,其中为 True 的位置表示对应的特征不是对象类型(即数值类型)
# .index 提取数值类型特征的索引,并在numeric_features中存储
print(all_features.dtypes) # 可以知道每一列分别为什么类型特征
numeric_features = all_features.dtypes[all_features.dtypes != 'object'].index
print(numeric_features)
# 对all_features中所有数值类型特征进行标准化处理
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)

输出:
在这里插入图片描述




接下来,我们处理离散值。 这包括诸如“MSZoning”之类的特征。我们用独热编码替换它们), 例如,“MSZoning”包含值“RL”和“Rm”。 我们将创建两个新的指示器特征“MSZoning_RL”和“MSZoning_RM”,其值为01。 根据独热编码,如果“MSZoning”的原始值为“RL”, 则:“MSZoning_RL”为1,“MSZoning_RM”为0。 pandas软件包会自动为我们实现这一点。

# “Dummy_na=True”将“na”(缺失值)视为有效的特征值,并为其创建指示符特征
# 未指定column时,pd.get_dummies函数默认转换所有的分类列,包括MSZoning、SaleType、SaleCondition
# pd.get_dummies每个分类变量都会被转换成多列,每个唯一值对应一列
# 理解意思:某列有五类值,就将此列等价于变成五列,然后对应的列置1,不存在的列置0
all_features = pd.get_dummies(all_features, dummy_na=True)

未改变时的数据特征:

# 这里仅验证看一下特征的形状
print(all_features.shape)

输出:
在这里插入图片描述


改变后:

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

输出:
在这里插入图片描述
可以看到此转换会将特征的总数量从79个(去掉ID)增加到331个。



最后,通过values属性,我们可以从pandas格式中提取NumPy格式,并将其转换为张量表示用于训练。

# 获取训练集的行数,即训练样本数量,并将其存储在n_train中
n_train = train_data.shape[0]
# [:n_train].values是选择前n_train作为训练集的特征将数据转换为Numpy数组
train_features = torch.tensor(all_features[:n_train].values, dtype=torch.float32)
test_features = torch.tensor(all_features[n_train:].values, dtype=torch.float32)
# 将SalePrice列单独提取为标签
train_labels = torch.tensor(
    train_data.SalePrice.values.reshape(-1, 1), dtype=torch.float32)



训练

首先,我们训练一个带有损失平方的线性模型。 显然线性模型很难让我们在竞赛中获胜,但线性模型提供了一种健全性检查,以查看数据中是否存在有意义的信息。 如果我们在这里不能做得比随机猜测更好,那么我们很可能存在数据处理错误。 如果一切顺利,线性模型将作为基线(baseline)模型, 让我们直观地知道最好的模型有超出简单的模型多少。

loss = nn.MSELoss()
in_features = train_features.shape[1]
 
def get_net():
    net = nn.Sequential(nn.Linear(in_features,1))
    return net

房价就像股票价格一样,我们关心的是相对数量,而不是绝对数量。因此,我们更关心相对误差,而不是绝对误差 。 例如,如果我们在俄亥俄州农村地区估计一栋房子的价格时,假设我们的预测偏差了10万美元,然而那里一栋典型的房子的价值是12.5万美元,那么模型可能做得很糟糕。另一方面,如果我们在加州豪宅区的预测出现同样的10万美元的偏差,(在那里,房价中位数超过400万美元) 这可能是一个不错的预测。

(解决这个问题的一种方法是用价格预测的对数来衡量差异)。 事实上,这也是比赛中官方用来评价提交质量的误差指标。 即将在这里插入图片描述转换为在这里插入图片描述。 这使得预测价格的对数真实标签价格的对数之间出现以下均方根误差
在这里插入图片描述

# rmse=Root Mean Squared Error,对数均方根误差
def log_rmse(net, features, labels):
	# 通过神经网络net对输入特征features进行前向传播得到预测值
    # 然后为了在取对数时进一步稳定该值,用torch.clamp()函数将小于1的值设置为1
    # 第三个参数:不设置上限,即预测可以无限大
    clipped_preds = torch.clamp(net(features), 1, float('inf'))
    rmse = torch.sqrt(loss(torch.log(clipped_preds),
                           torch.log(labels)))
    # .item() 方法将rmse转换为Python标量返回
    return rmse.item()

我们的训练函数将借助Adam优化器,Adam优化器的主要吸引力在于它对初始学习率不那么敏感

# weight_decay权重衰退
def train(net, train_features, train_labels, test_features, test_labels,
          num_epochs, learning_rate, weight_decay, batch_size):
    # 用于存储每一轮训练和测试集上的log rmse值
    train_ls, test_ls = [], []
    train_iter = d2l.load_array((train_features, train_labels), batch_size)
    # 这里使用的是Adam优化算法,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() # 更新模型参数
        # 将log_rmse函数返回的误差值追加到train_ls列表中就记录了每一轮迭代
        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折交叉验证

K折交叉验证有助于模型选择和超参数调整。定义一个函数,在 𝐾 折交叉验证过程中返回第 𝑖 折的数据。具体地说,它选择第 𝑖 个切片作为验证数据其余部分作为训练数据。注意,这并不是处理数据的最有效方法,如果我们的数据集大得多,会有其他解决办法。

# 四个参数:表示将数据集分为多少个子集、当前折的索引、特征数据集、标签数据集
def get_k_fold_data(k, i, X, y):
	# 断言确保折数大于1,因为至少两个子集才能进行交叉验证
    assert k > 1
    # 每个子集(折)的大小=数据集的总行数除以折数,余数在最后一个子集处理
    fold_size = X.shape[0] // k
    # 初始化训练集
    X_train, y_train = None, None
    for j in range(k):
        # idx将原始数据集的索引切片为 (j * fold_size) 到 ((j + 1) * fold_size),获得当前折数据
        idx = slice(j * fold_size, (j + 1) * fold_size)
        X_part, y_part = X[idx, :], y[idx]
        # 当前折的索引 j 与指定用于验证的索引 i 匹配,将该折的数据分配给验证集(X_valid 和 y_valid)
        if j == i:
        	# 将该折的数据分配给验证集
            X_valid, y_valid = X_part, y_part
        # 如不匹配且训练集为空(第一次看到),则将该折的数据分配给训练集(X_train 和 y_train)
        elif X_train is None:
            X_train, y_train = X_part, y_part
        # 如不匹配且训练集不为空,则将该折的数据追加到训练集(X_train 和 y_train)中
        else:
        	# 表示将X_train, X_part沿着第一个维度拼接起来
            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):
    	# 做k次,每次拿到第i折,把数据data拿出来即拿到从训练集和验证集
        data = get_k_fold_data(k, i, X_train, y_train)
        # 每次循环都会创建一个新的模型实例,确保每折的评估是独立的
        net = get_net()
        # 调用train函数训练模型并返回训练损失列表和验证损失列表
        train_ls, valid_ls = train(net, *data, num_epochs, learning_rate,
                                   weight_decay, batch_size)
        # 索引[-1]获取最后一个epoch的损失,即当前折的训练损失累加到train_l_sum 
        train_l_sum += train_ls[-1]
        valid_l_sum += valid_ls[-1]
        if i == 0: # 如果当前是第i折
        	# 第一个参数:用作x轴数据
        	# 第二个参数:一个列表储存了每一轮训练后的训练损失和验证损失
            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折交叉验证往往对多次测试具有相当的稳定性。然而,如果我们尝试了不合理的超参数,我们可能会发现验证效果不再代表真正的误差。

#数据集被分成了5份,然后执行5次训练和验证过程,每次过程中,4份数据用于训练模型,剩下的一份用于
k, num_epochs, lr, weight_decay, batch_size = 5, 100, 5, 0, 64
# 分别包含每次迭代的训练集和验证集的log rmse值
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}')

我们关心的是平均验证log rmse




总代码

import hashlib
import os
import tarfile
import zipfile
import requests
import pandas as pd
import torch
from torch import nn
from d2l import torch as d2l


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)


def get_net():
    net = nn.Sequential(nn.Linear(in_features, 1))
    return net


# rmse=Root Mean Squared Error,对数均方根误差
def log_rmse(net, features, labels):
    # 通过神经网络net对输入特征features进行前向传播得到预测值
    # 然后为了在取对数时进一步稳定该值,用torch.clamp()函数将小于1的值设置为1
    # 第三个参数:不设置上限,即预测可以无限大
    clipped_preds = torch.clamp(net(features), 1, float('inf'))
    rmse = torch.sqrt(loss(torch.log(clipped_preds),
                           torch.log(labels)))
    # .item() 方法将rmse转换为Python标量返回
    return rmse.item()


# weight_decay权重衰退
def train(net, train_features, train_labels, test_features, test_labels,
          num_epochs, learning_rate, weight_decay, batch_size):
    # 用于存储每一轮训练和测试集上的log rmse值
    train_ls, test_ls = [], []
    train_iter = d2l.load_array((train_features, train_labels), batch_size)
    # 这里使用的是Adam优化算法,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()  # 更新模型参数
        # 将log_rmse函数返回的误差值追加到train_ls列表中就记录了每一轮迭代
        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


# 四个参数:表示将数据集分为多少个子集、当前折的索引、特征数据集、标签数据集
def get_k_fold_data(k, i, X, y):
    # 断言确保折数大于1,因为至少两个子集才能进行交叉验证
    assert k > 1
    # 每个子集(折)的大小=数据集的总行数除以折数,余数在最后一个子集处理
    fold_size = X.shape[0] // k
    # 初始化训练集
    X_train, y_train = None, None
    for j in range(k):
        # idx将原始数据集的索引切片为 (j * fold_size) 到 ((j + 1) * fold_size),获得当前折数据
        idx = slice(j * fold_size, (j + 1) * fold_size)
        X_part, y_part = X[idx, :], y[idx]
        # 当前折的索引 j 与指定用于验证的索引 i 匹配,将该折的数据分配给验证集(X_valid 和 y_valid)
        if j == i:
            # 将该折的数据分配给验证集
            X_valid, y_valid = X_part, y_part
        # 如不匹配且训练集为空(第一次看到),则将该折的数据分配给训练集(X_train 和 y_train)
        elif X_train is None:
            X_train, y_train = X_part, y_part
        # 如不匹配且训练集不为空,则将该折的数据追加到训练集(X_train 和 y_train)中
        else:
            # 表示将X_train, X_part沿着第一个维度拼接起来
            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):
        # 做k次,每次拿到第i折,把数据data拿出来即拿到从训练集和验证集
        data = get_k_fold_data(k, i, X_train, y_train)
        # 每次循环都会创建一个新的模型实例,确保每折的评估是独立的
        net = get_net()
        # 调用train函数训练模型并返回训练损失列表和验证损失列表
        train_ls, valid_ls = train(net, *data, num_epochs, learning_rate,
                                   weight_decay, batch_size)
        # 索引[-1]获取最后一个epoch的损失,即当前折的训练损失累加到train_l_sum
        train_l_sum += train_ls[-1]
        valid_l_sum += valid_ls[-1]
        if i == 0:  # 如果当前是第i折
            # 第一个参数:用作x轴数据
            # 第二个参数:一个列表储存了每一轮训练后的训练损失和验证损失
            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


# @save
DATA_HUB = dict()
DATA_URL = 'http://d2l-data.s3-accelerate.amazonaws.com/'
DATA_HUB['kaggle_house_train'] = (DATA_URL + 'kaggle_house_pred_train.csv', '585e9cc9370b9160e7921475fbcd7d31219ce')
DATA_HUB['kaggle_house_test'] = (DATA_URL + 'kaggle_house_pred_test.csv', 'fal9780a7b011d9b009e8bff8e99922a8ee2eb90')
train_data = pd.read_csv(download('kaggle_house_train'))
test_data = pd.read_csv(download('kaggle_house_test'))

# print(train_data.shape) # 1460个样本,80个特征,1个标号label
# print(test_data.shape) # 测试样本没有标号label
# 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:]))
# print(all_features.shape)


# 若无法获得测试数据,则可根据训练数据计算均值和标准差
# all_features.dtypes 获取每个特征的数据类型
# all_features.dtypes != 'object' 返回一个布尔值的 Series,其中为 True 的位置表示对应的特征不是对象类型(即数值类型)
# .index 提取数值类型特征的索引,并在numeric_features中存储
# print(all_features.dtypes)  # 可以知道每一列分别为什么类型特征
numeric_features = all_features.dtypes[all_features.dtypes != 'object'].index
# print(numeric_features)
# 对all_features中所有数值类型特征进行标准化处理
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”(缺失值)视为有效的特征值,并为其创建指示符特征
# 未指定column时,pd.get_dummies函数默认转换所有的分类列,包括MSZoning、SaleType、SaleCondition
# pd.get_dummies每个分类变量都会被转换成多列,每个唯一值对应一列
# 理解意思:某列有五类值,就将此列等价于变成五列,然后对应的列置1,不存在的列置0
all_features = pd.get_dummies(all_features, dummy_na=True)

# 获取训练集的行数,即训练样本数量,并将其存储在n_train中
n_train = train_data.shape[0]
# [:n_train].values是选择前n_train作为训练集的特征将数据转换为Numpy数组
train_features = torch.tensor(all_features[:n_train].values, dtype=torch.float32)
test_features = torch.tensor(all_features[n_train:].values, dtype=torch.float32)
# 将SalePrice列单独提取为标签
train_labels = torch.tensor(
    train_data.SalePrice.values.reshape(-1, 1), dtype=torch.float32)
loss = nn.MSELoss()
in_features = train_features.shape[1]

# 数据集被分成了5份,然后执行5次训练和验证过程,每次过程中,4份数据用于训练模型,剩下的一份用于
k, num_epochs, lr, weight_decay, batch_size = 5, 100, 5, 0, 64
# 分别包含每次迭代的训练集和验证集的log rmse值
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}')

输出:

在这里插入图片描述
在这里插入图片描述




提交你的Kaggle预测

既然我们知道应该选择什么样的超参数, 我们不妨使用所有数据对其进行训练 (而不是仅使用交叉验证中使用的 1− 1 K \frac 1K K1 的数据)。 然后,我们通过这种方式获得的模型可以应用于测试集。 将预测保存在CSV文件中可以简化将结果上传到Kaggle的过程。

def train_and_pred(train_features, test_features, train_labels, test_data,
                   num_epochs, lr, weight_decay, batch_size):
    net = get_net()
    # 返回两个值,一个包含每个epoch训练,log mose的列表,一个未在代码片段中使用的值
    train_ls, _ = train(net, train_features, train_labels, None, None,
                        num_epochs, lr, weight_decay, batch_size)
    # 第一个参数和第二个参数:接收一个epoch数组和一个包含训练log rmse的列表作为输入
    d2l.plot(np.arange(1, num_epochs + 1), [train_ls], xlabel='epoch',
             ylabel='log rmse', xlim=[1, num_epochs], yscale='log')
    # 打印最后一个epoch训练log rmse来评估模型的最终训练性能
    print(f'训练log rmse:{float(train_ls[-1]):f}')
    # 将训练好的网络模型应用于测试特征
    preds = net(test_features).detach().numpy()
    # 将其重新格式化以导出到Kaggle
    # pd.Series()封装成一个Pandas的Series对象,以确保数据类型与DataFrame的列兼容。
    test_data['SalePrice'] = pd.Series(preds.reshape(1, -1)[0])
    submission = pd.concat([test_data['Id'], test_data['SalePrice']], axis=1)
    # 用于将DataFrame保存为csv文件
    submission.to_csv('submission.csv', index=False)

如果测试集上的预测𝐾 倍交叉验证过程中的预测相似, 那就是时候把它们上传到Kaggle了。 下面的代码将生成一个名为submission.csv的文件。

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

输出:
在这里插入图片描述

在这里插入图片描述

import hashlib
import os
import tarfile
import zipfile
import requests
import numpy as np
import pandas as pd
import torch
from torch import nn
from d2l import torch as d2l


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)


def get_net():
    net = nn.Sequential(nn.Linear(in_features, 1))
    return net


# rmse=Root Mean Squared Error,对数均方根误差
def log_rmse(net, features, labels):
    # 通过神经网络net对输入特征features进行前向传播得到预测值
    # 然后为了在取对数时进一步稳定该值,用torch.clamp()函数将小于1的值设置为1
    # 第三个参数:不设置上限,即预测可以无限大
    clipped_preds = torch.clamp(net(features), 1, float('inf'))
    rmse = torch.sqrt(loss(torch.log(clipped_preds),
                           torch.log(labels)))
    # .item() 方法将rmse转换为Python标量返回
    return rmse.item()


# weight_decay权重衰退
def train(net, train_features, train_labels, test_features, test_labels,
          num_epochs, learning_rate, weight_decay, batch_size):
    # 用于存储每一轮训练和测试集上的log rmse值
    train_ls, test_ls = [], []
    train_iter = d2l.load_array((train_features, train_labels), batch_size)
    # 这里使用的是Adam优化算法,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()  # 更新模型参数
        # 将log_rmse函数返回的误差值追加到train_ls列表中就记录了每一轮迭代
        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


def train_and_pred(train_features, test_features, train_labels, test_data,
                   num_epochs, lr, weight_decay, batch_size):
    net = get_net()
    # 返回两个值,一个包含每个epoch训练,log mose的列表,一个未在代码片段中使用的值
    train_ls, _ = train(net, train_features, train_labels, None, None,
                        num_epochs, lr, weight_decay, batch_size)
    # 第一个参数和第二个参数:接收一个epoch数组和一个包含训练log rmse的列表作为输入
    d2l.plot(np.arange(1, num_epochs + 1), [train_ls], xlabel='epoch',
             ylabel='log rmse', xlim=[1, num_epochs], yscale='log')
    # 打印最后一个epoch训练log rmse来评估模型的最终训练性能
    print(f'训练log rmse:{float(train_ls[-1]):f}')
    # 将训练好的网络模型应用于测试特征
    preds = net(test_features).detach().numpy()
    # 将其重新格式化以导出到Kaggle
    # pd.Series()封装成一个Pandas的Series对象,以确保数据类型与DataFrame的列兼容。
    test_data['SalePrice'] = pd.Series(preds.reshape(1, -1)[0])
    submission = pd.concat([test_data['Id'], test_data['SalePrice']], axis=1)
    # 用于将DataFrame保存为csv文件
    submission.to_csv('submission.csv', index=False)


# @save
DATA_HUB = dict()
DATA_URL = 'http://d2l-data.s3-accelerate.amazonaws.com/'
DATA_HUB['kaggle_house_train'] = (DATA_URL + 'kaggle_house_pred_train.csv', '585e9cc9370b9160e7921475fbcd7d31219ce')
DATA_HUB['kaggle_house_test'] = (DATA_URL + 'kaggle_house_pred_test.csv', 'fal9780a7b011d9b009e8bff8e99922a8ee2eb90')
train_data = pd.read_csv(download('kaggle_house_train'))
test_data = pd.read_csv(download('kaggle_house_test'))

# print(train_data.shape) # 1460个样本,80个特征,1个标号label
# print(test_data.shape) # 测试样本没有标号label
# 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:]))
# print(all_features.shape)


# 若无法获得测试数据,则可根据训练数据计算均值和标准差
# all_features.dtypes 获取每个特征的数据类型
# all_features.dtypes != 'object' 返回一个布尔值的 Series,其中为 True 的位置表示对应的特征不是对象类型(即数值类型)
# .index 提取数值类型特征的索引,并在numeric_features中存储
# print(all_features.dtypes)  # 可以知道每一列分别为什么类型特征
numeric_features = all_features.dtypes[all_features.dtypes != 'object'].index
# print(numeric_features)
# 对all_features中所有数值类型特征进行标准化处理
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”(缺失值)视为有效的特征值,并为其创建指示符特征
# 未指定column时,pd.get_dummies函数默认转换所有的分类列,包括MSZoning、SaleType、SaleCondition
# pd.get_dummies每个分类变量都会被转换成多列,每个唯一值对应一列
# 理解意思:某列有五类值,就将此列等价于变成五列,然后对应的列置1,不存在的列置0
all_features = pd.get_dummies(all_features, dummy_na=True)

# 获取训练集的行数,即训练样本数量,并将其存储在n_train中
n_train = train_data.shape[0]
# [:n_train].values是选择前n_train作为训练集的特征将数据转换为Numpy数组
train_features = torch.tensor(all_features[:n_train].values, dtype=torch.float32)
test_features = torch.tensor(all_features[n_train:].values, dtype=torch.float32)
# 将SalePrice列单独提取为标签
train_labels = torch.tensor(
    train_data.SalePrice.values.reshape(-1, 1), dtype=torch.float32)
loss = nn.MSELoss()
in_features = train_features.shape[1]

num_epochs, lr, weight_decay, batch_size = 100, 5, 0, 64
train_and_pred(train_features, test_features, train_labels, test_data,
               num_epochs, lr, weight_decay, batch_size)
d2l.plt.show()




提交Kaggle

我们可以提交预测到Kaggle上,并查看在测试集上的预测实际房价(标签)的比较情况。

 ①登录Kaggle网站,访问房价预测竞赛页面。
 ②点击“Submit Predictions”按钮。
 ③点击页面底部虚线框中的“Upload Submission File”按钮,选择要上传的预测文件。
 ④点击页面底部的“Submission”按钮,即可查看结果。

Kaggle注册链接

Kaggle登录链接

房价预测比赛页面(如下图 所示)的"Data"选项卡下可以找到数据集。我们可以通过下面的网址提交预测,并查看排名:

House Prices - Advanced Regression Techniques

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

这里是第一次提交(未优化)

在这里插入图片描述




1、用平均值替换缺失值总是好主意吗?提示:能构造一个不随机丢失值的情况吗?
    用平均值替换缺失值并不总是一个好主意,因为这可能会导致数据失真或者模型表现下降。替代缺失值的方法应该根据数据的特点和缺失值产生的原因来选择。
    比如,一个数据集中的某个属性代表的是某种物质的浓度,这种物质在某个特定温度下会分解,因此在这个温度下所有样本的该属性值都是缺失的。在这种情况下,如果直接用平均值替换缺失值,则可能会导致数据严重失真。


2、通过 𝐾 折交叉验证调整超参数,从而提高Kaggle的得分。

import hashlib
import os
import tarfile
import zipfile
import requests
import numpy as np
import pandas as pd
import torch
from torch import nn
from d2l import torch as d2l


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)


def get_net():
    net = nn.Sequential(nn.Linear(in_features, 1))
    return net


# rmse=Root Mean Squared Error,对数均方根误差
def log_rmse(net, features, labels):
    # 通过神经网络net对输入特征features进行前向传播得到预测值
    # 然后为了在取对数时进一步稳定该值,用torch.clamp()函数将小于1的值设置为1
    # 第三个参数:不设置上限,即预测可以无限大
    clipped_preds = torch.clamp(net(features), 1, float('inf'))
    rmse = torch.sqrt(loss(torch.log(clipped_preds),
                           torch.log(labels)))
    # .item() 方法将rmse转换为Python标量返回
    return rmse.item()


# weight_decay权重衰退
def train(net, train_features, train_labels, test_features, test_labels,
          num_epochs, learning_rate, weight_decay, batch_size):
    # 用于存储每一轮训练和测试集上的log rmse值
    train_ls, test_ls = [], []
    train_iter = d2l.load_array((train_features, train_labels), batch_size)
    # 这里使用的是Adam优化算法,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()  # 更新模型参数
        # 将log_rmse函数返回的误差值追加到train_ls列表中就记录了每一轮迭代
        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


def train_and_pred(train_features, test_features, train_labels, test_data,
                   num_epochs, lr, weight_decay, batch_size):
    net = get_net()
    # 返回两个值,一个包含每个epoch训练,log mose的列表,一个未在代码片段中使用的值
    train_ls, _ = train(net, train_features, train_labels, None, None,
                        num_epochs, lr, weight_decay, batch_size)
    # 第一个参数和第二个参数:接收一个epoch数组和一个包含训练log rmse的列表作为输入
    d2l.plot(np.arange(1, num_epochs + 1), [train_ls], xlabel='epoch',
             ylabel='log rmse', xlim=[1, num_epochs], yscale='log')
    # 打印最后一个epoch训练log rmse来评估模型的最终训练性能
    print(f'训练log rmse:{float(train_ls[-1]):f}')
    # 将训练好的网络模型应用于测试特征
    preds = net(test_features).detach().numpy()
    # 将其重新格式化以导出到Kaggle
    # pd.Series()封装成一个Pandas的Series对象,以确保数据类型与DataFrame的列兼容。
    test_data['SalePrice'] = pd.Series(preds.reshape(1, -1)[0])
    submission = pd.concat([test_data['Id'], test_data['SalePrice']], axis=1)
    # 用于将DataFrame保存为csv文件
    submission.to_csv('submission.csv', index=False)


# 四个参数:表示将数据集分为多少个子集、当前折的索引、特征数据集、标签数据集
def get_k_fold_data(k, i, X, y):
    # 断言确保折数大于1,因为至少两个子集才能进行交叉验证
    assert k > 1
    # 每个子集(折)的大小=数据集的总行数除以折数,余数在最后一个子集处理
    fold_size = X.shape[0] // k
    # 初始化训练集
    X_train, y_train = None, None
    for j in range(k):
        # idx将原始数据集的索引切片为 (j * fold_size) 到 ((j + 1) * fold_size),获得当前折数据
        idx = slice(j * fold_size, (j + 1) * fold_size)
        X_part, y_part = X[idx, :], y[idx]
        # 当前折的索引 j 与指定用于验证的索引 i 匹配,将该折的数据分配给验证集(X_valid 和 y_valid)
        if j == i:
            # 将该折的数据分配给验证集
            X_valid, y_valid = X_part, y_part
        # 如不匹配且训练集为空(第一次看到),则将该折的数据分配给训练集(X_train 和 y_train)
        elif X_train is None:
            X_train, y_train = X_part, y_part
        # 如不匹配且训练集不为空,则将该折的数据追加到训练集(X_train 和 y_train)中
        else:
            # 表示将X_train, X_part沿着第一个维度拼接起来
            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):
        # 做k次,每次拿到第i折,把数据data拿出来即拿到从训练集和验证集
        data = get_k_fold_data(k, i, X_train, y_train)
        # 每次循环都会创建一个新的模型实例,确保每折的评估是独立的
        net = get_net()
        # 调用train函数训练模型并返回训练损失列表和验证损失列表
        train_ls, valid_ls = train(net, *data, num_epochs, learning_rate,
                                   weight_decay, batch_size)
        # 索引[-1]获取最后一个epoch的损失,即当前折的训练损失累加到train_l_sum
        train_l_sum += train_ls[-1]
        valid_l_sum += valid_ls[-1]
        if i == 0:  # 如果当前是第i折
            # 第一个参数:用作x轴数据
            # 第二个参数:一个列表储存了每一轮训练后的训练损失和验证损失
            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


# @save
DATA_HUB = dict()
DATA_URL = 'http://d2l-data.s3-accelerate.amazonaws.com/'
DATA_HUB['kaggle_house_train'] = (DATA_URL + 'kaggle_house_pred_train.csv', '585e9cc9370b9160e7921475fbcd7d31219ce')
DATA_HUB['kaggle_house_test'] = (DATA_URL + 'kaggle_house_pred_test.csv', 'fal9780a7b011d9b009e8bff8e99922a8ee2eb90')
train_data = pd.read_csv(download('kaggle_house_train'))
test_data = pd.read_csv(download('kaggle_house_test'))

# print(train_data.shape) # 1460个样本,80个特征,1个标号label
# print(test_data.shape) # 测试样本没有标号label
# 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:]))
# print(all_features.shape)


# 若无法获得测试数据,则可根据训练数据计算均值和标准差
# all_features.dtypes 获取每个特征的数据类型
# all_features.dtypes != 'object' 返回一个布尔值的 Series,其中为 True 的位置表示对应的特征不是对象类型(即数值类型)
# .index 提取数值类型特征的索引,并在numeric_features中存储
# print(all_features.dtypes)  # 可以知道每一列分别为什么类型特征
numeric_features = all_features.dtypes[all_features.dtypes != 'object'].index
# print(numeric_features)
# 对all_features中所有数值类型特征进行标准化处理
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”(缺失值)视为有效的特征值,并为其创建指示符特征
# 未指定column时,pd.get_dummies函数默认转换所有的分类列,包括MSZoning、SaleType、SaleCondition
# pd.get_dummies每个分类变量都会被转换成多列,每个唯一值对应一列
# 理解意思:某列有五类值,就将此列等价于变成五列,然后对应的列置1,不存在的列置0
all_features = pd.get_dummies(all_features, dummy_na=True)

# 获取训练集的行数,即训练样本数量,并将其存储在n_train中
n_train = train_data.shape[0]
# [:n_train].values是选择前n_train作为训练集的特征将数据转换为Numpy数组
train_features = torch.tensor(all_features[:n_train].values, dtype=torch.float32)
test_features = torch.tensor(all_features[n_train:].values, dtype=torch.float32)
# 将SalePrice列单独提取为标签
train_labels = torch.tensor(
    train_data.SalePrice.values.reshape(-1, 1), dtype=torch.float32)
loss = nn.MSELoss()
in_features = train_features.shape[1]

# 数据集被分成了5份,然后执行5次训练和验证过程,每次过程中,4份数据用于训练模型,剩下的一份用于
k, num_epochs, lr, weight_decay, batch_size = 5, 100, 10, 0, 32
# 分别包含每次迭代的训练集和验证集的log rmse值
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}')
train_and_pred(train_features, test_features, train_labels, test_data,
               num_epochs, lr, weight_decay, batch_size)
d2l.plt.show()

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述




3、通过改进模型(例如,层、权重衰减和dropout)来提高分数。

import hashlib
import os
import tarfile
import zipfile
import requests
import numpy as np
import pandas as pd
import torch
from torch import nn
from d2l import torch as d2l


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)


def init_weights(m):
    if type(m) == nn.Linear:
        nn.init.normal_(m.weight, std=0.01)


def get_net():
    net = nn.Sequential(nn.Flatten(),
                        nn.Linear(in_features, 512),
                        nn.ReLU(),
                        nn.Linear(512, 1))
    net.apply(init_weights)
    return net


# rmse=Root Mean Squared Error,对数均方根误差
def log_rmse(net, features, labels):
    # 通过神经网络net对输入特征features进行前向传播得到预测值
    # 然后为了在取对数时进一步稳定该值,用torch.clamp()函数将小于1的值设置为1
    # 第三个参数:不设置上限,即预测可以无限大
    clipped_preds = torch.clamp(net(features), 1, float('inf'))
    rmse = torch.sqrt(loss(torch.log(clipped_preds),
                           torch.log(labels)))
    # .item() 方法将rmse转换为Python标量返回
    return rmse.item()


# weight_decay权重衰退
def train(net, train_features, train_labels, test_features, test_labels,
          num_epochs, learning_rate, weight_decay, batch_size):
    # 用于存储每一轮训练和测试集上的log rmse值
    train_ls, test_ls = [], []
    train_iter = d2l.load_array((train_features, train_labels), batch_size)
    # 这里使用的是Adam优化算法,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()  # 更新模型参数
        # 将log_rmse函数返回的误差值追加到train_ls列表中就记录了每一轮迭代
        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


def train_and_pred(train_features, test_features, train_labels, test_data,
                   num_epochs, lr, weight_decay, batch_size):
    net = get_net()
    # 返回两个值,一个包含每个epoch训练,log mose的列表,一个未在代码片段中使用的值
    train_ls, _ = train(net, train_features, train_labels, None, None,
                        num_epochs, lr, weight_decay, batch_size)
    # 第一个参数和第二个参数:接收一个epoch数组和一个包含训练log rmse的列表作为输入
    d2l.plot(np.arange(1, num_epochs + 1), [train_ls], xlabel='epoch',
             ylabel='log rmse', xlim=[1, num_epochs], yscale='log')
    # 打印最后一个epoch训练log rmse来评估模型的最终训练性能
    print(f'训练log rmse:{float(train_ls[-1]):f}')
    # 将训练好的网络模型应用于测试特征
    preds = net(test_features).detach().numpy()
    # 将其重新格式化以导出到Kaggle
    # pd.Series()封装成一个Pandas的Series对象,以确保数据类型与DataFrame的列兼容。
    test_data['SalePrice'] = pd.Series(preds.reshape(1, -1)[0])
    submission = pd.concat([test_data['Id'], test_data['SalePrice']], axis=1)
    # 用于将DataFrame保存为csv文件
    submission.to_csv('submission.csv', index=False)


# 四个参数:表示将数据集分为多少个子集、当前折的索引、特征数据集、标签数据集
def get_k_fold_data(k, i, X, y):
    # 断言确保折数大于1,因为至少两个子集才能进行交叉验证
    assert k > 1
    # 每个子集(折)的大小=数据集的总行数除以折数,余数在最后一个子集处理
    fold_size = X.shape[0] // k
    # 初始化训练集
    X_train, y_train = None, None
    for j in range(k):
        # idx将原始数据集的索引切片为 (j * fold_size) 到 ((j + 1) * fold_size),获得当前折数据
        idx = slice(j * fold_size, (j + 1) * fold_size)
        X_part, y_part = X[idx, :], y[idx]
        # 当前折的索引 j 与指定用于验证的索引 i 匹配,将该折的数据分配给验证集(X_valid 和 y_valid)
        if j == i:
            # 将该折的数据分配给验证集
            X_valid, y_valid = X_part, y_part
        # 如不匹配且训练集为空(第一次看到),则将该折的数据分配给训练集(X_train 和 y_train)
        elif X_train is None:
            X_train, y_train = X_part, y_part
        # 如不匹配且训练集不为空,则将该折的数据追加到训练集(X_train 和 y_train)中
        else:
            # 表示将X_train, X_part沿着第一个维度拼接起来
            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):
        # 做k次,每次拿到第i折,把数据data拿出来即拿到从训练集和验证集
        data = get_k_fold_data(k, i, X_train, y_train)
        # 每次循环都会创建一个新的模型实例,确保每折的评估是独立的
        net = get_net()
        # 调用train函数训练模型并返回训练损失列表和验证损失列表
        train_ls, valid_ls = train(net, *data, num_epochs, learning_rate,
                                   weight_decay, batch_size)
        # 索引[-1]获取最后一个epoch的损失,即当前折的训练损失累加到train_l_sum
        train_l_sum += train_ls[-1]
        valid_l_sum += valid_ls[-1]
        if i == 0:  # 如果当前是第i折
            # 第一个参数:用作x轴数据
            # 第二个参数:一个列表储存了每一轮训练后的训练损失和验证损失
            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


# @save
DATA_HUB = dict()
DATA_URL = 'http://d2l-data.s3-accelerate.amazonaws.com/'
DATA_HUB['kaggle_house_train'] = (DATA_URL + 'kaggle_house_pred_train.csv', '585e9cc9370b9160e7921475fbcd7d31219ce')
DATA_HUB['kaggle_house_test'] = (DATA_URL + 'kaggle_house_pred_test.csv', 'fal9780a7b011d9b009e8bff8e99922a8ee2eb90')
train_data = pd.read_csv(download('kaggle_house_train'))
test_data = pd.read_csv(download('kaggle_house_test'))

# print(train_data.shape) # 1460个样本,80个特征,1个标号label
# print(test_data.shape) # 测试样本没有标号label
# 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:]))
# print(all_features.shape)


# 若无法获得测试数据,则可根据训练数据计算均值和标准差
# all_features.dtypes 获取每个特征的数据类型
# all_features.dtypes != 'object' 返回一个布尔值的 Series,其中为 True 的位置表示对应的特征不是对象类型(即数值类型)
# .index 提取数值类型特征的索引,并在numeric_features中存储
# print(all_features.dtypes)  # 可以知道每一列分别为什么类型特征
numeric_features = all_features.dtypes[all_features.dtypes != 'object'].index
# print(numeric_features)
# 对all_features中所有数值类型特征进行标准化处理
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”(缺失值)视为有效的特征值,并为其创建指示符特征
# 未指定column时,pd.get_dummies函数默认转换所有的分类列,包括MSZoning、SaleType、SaleCondition
# pd.get_dummies每个分类变量都会被转换成多列,每个唯一值对应一列
# 理解意思:某列有五类值,就将此列等价于变成五列,然后对应的列置1,不存在的列置0
all_features = pd.get_dummies(all_features, dummy_na=True)

# 获取训练集的行数,即训练样本数量,并将其存储在n_train中
n_train = train_data.shape[0]
# [:n_train].values是选择前n_train作为训练集的特征将数据转换为Numpy数组
train_features = torch.tensor(all_features[:n_train].values, dtype=torch.float32)
test_features = torch.tensor(all_features[n_train:].values, dtype=torch.float32)
# 将SalePrice列单独提取为标签
train_labels = torch.tensor(
    train_data.SalePrice.values.reshape(-1, 1), dtype=torch.float32)
loss = nn.MSELoss()
in_features = train_features.shape[1]

# 数据集被分成了5份,然后执行5次训练和验证过程,每次过程中,4份数据用于训练模型,剩下的一份用于
k, num_epochs, lr, weight_decay, batch_size = 5, 100, 0.01, 300, 32
# 分别包含每次迭代的训练集和验证集的log rmse值
train_l, valid_l = k_fold(k, train_features, train_labels, num_epochs, lr,
                          weight_decay, batch_size)

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

print(f'{k}-折验证: 平均训练log rmse: {float(train_l):f}, '
      f'平均验证log rmse: {float(valid_l):f}')
d2l.plt.show()

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述




十行代码:

# 预测
import pandas as pd
from autogluon.tabular import TabularDataset, TabularPredictor

# 训练
train_data = TabularDataset('./data/California house price/house-prices-advanced-regression-techniques/train.csv')

id, label = 'Id', 'SalePrice'
predictor = TabularPredictor(label=label).fit(train_data.drop(columns=[id]))

test_data = TabularDataset('./data/California house price/house-prices-advanced-regression-techniques/test.csv')
preds = predictor.predict(test_data.drop(columns=[id]))
submission = pd.DataFrame({id: test_data[id], label: preds})
submission.to_csv('./kaggle_submission/submission_4.csv', index=False)

后面两个代码均为测试,发现并没有那么好。

import hashlib
import os
import tarfile
import zipfile
import requests
import numpy as np
import pandas as pd
import torch
from torch import nn
from d2l import torch as d2l



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)


def init_weights(m):
    if isinstance(m, nn.Linear):
        nn.init.normal_(m.weight, std=0.01)


def get_net():
    net = nn.Sequential(
        nn.Flatten(),
        nn.Linear(in_features, 512),
        nn.ReLU(),
        nn.Dropout(p=0.5),  # 添加 Dropout 层
        nn.Linear(512, 256),
        nn.ReLU(),
        nn.Dropout(p=0.5),  # 添加 Dropout 层
        nn.Linear(256, 128),
        nn.ReLU(),
        nn.Dropout(p=0.5),  # 添加 Dropout 层
        nn.Linear(128, 1)
    )
    net.apply(init_weights)
    return net


def log_rmse(net, features, labels):
    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)
    optimizer = torch.optim.Adam(net.parameters(),
                                 lr=learning_rate,
                                 weight_decay=weight_decay)
    scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.7)

    for epoch in range(num_epochs):
        net.train()  # 确保模型处于训练模式
        for X, y in train_iter:
            optimizer.zero_grad()
            l = loss(net(X), y)
            l.backward()
            optimizer.step()

        scheduler.step()  # 更新学习率

        net.eval()  # 切换到评估模式
        with torch.no_grad():
            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


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}')

    net.eval()  # 切换到评估模式
    with torch.no_grad():
        preds = net(test_features).detach().numpy()

    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)


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


# @save
DATA_HUB = dict()
DATA_URL = 'http://d2l-data.s3-accelerate.amazonaws.com/'
DATA_HUB['kaggle_house_train'] = (DATA_URL + 'kaggle_house_pred_train.csv', '585e9cc9370b9160e7921475fbcd7d31219ce')
DATA_HUB['kaggle_house_test'] = (DATA_URL + 'kaggle_house_pred_test.csv', 'fal9780a7b011d9b009e8bff8e99922a8ee2eb90')
train_data = pd.read_csv(download('kaggle_house_train'))
test_data = pd.read_csv(download('kaggle_house_test'))

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()))
all_features[numeric_features] = all_features[numeric_features].fillna(0)

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

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]

k, num_epochs, lr, weight_decay, batch_size = 5, 100, 0.01, 300, 32
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}')
train_and_pred(train_features, test_features, train_labels, test_data,
               num_epochs, lr, weight_decay, batch_size)
d2l.plt.show()


输出:

在这里插入图片描述

在这里插入图片描述




import hashlib
import os
import tarfile
import zipfile
import requests
import numpy as np
import pandas as pd
import torch
from torch import nn
from d2l import torch as d2l


# 数据下载和提取功能
def download(name, cache_dir=os.path.join('..', 'data')):
    """下载一个DATA_HUB中的文件,返回本地文件名"""
    if name not in DATA_HUB:
        raise ValueError(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:
            for chunk in iter(lambda: f.read(1048576), b''):
                sha1.update(chunk)
        if sha1.hexdigest() == sha1_hash:
            return fname

    print(f'正在从 {url} 下载 {fname}...')
    response = requests.get(url, stream=True)
    response.raise_for_status()
    with open(fname, 'wb') as f:
        f.write(response.content)
    return fname


def download_extract(name, folder=None):
    """下载并解压zip/tar文件"""
    fname = download(name)
    base_dir = os.path.dirname(fname)
    ext = os.path.splitext(fname)[1]

    if ext == '.zip':
        with zipfile.ZipFile(fname, 'r') as zip_ref:
            zip_ref.extractall(base_dir)
    elif ext in ('.tar', '.gz'):
        with tarfile.open(fname, 'r') as tar_ref:
            tar_ref.extractall(base_dir)
    else:
        raise ValueError('仅支持zip和tar文件的解压缩')

    return os.path.join(base_dir, folder) if folder else base_dir


# 初始化权重
def init_weights(m):
    if isinstance(m, nn.Linear):
        nn.init.normal_(m.weight, std=0.01)


# 创建网络
def get_net():
    net = nn.Sequential(
        nn.Flatten(),
        nn.Linear(in_features, 1024),  # 增加神经元数量
        nn.ReLU(),
        nn.Dropout(0.3),  # 调整 Dropout 率
        nn.Linear(1024, 512),  # 增加神经元数量
        nn.ReLU(),
        nn.Dropout(0.3),  # 调整 Dropout 率
        nn.Linear(512, 256),
        nn.ReLU(),
        nn.Dropout(0.3),  # 调整 Dropout 率
        nn.Linear(256, 1)
    )
    net.apply(init_weights)
    return net


# 计算对数均方根误差
def log_rmse(net, features, labels):
    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)
    optimizer = torch.optim.Adam(net.parameters(), lr=learning_rate, weight_decay=weight_decay)
    scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=num_epochs)

    for epoch in range(num_epochs):
        net.train()
        for X, y in train_iter:
            optimizer.zero_grad()
            l = loss(net(X), y)
            l.backward()
            optimizer.step()

        scheduler.step()

        net.eval()
        with torch.no_grad():
            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


# 训练并预测
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:{train_ls[-1]:f}')

    net.eval()
    with torch.no_grad():
        preds = net(test_features).detach().numpy()

    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)


# K折交叉验证数据获取
def get_k_fold_data(k, i, X, y):
    assert k > 1, '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], dim=0)
            y_train = torch.cat([y_train, y_part], dim=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]

        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: {train_ls[-1]:.6f}, 验证log rmse: {valid_ls[-1]:.6f}')

    return train_l_sum / k, valid_l_sum / k


# 数据准备
DATA_HUB = {
    'kaggle_house_train': (
    'http://d2l-data.s3-accelerate.amazonaws.com/kaggle_house_pred_train.csv', '585e9cc9370b9160e7921475fbcd7d31219ce'),
    'kaggle_house_test': ('http://d2l-data.s3-accelerate.amazonaws.com/kaggle_house_pred_test.csv',
                          'fal9780a7b011d9b009e8bff8e99922a8ee2eb90')
}
train_data = pd.read_csv(download('kaggle_house_train'))
test_data = pd.read_csv(download('kaggle_house_test'))

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()).fillna(0)
all_features = pd.get_dummies(all_features, dummy_na=True)

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]

# 设置训练参数
k, num_epochs, lr, weight_decay, batch_size = 5, 100, 0.01, 0, 32

# 执行K折交叉验证
train_l, valid_l = k_fold(k, train_features, train_labels, num_epochs, lr, weight_decay, batch_size)
print(f'{k}-折验证: 平均训练log rmse: {train_l:.6f}, 平均验证log rmse: {valid_l:.6f}')

# 训练模型并生成预测
train_and_pred(train_features, test_features, train_labels, test_data, num_epochs, lr, weight_decay, batch_size)
d2l.plt.tight_layout()  # 调整子图参数,使之填充整个图像区域
d2l.plt.show()

输出:
在这里插入图片描述

在这里插入图片描述




4、如果我们没有像本节所做的那样标准化连续的数值特征,会发生什么?

    可能导致模型训练不稳定、性能下降、泛化能力差:未标准化的特征可能具有不同的尺度,这可能导致某些特征在模型训练过程中权重过大或过小,使得模型的收敛速度变慢,甚至无法收敛,标准化可以使得以确保所有特征对模型的影响是均衡的,从而提高算法的性能、收敛速度以及泛化能力。
    可能导致数值问题:如果数据的特征值范围非常广泛,可能会导致数值溢出或者精度问题。标准化可以减少这些问题的发生,确保计算的稳定性和准确性。
    可能导致特征重要性误判:未标准化的特征可能会导致对特征重要性的误判。模型可能会错误地认为某些特征对目标变量的影响更大,而实际上这只是因为该特征的值范围较大而已。

  • 9
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值