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)