通过前面的学习,实战一个房价预测项目,kaggle房价预测,该数据集涵盖了2006-2010年期间亚利桑那州埃姆斯市的房价,包含了很多特征。
导入所需的包
%matplotlib inline
import pandas as pd
import torch
import numpy as np
from torch import nn
from d2l import torch as d2l
使用pands读取数据并查看
train_data = pd.read_csv('D:\kaggle\data\kaggle_house/train.csv')
test_data = pd.read_csv('D:\kaggle\data\kaggle_house/test.csv')
print(train_data.shape)
print(test_data.shape)
(1460, 81)
(1459, 80)
可以看到,训练数据集包括1460个样本,每个样本80个特征和1个标签, 而测试数据集包含1459个样本,每个样本80个特征。
查看一些特征:
print(train_data.iloc[0: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,它对预测起不到任何作用,因此,在将数据提供给模型之前,我们将其从数据集中删除。
all_features = pd.concat((train_data.iloc[:, 1:-1], test_data.iloc[:, 1:]))
数据预处理
在开始建立模型之前, 需要对数据进行预处理,要考虑数据是否过大,数据缺失,离散数据的问题。
我们先将所有的数据放到一个共同的尺度上,通过数据均值和标准差进行处理:
并且对数据中缺失的值填充为 0 :
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()))
all_features[numeric_features] = all_features[numeric_features].fillna(0)
然后对离散的数据进行处理,使用 pd.get_dunmmies方法,例如 MSZoning 包含RL,RM值,该方法会创建两个新的特征,MSEoning_RL,MSEoning_RL ,它们值为 0 或 1,这样就能对离散的数据进行表示:
all_features = pd.get_dummies(all_features, dummy_na=True)
all_features.shape
这样处理完之后,特征的总数据增加到了 331 个:
2919, 331)
最后将数据转换成 tensor ,得到train_features,test_features,便于训练:
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)
训练
我们仍然使用损失平方的线性模型,并构建神经网络:
loss = nn.MSELoss()
in_features = train_features.shape[1]def get_net():net = nn.Sequential(nn.Linear(in_features,1))return net
还要考虑的一个关键问题是在最后测试房价预测情况时,用什么函数进行误差分析,如果单纯的考虑绝对误差 时,比如在农村地区估计房价时,假设那里的房子平均价格 13 万美元,模型预测偏差了 10 万美元,这样的误差很大,但是相同的误差在豪宅区的房价预测就是比较优秀的预测,比如那里房子平均价格 400 万美元。
这里用价格的对数来衡量差异:
def log_rmse(net, features, labels):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 算法和传统的随机梯度下降不同。随机梯度下降保持单一的学习率更新所有的权重,学习率在训练过程中并不会改变。而 Adam 通过计算梯度的一阶矩估计和二阶矩估计而为不同的参数设计独立的自适应性学习率。
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
K折交叉验证
该算法需要先将数据分成 K 份,然后先是第一份数据是验证集,剩余数据合并成训练集,然后第二份数据是验证集,剩余数据合并成训练集 …… 最后训练 K 次完,返回训练集和验证集的误差的平均值:
def get_k_fold_data(k, i, X, y):assert k > 1fold_size = X.shape[0] // kX_train, y_train = None, Nonefor 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_partelif X_train is None:X_train, y_train = X_part, y_partelse:X_train = np.concatenate([X_train, X_part], 0)y_train = np.concatenate([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, 0for 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 折交叉验证设置合理的参数
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,训练log rmse0.169478, 验证log rmse0.156870
折2,训练log rmse0.161967, 验证log rmse0.188370
折3,训练log rmse0.164111, 验证log rmse0.168343
折4,训练log rmse0.167733, 验证log rmse0.154772
折5,训练log rmse0.162926, 验证log rmse0.182832
5-折验证: 平均训练log rmse: 0.165243, 平均验证log rmse: 0.170237
<Figure size 252x180 with 1 Axes>
最终得到了 5- 折验证的平均训练损失和平均验证损失,由于我们对数据进行了充分的利用,该验证具有不错的稳定性,使得最终的数据集上较难发生过拟合,那么就可以把该损失当成一个指标。可以对模型中的一些参数进行调整,最终得到更低的损失。
并且,有时调整参数后的训练误差可能非常低,但K折交叉验证的误差要高得多, 这表明模型过拟合了。 在整个训练过程中,更加希望监控训练误差和验证误差。较少的过拟合可能表明现有数据可以支撑一个更强大的模型。
最后对整个训练集进行训练得到的误差也是接近 K 折交叉验证得到的误差:
def trainp(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}')
trainp(train_features, test_features, train_labels, test_data,num_epochs, lr, weight_decay, batch_size)
训练log rmse:0.162241