现在我们已经介绍了一些用于构建和训练深度网络并使用包括权重衰减和丢失在内的技术对其进行正则化的基本工具,我们准备通过参加 Kaggle 竞赛将所有这些知识付诸实践。房价预测竞赛是一个很好的起点。数据相当通用,没有表现出可能需要专门模型(如音频或视频可能)的奇异结构。该数据集由 De Cock ( 2011 )收集,涵盖 2006 年至 2010 年爱荷华州埃姆斯的房价。 它比Harrison 和 Rubinfeld (1978)著名的波士顿住房数据集大得多,拥有更多的例子和更多的特征。
在本节中,我们将带您了解数据预处理、模型设计和超参数选择的详细信息。我们希望通过实践方法,您将获得一些直觉,这些直觉将指导您作为数据科学家的职业生涯。
%matplotlib inline import pandas as pd import torch from torch import nn from d2l import torch as d2l
%matplotlib inline import pandas as pd from mxnet import autograd, gluon, init, np, npx from mxnet.gluon import nn from d2l import mxnet as d2l npx.set_np()
%matplotlib inline import jax import numpy as np import pandas as pd from jax import numpy as jnp from d2l import jax as d2l
No GPU/TPU found, falling back to CPU. (Set TF_CPP_MIN_LOG_LEVEL=0 and rerun for more info.)
%matplotlib inline import pandas as pd import tensorflow as tf from d2l import tensorflow as d2l
5.7.1. 下载数据
在整本书中,我们将在各种下载的数据集上训练和测试模型。在这里,我们实现了两个实用函数来下载文件和提取 zip 或 tar 文件。同样,我们将它们的实现推迟到 第 23.7 节。
def download(url, folder, sha1_hash=None): """Download a file to folder and return the local filepath.""" def extract(filename, folder): """Extract a zip/tar file into folder."""
5.7.2. 格格
Kaggle是一个举办机器学习竞赛的流行平台。每场比赛都以数据集为中心,许多比赛由利益相关者赞助,他们为获胜的解决方案提供奖励。该平台帮助用户通过论坛和共享代码进行交互,促进协作和竞争。虽然排行榜追逐经常失控,研究人员短视地关注预处理步骤而不是提出基本问题,但平台的客观性也具有巨大价值,该平台有助于竞争方法之间的直接定量比较以及代码共享,以便每个人都可以了解哪些有效,哪些无效。如果你想参加 Kaggle 比赛,你首先需要注册一个账号(见图 5.7.1)。
图 5.7.1 Kaggle 网站。
在房价预测比赛页面,如图 5.7.2所示,可以找到数据集(在“数据”选项卡下),提交预测,就可以看到你的排名,网址在这里:
https://www.kaggle.com/c/house-prices-advanced-regression-techniques
图 5.7.2房价预测比赛页面。
5.7.3. 访问和读取数据集
请注意,比赛数据分为训练集和测试集。每条记录包括房屋的属性值和街道类型、建造年份、屋顶类型、地下室状况等属性。特征由各种数据类型组成。例如,建造年份用整数表示,屋顶类型用离散的分类分配表示,其他特征用浮点数表示。这就是现实使事情复杂化的地方:例如,一些数据完全缺失,缺失值简单地标记为“na”。每个房子的价格仅包含在训练集中(毕竟这是一场比赛)。我们希望对训练集进行分区以创建验证集,但我们只能在将预测上传到 Kaggle 后才能在官方测试集上评估我们的模型。图 5.7.2有下载数据的链接。
首先,我们将pandas使用我们在第 2.2 节中介绍的方法读入和处理数据。为了方便起见,我们可以下载并缓存 Kaggle 住房数据集。如果与此数据集对应的文件已存在于缓存目录中并且其 SHA-1 匹配sha1_hash,我们的代码将使用缓存文件以避免因冗余下载而阻塞您的互联网。
class KaggleHouse(d2l.DataModule): def __init__(self, batch_size, train=None, val=None): super().__init__() self.save_hyperparameters() if self.train is None: self.raw_train = pd.read_csv(d2l.download( d2l.DATA_URL + 'kaggle_house_pred_train.csv', self.root, sha1_hash='585e9cc93e70b39160e7921475f9bcd7d31219ce')) self.raw_val = pd.read_csv(d2l.download( d2l.DATA_URL + 'kaggle_house_pred_test.csv', self.root, sha1_hash='fa19780a7b011d9b009e8bff8e99922a8ee2eb90'))
训练数据集包含 1460 个示例、80 个特征和 1 个标签,而验证数据包含 1459 个示例和 80 个特征。
data = KaggleHouse(batch_size=64) print(data.raw_train.shape) print(data.raw_val.shape)
Downloading ../data/kaggle_house_pred_train.csv from http://d2l-data.s3-accelerate.amazonaws.com/kaggle_house_pred_train.csv... Downloading ../data/kaggle_house_pred_test.csv from http://d2l-data.s3-accelerate.amazonaws.com/kaggle_house_pred_test.csv... (1460, 81) (1459, 80)
data = KaggleHouse(batch_size=64) print(data.raw_train.shape) print(data.raw_val.shape)
Downloading ../data/kaggle_house_pred_train.csv from http://d2l-data.s3-accelerate.amazonaws.com/kaggle_house_pred_train.csv... Downloading ../data/kaggle_house_pred_test.csv from http://d2l-data.s3-accelerate.amazonaws.com/kaggle_house_pred_test.csv... (1460, 81) (1459, 80)
data = KaggleHouse(batch_size=64) print(data.raw_train.shape) print(data.raw_val.shape)
Downloading ../data/kaggle_house_pred_train.csv from http://d2l-data.s3-accelerate.amazonaws.com/kaggle_house_pred_train.csv... Downloading ../data/kaggle_house_pred_test.csv from http://d2l-data.s3-accelerate.amazonaws.com/kaggle_house_pred_test.csv... (1460, 81) (1459, 80)
data = KaggleHouse(batch_size=64) print(data.raw_train.shape) print(data.raw_val.shape)
Downloading ../data/kaggle_house_pred_train.csv from http://d2l-data.s3-accelerate.amazonaws.com/kaggle_house_pred_train.csv... Downloading ../data/kaggle_house_pred_test.csv from http://d2l-data.s3-accelerate.amazonaws.com/kaggle_house_pred_test.csv... (1460, 81) (1459, 80)
5.7.4. 数据预处理
我们来看看前四个和最后两个特征以及前四个示例中的标签 (SalePrice)。
print(data.raw_train.iloc[:4, [0, 1, 2, 3, -3, -2, -1]])
Id MSSubClass MSZoning LotFrontage SaleType SaleCondition SalePrice 0 1 60 RL 65.0 WD Normal 208500 1 2 20 RL 80.0 WD Normal 181500 2 3 60 RL 68.0 WD Normal 223500 3 4 70 RL 60.0 WD Abnorml 140000
print(data.raw_train.iloc[:4, [0, 1, 2, 3, -3, -2, -1]])
Id MSSubClass MSZoning LotFrontage SaleType SaleCondition SalePrice 0 1 60 RL 65.0 WD Normal 208500 1 2 20 RL 80.0 WD Normal 181500 2 3 60 RL 68.0 WD Normal 223500 3 4 70 RL 60.0 WD Abnorml 140000
print(data.raw_train.iloc[:4, [0, 1, 2, 3, -3, -2, -1]])
Id MSSubClass MSZoning LotFrontage SaleType SaleCondition SalePrice 0 1 60 RL 65.0 WD Normal 208500 1 2 20 RL 80.0 WD Normal 181500 2 3 60 RL 68.0 WD Normal 223500 3 4 70 RL 60.0 WD Abnorml 140000
print(data.raw_train.iloc[:4, [0, 1, 2, 3, -3, -2, -1]])
Id MSSubClass MSZoning LotFrontage SaleType SaleCondition SalePrice 0 1 60 RL 65.0 WD Normal 208500 1 2 20 RL 80.0 WD Normal 181500 2 3 60 RL 68.0 WD Normal 223500 3 4 70 RL 60.0 WD Abnorml 140000
我们可以看到,在每个示例中,第一个特征是 ID。这有助于模型识别每个训练示例。虽然这很方便,但它不携带任何用于预测目的的信息。因此,我们将在将数据输入模型之前将其从数据集中删除。此外,鉴于数据类型多种多样,我们需要在开始建模之前对数据进行预处理。
让我们从数字特征开始。首先,我们应用启发式方法,用相应特征的平均值替换所有缺失值。然后,为了将所有特征放在一个共同的尺度上,我们通过将特征重新缩放为零均值和单位方差来标准化数据:
(5.7.1)x←x−μσ,
在哪里μ和σ分别表示平均值和标准偏差。为了验证这确实改变了我们的特征(变量),使其具有零均值和单位方差,请注意 E[x−μσ]=μ−μσ=0然后 E[(x−μ)2]=(σ2+μ2)−2μ2+μ2=σ2. 直觉上,我们标准化数据有两个原因。首先,证明它便于优化。其次,因为我们先验地不知道 哪些特征是相关的,所以我们不想惩罚分配给一个特征的系数比其他任何特征更多。
接下来我们处理离散值。这包括诸如“MSZoning”之类的功能。我们将它们替换为 one-hot 编码,其方式与我们之前将多类标签转换为向量的方式相同(请参阅 第 4.1.1 节)。例如,“MSZoning”采用值“RL”和“RM”。删除“MSZoning”特征,创建两个新的指标特征“MSZoning_RL”和“MSZoning_RM”,其值为0或1。根据one-hot编码,如果“MSZoning”的原始值为“RL”,则“ MSZoning_RL” 为 1,“MSZoning_RM” 为 0。pandas包会自动为我们完成此操作。
@d2l.add_to_class(KaggleHouse) def preprocess(self): # Remove the ID and label columns label = 'SalePrice' features = pd.concat( (self.raw_train.drop(columns=['Id', label]), self.raw_val.drop(columns=['Id']))) # Standardize numerical columns numeric_features = features.dtypes[features.dtypes!='object'].index features[numeric_features] = features[numeric_features].apply( lambda x: (x - x.mean()) / (x.std())) # Replace NAN numerical features by 0 features[numeric_features] = features[numeric_features].fillna(0) # Replace discrete features by one-hot encoding features = pd.get_dummies(features, dummy_na=True) # Save preprocessed features self.train = features[:self.raw_train.shape[0]].copy() self.train[label] = self.raw_train[label] self.val = features[self.raw_train.shape[0]:].copy()
您可以看到此转换将特征数量从 79 增加到 331(不包括 ID 和标签列)。
data.preprocess() data.train.shape
(1460, 332)
data.preprocess() data.train.shape
(1460, 332)
data.preprocess() data.train.shape
(1460, 332)
data.preprocess() data.train.shape
(1460, 332)
5.7.5. 误差测量
首先,我们将训练一个具有平方损失的线性模型。毫不奇怪,我们的线性模型不会导致提交竞赛获胜,但它提供了完整性检查以查看数据中是否存在有意义的信息。如果我们在这里不能比随机猜测做得更好,那么我们很可能遇到数据处理错误。如果一切正常,线性模型将作为基线,让我们直观地了解简单模型与最佳报告模型的接近程度,让我们了解我们应该从更高级的模型中获得多少收益。
对于房价,就像股票价格一样,我们更关心相对数量而不是绝对数量。因此我们更倾向于关心相对误差y−y^y比关于绝对误差y−y^. 例如,如果我们在估计俄亥俄州农村地区的房屋价格时预测偏离 100,000 美元,那里典型房屋的价值为 125,000 美元,那么我们可能做得很糟糕。另一方面,如果我们在加利福尼亚州的 Los Altos Hills 出现这个数额的错误,这可能代表了一个惊人准确的预测(那里的房价中位数超过 400 万美元)。
解决这个问题的一种方法是衡量价格估计的对数差异。事实上,这也是比赛用来评估提交质量的官方误差衡量标准。毕竟价值不高δ为了 |logy−logy^|≤δ翻译成 e−δ≤y^y≤eδ. 这导致预测价格的对数与标签价格的对数之间存在以下均方根误差:
(5.7.2)1n∑i=1n(logyi−logy^i)2.
@d2l.add_to_class(KaggleHouse) def get_dataloader(self, train): label = 'SalePrice' data = self.train if train else self.val if label not in data: return get_tensor = lambda x: torch.tensor(x.values, dtype=torch.float32) # Logarithm of prices tensors = (get_tensor(data.drop(columns=[label])), # X torch.log(get_tensor(data[label])).reshape((-1, 1))) # Y return self.get_tensorloader(tensors, train)
@d2l.add_to_class(KaggleHouse) def get_dataloader(self, train): label = 'SalePrice' data = self.train if train else self.val if label not in data: return get_tensor = lambda x: np.array(x.values, dtype=np.float32) # Logarithm of prices tensors = (get_tensor(data.drop(columns=[label])), # X np.log(get_tensor(data[label])).reshape((-1, 1))) # Y return self.get_tensorloader(tensors, train)
@d2l.add_to_class(KaggleHouse) def get_dataloader(self, train): label = 'SalePrice' data = self.train if train else self.val if label not in data: return get_tensor = lambda x: jnp.array(x.values, dtype=jnp.float32) # Logarithm of prices tensors = (get_tensor(data.drop(columns=[label])), # X jnp.log(get_tensor(data[label])).reshape((-1, 1))) # Y return self.get_tensorloader(tensors, train)
@d2l.add_to_class(KaggleHouse) def get_dataloader(self, train): label = 'SalePrice' data = self.train if train else self.val if label not in data: return get_tensor = lambda x: tf.constant(x.values, dtype=tf.float32) # Logarithm of prices tensors = (get_tensor(data.drop(columns=[label])), # X tf.reshape(tf.math.log(get_tensor(data[label])), (-1, 1))) # Y return self.get_tensorloader(tensors, train)
5.7.6.K-折叠交叉验证
您可能还记得我们在第 3.6.3 节中介绍了交叉验证 ,我们在那里讨论了如何处理模型选择。我们将充分利用它来选择模型设计和调整超参数。我们首先需要一个返回ith将数据折叠成 K-折叠交叉验证程序。它通过切出ith段作为验证数据并将其余部分作为训练数据返回。请注意,这不是处理数据的最有效方式,如果我们的数据集相当大,我们肯定会做一些更聪明的事情。但是这种增加的复杂性可能会不必要地混淆我们的代码,因此由于问题的简单性,我们可以在这里安全地省略它。
def k_fold_data(data, k): rets = [] fold_size = data.train.shape[0] // k for j in range(k): idx = range(j * fold_size, (j+1) * fold_size) rets.append(KaggleHouse(data.batch_size, data.train.drop(index=idx), data.train.loc[idx])) return rets
我们训练时返回平均验证错误K时间在K- 折叠交叉验证。
def k_fold(trainer, data, k, lr): val_loss, models = [], [] for i, data_fold in enumerate(k_fold_data(data, k)): model = d2l.LinearRegression(lr) model.board.yscale='log' if i != 0: model.board.display = False trainer.fit(model, data_fold) val_loss.append(float(model.board.data['val_loss'][-1].y)) models.append(model) print(f'average validation log mse = {sum(val_loss)/len(val_loss)}') return models
5.7.7. 选型
在这个例子中,我们选择了一组未调整的超参数,并留给读者来改进模型。找到一个好的选择可能需要时间,具体取决于优化了多少变量。有了足够大的数据集和正常的超参数,K-折叠交叉验证往往对多重测试具有合理的弹性。然而,如果我们尝试了不合理的大量选项,我们可能会幸运地发现我们的验证性能不再代表真正的错误。
trainer = d2l.Trainer(max_epochs=10) models = k_fold(trainer, data, k=5, lr=0.01)
average validation log mse = 0.17563143908977508
trainer = d2l.Trainer(max_epochs=10) models = k_fold(trainer, data, k=5, lr=0.01)
average validation log mse = 0.1256050333380699
trainer = d2l.Trainer(max_epochs=10) models = k_fold(trainer, data, k=5, lr=0.01)
average validation log mse = 0.1252850264310837
trainer = d2l.Trainer(max_epochs=10) models = k_fold(trainer, data, k=5, lr=0.01)
average validation log mse = 0.17464343845844268
请注意,有时一组超参数的训练错误数量可能非常低,即使 K-折叠交叉验证要高得多。这表明我们过度拟合。在整个培训过程中,您需要监控这两个数字。较少的过度拟合可能表明我们的数据可以支持更强大的模型。大规模过度拟合可能表明我们可以通过结合正则化技术来获益。
5.7.8. 在 Kaggle 上提交预测
现在我们知道应该选择什么样的超参数,我们可以计算所有超参数对测试集的平均预测 K楷模。将预测保存在 csv 文件中将简化将结果上传到 Kaggle 的过程。以下代码将生成一个名为submission.csv.
preds = [model(torch.tensor(data.val.values, dtype=torch.float32)) for model in models] # Taking exponentiation of predictions in the logarithm scale ensemble_preds = torch.exp(torch.cat(preds, 1)).mean(1) submission = pd.DataFrame({'Id':data.raw_val.Id, 'SalePrice':ensemble_preds.detach().numpy()}) submission.to_csv('submission.csv', index=False)
preds = [model(np.array(data.val.values, dtype=np.float32)) for model in models] # Taking exponentiation of predictions in the logarithm scale ensemble_preds = np.exp(np.concatenate(preds, 1)).mean(1) submission = pd.DataFrame({'Id':data.raw_val.Id, 'SalePrice':ensemble_preds.asnumpy()}) submission.to_csv('submission.csv', index=False)
preds = [model.apply({'params': trainer.state.params}, jnp.array(data.val.values, dtype=jnp.float32)) for model in models] # Taking exponentiation of predictions in the logarithm scale ensemble_preds = jnp.exp(jnp.concatenate(preds, 1)).mean(1) submission = pd.DataFrame({'Id':data.raw_val.Id, 'SalePrice':np.asarray(ensemble_preds)}) submission.to_csv('submission.csv', index=False)
preds = [model(tf.constant(data.val.values, dtype=tf.float32)) for model in models] # Taking exponentiation of predictions in the logarithm scale ensemble_preds = tf.reduce_mean(tf.exp(tf.concat(preds, 1)), 1) submission = pd.DataFrame({'Id':data.raw_val.Id, 'SalePrice':ensemble_preds.numpy()}) submission.to_csv('submission.csv', index=False)
接下来,如图5.7.3所示,我们可以在 Kaggle 上提交我们的预测,并查看它们如何与测试集上的实际房价(标签)进行比较。步骤很简单:
登录Kaggle网站,访问房价预测比赛页面。
单击“提交预测”或“延迟提交”按钮(截至撰写本文时,该按钮位于右侧)。
单击页面底部虚线框中的“上传提交文件”按钮,然后选择您要上传的预测文件。
单击页面底部的“提交”按钮以查看结果。
图 5.7.3向 Kaggle 提交数据
5.7.9. 概括
真实数据通常包含不同数据类型的混合,需要进行预处理。将实值数据重新调整为零均值和单位方差是一个很好的默认值。用平均值替换缺失值也是如此。此外,将分类特征转换为指示特征允许我们将它们视为单热向量。当我们更关心相对误差而不是绝对误差时,我们可以用预测的对数来衡量差异。要选择模型并调整超参数,我们可以使用K折叠交叉验证。
5.7.10。练习
将你对本节的预测提交给 Kaggle。你的预测有多好?
用平均值替换缺失值总是一个好主意吗?提示:你能构造一个值不随机缺失的情况吗?
通过调整超参数来提高 Kaggle 的分数 K- 折叠交叉验证。
通过改进模型(例如,层数、权重衰减和丢失)来提高分数。
如果我们不像本节所做的那样对连续数值特征进行标准化,会发生什么情况?
-
pytorch
+关注
关注
2文章
807浏览量
13195
发布评论请先 登录
相关推荐
评论