目录
[1 基本原理](#1 基本原理)
[2 Kaggle](#2 Kaggle)
[3 数据集](#3 数据集)
[3.2 数据预处理](#3.2 数据预处理)
[4 训练](#4 训练)
[4.1 定义模型](#4.1 定义模型)
[4.2 模型选择](#4.2 模型选择)
[5 预测并提交](#5 预测并提交)
1 基本原理
本章是第一次kaggle实战,我将先讲解书中关于数据集下载的方法和kaggle的注册部分,然后我们按照数据预处理、模型训练与参数选择、提交预测结果的顺序来完成实战。
2 Kaggle

Kaggle是一个当今流行举办机器学习比赛的平台。注册时如果无法显示验证码,参考该贴解决(这里)。
房价预测的项目由此进入(这里)。如下图所示,点击右上角加入比赛

3 数据集
3.1数据下载
按照书中代码来编写,首先定义了DATA_HUB用于存放 dataName->[url,sha-1key] 的映射,其中sha-1秘钥用于检查文件完整性。然后定义了DATA_URL,这是接下来用到的各种数据集的下载地址根目录。
python
import hashlib
import os
import tarfile
import zipfile
import requests
#@save
DATA_HUB = dict()
DATA_URL = 'http://d2l-data.s3-accelerate.amazonaws.com/'
接下来定义了三个函数,分别实现指定数据集的下载,指定数据集下载和解压以及下载DATA_HUB中所有数据集。
python
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)
接下来使用上面定义的函数下载房价预测的数据集
python
%matplotlib inline
import numpy as np
import pandas as pd
import torch
from torch import nn
from d2l import torch as d2l
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'))
查看下载好的数据集形状
python
print(train_data.shape)
print(test_data.shape)

训练集和测试集的大小分别为1460和1459,训练集每个样本有80个特征和1个标签,而测试集样本没有标签。
查看训练集第一行的前三列和后三列
python
print(train_data.iloc[0:1, [0, 1, 2, -3, -2, -1]])

可以看到第一列Id并不适合作为特征值参与训练,在整理数据集时我们将训练集第一列和最后一列(标签)去掉,测试集第一列去掉,然后两组数据特征合并来参与后续处理。
python
all_features = pd.concat((train_data.iloc[:, 1:-1], test_data.iloc[:, 1:]))
3.2 数据预处理
处理前先查看all_freatures的形状

由于all_freatures是DataFrame类型,可以通过dtypes查看各列的类型

可以看到特征值有各种不同的数据类型。首先我们对其中数值型的特征值进行标准化处理
python
# 若无法获得测试数据,则可根据训练数据计算均值和标准差
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.dtypes是series类型,也可以看做自定义索引的list类型,所以可以像普通list一样用同规格的bool型series作为索引从而提取出类型不为object的所有元素。再用.index取出索引值,这样numeric_features中就包含了所有数值型特征名。再用其作为all_features的索引批量应用lambda函数进行标准化,使得所有数值型特征值满足均值为0,方差为1。标准化以后各特征值的均值都为0,所以对于缺失值使用均值填充时填入0即可。
接下来处理非数值项(离散值)。采用独热编码处理即可。
独热编码(One-Hot Encoding)是一种将分类变量转换为二进制向量的技术。它的主要目的是将分类变量转换为机器学习算法能够处理的格式,从而避免数值关系的误判。
独热编码的基本原理是为每个分类特征的每个可能值创建一个新的二进制特征。在任何给定时间,只有一个特征被激活(标记为1),而其他所有特征都被标记为0。例如,对于动物类别"猫、狗、乌龟、鱼",可以将其编码为:
-
猫:[1, 0, 0, 0]
-
狗:[0, 1, 0, 0]
-
乌龟:[0, 0, 1, 0]
-
鱼:[0, 0, 0, 1]
python
# "Dummy_na=True"将"na"(缺失值)视为有效的特征值,并为其创建指示符特征
all_features = pd.get_dummies(all_features, dummy_na=True)
独热编码调用pd.get_dummies()函数即可自动完成,其中选项dummy_na=True表示显示地将缺失值作为一列参与编码。
查看编码后的特征维度,可以看到经过独热编码后特征数从79变为了331

数据处理完成后,重新将all_featrues划分为训练集和测试集,并转换为tensor
python
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)
4 训练
4.1 定义模型
定义一个函数,返回单层的线性模型。该模型训练时采用均方误差损失。
python
loss = nn.MSELoss()
in_features = train_features.shape[1]
def get_net():
net = nn.Sequential(nn.Linear(in_features,1))
return net
定义误差评价函数。由于我们更关心相对误差,所以根据以下公式得到函数
python
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()
定义训练函数。与前面的SGD优化器不同,这里使用了Adam优化器,主要区别在于它具有自适应功能,对初始学习率不那么敏感。注意,这里训练中用的均方误差作为损失,而评估时用的对数均方误差根作为损失,因为前者更关注损失函数的可微性、平滑性、数值稳定性等,而后者更注重评价指标的业务可解释性、与实际问题的相关性等。
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优化算法
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
4.2 模型选择
这里采用前面提到的k折交叉验证(这里)来完成模型参数的选择。
首先定义k折交叉验证的数据划分函数和训练函数。
python
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
如上述代码所示,k折交叉验证每一轮训练时都会重新初始化一个新的线性模型,训练之后进行验证。所以其主要目的在于多次验证用于初始化模型的超参数设置是否合理。
接下来设置一组超参数进行5折交叉验证
python
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}')

为了寻找更好的模型,下面进行了几种调整
1.调整学习率5->14

2.为模型增加隐藏层,并调整学习率、训练轮次和权重衰减
python
net = nn.Sequential(nn.Linear(in_features,3),nn.ReLU(),nn.Linear(3,1))
python
k, num_epochs, lr, weight_decay, batch_size = 5, 50, 0.3, 10, 64

5 预测并提交
采用调整好的模型预测测试集数据
python
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 = net(test_features).detach().numpy()
# 将其重新格式化以导出到Kaggle
test_data['SalePrice'] = pd.Series(preds.reshape(1, -1)[0])
submission = pd.concat([test_data['Id'], test_data['SalePrice']], axis=1)
submission.to_csv('submission.csv', index=False)
python
train_and_pred(train_features, test_features, train_labels, test_data,
num_epochs, lr, weight_decay, batch_size)
jupyter notebook默认工作路径在%HOMEPATH%,运行上述代码后即可在该路径下看到预测结果的csv文件

在kaggle比赛界面中点击右上角提交按钮,在将该文件拖拽至提交框提交即可。
下面分别展示提交书中原始模型以及两种优化后的模型得分

以及菜菜的排名展示

参考文献
1\]《动手学深度学习》,[https://zh-v2.d2l.ai/](https://zh-v2.d2l.ai/ "https://zh-v2.d2l.ai/") \[2\]Kaggle,[https://www.kaggle.com/](https://www.kaggle.com/ "https://www.kaggle.com/") \[3\]如何注册kaggle账号,[https://zhuanlan.zhihu.com/p/623467866](https://zhuanlan.zhihu.com/p/623467866 "https://zhuanlan.zhihu.com/p/623467866")