持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第20天,点击查看活动详情
1.登入kaggle并选择比赛
Kaggle是一个著名的供机器学习爱好者交流的平台。
我们可以在房价预测比赛的页面了解比赛信息和参赛者成绩,也可以下载数据集并提交自己的预测结果。
2.环境配置及要求
- pytorch1.12.1
- numpy 1.23.3
- python 3.8
- pandas 1.2.4
- matplotlib 3.5.1
- jupyter note运行
下载提供的两个数据集:test.csv和train.csv。将其保存在路径中我这里为
E:\360MoveData\Users\12079\Desktop\数据集\csv\data
3.获取和读取数据集
3.1 导入环境
import matplotlib_inline
import torch
import torch.nn as nn
import numpy as np
import pandas as pd
import sys
sys.path.append(r"E:\anaconda\envs\pytorch\Lib\d2lzh_pytorch")
import d2lzh_pytorch as d2l
torch.set_default_tensor_type(torch.FloatTensor)
3.2 导入数据
下面对数据进行读取,使用pandas中的read_csv方式来读取,需要注意的是这个位置是我们将数据保存的位置,需要自己导入路径。
train_data=pd.read_csv(r'E:\360MoveData\Users\12079\Desktop\数据集\csv\data\train.csv')
test_data=pd.read_csv(r'E:\360MoveData\Users\12079\Desktop\数据集\csv\data\test.csv')
训练数据集包括1460个样本、80个特征和一个标签,测试数据集包括1459个样本和80个特征。我们需要将测试数据集中每个样本的标签预测出来。
可以看到第一个特征是ID,能帮助模型标记每个训练样本,但难以推广到测试样本,所以不使用它来训练。我们将所有的训练数据和测试数据的79个特征按样本连结。
all_features=pd.concat((train_data.iloc[:,1:-1],test_data.iloc[:,1:]))
可以看出这里数值下标从1开始,跳过了0,即略去第一个特征连结后面79个特征。
4.预处理数据
我们对连续熟知的特征做标准化:设该特征在整个数据集上的均值为u标准差为o,那么我们可以将该特征的每个值先减去u再除以o得到标准化后的每个特征值。对于缺失的特征值,我们将其替换成该特征的均值。
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,所以可以直接用0来替换缺失值
all_features=all_features.fillna(0)
假设特征MSZoning里面有两个不同的离散值RL和RM,那么这一步转换将去掉MSZoning特征,并新加两个特征MSZoning_RL和MSZoning_RM,其值为0或1。如果一个样本原来在MSZoning里的值为RL,那么有MSZoning_RL=1且MSZoning_RM=0。
可以看到这一步转换将特征数从79增加到354,最后,通过value属性得到Numpy格式的数据,并转成NDArray数组方便后面训练。
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)
5.训练模型
很容易想到,可以使用基本的线性回归模型和平方损失函数来训练模型。
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
对数均方根误差的实现如下。
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(2*loss(clipped_preds.log(),labels.log()).mean())
return rmse.item()
下面我们设置训练函数,在这里我们使用了Adam优化算法:
Adam 是一种可以替代传统随机梯度下降过程的一阶优化算法,它能基于训练数据迭代地更新神经网络权重,直截了当地实现高效的计算,所需内存少,梯度对角缩放的不变性,适合解决含大规模数据和参数的优化问题,适用于非稳态(non-stationary)目标,适用于解决包含很高噪声或稀疏梯度的问题,超参数可以很直观地解释,并且基本上只需极少量的调参。
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
6.K折交叉验证
我们之前了解过这个K折交叉验证,它的用处就是选择模型设计并调节超参数。
所谓K折交叉验证,就是将数据集等比例划分成K份,以其中的一份作为测试数据,其他的K-1份数据作为训练数据。然后,这样算是一次实验,而K折交叉验证只有实验K次才算完成完整的一次,也就是说交叉验证实际是把实验重复做了K次,每次实验都是从K个部分选取一份不同的数据部分作为测试数据(保证K个部分的数据都分别做过测试数据),剩下的K-1个当作训练数据,最后把得到的K个实验结果进行平分。
def get_k_fold_data(k,i,X,y):
#返回第i折交叉验证时所需的训练和验证数据
assert k>1
fold_size=X.shape[0]//k
X_train,y_train=None,None
for j in range(k):
idk=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
在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('%d-fold validation:avg train rmse %f,avg valid rmse %f'%(k,train_l,valid_l))
7.预测并在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')
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(r'E:\360MoveData\Users\12079\Desktop\数据集\csv\data\submission.csv',index=False)
train_and_pred(train_features,test_features,train_labels,test_data,num_epochs,lr,weight_decay,batch_size)
设计好模型并调好超参数之后,下一步就是对测试数据集上的房屋样本做价格预测。如果我们得到与交
叉验证时差不多的训练误差,那么这个结果很可能是理想的,可以在Kaggle上提交结果。上述代码执行完之后会⽣生成一个submission.csv文件。这个文件是符合Kaggle比赛要求的提交格式的。这时,我们可以在Kaggle上提交我们预测得出的结果,并且查看与测试数据集上真实房价(标签)的误差。
8.可能会出现的问题
这里只需要在路径前加个字母‘r’就可以解决