【机器学习入门与实践】数据挖掘-二手车价格交易预测(含 EDA 探索、特征工程、特征优化、模型融合等)
note:项目链接以及码源见文末
1.赛题简介
了解赛题
赛题概况
数据概况
预测指标
分析赛题
数据读取 pandas
分类指标评价计算示例
回归指标评价计算示例
EDA 探索
特征工程
建模调参,相关原理介绍与推荐
线性回归模型
决策树模型
GBDT 模型
XGBoost 模型
LightGBM 模型
推荐教材
读取数据
线性回归 & 五折交叉验证 & 模拟真实业务情况
多种模型对比
模型调参
模型融合
回归\分类概率-融合
分类模型融合
一些其它方法
本赛题示例
1.1 数据说明
比赛要求参赛选手根据给定的数据集,建立模型,二手汽车的交易价格。
来自 Ebay Kleinanzeigen 报废的二手车,数量超过 370,000,包含 20 列变量信息,为了保证比赛的公平性,将会从中抽取 10 万条作为训练集,5 万条作为测试集 A,5 万条作为测试集 B。同时会对名称、车辆类型、变速箱、model、燃油类型、品牌、公里数、价格等信息进行脱敏。
一般而言,对于数据在比赛界面都有对应的数据概况介绍(匿名特征除外),说明列的性质特征。了解列的性质会有助于我们对于数据的理解和后续分析。Tip:匿名特征,就是未告知数据列所属的性质的特征列。
train.csv
name - 汽车编码
regDate - 汽车注册时间
model - 车型编码
brand - 品牌
bodyType - 车身类型
fuelType - 燃油类型
gearbox - 变速箱
power - 汽车功率
kilometer - 汽车行驶公里
notRepairedDamage - 汽车有尚未修复的损坏
regionCode - 看车地区编码
seller - 销售方
offerType - 报价类型
creatDate - 广告发布时间
price - 汽车价格
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'(根据汽车的评论、标签等大量信息得到的 embedding 向量)【人工构造 匿名特征】
数字全都脱敏处理,都为 label encoding 形式,即数字形式
1.2 预测指标
本赛题的评价标准为 MAE(Mean Absolute Error):
$$MAE=\frac{\sum_{i=1}^{n}\left|y_{i}-\hat{y}{i}\right|}{n}$其中y{i}代表第i个样本的真实值,其中\hat{y}_{i}代表第i$个样本的预测值。
一般问题评价指标说明:
什么是评估指标:
评估指标即是我们对于一个模型效果的数值型量化。(有点类似与对于一个商品评价打分,而这是针对于模型效果和理想效果之间的一个打分)
一般来说分类和回归问题的评价指标有如下一些形式:
分类算法常见的评估指标如下:
对于回归预测类常见的评估指标如下:
平均绝对误差平均绝对误差(Mean Absolute Error,MAE)
:平均绝对误差,其能更好地反映预测值与真实值误差的实际情况,其计算公式如下:
均方误差均方误差(Mean Squared Error,MSE)
,均方误差,其计算公式为:
MSE=N1i=1∑N(yi−y^i)2
R2(R-Square)的公式为:残差平方和:$$SS_{res}=\sum\left(y_{i}-\hat{y}{i}\right)^{2}$总平均值:$SS{tot}=\sum\left(y_{i}-\overline{y}_{i}\right)^{2}$$
其中y表示y的平均值得到R2表达式为:$$R^{2}=1-\frac{SS_{res}}{SS_{tot}}=1-\frac{\sum\left(y_{i}-\hat{y}{i}\right)^{2}}{\sum\left(y{i}-\overline{y}\right)^{2}}$$R2用于度量因变量的变异中可由自变量解释部分所占的比例,取值范围是 0~1,R2越接近 1,表明回归平方和占总平方和的比例越大,回归线与各观测点越接近,用 x 的变化来解释 y 值变化的部分就越多,回归的拟合程度就越好。所以R2也称为拟合优度(Goodness of Fit)的统计量。
yi表示真实值,$\hat{y}{i}表示预测值,\overline{y}{i}$表示样本均值。得分越高拟合效果越好。
1.3 分析赛题
此题为传统的数据挖掘问题,通过数据科学以及机器学习深度学习的办法来进行建模得到结果。
此题是一个典型的回归问题。
主要应用 xgb、lgb、catboost,以及 pandas、numpy、matplotlib、seabon、sklearn、keras 等等数据挖掘常用库或者框架来进行数据挖掘任务。
2.数据探索
# 下载数据
!wget http://tianchi-media.oss-cn-beijing.aliyuncs.com/dragonball/DM/data.zip
# 解压下载好的数据
!unzip data.zip
复制代码
# 导入函数工具
## 基础工具
import numpy as np
import pandas as pd
import warnings
import matplotlib
import matplotlib.pyplot as plt
import seaborn as sns
from scipy.special import jn
from IPython.display import display, clear_output
import time
warnings.filterwarnings('ignore')
%matplotlib inline
## 模型预测的
from sklearn import linear_model
from sklearn import preprocessing
from sklearn.svm import SVR
from sklearn.ensemble import RandomForestRegressor,GradientBoostingRegressor
## 数据降维处理的
from sklearn.decomposition import PCA,FastICA,FactorAnalysis,SparsePCA
import lightgbm as lgb
import xgboost as xgb
## 参数搜索和评价的
from sklearn.model_selection import GridSearchCV,cross_val_score,StratifiedKFold,train_test_split
from sklearn.metrics import mean_squared_error, mean_absolute_error
复制代码
2.1 数据读取
## 通过Pandas对于数据进行读取 (pandas是一个很友好的数据读取函数库)
Train_data = pd.read_csv('/home/aistudio/dataset/used_car_train_20200313.csv', sep=' ')
TestA_data = pd.read_csv('/home/aistudio/dataset/used_car_testA_20200313.csv', sep=' ')
## 输出数据的大小信息
print('Train data shape:',Train_data.shape)
print('TestA data shape:',TestA_data.shape)
复制代码
Train data shape: (150000, 31)
TestA data shape: (50000, 30)
复制代码
2.2 数据简要浏览
## 通过.head() 简要浏览读取数据的形式
Train_data.head()
复制代码
.dataframe tbody tr th:only-of-type { vertical-align: middle; }<pre><code>.dataframe tbody tr th {<br/> vertical-align: top;<br/>}<br/><br/>.dataframe thead th {<br/> text-align: right;<br/>}<br/></code></pre><p>
5 rows × 31 columns
2.3 数据信息查看
## 通过 .info() 简要可以看到对应一些数据列名,以及NAN缺失信息
Train_data.info()
复制代码
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 150000 entries, 0 to 149999
Data columns (total 31 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 SaleID 150000 non-null int64
1 name 150000 non-null int64
2 regDate 150000 non-null int64
3 model 149999 non-null float64
4 brand 150000 non-null int64
5 bodyType 145494 non-null float64
6 fuelType 141320 non-null float64
7 gearbox 144019 non-null float64
8 power 150000 non-null int64
9 kilometer 150000 non-null float64
10 notRepairedDamage 150000 non-null object
11 regionCode 150000 non-null int64
12 seller 150000 non-null int64
13 offerType 150000 non-null int64
14 creatDate 150000 non-null int64
15 price 150000 non-null int64
16 v_0 150000 non-null float64
17 v_1 150000 non-null float64
18 v_2 150000 non-null float64
19 v_3 150000 non-null float64
20 v_4 150000 non-null float64
21 v_5 150000 non-null float64
22 v_6 150000 non-null float64
23 v_7 150000 non-null float64
24 v_8 150000 non-null float64
25 v_9 150000 non-null float64
26 v_10 150000 non-null float64
27 v_11 150000 non-null float64
28 v_12 150000 non-null float64
29 v_13 150000 non-null float64
30 v_14 150000 non-null float64
dtypes: float64(20), int64(10), object(1)
memory usage: 35.5+ MB
复制代码
## 通过 .columns 查看列名
Train_data.columns
复制代码
Index(['SaleID', 'name', 'regDate', 'model', 'brand', 'bodyType', 'fuelType',
'gearbox', 'power', 'kilometer', 'notRepairedDamage', 'regionCode',
'seller', 'offerType', 'creatDate', 'price', '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'],
dtype='object')
复制代码
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 50000 entries, 0 to 49999
Data columns (total 30 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 SaleID 50000 non-null int64
1 name 50000 non-null int64
2 regDate 50000 non-null int64
3 model 50000 non-null float64
4 brand 50000 non-null int64
5 bodyType 48587 non-null float64
6 fuelType 47107 non-null float64
7 gearbox 48090 non-null float64
8 power 50000 non-null int64
9 kilometer 50000 non-null float64
10 notRepairedDamage 50000 non-null object
11 regionCode 50000 non-null int64
12 seller 50000 non-null int64
13 offerType 50000 non-null int64
14 creatDate 50000 non-null int64
15 v_0 50000 non-null float64
16 v_1 50000 non-null float64
17 v_2 50000 non-null float64
18 v_3 50000 non-null float64
19 v_4 50000 non-null float64
20 v_5 50000 non-null float64
21 v_6 50000 non-null float64
22 v_7 50000 non-null float64
23 v_8 50000 non-null float64
24 v_9 50000 non-null float64
25 v_10 50000 non-null float64
26 v_11 50000 non-null float64
27 v_12 50000 non-null float64
28 v_13 50000 non-null float64
29 v_14 50000 non-null float64
dtypes: float64(20), int64(9), object(1)
memory usage: 11.4+ MB
复制代码
2.4 数据统计信息浏览
## 通过 .describe() 可以查看数值特征列的一些统计信息
Train_data.describe()
复制代码
.dataframe tbody tr th:only-of-type { vertical-align: middle; }<pre><code>.dataframe tbody tr th {<br/> vertical-align: top;<br/>}<br/><br/>.dataframe thead th {<br/> text-align: right;<br/>}<br/></code></pre><p>
8 rows × 30 columns
.dataframe tbody tr th:only-of-type { vertical-align: middle; }<pre><code>.dataframe tbody tr th {<br/> vertical-align: top;<br/>}<br/><br/>.dataframe thead th {<br/> text-align: right;<br/>}<br/></code></pre><p>
8 rows × 29 columns
3.数据分析
#### 1) 提取数值类型特征列名
numerical_cols = Train_data.select_dtypes(exclude = 'object').columns
print(numerical_cols)
复制代码
Index(['SaleID', 'name', 'regDate', 'model', 'brand', 'bodyType', 'fuelType',
'gearbox', 'power', 'kilometer', 'regionCode', 'seller', 'offerType',
'creatDate', 'price', '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'],
dtype='object')
复制代码
categorical_cols = Train_data.select_dtypes(include = 'object').columns
print(categorical_cols)
复制代码
Index(['notRepairedDamage'], dtype='object')
复制代码
#### 2) 构建训练和测试样本
## 选择特征列
feature_cols = [col for col in numerical_cols if col not in ['SaleID','name','regDate','creatDate','price','model','brand','regionCode','seller']]
feature_cols = [col for col in feature_cols if 'Type' not in col]
## 提前特征列,标签列构造训练样本和测试样本
X_data = Train_data[feature_cols]
Y_data = Train_data['price']
X_test = TestA_data[feature_cols]
print('X train shape:',X_data.shape)
print('X test shape:',X_test.shape)
复制代码
X train shape: (150000, 18)
X test shape: (50000, 18)
复制代码
## 定义了一个统计函数,方便后续信息统计
def Sta_inf(data):
print('_min',np.min(data))
print('_max:',np.max(data))
print('_mean',np.mean(data))
print('_ptp',np.ptp(data))
print('_std',np.std(data))
print('_var',np.var(data))
复制代码
#### 3) 统计标签的基本分布信息
print('Sta of label:')
Sta_inf(Y_data)
复制代码
Sta of label:
_min 11
_max: 99999
_mean 5923.327333333334
_ptp 99988
_std 7501.973469876635
_var 56279605.942732885
复制代码
## 绘制标签的统计图,查看标签分布
plt.hist(Y_data)
plt.show()
plt.close()
复制代码
#### 4) 缺省值用-1填补
X_data = X_data.fillna(-1)
X_test = X_test.fillna(-1)
复制代码
4. 模型训练与预测(特征工程、模型融合)
4.1 利用 xgb 进行五折交叉验证查看模型的参数效果
## xgb-Model
xgr = xgb.XGBRegressor(n_estimators=120, learning_rate=0.1, gamma=0, subsample=0.8,\
colsample_bytree=0.9, max_depth=7) #,objective ='reg:squarederror'
scores_train = []
scores = []
## 5折交叉验证方式
sk=StratifiedKFold(n_splits=5,shuffle=True,random_state=0)
for train_ind,val_ind in sk.split(X_data,Y_data):
train_x=X_data.iloc[train_ind].values
train_y=Y_data.iloc[train_ind]
val_x=X_data.iloc[val_ind].values
val_y=Y_data.iloc[val_ind]
xgr.fit(train_x,train_y)
pred_train_xgb=xgr.predict(train_x)
pred_xgb=xgr.predict(val_x)
score_train = mean_absolute_error(train_y,pred_train_xgb)
scores_train.append(score_train)
score = mean_absolute_error(val_y,pred_xgb)
scores.append(score)
print('Train mae:',np.mean(score_train))
print('Val mae',np.mean(scores))
复制代码
4.2 定义 xgb 和 lgb 模型函数
def build_model_xgb(x_train,y_train):
model = xgb.XGBRegressor(n_estimators=150, learning_rate=0.1, gamma=0, subsample=0.8,\
colsample_bytree=0.9, max_depth=7) #, objective ='reg:squarederror'
model.fit(x_train, y_train)
return model
def build_model_lgb(x_train,y_train):
estimator = lgb.LGBMRegressor(num_leaves=127,n_estimators = 150)
param_grid = {
'learning_rate': [0.01, 0.05, 0.1, 0.2],
}
gbm = GridSearchCV(estimator, param_grid)
gbm.fit(x_train, y_train)
return gbm
复制代码
4.3 切分数据集(Train,Val)进行模型训练,评价和预测
## Split data with val
x_train,x_val,y_train,y_val = train_test_split(X_data,Y_data,test_size=0.3)
复制代码
print('Train lgb...')
model_lgb = build_model_lgb(x_train,y_train)
val_lgb = model_lgb.predict(x_val)
MAE_lgb = mean_absolute_error(y_val,val_lgb)
print('MAE of val with lgb:',MAE_lgb)
print('Predict lgb...')
model_lgb_pre = build_model_lgb(X_data,Y_data)
subA_lgb = model_lgb_pre.predict(X_test)
print('Sta of Predict lgb:')
Sta_inf(subA_lgb)
复制代码
print('Train xgb...')
model_xgb = build_model_xgb(x_train,y_train)
val_xgb = model_xgb.predict(x_val)
MAE_xgb = mean_absolute_error(y_val,val_xgb)
print('MAE of val with xgb:',MAE_xgb)
print('Predict xgb...')
model_xgb_pre = build_model_xgb(X_data,Y_data)
subA_xgb = model_xgb_pre.predict(X_test)
print('Sta of Predict xgb:')
Sta_inf(subA_xgb)
复制代码
4.4 进行两模型的结果加权融合
## 这里我们采取了简单的加权融合的方式
val_Weighted = (1-MAE_lgb/(MAE_xgb+MAE_lgb))*val_lgb+(1-MAE_xgb/(MAE_xgb+MAE_lgb))*val_xgb
val_Weighted[val_Weighted<0]=10 # 由于我们发现预测的最小值有负数,而真实情况下,price为负是不存在的,由此我们进行对应的后修正
print('MAE of val with Weighted ensemble:',mean_absolute_error(y_val,val_Weighted))
复制代码
sub_Weighted = (1-MAE_lgb/(MAE_xgb+MAE_lgb))*subA_lgb+(1-MAE_xgb/(MAE_xgb+MAE_lgb))*subA_xgb
## 查看预测值的统计进行
plt.hist(Y_data)
plt.show()
plt.close()
复制代码
4.5.输出结果
sub = pd.DataFrame()
sub['SaleID'] = TestA_data.SaleID
sub['price'] = sub_Weighted
sub.to_csv('./sub_Weighted.csv',index=False)
复制代码
5. 项目详细展开
因篇幅内容限制,将原学习项目拆解成多个 notebook 方便学习,只需一键 fork。
5.1 数据分析详解
载入各种数据科学以及可视化库:
数据科学库 pandas、numpy、scipy;
可视化库 matplotlib、seabon;
其他;
载入数据:
载入训练集和测试集;
简略观察数据(head()+shape);
数据总览:
通过 describe()来熟悉数据的相关统计量
通过 info()来熟悉数据类型
判断数据缺失和异常
查看每列的存在 nan 情况
异常值检测
了解预测值的分布
总体分布概况(无界约翰逊分布等)
查看 skewness and kurtosis
查看预测值的具体频数
特征分为类别特征和数字特征,并对类别特征查看 unique 分布
数字特征分析
相关性分析
查看几个特征得 偏度和峰值
每个数字特征得分布可视化
数字特征相互之间的关系可视化
多变量互相回归关系可视化
类型特征分析
unique 分布
类别特征箱形图可视化
类别特征的小提琴图可视化
类别特征的柱形图可视化类别
特征的每个类别频数可视化(count_plot)
用 pandas_profiling 生成数据报告
5.2 特征工程
异常处理:
通过箱线图(或 3-Sigma)分析删除异常值;
BOX-COX 转换(处理有偏分布);
长尾截断;
特征归一化/标准化:
标准化(转换为标准正态分布);
归一化(抓换到 [0,1] 区间);
针对幂律分布,可以采用公式: log(1+median1+x)
数据分桶:
等频分桶;
等距分桶;
Best-KS 分桶(类似利用基尼指数进行二分类);
卡方分桶;
缺失值处理:
不处理(针对类似 XGBoost 等树模型);
删除(缺失数据太多);
插值补全,包括均值/中位数/众数/建模预测/多重插补/压缩感知补全/矩阵补全等;
分箱,缺失值一个箱;
特征构造:
构造统计量特征,报告计数、求和、比例、标准差等;
时间特征,包括相对时间和绝对时间,节假日,双休日等;
地理信息,包括分箱,分布编码等方法;
非线性变换,包括 log/ 平方/ 根号等;
特征组合,特征交叉;
仁者见仁,智者见智。
特征筛选
过滤式(filter):先对数据进行特征选择,然后在训练学习器,常见的方法有 Relief/方差选择发/相关系数法/卡方检验法/互信息法;
包裹式(wrapper):直接把最终将要使用的学习器的性能作为特征子集的评价准则,常见方法有 LVM(Las Vegas Wrapper) ;
嵌入式(embedding):结合过滤式和包裹式,学习器训练过程中自动进行了特征选择,常见的有 lasso 回归;
降维
PCA/ LDA/ ICA;
特征选择也是一种降维。
5.3 模型优化
线性回归模型:
线性回归对于特征的要求;
处理长尾分布;
理解线性回归模型;
模型性能验证:
评价函数与目标函数;
交叉验证方法;
留一验证方法;
针对时间序列问题的验证;
绘制学习率曲线;
绘制验证曲线;
嵌入式特征选择:
Lasso 回归;
Ridge 回归;
决策树;
模型对比:
常用线性模型;
常用非线性模型;
模型调参:
贪心调参方法;
网格调参方法;
贝叶斯调参方法;
5.4 模型融合
简单加权融合:
回归(分类概率):算术平均融合(Arithmetic mean),几何平均融合(Geometric mean);
分类:投票(Voting)
综合:排序融合(Rank averaging),log 融合
stacking/blending:
构建多层模型,并利用预测结果再拟合预测。
boosting/bagging(在 xgboost,Adaboost,GBDT 中已经用到):
多树的提升方法
训练:
预测:
6.总结
二手车预测项目是非常经典项目,数据挖掘实践(二手车价格预测)的内容来自 Datawhale 与天池联合发起的,现在通过整理和调整让更多对机器学习感兴趣可以上手实战一下
因篇幅内容限制,将原学习项目拆解成多个 notebook 方便学习,只需一键 fork。
项目链接:
一键 fork 直接运行,所有项目码源都在里面
https://www.heywhale.com/mw/project/64367e0a2a3d6dc93d22054f
机器学习数据挖掘专栏:https://www.heywhale.com/home/column/64141d6b1c8c8b518ba97dcc
参考链接:
https://github.com/datawhalechina/team-learning-data-mining/tree/master/SecondHandCarPriceForecast
评论