动手学深度学习 实战Kaggle比赛-房价预测
本文介绍Kaggle比赛的房价预测实战
之前阅读了权重衰减和dropout等方法
结合前几篇文章介绍的多层感知机 所以写此文来记录并巩固
文章目录
前言
附上比赛原文地址
点击“Data”标签 下载需要用到的数据
正文
导包
import os
import pandas as pd
import torch
from torch import nn
import d2lzh as d2l
import matplotlib.pyplot as plt
读取数据集
比赛数据分为训练数据集和测试数据集。
两个数据集都包括每栋房子的特征
- 如道类型、建造年份、房顶类型、地下室状况等特征值。
这些特征值有连续的数字、离散的标签甚至是缺失值“na”。只有训练数据集包括了每栋房子的价格,也就是标签。
train_data = pd.read_csv('train.csv')
test_data = pd.read_csv('test.csv')
## 观察数据结构
print(train_data.shape) # (1460, 81)
print(test_data.shape) # (1459, 80)
print(train_data.iloc[0:4, [0, 1, 2, 3, -3, -2, -1]])
训练数据集包括1460个样本、80个特征和1个标签。
测试数据集包括1459个样本和80个特征。我们需要将测试数据集中每个样本的标签预测出来。
(1460, 81)
(1459, 80)
查看前4个样本的前4个特征、后2个特征和标签(SalePrice)
很明显发现Id这列在训练中是没有什么作用的,所以在之后的预处理阶段可以删除
预处理数据集
删除 Id 特征列
all_features = pd.concat((train_data.iloc[:, 1:-1], test_data.iloc[:, 1:]))
对连续数值的特征做标准化
标准化在很多机器学习案例的预处理阶段都会使用到,它所针对的对象主要是连续数值
所以要先获取 数值类型的索引
numeric_features = all_features.dtypes[all_features.dtypes != 'object'].index # 获取数值信息的索引
解析: dtypes
获取到每一列的类型 通过条件切片的方式去除那些非数值型的列, 然后返回 index
这里返回的其实就是每一列的列名
使用 apply 方法对数值列标准化
all_features[numeric_features] = all_features[numeric_features].apply(
lambda x: (x - x.mean()) / (x.std())
)
标准化之后,有些列可能还存在缺失值,这个时候我们就可以用每一列的均值来填充这些缺失值
而因为经过了标准化,每个特征的均值变为0
所以可以直接用0来替换缺失值,如下
all_features[numeric_features] = all_features[numeric_features].fillna(0)
接下来将离散的数值信息转换为指示特征(one hot)编码
举例说明: 如df中含有一列color列, 其中包含了非数值型数据
那么通过get_dummies
方法可以将其转换为多列, 由0
,1
构成, 如下
这里也采用类似的操作
# dummy_na=True将缺失值也当作合法的特征值并为其创建指示特征
all_features = pd.get_dummies(all_features, dummy_na=True)
print(all_features.shape)
(2919, 331)
可以看到, 因为里面包含了一些 非数值型特征, 经过one hot编码之后,使得每一列都变成了数值型数据但是特征的个数也增加到了331
转换格式为 tensor`
.values
方法将Series
对象转换为Numpy的格式的数据, 方便直接转换为torch.tensor
这里保存了训练的特征, 测试的特征以及训练的标签
n_train = train_data.shape[0] # 获取训练集的数量
train_features = torch.tensor(all_features[:n_train].values, dtype=torch.float) # 训练特征
test_features = torch.tensor(all_features[n_train:].values, dtype=torch.float) # 测试特征
train_labels = torch.tensor(train_data.SalePrice.values, dtype=torch.float).view(-1, 1) # 训练标签
训练模型
书中使用一个基本的线性回归模型和平方损失函数来训练模型
loss = torch.nn.MSELoss()
def get_net(feature_num):
net = nn.Linear(feature_num, 1)
for param in net.parameters():
nn.init.normal_(param, mean=0, std=0.01)
return net
定义均方根误差
这里要注意,自己定义返回的时候不要忘记加item()
在函数中做了个数据操作
- 将小于1的值设成1,使得取对数时数值更稳定
def log_rmse(net, features, labels):
with torch.no_grad():
# 将小于1的值设成1,使得取对数时数值更稳定
clipped_preds = torch.max(net(features), torch.tensor(1.0))
rmse = torch.sqrt(loss(clipped_preds.log(), labels.log()))
return rmse.item()
训练函数的时候采用 Adam优化算法,它对学习率相对不那么敏感
def train(net, train_features, train_labels, test_features, test_labels,
num_epochs, learning_rate, weight_decay, batch_size):
train_ls, test_ls = [], []
dataset = torch.utils.data.TensorDataset(train_features, train_labels)
train_iter = torch.utils.data.DataLoader(dataset, batch_size, shuffle=True)
# 这里使用了Adam优化器
optimizer = torch.optim.Adam(params=net.parameters(), lr=learning_rate, weight_decay=weight_decay)
net = net.float()
for epoch in range(num_epochs):
for X, y in train_iter:
l = loss(net(X.float()), y.float())
optimizer.zero_grad()
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
k折交叉验证
之前学习了交叉验证,书本在本例中给出了实现代码
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), dim=0)
y_train = torch.cat((y_train, y_part), dim=0)
return X_train, y_train, X_valid, y_valid
slice
函数的用法如下
训练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(X_train.shape[1])
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.semilogy(range(1, num_epochs + 1), train_ls, 'epochs', 'rmse',
range(1, num_epochs + 1), valid_ls, ['train', 'valid'])
print('fold %d, train rmse %f, valid rmse %f' % (i, train_ls[-1], valid_ls[-1]))
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('%d-fold validation: avg train rmse %f, avg valid rmse %f' % (k, train_l, valid_l))
有时候你会发现一组参数的训练误差可以达到很低,但是在折交叉验证上的误差可能反而较高。
这种现象很可能是由过拟合造成的。因此,当训练误差降低时,我们要观察折交叉验证上的误差是否
也相应降低。
预测并在Kaggle提交结果
def train_and_pred(train_features, test_features, train_labels, test_data,
num_epochs, lr, weight_decay, batch_size):
net = get_net(train_features.shape[1])
train_ls, _ = train(net, train_features, train_labels, None, None,
num_epochs, lr, weight_decay, batch_size)
d2l.semilogy(range(1, num_epochs + 1), train_ls, 'epochs', 'rmse')
plt.show()
print('train rmse %f' % train_ls[-1])
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)
train_and_pred(train_features, test_features, train_labels, test_data, num_epochs, lr, weight_decay, batch_size)
引用资料来源
本文内容来自吴振宇博士的Github项目
对中文版《动手学深度学习》中的代码进行整理,并用Pytorch实现
【深度学习】李沐《动手学深度学习》的PyTorch实现已完成