机器学习:实战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
import matplotlib.pyplot as plt

DATA_HUB = dict()
DATA_URL = 'http://d2l-data.s3-accelerate.amazonaws.com/'


def download(name, cache_dir=os.path.join('..', 'data')):
    """下载一个DATA_HUB中的文件,返回本地文件名"""
    assert name in DATA_HUB, f"{name} 不存在于 {DATA_HUB}"  # 使用assert语句检查name是否在DATA_HUB字典中。如果不存在,则抛出一个异常。
    url, shal_hash = DATA_HUB[name]
    os.makedirs(cache_dir, exist_ok=True)  # 使用os.makedirs函数创建cache_dir目录,如果目录已经存在,则不会抛出异常。
    fname = os.path.join(cache_dir, url.split('/')[-1])  # 构造本地文件名,它将是URL的最后一个部分。
    if os.path.exists(fname):  # 检查文件是否已经存在于本地。
        shal = hashlib.sha1() # 如果文件已经存在,则创建一个SHA-1哈希对象
        with open(fname, 'rb') as f:
            while True:
                data = f.read(1048576)  # 每次读取1MB的数据。
                if not data:  # 如果没有更多的数据,则跳出循环。
                    break
                shal.update(data)  # 使用读取到的数据更新SHA-1哈希对象。
        if shal.hexdigest() == shal_hash:  # 计算文件的SHA-1哈希值,并将其与预期的哈希值进行比较。如果它们匹配,则返回文件名。
            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):
    """下载并解压zip/tar文件"""
    fname = download(name)
    base_dir = os.path.dirname(fname)
    data_dir, ext = os.path.splitext(fname)  # 使用os.path.splitext函数将fname文件的文件名和扩展名分开,并将它们分别存储在data_dir和ext变量中。
    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)  # 将文件解压到base_dir目录中。
    # 返回解压后的文件夹路径。如果folder参数被提供,则返回base_dir/folder路径,否则返回data_dir路径。
    return os.path.join(base_dir, folder) if folder else data_dir


def download_all():
    """下载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]])

# 将train_data中的id和最后的SalePrice以及test_data中的id去掉
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)
all_features = pd.get_dummies(all_features, dummy_na=True)  # 对所有类别特征进行独热编码(one-hot encoding),并将缺失值也作为一个类别编码。
print(all_features.shape)

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)
# 从训练数据中获取目标值(SalePrice),将其重塑为二维张量(每个样本为一个单独的行向量),并转换为PyTorch张量,数据类型为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))  # 输入特征数:in_features  输出特征数:1
    net = nn.Sequential(nn.Flatten(),
                        nn.Linear(in_features, 1024),
                        nn.ReLU(),
                        nn.Linear(1024, 1))
    return net


# 计算模型的预测结果和真实标签之间的对数均方根误差
# net:已经训练好的神经网络模型。
# features:输入特征
# labels:真实标签
def log_rmse(net, features, labels):
    # 使用 torch.clamp 函数将预测结果限制在 [1, +∞) 的范围内。这是因为在计算对数均方根误差时,如果预测结果小于等于 0,会导致计算出现错误或不稳定。
    clipped_preds = torch.clamp(net(features), 1, float('inf'))
    rmse = torch.sqrt(loss(torch.log(clipped_preds), torch.log(labels)))  # 预测结果和真实标签分别取对数后loss之后再开平方根
    return rmse.item()  # 将对数均方根误差从 PyTorch 张量转换为 Python 浮点数。


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优化算法
    # 创建一个 Adam 优化器对象,用于更新模型的参数。
    # 优化器的参数包括:学习率(lr)、权重衰减(weight_decay)和模型的参数(net.parameters())。
    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))  # 在每个训练轮次结束后,计算训练集的对数均方根误差,并将其添加到 train_ls 列表中。
        if test_labels is not None:
            test_ls.append(log_rmse(net, test_features, test_labels))
    return train_ls, test_ls  # 返回包含每个训练轮次的训练集和测试集的对数均方根误差的列表。这样可以方便地在训练过程中观察和记录模型的性能变化。


# 获取 k 折交叉验证中的数据集划分
# k(折数)、i(当前折的索引)、X(特征矩阵)和 y(标签向量)。
def get_k_fold_data(k, i, X, y):
    assert k > 1  # 断言 k 必须大于 1,确保进行的是交叉验证。
    fold_size = X.shape[0] // k  # 计算每个折的大小。将特征矩阵 X 的第一维(即样本数)除以 k,得到每个折应有的样本数量。
    X_train, y_train = None, None
    for j in range(k):
        # 计算当前折的索引范围。使用 Python 的 slice 对象来指定当前折在特征矩阵 X 和标签向量 y 中的起始和结束位置。
        idx = slice(j * fold_size, (j + 1) * fold_size)
        X_part, y_part = X[idx, :], y[idx]  # 根据当前折的索引范围,从特征矩阵 X 和标签向量 y 中提取出当前折的数据。
        if j == i:
            X_valid, y_valid = X_part, y_part  # 如果是当前折,将当前折的数据赋值给验证集的特征矩阵和标签向量。
        elif X_train is None:
            # 如果当前折不是当前索引 i 对应的折,并且训练集的特征矩阵和标签向量还未初始化,将当前折的数据赋值给训练集的特征矩阵和标签向量。
            X_train, y_train = X_part, y_part
        else:
            # 如果当前折不是当前索引 i 对应的折,并且训练集的特征矩阵和标签向量已经初始化,将当前折的数据与训练集的特征矩阵和标签向量进行拼接。
            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(折数)、X_train(训练特征矩阵)、y_train(训练标签向量)、num_epochs(训练轮数)、
# learning_rate(学习率)、weight_decay(权重衰减)和 batch_size(批次大小)。
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]  # train_ls[-1]:获取train_ls中的最后一个值,因为前面的值都是前面训练轮次的,不是最后结果
        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')  # 如果当前折是第一折,使用 d2l.plot 函数绘制训练和验证的对数均方根误差曲线图。
            plt.show()
        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, 0.1, 0.001, 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()
    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')
    plt.show()
    print(f'训练log rmse:{float(train_ls[-1]):f}')
    # 将网络应用于测试集。
    # 使用训练好的模型对测试特征进行预测,并将预测结果存储在 preds 变量中。这里使用了 detach 方法来断开计算图,以便将预测结果转换为 NumPy 数组。
    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('../data/submission.csv', index=False)


train_and_pred(train_features, test_features, train_labels, test_data,
               num_epochs, lr, weight_decay, batch_size)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值