目录

一、项目背景

二、数据挖掘技术的应用

三、实验数据探索分析

四、特征工程

五、LightGBM模型预测

六、总结

一、项目背景

1、作者说

我们在学习一项技术时候,实际上非常希望能够通过实战训练来检验自己的学习成果,并发现不足进一步提升自己。有关注阅读我的文章的朋友应该知道我个人更喜欢在实战的案例中学习,因此,这一篇我将完整地分享自己在项目中的学习过程,这个题目为《基于机器学习模型的二手车交易价格预测》,很直观可以了解到项目是要做什么的,以及使用到的技术。那么现在就开启实战之旅!

2、选题来源

题目内容来自阿里天池比赛——二手车交易价格预测大赛,要求预测二手汽车的交易价格,我们以该比赛和数据为基础,对本项目进行数据挖掘的研究,结合数据挖掘技术和机器学习模型来完成这个任务。

二、数据挖掘技术的应用

随着数据挖掘技术的日益发展,其强大的分析能力在二手车交易价格预测的应用非常适合,当然,数据挖掘技术也可以在客户交易记录分析,上架汽车分析,用户个性化推送等方面应用。在体量巨大的平台或企业中,其蕴含的海量数据资源,都可为数据挖掘技术服务,通过诸如回归技术,分类技术等,获取更多或更精确的潜在用户,用户购买行为,用户喜好等。

目前,各个二手车交易平台都会要求卖家在上传商品时提供相应的汽车数据以供买家参考,但是众多数据是否真的每个都可以作为买家购买选择的评判标准呢?关联性较小的数据不仅无法作为评判标准,还有可能影响买家的判断甚至误导买家,因此,在进行数据挖掘过程中,探索数据、分析各种属性的相关性,我们可以给出正确合理的决策提供给平台和用户作为参考。

三、实验数据探索分析

1、实验数据集

这个比赛提供的数据集包括:训练集15万条,测试集5万条,同时会对name、model、brand和regionCode等特征信息进行脱敏,包含31列变量信息,其中15列为匿名变量。

训练集中price属性给出值,测试集price则是预测目标。

2、数据探索(EDA可视化)

我们拿到数据后,需要对数据进行初步探索,可以通过可视化图表等方式观察数据分布情况。使用EDA技术了解预测值的分布、特征分析等工作。

数据集中有部分较明显地存在缺省,因此我们根据实际情况进行相应的填充/特征构造/删除/不处理等操作,这个步骤有利于后面的特征工程。

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import KFold
from lightgbm.sklearn import LGBMRegressor
from sklearn.metrics import mean_squared_error, mean_absolute_error
import time

Train_data = pd.read_csv('..\\car_data\\used_car_train_20200313.csv',
                         sep=' ')  # handle_used_car_train.csv
Test_data = pd.read_csv('..\\car_data\\used_car_testB_20200421.csv', sep=' ')

# 合并方便后面的操作
df = pd.concat([Train_data, Test_data], ignore_index=True)
print(df.head().append(df.tail()))

 

对价格分布情况的查看,如下左图,发现价格price是偏分布,所以进行了对数转换:

# 对'price'做对数变换
df['price'] = np.log1p(df['price'])

另外,对部分特征的数据分布进行可视化,直观了解数据的分布情况:

3、预处理

接着上一步的实验结果,现在需要对数据进行初步地处理,获取有效的特征信息,更好地给目标预测结果提供正确的预测方向。

# 用众数填充缺失值
df['fuelType'] = df['fuelType'].fillna(0)
df['gearbox'] = df['gearbox'].fillna(0)
df['bodyType'] = df['bodyType'].fillna(0)
df['model'] = df['model'].fillna(0)

# 处理异常值
df['power'] = df['power'].map(lambda x: 600 if x > 600 else x)  # 赛题限定power<=600
df['notRepairedDamage'] = df['notRepairedDamage'].astype('str').apply(lambda x: x if x != '-' else None).astype(
    'float32')

# 对可分类的连续特征进行分桶,kilometer是已经分桶了
bin = [i * 10 for i in range(31)]
df['power_bin'] = pd.cut(df['power'], bin, labels=False)

bin = [i * 10 for i in range(24)]
df['model_bin'] = pd.cut(df['model'], bin, labels=False)

到这里,清洗好的数据基本上可以达到标准,开始很关键的步骤:特征工程。

四、特征工程

1、特征构造

经过数据分析,会发现数据存在数值型和类别型的属性,实际上它们之间存在许多联系,比如时间信息、行驶路程等数值型特征,对于价格的影响是很大的,所以,我们可以采用特征构造的方法,提取更有效的数据信息。

# 特征工程
# 时间提取出年,月,日和使用时间
from datetime import datetime


def date_process(x):
    year = int(str(x)[:4])
    month = int(str(x)[4:6])
    day = int(str(x)[6:8])

    if month < 1:
        month = 1

    date = datetime(year, month, day)
    return date


df['regDate'] = df['regDate'].apply(date_process)
df['creatDate'] = df['creatDate'].apply(date_process)
df['regDate_year'] = df['regDate'].dt.year
df['regDate_month'] = df['regDate'].dt.month
df['regDate_day'] = df['regDate'].dt.day
df['creatDate_year'] = df['creatDate'].dt.year
df['creatDate_month'] = df['creatDate'].dt.month
df['creatDate_day'] = df['creatDate'].dt.day

# 二手车使用天数
df['car_age_day'] = (df['creatDate'] - df['regDate']).dt.days
# 二手车使用年数
df['car_age_year'] = round(df['car_age_day'] / 365, 1)

# 行驶路程与功率统计
kk = ['kilometer', 'power']
t1 = Train_data.groupby(kk[0], as_index=False)[kk[1]].agg(
    {kk[0] + '_' + kk[1] + '_count': 'count', kk[0] + '_' + kk[1] + '_max': 'max',
     kk[0] + '_' + kk[1] + '_median': 'median',
     kk[0] + '_' + kk[1] + '_min': 'min', kk[0] + '_' + kk[1] + '_sum': 'sum', kk[0] + '_' + kk[1] + '_std': 'std',
     kk[0] + '_' + kk[1] + '_mean': 'mean'})
df = pd.merge(df, t1, on=kk[0], how='left')

2、类别特征交叉

通过对14个匿名特征的相关性探索,发现匿名特征v_0,v_3,v_8,v_12与价格存在明显较大相关性。作为数值型特征,尝试进行了四则运算简单组合,交叉构造新的特征,作为模型训练特征信息,对汽车价格预测的准确性进一步提高。

# v_0,v_3,v_8,v_12与price的相关性很高,所以做四则运算简单组合,发现效果不错
num_cols = [0, 3, 8, 12]
for i in num_cols:
    for j in num_cols:
        df['new' + str(i) + '*' + str(j)] = df['v_' + str(i)] * df['v_' + str(j)]

for i in num_cols:
    for j in num_cols:
        df['new' + str(i) + '+' + str(j)] = df['v_' + str(i)] + df['v_' + str(j)]

for i in num_cols:
    for j in num_cols:
        df['new' + str(i) + '-' + str(j)] = df['v_' + str(i)] - df['v_' + str(j)]

for i in range(15):
    df['new' + str(i) + '*year'] = df['v_' + str(i)] * df['car_age_year']

特征工程到此基本完成,下面开始构建模型,预测目标price。

五、LightGBM模型预测

我使用的模型是LightGBM的回归算法,是基于梯度提升的决策树算法,训练很快,在机器学习中应用广泛。这里将数据进行五折交叉验证,模型对训练数据进行训练,最后给出预测结果。

# 建立模型预测
# 划分训练数据和测试数据
df1 = df.copy()
test = df1[df1['price'].isnull()]
X_train = df1[df1['price'].notnull()].drop(['price', 'regDate', 'creatDate', 'SaleID', 'regionCode'], axis=1)
Y_train = df1[df1['price'].notnull()]['price']
X_test = df1[df1['price'].isnull()].drop(['price', 'regDate', 'creatDate', 'SaleID', 'regionCode'], axis=1)
print(X_train.head().append(X_train.tail()))
# 五折交叉检验
cols = list(X_train)
oof = np.zeros(X_train.shape[0])
sub = test[['SaleID']].copy()
sub['price'] = 0
feat_df = pd.DataFrame({'feat': cols, 'imp': 0})
skf = KFold(n_splits=4, shuffle=True, random_state=2020)

clf = LGBMRegressor(
    n_estimators=10000,
    learning_rate=0.07,  # 0.02,
    boosting_type='gbdt',
    objective='regression_l1',
    max_depth=-1,
    num_leaves=31,
    min_child_samples=20,
    feature_fraction=0.8,
    bagging_freq=1,
    bagging_fraction=0.8,
    lambda_l2=2,
    random_state=2020,
    metric='mae'
)

mae = 0
for i, (trn_idx, val_idx) in enumerate(skf.split(X_train, Y_train)):
    print('--------------------- {} fold ---------------------'.format(i + 1))
    trn_x, trn_y = X_train.iloc[trn_idx].reset_index(drop=True), Y_train[trn_idx]
    val_x, val_y = X_train.iloc[val_idx].reset_index(drop=True), Y_train[val_idx]
    clf.fit(
        trn_x, trn_y,
        eval_set=[(val_x, val_y)],
        eval_metric='mae',
        early_stopping_rounds=300,
        verbose=300
    )

    sub['price'] += np.expm1(clf.predict(X_test)) / skf.n_splits
    oof[val_idx] = clf.predict(val_x)
    print('val mae:', mean_absolute_error(np.expm1(val_y), np.expm1(oof[val_idx])))
    mae += mean_absolute_error(np.expm1(val_y), np.expm1(oof[val_idx])) / skf.n_splits

print('cv mae:', mae)

每一折验证打印结果大概如下:

--------------------- 1 fold ---------------------
[LightGBM] [Warning] feature_fraction is set=0.8, colsample_bytree=1.0 will be ignored. Current value: feature_fraction=0.8

[LightGBM] [Warning] bagging_fraction is set=0.8, subsample=1.0 will be ignored. Current value: bagging_fraction=0.8

[LightGBM] [Warning] lambda_l2 is set=2, reg_lambda=0.0 will be ignored. Current value: lambda_l2=2

[LightGBM] [Warning] bagging_freq is set=1, subsample_freq=0 will be ignored. Current value: bagging_freq=1

[10000]    valid_0's l1: 0.109922
val mae: 467.63419701364296

然后生成提交结果文件:

# 生成提交文件
sub.to_csv('..\\submit_v2.1.csv', index=False)

线上提交结果,评判之后就可以看到自己的排名和成绩。

六、总结

这次我分享了之前参加的一个比赛:二手车交易价格预测。这篇文章主要写了项目的来龙去脉以及详细的实验步骤,相信大家在学习过程中可以看到比较清晰的流程,这也是我按照最基础最有效的方式完成的。

当然,在机器学习中的模型选择是多种多样的,可以根据自己的理解和应用选择更好的模型进行训练预测,而且在后面的调参过程也十分重要,上面比赛的提交结果也是我经过不断分析调参选型获得的,因此,很多人常说,在机器学习中,调参是一门很讲究的手艺,是完成前置工作后一项必不可少的步骤,就是为了找到模型的最佳表现,得到更好的效果。

如果觉得不错欢迎三连,点赞收藏关注,一起加油进步!原创作者:Charzous.