AI实践学习笔记(三)----阿里二手车价格预测赛事实践

第三节课课堂简介:
 

讲师:秦鹤亮、刘宇涵        

日期:2024.7.25

内容:阿里云天池baseline讲解与提升

二手车价格预测

        

赛题理解

       赛题以预测二手车的交易价格为任务,数据集报名后可见并可下载,该数据来自某交易平台的二手车交易记录,总数据量超过40w,包含31列变量信息,其中15列为匿名变量。为了保证比赛的公平性,将会从中抽取15万条作为训练集,5万条作为测试集A,5万条作为测试集B,同时会对name、model、brand和regionCode等信息进行脱敏。 

以下为整理后的数据概况:

SaleID 交易ID,唯一编码

name 汽车交易名称,已脱敏 (意思是对敏感数据已经进行过变形处理了)

regDate 汽车注册日期,例如20160101,2016年01月01日

model 车型编码,已脱敏

brand 汽车品牌,已脱敏

bodyType 车身类型:豪华轿车:0,微型车:1,厢型车:2,大巴车:3,敞篷车:4,双门汽车:5,商务车:6,搅拌车:7

fuelType 燃油类型:汽油:0,柴油:1,液化石油气:2,天然气:3,混合动力:4,其他:5,电动:6

gearbox 变速箱:手动:0,自动:1

power 发动机功率:范围 [ 0, 600 ]

kilometer 汽车已行驶公里,单位万km

notRepairedDamage 汽车有尚未修复的损坏:是:0,否:1

regionCode 地区编码,已脱敏

seller 销售方:个体:0,非个体:1

offerType 报价类型:提供:0,请求:1

creatDate 汽车上线时间,即开始售卖时间

price 二手车交易价格(预测目标)

v系列特征 匿名特征,包含v0-14在内13个匿名特征。

所以我们可以知道:

1.此题为传统的数据挖掘问题,通过数据科学以及机器学习深度学习的办法来进行建模得到结果。
2.此题是一个典型的回归问题。
3.主要应用xgb、lgb、catboost,以及pandas、numpy、matplotlib、seabon、sklearn、keras等等4.数据挖掘常用库或者框架来进行数据挖掘任务。
5.通过EDA来挖掘数据的联系和自我熟悉数据。

                   

数据分析

      在机器学习中,数据分析过程是准备和优化数据以便进行有效建模的关键环节。对一些重要代码进行摘录讲解。

读取数据

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

2. 数据合并与清洗

合并训练集和测试集以便统一处理。

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


对于数据中的特殊符号(如'-'),将其替换为数值(如'-1'),以统一数据格式。data.replace('-', '-1'):将数据中的 '-' 替换为 '-1'。对训练集数据进行简略查看,发现训练集中['model','bodyType','fuelType',gearbox']这4列均有缺失值,并且'notRepairedDamage'列为字符串类型变量'object',其中”-“有缺失项,因此用‘-1’代替。这通常是为了处理缺失值或异常值,使得后续处理变得更一致。

data = data.replace('-', '-1')


处理缺失值,例如使用众数、中位数或特定值填充。

data['notRepairedDamage'] = data['notRepairedDamage'].astype('float32')
data['power'] = data['power'].apply(lambda x: x if x <= 600 else 600)


去除可能无关或冗余的列,如name和regionCode。

data.drop(['name', 'regionCode'], axis=1, inplace=True)

数字特征

#绘制所有变量的柱形图,查看数据
data.hist(bins=100,figsize=(15,15))
plt.cla()  #清除axes

特征工程

   

特征处理(Feature Engineering)是指在机器学习模型开发过程中,对原始数据进行预处理,以提取更有价值的特征供模型使用的过程。特征处理的好坏直接影响到模型的性能。特征处理过程中通常会涉及到数据清洗、特征编码、特征缩放、特征选择、特征衍生、特征降维等操作。对一些重要代码进行摘录讲解。

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']
}
  1. epoch: 训练的轮数,这里设置为 10 轮。

  2. batch_size: 每批次处理的样本数量,你设置为 512。

  3. learning_rate: 学习率,控制模型权重更新的幅度,设置为 (8 \times 10^{-3})(即 0.008)。

  4. device: 计算设备,cuda 表示你将使用 NVIDIA GPU 进行训练。

  5. num_cols: 数值特征的列名列表,这些列在模型中会被视为连续变量。

  6. cate_cols: 类别特征的列名列表,这些列会被视为分类变量,可能需要编码或转换处理。

# 定义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

  oneHotEncode 函数的作用是将数据中的分类变量(即类别特征)转换为One-Hot编码格式。One-Hot编码是处理分类数据时的一种常用方法,它将每个类别转换为一个二进制向量,使得机器学习模型可以有效地使用这些数据。 


data.notRepairedDamage = data.notRepairedDamage.astype('float32')

# 处理离散数据
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.notRepairedDamage = data.notRepairedDamage.astype('float32'):将 notRepairedDamage 列的数据类型转换为 float32。这是为了节省内存或符合模型的输入要求。

  • for col in config['cate_cols']: data[col] = data[col].fillna('-1'):对 config['cate_cols'] 中的所有分类特征列,用 '-1' 填充缺失值。这使得缺失的数据可以作为一个特殊类别来处理。

  • data = oneHotEncode(data, config['cate_cols']):使用前面定义的 oneHotEncode 函数将这些分类特征列进行One-Hot编码,转换为模型可以处理的格式。

  • for col in config['num_cols']: data[col] = data[col].fillna(0):对 config['num_cols'] 中的所有连续特征列,用0填充缺失值。

  • data[col] = (data[col] - data[col].min()) / (data[col].max() - data[col].min()):对连续特征进行归一化,将数据缩放到 [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)
train_data.shape
# 删除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]
train_data = train_data[10000:]
validation_target = train_target[:10000]
train_target = train_target[10000:]

1.这行代码从原始数据 data 中筛选出价格 (price) 为缺失值 (NaN) 的记录,存储在 test_data 中,并将其保存为 CSV 文件 one_hot_testB.csv。这通常是为了后续进行预测或模型评估。

2.reset_index 用于重置索引以确保索引顺序从零开始。然后,从 data 中删除价格为缺失值的记录,得到 train_data。这样,train_data 只包含有价格信息的记录。

3.这行代码从 train_data 中删除 SaleID 列。通常,ID 列对模型训练没有帮助,可能只会增加噪声。

4.使用 sample(frac=1) 方法将数据打乱,以确保数据的随机性。这有助于提高模型训练的泛化能力。

5.将目标变量 price 从 train_data 中分离出来,存储在 train_target 中,然后从 train_data 中删除 price 和 index 列,留下特征列。

6.将 train_data 和 train_target 的前 10,000 条记录分离出来作为验证集,其余部分作为实际训练集。这种方法可以帮助在训练过程中监控模型在未见过的数据上的表现,从而避免过拟合。

        建模指的是选择一个合适的模型,并用数据对其进行训练。常见的模型包括线性回归、决策树、支持向量机、神经网络等。我们需要考虑数据类型、任务类型、模型复杂度等因素来选择合适的模型。

       调参(Parameter Tuning)是指调整模型的超参数,以获得最佳的模型性能。超参数是模型在训练过程中设置的参数,不会通过训练数据直接学习到。例如:

  • 线性回归:正则化参数(L1、L2)。
  • 决策树:树的深度、最小样本分裂数。
  • 支持向量机:惩罚参数(C)、核函数的类型和参数。
  • 神经网络:学习率、层数、每层的神经元数目、激活函数类型等。

调参的方法包括:

  1. 网格搜索(Grid Search):定义一个超参数的范围,系统地尝试所有可能的组合。
  2. 随机搜索(Random Search):在超参数的空间中随机选择组合进行测试,相比网格搜索更高效。
  3. 贝叶斯优化(Bayesian Optimization):通过建立超参数和模型性能之间的概率模型,逐步选择最有可能提升性能的超参数组合。
  4. 交叉验证(Cross-Validation):将数据集划分为多个子集,多次训练和验证模型,以评估模型的性能和稳定性。

       调参的目标是找到一组超参数,使模型在验证集或测试集上的表现最优。有效的调参可以显著提高模型的预测性能。

    我们选择了深度神经网络的模型。

# 定义网络结构
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
# 定义网络
model = Network(train_data.shape[1], 256, 256, 256, 32)
model.to(config['device'])

# 使用Xavier初始化权重
for line in model.layers:
    if type(line) == nn.Linear:
        print(line)
        nn.init.xavier_uniform_(line.weight)
  • __init__ 方法:定义了网络的层结构。使用了 nn.Sequential 来简化模型的创建流程,其中包括多个全连接层 (nn.Linear)、批归一化层 (nn.BatchNorm1d) 和激活函数 (nn.ReLU)。
  • forward 方法:定义了前向传播的过程,通过 self.layers 处理输入数据 x,并返回网络输出。
  • 通过传入特征维度(train_data.shape[1])和隐藏层的尺寸(256, 256, 256, 32),实例化 Network 类。
  • model.to(config['device']) 将模型转移到指定的设备上(例如 GPU)。
  • 遍历模型中的每一层,如果该层是 nn.Linear 类型的线性层,则应用 Xavier 初始化 (nn.init.xavier_uniform_) 对其权重进行初始化。Xavier 初始化有助于保持前向传播时的激活值和反向传播时的梯度稳定。

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

# 将数据转化为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'])

 

  • nn.MSELoss():这是均方误差损失函数(Mean Squared Error Loss),适用于回归任务。它计算预测值和真实值之间的均方误差。
  • criterion.to(config['device']):将损失函数移动到指定的设备上(例如 GPU)。不过,实际上,PyTorch 中的损失函数通常不需要移动到 GPU,因为计算损失时,输入和目标张量都已经在相同的设备上,损失函数会自动处理。可以省略这一步。
  • optim.Adam:这是 Adam 优化器,一种常用的自适应学习率优化算法。Adam 在很多情况下表现得比传统的梯度下降优化器更好,因为它结合了动量和自适应学习率。
  • model.parameters():这个方法返回模型中所有可学习的参数,这些参数将被 Adam 优化器更新。
  • lr=config['learning_rate']:设置优化器的学习率。config['learning_rate'] 应该在配置文件中定义,控制学习过程的步长。

开始训练模型并评估

# 开始训练

mae_list = []

for epoch in range(config['epoch']):
    losses = []
    model.train()
    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]
        pred = model(mini_batch)
        pred = pred.squeeze()
        loss = criterion(pred, mini_batch_label)

        if torch.isnan(loss):
            break
        mae = torch.abs(mini_batch_label-pred).sum()/(end-i)
        losses.append(mae.item())
        
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
    
    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')

 

  • for epoch in range(config['epoch'])::循环遍历每个训练周期,epoch 代表当前进行到第几轮训练。
  • losses = []:每一轮的每次迭代中,都会记录下损失。
  • for i in range(0, train_num, config['batch_size']):遍历训练数据集,将数据划分成批次。
  • mini_batch = train_features[i: end]:根据 i 到 end 的索引切片,获取当前批次的数据特征。
  • mini_batch_label = train_labels[i: end]:获取该批次的标签。
  • pred = model(mini_batch):将批次数据通过模型得到预测值。
  • pred = pred.squeeze():如果有必要,去掉维度上多余的维度,例如如果模型输出是 (batch_size, 1),可以通过 .squeeze() 变成 (batch_size)。
  • loss = criterion(pred, mini_batch_label):计算当前批次的损失。
  • if torch.isnan(loss): break:检查损失是否是无穷大或NaN,这些情况可能表示模型陷入了数值问题,如梯度爆炸或消失。
  • mae = torch.abs(mini_batch_label-pred).sum()/(end-i):计算当前批次的前向预测的平均绝对误差(MAE)。
  • losses.append(mae.item()):将当前批次的MAE保存到 losses 列表中。
  • optimizer.zero_grad():在每次迭代开始时,将梯度清零。
  • loss.backward():计算损失函数相对于模型参数的梯度。
  • optimizer.step():根据梯度更新模型参数。

评估模型

  • model.eval():将模型设置为评估模式,这可能会关闭如 dropout 等在评估时不使用的特性。
  • 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)):将每轮的训练损失和验证集上的MAE保存到列表中,用于后续分析。

保存模型

  • torch.save(model, 'model.pth'):将模型在当前最佳性能(比如最低的MAE值)时保存,可以用作后续的预测或验证。

输出回归模型

x = np.arange(0, config['epoch'])
y1, y2 = zip(*mae_list)
plt.plot(x, y1, label='train')
plt.plot(x, y2, label='valid')
plt.legend()
plt.show()

 

进行预测并导出csv文件

import pandas as pd
import torch
from torch import nn
from settings import config
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
model = torch.load('model.pth', map_location=config['device'])
data = pd.read_csv('./one_hot_testB.csv')
data = data.drop(columns=['Unnamed: 0', 'price'])
test = data.drop(columns='SaleID')
test = torch.tensor(test.values, dtype=torch.float32)
pred = model(test)
price = pd.DataFrame(pred.detach().cpu().numpy(), columns=['price'])
res = pd.concat([data.SaleID, price], axis=1)
res.to_csv('output.csv')

 

两种不同提升方法

       此外讲师还介绍了两种进阶方法,依靠这两种方法做出的预测模型更为精确,误差更小。现在在下面进行简要的介绍。

调库法

      调库版是指使用现成的机器学习库和工具来构建和训练模型。这通常包括利用流行的机器学习框架(如 TensorFlow、PyTorch 或 scikit-learn)和一些数据预处理库(如 pandas 和 NumPy)来快速实现和调整模型。

特点:
  • 高效性:利用成熟的库,能够快速搭建和训练模型。
  • 可维护性:使用的库和工具有完善的文档和社区支持。
  • 便捷性:现成的库通常提供了许多高效的算法实现和优化策略。
步骤:
  1. 数据预处理:使用 pandas 对数据进行清洗、处理缺失值和特征工程。
  2. 特征选择和缩放:利用 scikit-learn 提供的特征选择和缩放工具。
  3. 模型构建和训练:使用 TensorFlow 或 PyTorch 构建深度学习模型,或使用 scikit-learn 中的传统机器学习算法。
  4. 调参:利用库中的网格搜索(GridSearchCV)或随机搜索(RandomizedSearchCV)工具调整模型超参数。
  5. 评估和测试:使用库提供的评估函数计算模型的性能指标,如 MAE、RMSE 等。

手搓法

       手搓版是指从头开始手动实现整个模型和训练过程。这通常涉及从基础开始,自己编写数据预处理、特征工程和模型训练代码。这种方式要求较高的技术能力,但可以提供更大的灵活性。

特点:
  • 灵活性:可以完全控制每一个细节,进行自定义的优化。
  • 学习机会:通过手动实现,可以深入理解机器学习和深度学习的工作原理。
  • 挑战性:需要手动实现数据处理、模型构建和优化等多个环节。
步骤:
  1. 数据预处理:手动编写数据清洗和特征工程代码。
  2. 模型构建:从头实现机器学习模型或深度学习模型(例如用 NumPy 实现线性回归)。
  3. 训练过程:手动实现前向传播、损失计算和梯度更新等步骤。
  4. 评估和测试:计算模型的性能指标。

总结感想

        学习机器学习的过程是一段充满挑战与乐趣的旅程。首先,机器学习让我深刻理解了数据在现代决策中的核心地位。通过系统学习,我掌握了从数据收集、清洗到建模和优化的全过程。这个过程不仅要求扎实的数学和统计学基础,还需要具备编程能力和对业务场景的深刻理解。

       其次,机器学习的实践让我认识到,理论与实践的结合至关重要。尽管算法和模型在书本上看起来简单,但实际应用时面对的挑战往往复杂且多变。每一个数据集的特点都可能影响模型的效果,这使得特征工程和模型调优变得尤为重要。

       此外,我还感受到了机器学习的巨大潜力和广泛应用。从图像识别到自然语言处理,再到推荐系统,机器学习正不断改变我们的生活和工作方式。每一个成功的模型背后,都凝聚着大量的数据和智慧,这不仅是技术的胜利,更是对科学和人类智能的深刻探索。

        总的来说,学习机器学习不仅提高了我的技术能力,更让我对数据和智能有了全新的认识。这个领域的快速发展和应用前景,让我充满期待和信心,愿意不断探索和进步。 

  • 10
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值