- 人工智能
- 深度学习
- 机器学习
- 教程
【免费下载链接】d2l-zh
《动手学深度学习》:面向中文读者、能运行、可讨论。中英文版被70多个国家的500多所大学用于教学。
本篇指南以《动手学深度学习》(d2l-zh)多层级感知机章节的 Kaggle 房价预测实战为核心,完整讲解如何从零参与 Kaggle 房价预测竞赛(House Prices: Advanced Regression Techniques):包括带缓存与 SHA-1 校验的数据集下载工具、基于pandas的数值/类别特征预处理流水线、线性基线模型与对数 RMSE 评估指标、Adam 优化器训练流程、K 折交叉验证的模型选择方法,以及最终生成submission.csv并提交到 Kaggle 的完整链路。读完本文,你将掌握一套可复用到任意表格类回归竞赛的标准化实战方案。
竞赛背景与数据集概况
Kaggle 房价预测比赛是一个非常适合入门深度学习的竞赛起点。该数据集由 Bart de Cock 于 2011 年收集,覆盖 2006–2010 年间美国爱荷华州埃姆斯市(Ames, IA)的房价记录,相比经典的波士顿房价数据集,它在样本数量和特征数量上都更为庞大。它属于通用表格数据,不包含音频、视频等需要专门模型的奇异结构,因此是练习数据预处理、模型设计与超参数选择的理想载体。
在原始章节中,作者通过这一实战项目展示了真实数据科学任务的完整形态:数据缺失、多种数据类型混杂、官方评测指标特殊、需要本地划分验证集而只能通过平台评测测试集。
下载与缓存数据集
全书中的数据集都通过一套统一的工具函数下载。核心机制是维护一个全局字典DATA_HUB,将数据集名称映射为包含URL 与 SHA-1 哈希的二元组;所有数据集托管在DATA_URL指定的站点:
import os import requests import zipfile import tarfile import hashlib DATA_HUB = dict() DATA_URL = 'http://d2l-data.s3-accelerate.amazonaws.com/'download函数负责实际下载,并将数据集缓存到本地目录(默认../data)。它先检查本地缓存:若文件已存在且其 SHA-1 与DATA_HUB中记录一致,则直接复用缓存文件,避免重复下载:
def download(name, cache_dir=os.path.join('..', 'data')): """下载一个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另有两个配套工具:download_extract用于下载并解压 zip/tar 压缩包;download_all则遍历DATA_HUB把书中用到的全部数据集一次性拉取到缓存目录。这套工具已经沉淀进 d2l 工具库中,例如 d2l/paddle.py 中同样注册了本次比赛的数据集条目:
DATA_HUB['kaggle_house_train'] = ( DATA_URL + 'kaggle_house_pred_train.csv', '585e9cc93e70b39160e7921475f9bcd7d31219ce') DATA_HUB['kaggle_house_test'] = ( DATA_URL + 'kaggle_house_pred_test.csv', 'fa19780a7b011d9b009e8bff8e99922a8ee2eb90')Kaggle 平台与竞赛页面
Kaggle 是当今流行的机器学习竞赛平台,每场比赛围绕至少一个数据集展开,许多比赛由赞助方提供奖金。平台通过论坛与共享代码促进协作与竞争:排行榜提供了各方案之间直接的定量比较,代码共享则让每个人都能学习哪些方法有效、哪些无效。参与比赛前需要先注册账户。
本次房价预测竞赛的页面地址为https://www.kaggle.com/c/house-prices-advanced-regression-techniques。在竞赛页面的Data 选项卡下可以找到数据集、提交预测并查看排名:
访问与读取数据集
竞赛数据分为训练集和测试集。每条记录包含房屋属性值(如街道类型、施工年份、屋顶类型、地下室状况等)与房屋价格标签;特征由多种数据类型构成——施工年份是整数、屋顶类型是离散类别、其余特征多为浮点数。现实数据的复杂性在这里显现:部分样本存在整体缺失,缺失值被标记为 "NA"。房屋价格只出现在训练集中(毕竟是竞赛)。我们只能自行划分训练集创建验证集,而官方测试集的评估只能在上传预测后由平台完成。
读取数据使用pandas(参见 pandas 章节)。若环境未安装,可在 Jupyter 中直接!pip install pandas。随后按框架导入工具库,例如 PyTorch 版:
%matplotlib inline from d2l import torch as d2l import torch from torch import nn import pandas as pd import numpy as np(MXNet、TensorFlow、Paddle 版本的导入方式对应为from d2l import mxnet/tensorflow/paddle as d2l。)
利用前述下载工具缓存比赛数据后,用pandas分别载入训练与测试 CSV:
train_data = pd.read_csv(download('kaggle_house_train')) test_data = pd.read_csv(download('kaggle_house_test')) print(train_data.shape) # (1460, 81):1460 个样本、80 个特征 + 1 个标签 print(test_data.shape) # (1459, 80):1459 个样本、80 个特征查看前 4 个样本的前 4 个与最后 2 个特征以及标签(SalePrice):
print(train_data.iloc[0:4, [0, 1, 2, 3, -3, -2, -1]])可以看到每个样本的第一个特征是Id,它只用于识别样本、不携带任何预测信息,因此送入模型前要将其剔除。训练集与测试集按列拼接,得到统一的特征矩阵:
all_features = pd.concat((train_data.iloc[:, 1:-1], test_data.iloc[:, 1:]))数据预处理
真实数据往往混合多种类型,建模前必须预处理。流程分三步:
1. 数值特征:均值填充 + 标准化
首先将所有缺失值替换为对应特征的均值;然后将特征重新缩放到零均值、单位方差:
$$x \leftarrow \frac{x - \mu}{\sigma},$$
其中 $\mu$、$\sigma$ 分别是特征的均值与标准差。标准化后 $E[\frac{x-\mu}{\sigma}]=0$ 且 $E[(x-\mu)^2]=\sigma^2$,特征满足零均值、单位方差。标准化的意义有二:一是方便优化收敛;二是因为我们事先不知道哪些特征相关,不希望惩罚分配给某个特征的系数超过其他特征(否则会引入不公平的先验偏置)。
# 选出所有非 object(数值型)特征 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)一个小技巧:标准化之后缺失值等价于"该特征取均值",因此直接填充 0 即可,无需单独记录均值。
2. 类别特征:独热编码
对 "MSZoning" 这类离散特征,使用独热编码(one-hot)替换——这与多分类标签转向量是同一思想。例如 "MSZoning" 取值 "RL" 与 "RM",去掉原特征后生成两个指示特征MSZoning_RL、MSZoning_RM:原始值为 "RL" 时前者为 1、后者为 0。pandas的get_dummies自动完成这一切,其中关键参数是dummy_na=True——它把 "NA"(缺失值)也当作一种合法取值,并为其单独创建指示特征:
all_features = pd.get_dummies(all_features, dummy_na=True) all_features.shape # 特征数从 79 增至 331这一转换使特征总数从 79 增加到 331。最后通过values属性把pandas数据转为 NumPy,再转成张量供训练使用:
n_train = train_data.shape[0] train_features = d2l.tensor(all_features[:n_train].values, dtype=d2l.float32) test_features = d2l.tensor(all_features[n_train:].values, dtype=d2l.float32) train_labels = d2l.tensor( train_data.SalePrice.values.reshape(-1, 1), dtype=d2l.float32)训练:线性基线模型与对数 RMSE
首先训练一个带平方损失(MSE)的线性模型。线性模型固然难以夺冠,但它提供了宝贵的健全性检查(sanity check):若连它都做不优于随机猜测,则说明大概率存在数据处理 bug;若一切正常,它又成为基线,帮助我们估计更复杂模型的提升空间。
loss = nn.MSELoss() in_features = train_features.shape[1] def get_net(): net = nn.Sequential(nn.Linear(in_features, 1)) return net相对误差与对数 RMSE
房价与股票类似,我们更关心相对误差$\frac{y-\hat{y}}{y}$ 而非绝对误差。例如在俄亥俄州农村(典型房价 12.5 万美元)偏差 10 万美元是灾难性的,而在加州豪宅区(房价中位数超 400 万美元)同样的偏差却是优秀预测。解决办法是对价格的对数衡量差异——这恰恰也是竞赛官方的评测指标:$|\log y - \log \hat{y}| \le \delta$ 等价于 $e^{-\delta} \le \frac{\hat{y}}{y} \le e^\delta$。由此定义预测价格对数与标签价格对数之间的均方根误差:
$$\sqrt{\frac{1}{n}\sum_{i=1}^n\left(\log y_i -\log \hat{y}_i\right)^2}.$$
实现上,为防止对小于 1 的预测取对数产生负值或溢出,先把预测值裁剪到不小于 1:
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()训练函数与 Adam 优化器
与前面章节不同,本节训练使用Adam 优化器(后续章节会详述)。其核心吸引力在于对初始学习率不那么敏感,即便在无限超参调优资源下未必优于别的优化器,实际使用中人们普遍发现它显著省心。训练函数在每个 epoch 结束后记录训练集(及可选的验证集)的log_rmse:
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这里的d2l.load_array在 d2l/torch.py 中实现为TensorDataset+DataLoader(is_train=True时打乱数据),weight_decay参数直接对接 Adam 的 L2 正则。TensorFlow 版使用tf.keras.losses.MeanSquaredError与tf.keras.optimizers.Adam,并借助tf.GradientTape手动计算梯度应用更新;Paddle 版使用paddle.optimizer.Adam,训练后调用optimizer.clear_grad()清空梯度。
K 折交叉验证
模型选择与超参数调整依靠 模型选择章节 引入的 K 折交叉验证。首先实现取第 $i$ 折的函数:把数据切分为 K 段,第 $i$ 段作为验证集,其余拼接为训练集。数据集规模不大,这种简单切分方式足够:
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 = d2l.concat([X_train, X_part], 0) y_train = d2l.concat([y_train, y_part], 0) return X_train, y_train, X_valid, y_valid然后执行 K 次训练,返回训练与验证误差的平均值,并在第一折时绘制训练/验证 log rmse 随 epoch 变化的曲线(纵轴为对数刻度):
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模型选择与过拟合监控
本节提供一组未调优的默认超参数,把进一步优化留给读者:
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}')这里lr=5(线性模型配合标准化特征可承受较大的学习率)、weight_decay=0(暂不施加权重衰减)、batch_size=64、训练 100 轮。训练中应同时监控训练误差与验证误差两个数字:
- 训练误差极低而验证误差显著更高 →过拟合,可引入正则化(权重衰减、dropout)或减少模型容量;
- 过拟合轻微 → 数据或许能支撑更强的模型。
注意,K 折交叉验证在数据集足够大、超参数组合合理时对多重检验相当稳健;但如果尝试的组合过多,可能"碰巧"取得优异的验证表现,而该表现不再代表真实泛化误差。
提交预测到 Kaggle
确定超参数后,改用全部训练数据重新训练(而非仅交叉验证使用的 $1-1/K$ 比例),再对测试集做预测,并整理成 Kaggle 要求的submission.csv(仅含Id与SalePrice两列):
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 = d2l.numpy(net(test_features)) # 重新格式化以导出到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)一个实用的健全性检查是:确认测试集上的预测表现与 K 折交叉验证的结果大致吻合;若一致,即可放心上传。提交步骤非常简单:
- 登录 Kaggle,进入房价预测竞赛页面;
- 点击 "Submit Predictions" 或 "Late Submission" 按钮(位于页面右侧);
- 点击页面底部虚线框中的 "Upload Submission File" 按钮,选择要上传的预测文件;
- 点击页面底部的 "Make Submission" 按钮查看结果。
小结与进阶练习
本节沉淀的可复用经验:
- 真实数据通常混合多种数据类型,必须预处理;
- 将实值数据重缩放为零均值、单位方差,并用均值填充缺失值,是良好的默认做法;
- 把类别特征转换为指示(独热)特征,可将其当作独热向量处理;
- 用 K 折交叉验证选择模型并调整超参数;
- 对数变换对相对误差类问题(价格、股价)非常有效。
若想继续提升成绩,可围绕以下方向练习:
- 将本节预测提交到 Kaggle,观察分数;
- 直接最小化价格的对数(即把
log(SalePrice)作为标签)能否改进模型?预测价格对数而非价格会发生什么? - 用均值填充缺失值是否总是好主意?(提示:能否构造"缺失并非随机"的情形?)
- 通过 K 折交叉验证调优超参数提升得分;
- 改进模型结构(增加层数、权重衰减、dropout);
- 若不对连续数值特征做标准化,会发生什么?
上述完整流程对应的可执行中文代码与逐框架(MXNet/PyTorch/TensorFlow/Paddle)实现可在 chapter_multilayer-perceptrons/kaggle-house-price.md 中查看,相关工具函数(load_array、plot、DATA_HUB等)沉淀于 d2l/torch.py、d2l/paddle.py、d2l/tensorflow.py 与 d2l/mxnet.py,可直接import d2l复用。
- 人工智能
- 深度学习
- 机器学习
- 教程
【免费下载链接】d2l-zh
《动手学深度学习》:面向中文读者、能运行、可讨论。中英文版被70多个国家的500多所大学用于教学。
相关推荐
突破Kaggle房价预测瓶颈:Gluon+K折交叉验证实战指南
突破Kaggle房价预测瓶颈:Gluon+K折交叉验证实战指南 在数据科学竞赛中,房价预测是一个经典的回归问题,而Kaggle平台上的房价预测竞赛更是检验机器学
MXNet Gluon 实战:基于 K 折交叉验证的 Kaggle 房价预测回归任务(House Prices)
MXNet Gluon 实战:基于 K 折交叉验证的 Kaggle 房价预测回归任务(House Prices) 本篇指南以仓库中的 example/gluon
深度学习人工智能机器学习分布式训练基于 Apache MXNet Gluon 的 Kaggle House Prices 房价预测实战:从数据预处理到 K 折交叉验证的完整回归管线
基于 Apache MXNet Gluon 的 Kaggle House Prices 房价预测实战:从数据预处理到 K 折交叉验证的完整回归管线 导读 本文围
人工智能深度学习机器学习
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考