Kaggle比赛:房价预测案例

目录

1.下载并解压数据集

2.数据预处理

3.定义模型

4.训练模型

5.K折交叉验证

6.预测并保存结果

7.一键粘贴运行代码


1.下载并解压数据集

数据分为训练数据集测试数据集。两个数据集都包括每栋房子的特征,如街道类型、建造年份、房顶类型、地下室状况等特征值。

1.该案列所引入的库 ,一定要确保安装所以的库:                          

import hashlib
import os
import tarfile
import zipfile

import numpy as np
import requests
import torch
from d2l import torch as d2l
from torch import nn
import torch.nn.functional as F
import pandas as pd

2.从网上下载所需要的数据文件,下载数据函数如下:

下面的download函数用来下载数据集, 将数据集缓存在本地目录(默认情况下为../data)中, 并返回下载文件的名称。
如果缓存目录中已经存在此数据集文件,并且其sha-1与存储在DATA_HUB中的相匹配, 我们将使用缓存的文件,以避免重复的下载。

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}"      #断言检查 name 是否存在于                                                 
                                                             #DATA_HUB 字典的键集合中
    url, sha1_hash = DATA_HUB[name]                        # 依赖合法 name 获取元数据

    os.makedirs(cache_dir, exist_ok=True)                  #通过递归创建多层目录结构

    fname = os.path.join(cache_dir, url.split("/")[-1]) #从URL中提取文件名并构建本地缓存路径 

    if os.path.exists(fname):

        sha1 = hashlib.sha1()         #创建 SHA-1 哈希算法对象

        with open(fname, 'rb') as f:  # 以二进制只读模式打开文件

            while True:

                data = f.read(1048576) # 1048576 是一个数值常量,表示每次从文件中读取的字节    
                                       #数;从文件对象f中读取1MB的数据,并将其存储到变量data中
                if not data:
                    break

                sha1.update(data)      #添加数据

        if sha1.hexdigest() == sha1_hash:  # 计算其 SHA1 校验码并与预期的 sha1_hash 进行比较
            return fname                   # 如果一致,则直接返回文件名,表示命中缓存

    print(f"正在从{url}下载{fname}...")  # 如果没有命中缓存,打印下载信息

    r = requests.get(url, stream=True, verify=True) #下载文件

    with open(fname, "wb") as f:           # 获取文件内容,并将其写入本地文件 
                                                     #fname 中。
        f.write(r.content)

    return fname  # 返回本地文件名,表示下载完成。

3.我们还需实现两个实用函数: 一个将下载并解压缩一个zip或tar文件,另一个是将使用的所有数据集从DATA_HUB下载到缓存目录中。


# folder 是一个可选参数,表示要将文件解压到哪个文件夹中。
def download_extract(name, folder=None):  # @save
    """下载并解压zip/tar文件"""
    fname = download(name)
    base_dir = os.path.dirname(fname)  # 获取 fname 的目录名
    data_dir, ext = os.path.splitext(fname)
    """
    获取 fname 的扩展名,并将其存储在变量 ext 中。
    同时,也获取了文件名不包含扩展名的部分,并将其存储在 data_dir 变量中。
    """
    if ext == ".zip":      # 根据文件扩展名选择使用模块打开文件,并将文件对象存储在 fp 变量中。
        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
    # 如果传入了 folder 参数,则返回指定目录下的路径。否则,返回原始文件名(不包含扩展名)所在的 
    #目录


# 用于下载 DATA_HUB 中所有的文件
def download_all():  # @save
    """下载DATA_HUB中的所有文件"""
    for name in DATA_HUB:
        download(name)

4.访问和读取下载的数据。

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


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

#训练数据集包括1460个样本,每个样本80个特征和1个标签, 而测试数据集包含1459个样本,每个样本80个特征。
print(train_data.shape)
print(test_data.shape)

#让我们看看[前四个和最后两个特征,以及相应标签](房价)。
print(train_data.iloc[0:4, [0, 1, 2, 3, -3, -2, -1]])

2.数据预处理

#移除第一列ID值以及最后的标签值,只获取所有特征列,将训练集和测试集的特征列合并,便于统一预处理
all_features = pd.concat((train_data.iloc[:,1:-1],test_data.iloc[:,1:]))
#print(all_features.iloc[0:4,0:-1])        #可以查看数据

# 先对数值型数据标准化
# all_features.dtypes 返回 DataFrame 中所有列的数据类型(如 float64、int64、object 等)
# all_features.dtypes != 'object' 生成布尔掩码,标记非对象类型(即数值型)的列
#.index 提取满足条件(数值型)的列名索引
numeric_features = all_features.dtypes[all_features.dtypes != 'object'].index
print(numeric_features)

#标准化
# all_features[numeric_features] = all_features[numeric_features].apply(
#     lambda x: (x - x.mean()) / (x.std())
# )
# 直接计算均值和标准差
means = all_features[numeric_features].mean()
stds = all_features[numeric_features].std()
all_features[numeric_features] = (all_features[numeric_features] - means) / stds

# 标准化后,所有均值为0,因此我们可以将缺失值设置为0
all_features[numeric_features] = all_features[numeric_features].fillna(0)

# 接下来则是处理离散值(包括诸如“MSZoning”之类的特征,用独热编码替换它们)
# “Dummy_na=True”将“na”(缺失值)视为有效的特征值,并为其创建指示符特征
#将分类变量转换为哑变量:get_dummies()
all_features = pd.get_dummies(all_features, dummy_na=True, dtype=np.float32)
#print(all_features.shape)

# 通过values属性,可以从pandas格式中提取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)
print(train_features.shape)

3.定义模型

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

class MnistModel(nn.Module):
    def __init__(self):
        super(MnistModel,self).__init__()
        #Linear()参数:in_features,输入的神经元个数  out_features,输出神经元个数  bias=True是否 
        #包含偏置
        self.fc1 = nn.Linear(in_features,1)

    def forward(self,input):
        #out = self.linear(input)
        # 全连接层 激活函数处理,relu函数
        input = F.relu(input)
        # 输出层
        out = self.fc1(input)
        return out

4.训练模型



def log_rmse(net, features, labels):
    # 为了在取对数时进一步稳定该值,将小于1的值设置为1
    clipped_pred = torch.clamp(net(features), 1, float("inf"))
    rmse = torch.sqrt(loss(torch.log(clipped_pred), torch.log(labels)))
    return rmse.item()


def train(net, train_features, train_labels, test_features, test_labels,
          num_epochs, lr, 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=lr,
                                 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

5.K折交叉验证

 它有助于模型选择和超参数调整(选择第i个切片作为验证数据,其余部分作为训练数据),可以与模型相比较,选择合适的模型或者调整超参数。

# K折交叉验证, 它有助于模型选择和超参数调整(选择第i个切片作为验证数据,其余部分作为训练数据)
# 返回第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(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

# 当我们在K折交叉验证中训练K次后,返回训练和验证误差的平均值。
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):
        data = get_k_fold_data(k, i, X_train, y_train)
        # 定义训练的实现过程
        net = MnistModel()

        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, num_epochs, lr, weight_decay, batch_size = 5, 100, 7, 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()

6.预测并保存结果

通过K折交叉验证,我们可以知道应该选择什么样的超参数, 所以不妨使用所有数据对其进行训练 (而不是仅使用交叉验证中使用的1−1/K的数据)。
然后,我们通过这种方式获得的模型可以应用于测试集。 将预测保存在CSV文件中可以简化将结果上传到Kaggle的过程。


def train_and_pred(train_features, test_features, train_labels, test_data,
                   num_epochs, lr, weight_decay, batch_size):
    # 定义训练的实现过程
    net = MnistModel()

    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)


# 如果测试集上的预测与K倍交叉验证过程中的预测相似, 那就是时候把它们上传到Kaggle了。
# 下面的代码将生成一个名为submission.csv的文件。
train_and_pred(train_features, test_features, train_labels, test_data,
               num_epochs, lr, weight_decay, batch_size)


d2l.plt.show()

7.一键粘贴运行代码

以下是本人能够在电脑上运行的代码。

import hashlib
import os
import tarfile
import zipfile

import numpy as np
import requests
import torch
from d2l import torch as d2l
from torch import nn
import torch.nn.functional as F
# @save
DATA_HUB = dict()
DATA_URL = 'http://d2l-data.s3-accelerate.amazonaws.com/'

"""
下面的download函数用来下载数据集, 将数据集缓存在本地目录(默认情况下为../data)中, 并返回下载文件的名称。 
如果缓存目录中已经存在此数据集文件,并且其sha-1与存储在DATA_HUB中的相匹配, 我们将使用缓存的文件,以避免重复的下载。
"""
def download(name, cache_dir=os.path.join("..", "data")):  # @save
    """ 下载一个DATA_HUB中的文件,返回本地文件名"""
    assert name in DATA_HUB, f"{name}不存在于{DATA_HUB}"  # 断言检查 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)  # 1048576 是一个数值常量,表示每次从文件中读取的字节数;从文件对象f中读取1MB的数据,并将其存储到变量data中
                if not data:
                    break
                sha1.update(data)
        if sha1.hexdigest() == sha1_hash:  # 计算其 SHA1 校验码并与预期的 sha1_hash 进行比较
            return fname  # 如果一致,则直接返回文件名,表示命中缓存
    print(f"正在从{url}下载{fname}...")  # 如果没有命中缓存,打印下载信息
    r = requests.get(url, stream=True, verify=True)  # 获取文件内容,并将其写入本地文件 fname 中。
    with open(fname, "wb") as f:
        f.write(r.content)
    return fname  # 返回本地文件名,表示下载完成。


"""
我们还需实现两个实用函数: 一个将下载并解压缩一个zip或tar文件,
另一个是将本书中使用的所有数据集从DATA_HUB下载到缓存目录中
"""


# folder 是一个可选参数,表示要将文件解压到哪个文件夹中。
def download_extract(name, folder=None):  # @save
    """下载并解压zip/tar文件"""
    fname = download(name)
    base_dir = os.path.dirname(fname)  # 获取 fname 的目录名
    data_dir, ext = os.path.splitext(fname)
    """
    获取 fname 的扩展名,并将其存储在变量 ext 中。
    同时,也获取了文件名不包含扩展名的部分,并将其存储在 data_dir 变量中。
    """
    if ext == ".zip":  # 根据文件扩展名选择使用模块打开文件,并将文件对象存储在 fp 变量中。
        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
    # 如果传入了 folder 参数,则返回指定目录下的路径。否则,返回原始文件名(不包含扩展名)所在的目录


# 用于下载 DATA_HUB 中所有的文件
def download_all():  # @save
    """下载DATA_HUB中的所有文件"""
    for name in DATA_HUB:
        download(name)


import pandas as pd

"""
访问和读取数据
"""
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')


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

#训练数据集包括1460个样本,每个样本80个特征和1个标签, 而测试数据集包含1459个样本,每个样本80个特征。
print(train_data.shape)
print(test_data.shape)

#让我们看看[前四个和最后两个特征,以及相应标签](房价)。
print(train_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:]))
#print(all_features.iloc[0:4,0:-1])
"""
数据预处理
"""
# 先对数值型数据标准化
# all_features.dtypes 返回 DataFrame 中所有列的数据类型(如 float64、int64、object 等)
# all_features.dtypes != 'object' 生成布尔掩码,标记非对象类型(即数值型)的列
#.index 提取满足条件(数值型)的列名索引
numeric_features = all_features.dtypes[all_features.dtypes != 'object'].index
print(numeric_features)

#标准化
# all_features[numeric_features] = all_features[numeric_features].apply(
#     lambda x: (x - x.mean()) / (x.std())
# )
# 直接计算均值和标准差
means = all_features[numeric_features].mean()
stds = all_features[numeric_features].std()
all_features[numeric_features] = (all_features[numeric_features] - means) / stds

# 标准化后,所有均值为0,因此我们可以将缺失值设置为0
all_features[numeric_features] = all_features[numeric_features].fillna(0)

# 接下来则是处理离散值(包括诸如“MSZoning”之类的特征,用独热编码替换它们)
# “Dummy_na=True”将“na”(缺失值)视为有效的特征值,并为其创建指示符特征
#将分类变量转换为哑变量:get_dummies()
all_features = pd.get_dummies(all_features, dummy_na=True, dtype=np.float32)
#print(all_features.shape)

# 通过values属性,可以从pandas格式中提取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)
print(train_features.shape)

#2.定义模型
loss = nn.MSELoss()
in_features = train_features.shape[1]

class MnistModel(nn.Module):
    def __init__(self):
        super(MnistModel,self).__init__()
        #Linear()参数:in_features,输入的神经元个数  out_features,输出神经元个数  bias=True是否包含偏置
        self.fc1 = nn.Linear(in_features,1)

    def forward(self,input):
        #out = self.linear(input)
        # 全连接层 激活函数处理,relu函数
        input = F.relu(input)
        # 输出层
        out = self.fc1(input)
        return out


def log_rmse(net, features, labels):
    # 为了在取对数时进一步稳定该值,将小于1的值设置为1
    clipped_pred = torch.clamp(net(features), 1, float("inf"))
    rmse = torch.sqrt(loss(torch.log(clipped_pred), torch.log(labels)))
    return rmse.item()


def train(net, train_features, train_labels, test_features, test_labels,
          num_epochs, lr, 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=lr,
                                 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折交叉验证, 它有助于模型选择和超参数调整(选择第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(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

# 当我们在K折交叉验证中训练K次后,返回训练和验证误差的平均值。
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):
        data = get_k_fold_data(k, i, X_train, y_train)
        # 定义训练的实现过程
        net = MnistModel()

        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, num_epochs, lr, weight_decay, batch_size = 5, 100, 7, 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()

"""
既然我们知道应该选择什么样的超参数, 我们不妨使用所有数据对其进行训练 (而不是仅使用交叉验证中使用的1−1/K的数据)。
然后,我们通过这种方式获得的模型可以应用于测试集。 将预测保存在CSV文件中可以简化将结果上传到Kaggle的过程。
"""

def train_and_pred(train_features, test_features, train_labels, test_data,
                   num_epochs, lr, weight_decay, batch_size):
    # 定义训练的实现过程
    net = MnistModel()

    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)


# 如果测试集上的预测与K倍交叉验证过程中的预测相似, 那就是时候把它们上传到Kaggle了。
# 下面的代码将生成一个名为submission.csv的文件。
train_and_pred(train_features, test_features, train_labels, test_data,
               num_epochs, lr, weight_decay, batch_size)


d2l.plt.show()

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

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

    抵扣说明:

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

    余额充值