深度学习 <实战Kaggle比赛:预测房价> 代码分析 跟李沐学AI

本文介绍了如何利用深度学习预测房价,首先进行数据处理,包括下载、解压、读取CSV文件,并对数值特征进行标准化和处理缺失值。然后,通过`pd.get_dummies()`将类别特征转换为数值。接着,使用单层线性模型,定义均方误差损失函数,并采用Adam优化器进行训练。文章还涉及了K折交叉验证来评估模型性能。
摘要由CSDN通过智能技术生成

4.10. 实战Kaggle比赛:预测房价 — 动手学深度学习 2.0.0 documentation

若有错误请指出

一.数据处理部分

1.下载部分 没啥好说的 

import hashlib
import os
import tarfile
import zipfile
import requests

#@save
DATA_HUB = dict()
DATA_URL = 'http://d2l-data.s3-accelerate.amazonaws.com/'

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)

2.还是下载部分跟导包 也跳过

# 如果没有安装pandas,请取消下一行的注释
# !pip install pandas

%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')

3.读表

train_data = pd.read_csv(download('kaggle_house_train'))
test_data = pd.read_csv(download('kaggle_house_test'))#读表 

print(train_data.shape)
print(test_data.shape)

#形状是这样的
(1460, 81)
(1459, 80)

4.初步看看样本

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


'''  注意第一行id对于训练没用 最后一行-1 是SalePrice 训练属于label(y)的部分 需要抽走
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
'''

5.抽走部分列

并all_features是两个表组合在了一起 以行形式叠加了    0-1459 调试点view dataframe拉到下面可以看到  所以下面抽取train的时候是0-1459 但表有2900多行  当时还愣了很久

all_features = pd.concat((train_data.iloc[:, 1:-1], test_data.iloc[:, 1:]))

6.数据处理

# 若无法获得测试数据,则可根据训练数据计算均值和标准差
#pandas的 object是python的string  此处是提取所有非string字符串的部分,就是数字部分(包括NAN)的index 列数
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
#fillna不是fill一开始看错了没看懂啥意思  就是所有na全变0了
all_features[numeric_features] = all_features[numeric_features].fillna(0)

这里方便理解 进调试模式debug 点view dataframe

 可以很直观看出MSZoning Street Alley LotShape LandContous这些是字符串项

 故numeric_features 得到的是 纯数字列的列名

然后对所有这些列 all_features[numeric_features]  通过将特征重新缩放到零均值和单位方差来标准化数据,好处:首先,它方便优化。 其次,因为我们不知道哪些特征是相关的, 所以我们不想让惩罚分配给一个特征的系数比分配给其他任何特征的系数更大。

原文:

 处理之后  明显看出都相应变小了 脱离了原本的大数值,领本身拥有对应的特征

 不懂之处1:但是代码注释的'所有均值消失'不知道什么意思

之后填充nan数值为0 之后变成 

这部分看不懂可以回想一下这小节 2.2. 数据预处理 — 动手学深度学习 2.0.0 documentation

7.

# “Dummy_na=True”将“na”(缺失值)视为有效的特征值,并为其创建指示符特征
all_features = pd.get_dummies(all_features, dummy_na=True)
all_features.shape

看不懂可以看看 【机器学习】pd.get_dummies()_pd.getdummies_洋气月的博客-CSDN博客 分类编程纯数字编码 0 1   也叫one hot

方便理解看看这列Alley  执行 all_features = pd.get_dummies(all_features, dummy_na=True)这行之前 

之后 alley列不见了 变成了   strng分类问题变成 01分类

以上步骤就把所有string转成了 只有数字的形式 这就是1-7步骤做的初步训练 

这种模式可以进行训练了 但是数据形式还要变一变    

''可以看到此转换会将特征的总数量从79个增加到331个。 最后,通过values属性,我们可以 从pandas格式中提取NumPy格式,并将其转换为张量表示用于训练。''

8.注意all_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)

all_features[:n_train]    0-1459行 所有列的values

先别急,看看 all_features[:n_train].values的样子

列名变成数字 符合输入形式

而test_features = torch.tensor(all_features[n_train:].values, dtype=torch.float32)

从n_train开始 就是取第二个表

因为all_features中已经删了saleprice的列了 要从原始train表取 并初始化成n*1的列向量形式

train_labels = torch.tensor(train_data.SalePrice.values.reshape(-1, 1), dtype=torch.float32)

二.训练部分

1.loss选均方损失函数   net选单层线性就是 Y=WX+b  

loss = nn.MSELoss()
in_features = train_features.shape[1]

def get_net():
    net = nn.Sequential(nn.Linear(in_features,1))
    return net

2.新增log_rmse处理误差  有点复杂

老师原话:房价就像股票价格一样,我们关心的是相对误差,而不是绝对误差。比如房价原本12.5万,误差10万;和房价原本420万,误差10万;显然是不一样的。即:

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()

3.不再用sgd优化而用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

4.K折交叉验证

4.4. 模型选择、欠拟合和过拟合 — 动手学深度学习 2.0.0 documentation

视频有详细讲解12分前后  11 模型选择 + 过拟合和欠拟合【动手学深度学习v2】_哔哩哔哩_bilibili

比如一张长条纸按照固定成K次,就K折了 每次取K-1个训练 剩下最后一个用来验证,而一共需要进行这样的操作K次循环,

翻到下面可以看到K取了5,带去k_fold来分析

fold_size = X.shape[0] // k

fold_size=1460/5 取整=292 这里刚好能整除  就是1460行折5下 每个区域292行

def get_k_fold_data(k, i, X, y):
    assert k > 1  
    fold_size = X.shape[0] // k  #整除k 一开始看成java的注释了
    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

已知fold_size=292  k=5  进入for循环 我们假定本次i为4 即是最后一次

slice()是内置函数本身就返回一个slice对象,可以被用于任何可以被切片的地方 左开右闭取0到291的值

j=0时,idx = slice(0, 292)  , X_part, y_part = X[idx, :], y[idx]

X_part为从X取0到291行的所有列;y_part为0,291行,因为只有一列就不用写后面的:

接着判断j是否为指定的i=4 本次j是0≠4

进入第一个elif 发现X_train为None,故进行第一次赋值X_train, y_train = X_part, y_part,之后结束本次循环

j=1时,idx =slice(292, 584), X_part为从X取292到583行的所有列;y_part为292到583行,本次j是1≠4

进入第一个elif 发现X_train不为None,故进入else,追加赋值cat,按行追加下去,之后结束本次循环

可以用这段进行验证:

#j>=i假如设置的j大于等于i,证明有一段折是给验证集的,于是减1,而此处结果为bool类型 于是+0强制转换成1
#而e1的第(j-((j>=i)+0)) * fold_size(比如292)就是X-part加上去的第一个
e1=X_train[(j-((j>=i)+0)) * fold_size]
e2=X_part[1]
c = e1.eq(e2).sum()#没想到更好的办法解决了 用e1和e2比较结果会出现全true的tensor 这时候sum把true全加起来 如果全true就是全1 加起来数字就是总列数331 
print(c.item()==X_train[1].shape[0])
#可以debug详细看看
#c.item() 从tensor中取数值
#X_train[1].shape是  tensor.size类型 取出来要用
#X_train[1].shape[0]

cat不熟悉的可以看看这图

j=2时,idx =slice(584, 876), X_part为从X取584到875行的所有列;y_part为584到875行,本次j是2≠4

追加赋值cat,之后结束本次循环

j=3时,idx =slice(876, 1168), X_part为从X取876到1167行的所有列;y_part为876到1167行,本次j是3≠4

追加赋值cat,之后结束本次循环

j=4时,idx =slice(1168, 1460), X_part为从X取1168到1459行的所有列;y_part为1168到1459行,本次j是4=4,因为此次等于用X_part,y_part做验证集,之后结束本次循环,结束所有循环返回对应参数

k_fold部分

train_l_sum += train_ls[-1]#只取最后第100次用训练好的参数的对训练y的损失

valid_l_sum += valid_ls[-1]##只取最后第100次用训练好的参数的对验证y的损失

不懂之处2:没太搞懂valid_l_sum是训练一次之后同时再验证一次吗?

否则全训练完用训练的参数验证valid_l_sum结果的话应该取平均?

画图部分不说了

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): #k折做k次,0~(k-1)
        data = get_k_fold_data(k, i, X_train, y_train) #data 是四个参数 训练x,y 验证x,y
        net = get_net() #取出net 此处是线性
        train_ls, valid_ls = train(net, *data, num_epochs, learning_rate,
                                   weight_decay, batch_size) #放入k折处理后的数据 
        #train_ls,valid_ls  返回100次epoch每次得到的值组合起来的训练损失
        train_l_sum += train_ls[-1]#只取最后第100次用训练好的参数的对训练y的损失

        valid_l_sum += valid_ls[-1]##只取最后第100次用训练好的参数的对验证y的损失
        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}')

需要显示图可以加

d2l.plt.show()

接下来是验证所有数据集

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)



train_and_pred(train_features, test_features, train_labels, test_data,
               num_epochs, lr, weight_decay, batch_size)

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值