实战Kaggle比赛:预测房价

引言

最近在看沐神《pytorch动手学深度学习》视频,本文记录一下自己跟着写的一个小实战。

内容

第一步:下载数据集

链接:https://pan.baidu.com/s/1YtH1FGIcraiDgJCmq84WKQ?pwd=l859

提取码:l859

我数据也是从别人那下的,自己没有去官网下。

第二步:导入所需要的包

numpy和pandas版本要和d2l的版本要正确对应,要不使用jupyter编写可能会出现内核中断问题。

%matplotlib inline
import numpy as np
import pandas as pd
import torch
from torch import nn
from d2l import torch as d2l

第三步:读取数据集

读取数据

train_data = pd.read_csv("data/kaggle_house/train.csv")
test_data = pd.read_csv("data/kaggle_house/test.csv")
print("train_data.shape:",train_data.shape)
print("test_data.shape:",test_data.shape)

查看前四个和最后两个特征,以及相应标签(房价)

print(test_data.iloc[0:4,[0,1,2,3,-3,-2,-1]])

将第一个特征Id删除,并将训练集和测试集的特征数据结合起来(按行合并)。训练集不要最后的预测结果
all_features = pd.concat([train_data.iloc[:,1:-1], test_data.iloc[:,1:]],axis=0)
print(all_features.iloc[0:4,[0,1,2,3,-3,-2,-1]])

第四步:数据预处理

数据中有连续型数据和离散型数据,并且存在缺失值。

对应连续性数值,每列将所有缺失的值替换为相应特征的平均值。然后,为了将所有特征放在一个共同的尺度上, 我们通过将特征重新缩放到零均值和单位方差来标准化数据:

先看看all_features.dtypes和all_features.dtypes.index的输出

"""
若无法获得测试数据,则可根据训练数据计算均值和标准差
"""
# 获取all_features是数值的列下标
numeric_features = all_features.dtypes[all_features.dtypes != "object"].index
#每一列变为均值为0,方差为1
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)

而对于离散型数据,只需要把他转为one-hot格式。举个例子,比如地区一共有三个值[北京,上海,天津],此时地区特征就会变成三个特征,分别是北京,上海,天津。如果之前的值为北京,则北京列值为1,其他为0。相当于扩展了列个数。

#将离散数据转换为one-hot
# “Dummy_na=True”将“na”(缺失值)视为有效的特征值,并为其创建指示符特征
all_features = pd.get_dummies(all_features, dummy_na=True)
print("all_features.shape",all_features.shape)

最后,通过values属性,提取numpy格式数据,并转换成tensor

#通过values属性,提取到numpy格式,并转换成张量
# 训练集个数
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)

第五步:定义损失函数

使用的是均方误差损失函数。但是视频说可能存在绝对数量的影响。比如一个房子值10.5W,但预测是0.5万,此时差出10W就是很不理想的。但是如果一个房子值1100W,但是最后预测是1090W,那个这个预测就还行。为了解决这个问题,将数值取对数,如下图。emmm,有一点没搞懂就是定义了这个函数,在模型训练时,并没有使用该函数,用的是MSELoss(),对于log_rmse()只是记录了使用对数损失函数的损失值。

# 均方误差损失函数
# l = (y_hat - y)^2
loss = nn.MSELoss()
#避免绝对数量的影响
def log_rmse(net, features, labels, return_item=True):
    # 为了在取对数时进一步稳定该值,将小于1的值设置为1
    clipped_preds = torch.clamp(net(features),1,float('inf'))
    rmse = torch.sqrt(loss(torch.log(clipped_preds),torch.log(labels)))
    #是得到只有一个元素张量里面的元素值。
    if return_item:
        return rmse.item()
    else:
        #返回一个tensor
        return rmse

第六步:定义模型

模型使用的最简单的线性回归

## in_features_num = train_features.shape[1]
# 线性回归模型
def get_net(in_features_num):
    return nn.Sequential(nn.Linear(in_features_num, 1))

第七步:训练

训练很简单,前向传播->计算损失->计算梯度->更新参数。之前看吴恩达老师的课是自己实现反向传播,沐神这个课是调用现有的API即可。

def train(net, train_features, train_labels, test_features, test_labels, num_epochs, lr, wd, batch_size):
    train_ls, test_ls = [], []
    # 定义优化器:Adam优化器的主要吸引力在于它对初始学习率不那么敏感。
    optimizer = torch.optim.Adam(net.parameters(), lr=lr, weight_decay=wd)
    # 读取小批量数据
    train_iter = d2l.load_array((train_features, train_labels),batch_size)
    for epoch in range(num_epochs):
        for X, y in train_iter:
            # 将梯度设置为0,因为梯度会默认累加
            optimizer.zero_grad()
            y_hat = net(X)
            #l = get_rmse(net,X, y,False)
            l = loss(y_hat, 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折交叉验证,需要我们编写一个在折交叉验证过程中返回第i折的数据。

#定义一个函数,在K折交叉验证过程中返回第i折的数据。
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(i * fold_size, (i+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折交叉验证

# 定义交叉验证函数
def k_fold(k, X_train, y_train, num_epochs, lr, wd, batch_size):
    train_ls_sum, valid_ls_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, lr, wd, batch_size)
        train_ls_sum += train_ls[-1]
        valid_ls_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_ls_sum / k, valid_ls_sum / k

最后:跑模型

主要是调了一下学习率的参数。

k, num_epochs, weight_decay, batch_size = 5, 100, 0, 64
for lr in np.arange(1,6,1):
    print("lr==%d时" % lr)
    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}')

posted @ 2022-10-30 17:09  littlemelon  阅读(629)  评论(0编辑  收藏  举报