二手车交易价格预测笔记

任务:利用神经网络完成对二手车交易价格的预测

代码解析

导入库

import pandas as pd
import numpy as np
from torch import nn, optim
import torch
import matplotlib.pyplot as plt

配置参数

config = {
    'epoch': 10,
    'batch_size': 512,
    'learning_rate': 8e-3,
    'device': 'cuda',
    "num_cols": ['regDate', 'creatDate', 'power', 'kilometer', 'v_0', 'v_1', 'v_2', 'v_3', 'v_4', 'v_5', 'v_6', 'v_7', 'v_8', 'v_9', 'v_10',
                 'v_11', 'v_12', 'v_13', 'v_14'],
    "cate_cols": ['model', 'brand', 'bodyType', 'fuelType', 'gearbox', 'seller', 'notRepairedDamage']
}
  • epoch:指定训练的周期数,即整个训练集被用来训练神经网络的次数
  • batch_size:每次更新模型权重时使用的样本数量
  • learning_rate:学习率是梯度下降算法中的一个重要参数,决定了权重更新的步长。较高的学习率意味着模型权重更新更快,但可能跳过局部最小值;较低的学习率则可能使训练过程缓慢
  • device:指定用于计算的设备。如果可用,将使用GPU(通常比CPU快很多)来进行计算。cuda 是 NVIDIA GPU 的 PyTorch 后端。
  • num_cols和cate_cols: 列出了数据集中用于模型训练的特征列名。"num_cols" 包含数值型特征,如日期、功率、里程等;"cate_cols" 包含分类型特征,如车型、品牌、车身类型、燃料类型等。

导入数据

test_data = pd.read_csv('/gemini/data-1/used_car_testB_20200421.csv', sep=' ')
train_data = pd.read_csv('/gemini/data-1/used_car_train_20200313.csv', sep=' ')

合并数据

data = pd.concat([train_data, test_data])

定义One-Hot编码函数

def oneHotEncode(df, colNames):
    for col in colNames:
        dummies = pd.get_dummies(df[col], prefix=col)
        df = pd.concat([df, dummies],axis=1)
        df.drop([col], axis=1, inplace=True)
    return df

函数效果:对dataframe中的分类变量进行one-hot编码,即将每个分类变量都拆分成多个二进制的列。

eg:

Color Size
0 Red Small
1 Blue Large
2 Green Medium
3 Red Small
4 Blue Medium

转化为

index Color_Blue Color_Green Color_Red Size_Large Size_Medium Size_Small
0 0 0 1 0 0 1
1 1 0 0 1 0 0
2 0 1 0 0 1 0
3 0 0 1 0 0 1
4 1 0 0 0 1 0

数据预处理

data = data.replace('-', '-1')
data.notRepairedDamage = data.notRepairedDamage.astype('float32')
data.loc[data['power']>600,'power'] = 600
  • 处理特殊标记,将'-'转化为'-1'
  • 数据类型转换,notRepairedDamage 列的数据类型转换为 32 位浮点数,确保这一列可以进行数值运算
  • 异常值处理,查找 power 列中大于 600 的所有值,并将这些值统一设置为 600,可以防止极端值对模型训练产生不良影响。
# 处理离散数据
for col in config['cate_cols']:
    data[col] = data[col].fillna('-1')
data = oneHotEncode(data, config['cate_cols'])

# 处理连续数据
for col in config['num_cols']:
    data[col] = data[col].fillna(0)
    data[col] = (data[col]-data[col].min()) / (data[col].max()-data[col].min())

# 处理(可能)无关数据 
data.drop(['name', 'regionCode'], axis=1, inplace=True)
  • 处理离散数据:用-1填充缺失值,然后对分类特征进行one-hot编码
  • 处理连续数据:用0填充缺失值,然后对数据进行归一化处理,即将数值范围缩放到 [0, 1] 之间。
  • 处理(可能)无关数据:移除

特征缩放

在使用多指标在综合评价某事物时,可能存在各指标的数量级和量纲不同导致的数据爆炸或各指标对分析的作用不合理等情况,因此需要在数据预处理时采用特征缩放的方法来平衡各指标之间的差异,从而优化算法。

如果不进行特征缩放

假如特征\(x_1\)的数值是100左右,特征\(x_2\)的数值是1左右,方程为𝑦=𝑏+𝑤1𝑥1+𝑤2𝑥2,那\(w_1\)对𝑦的影响就更大,对Loss的影响也更大,损失函数关于\(w_1\)的梯度也更大,而损失函数关于\(w_2\)的梯度却很小,因此两个特征就不能使用相同的学习率。

不进行特征缩放的话,Error Surface就是一个椭圆,梯度下降时不一定是朝着最优点(圆心),速度就慢。

如果进行了特征缩放,Error Surface会尽可能趋近于圆,因此梯度下降时会一直朝着最优点(圆心),所以速度快。

标准化(Standardization/Z-Score Normalization)

\[y = \frac{z-mean(z)}{Var(x)-eps} \]

  • 特点

    使数据的平均值变为0、标准差变为1,不改变数据的分布类型,数值范围不一定,消除了数据的量纲差异。

  • 假设

    标准化假设数据是正态分布,但这个要求并不十分严格,如果数据是正态分布则该技术会更有效。

  • 何时使用

    当我们使用的算法假设数据是正态分布时,可以使用Standardization,比如线性回归、逻辑回归、线性判别分析。

    因为Standardization使数据平均值为0,也可以在一些假设数据中心为0(zero centric data)的算法中使用,比如主成分分析(PCA)。

归一化(Normalization)
  • 特点

    把数据调整到[0,1],并且消除了数据的量纲差异。

    也可以把数据调到[-1,1],在使用SVM和Adaboost时就需要这样。

  • 何时使用

    当我们不知道数据分布时或者我们知道数据不是正态分布时,这是一个很好的方法。

    换种说法就是,当我们使用的算法没有假设数据的分布类型时,就可以使用Normalization,比如K近邻算法和人工神经网络。

    Mean Normalization
    • 定义

      \(y=\frac{x-mean(x)}{max(x)-min(x)}\)

    • 特点

      把数据调到[-1,1],平均值为0

    • 何时使用

      一些假设数据中心为0(zero centric data)的算法,比如主成分分析(PCA)。

    Min-Max Normalization
    • 定义

      \(y=\frac{x-min(x)}{max(x)-min(x)}\)

    • 特点

      把数据调到[0,1]

    • 何时使用

      当处理具有严格数值范围要求的数据(比如图片)时,这非常有用。

# 暂存处理后的test数据集
test_data = data[pd.isna(data.price)]
test_data.to_csv('./one_hot_testB.csv')
# 删除test数据(price is nan)
data.reset_index(inplace=True)
train_data = data
train_data = train_data.drop(data[pd.isna(data.price)].index)
# 删除ID
train_data.drop(['SaleID'], axis=1, inplace=True)
# 打乱
train_data = train_data.sample(frac=1) # 它将返回一个包含所有行的随机排序版本
# 分离目标
train_target = train_data['price']
train_data.drop(['price', 'index'], axis=1, inplace=True)
# 分离出验证集,用于观察拟合情况
validation_data = train_data[:10000] # 选取原始训练数据集中的前10000项作为验证集
train_data = train_data[10000:] # 将训练集更新为原训练集除前10000项之外的余项
validation_target = train_target[:10000] # 选取原始训练目标中的前10000项作为验证时的评估标准
train_target = train_target[10000:] # 调整训练集的训练目标

搭建网络

# 定义网络结构
class Network(nn.Module):
    def __init__(self, in_dim, hidden_1, hidden_2, hidden_3, hidden_4):
        super().__init__()
        self.layers = nn.Sequential(
            nn.Linear(in_dim, hidden_1),
            nn.BatchNorm1d(hidden_1),
            nn.ReLU(),
            nn.Linear(hidden_1, hidden_2),
            nn.BatchNorm1d(hidden_2),
            nn.ReLU(),
            nn.Linear(hidden_2, hidden_3),
            nn.BatchNorm1d(hidden_3),
            nn.ReLU(),
            nn.Linear(hidden_3, hidden_4),
            nn.BatchNorm1d(hidden_4),
            nn.ReLU(),
            nn.Linear(hidden_4, 1)
        )

    def forward(self, x):
        y = self.layers(x)
        return y
  • nn.Linear 是全连接层,负责从输入层到输出层的线性变换
  • nn.BatchNorm1d 层用于进行批量归一化,这有助于加速训练过程并提高模型的稳定性。
  • nn.ReLU 层是激活函数,用于引入非线性特性,使得网络能够学习更复杂的函数映射。
# 定义网络
model = Network(train_data.shape[1], 256, 256, 256, 32)
# 网络的输入维度由 train_data.shape[1] 确定,即训练数据的特征数量。其余参数分别为隐藏层的大小,分别是 25625625632。
model.to(config['device'])

# 使用Xavier初始化权重
for line in model.layers:
    if type(line) == nn.Linear:
        print(line)
        nn.init.xavier_uniform_(line.weight)

进行训练

# 将数据转化为tensor,并移动到cpu或cuda上

train_features = torch.tensor(train_data.values, dtype=torch.float32, device=config['device'])
train_num = train_features.shape[0]
train_labels = torch.tensor(train_target.values, dtype=torch.float32, device=config['device'])

validation_features = torch.tensor(validation_data.values, dtype=torch.float32, device=config['device'])
validation_num = validation_features.shape[0]
validation_labels = torch.tensor(validation_target.values, dtype=torch.float32, device=config['device'])
# 定义损失函数和优化器
criterion = nn.MSELoss()
criterion.to(config['device'])
optimizer = optim.Adam(model.parameters(), lr=config['learning_rate'])

损失函数

神经网络性能的“恶劣程度”的指标

均方误差(mean squared error)

  • yk是表示神经网络的输出,tk表示监督数据,k表示数据的维数。

  • t采用one-hot表示:将正确解标签表示为1,其他标签表示为0

  • 实现

    def mean_squared_error(y, t):    
    	return 0.5 * np.sum((y-t)**2)
    
交叉熵误差(cross entropy error)

image-20240222150048261

  • yk是神经网络的输出,tk是正确解标签。

  • t使用one-hot表示时,实际上只计算

    \[E = -log y_k \]

  • one-hot时的代码

    def cross_entropy_error(y, t):    
    	delta = 1e-7    
    	return -np.sum(t * np.log(y + delta))
    

    微小值delta:当出现np.log(0)时,np.log(0)会变为负无限大的-inf,这样一来就会导致后续计算无法进行。作为保护性对策,添加一个微小值可以防止负无限大的发生。

# 开始训练

mae_list = []

for epoch in range(config['epoch']):
    losses = []
    # 将模型设置为训练模式。在训练模式下,如 Batch Normalization 和 Dropout 等层的行为会有所不同。
    model.train() 
    # mini-batch 循环:
    for i in range(0, train_num, config['batch_size']):
        end = i + config['batch_size']
        if i + config['batch_size'] > train_num-1:
            end = train_num-1
        mini_batch = train_features[i: end]
        mini_batch_label = train_labels[i: end]
        #使用模型对当前 mini-batch 进行前向传播,得到预测值。
        pred = model(mini_batch)
        # 移除预测张量中的大小为 1 的维度。计算损失
        pred = pred.squeeze()
        loss = criterion(pred, mini_batch_label)
# 检查损失是否为 NaN,如果是,则停止当前 epoch 的训练。 
        if torch.isnan(loss):
            break
        # 计算平均绝对误差(Mean Absolute Error, MAE),然后将结果添加到 losses 列表中。
        mae = torch.abs(mini_batch_label-pred).sum()/(end-i)
        losses.append(mae.item())
        # 清零优化器的梯度缓存。
        optimizer.zero_grad()
        # 计算损失相对于模型参数的梯度。
        loss.backward()
        # 根据计算出的梯度更新模型参数。
        optimizer.step()
    # 将模型设置为评估模式,在评估模式下,Batch Normalization 和 Dropout 等层的行为会有所不同。
    model.eval()
    # 使用模型对验证集进行前向传播,得到预测值。
    pred = model(validation_features)
    validation_mae = torch.abs(validation_labels-pred.squeeze()).sum().item()/validation_num
    
    mae_list.append((sum(losses)/len(losses), validation_mae))
        
    print(f"epoch:{epoch + 1} MAE: {sum(losses)/len(losses)}, Validation_MAE: {validation_mae}")
    torch.save(model, 'model.pth')

整理并保存结果

test_data = test_data.drop(columns=['SaleID','price'])
test_data = torch.tensor(test_data.values, dtype=torch.float32,device='c
data = pd.read_csv('./one_hot_testB.csv')
data.drop(columns='price')
res = pd.concat([data.SaleID, price], axis=1)uda')
price = pd.DataFrame(pred.detach().cpu().numpy(), columns=['price'])
res.to_csv('submission.csv')
posted @   Melnis8  阅读(88)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
点击右上角即可分享
微信分享提示