前言
居然又拖到了最后一天才开始打卡,。果然 ddl 是第一生产力。
放上这次的教程链接:Datawhale 零基础入门数据挖掘-Task4 建模调参
看标题就知道这次的这次的内容是建模和调参。虽然说是零基础入门系列,但是这次的教程对真的零基础的人来讲并不是很友好,还是需要很多前置知识的。主要是机器学习模型方面的教程,教程里给出了几篇作者写的文章。个人感觉需要一定门槛,如果看不懂建议参考其他的。
这次打卡还是按照教程走一遍,先梳理一下主要内容的结构。这次任务主体分为五大部分:
- 线性回归模型
- 模型验证
- 嵌入式特征选择(上一次任务没讲的)
- 模型对比
- 模型调参
开始之前,先把数据准备好。
还是依旧要先读取数据,不过这次任务的读取数据有两点不同:
- 读取的数据是上一次任务中做特征工程时生成的,没有的话需要运行一下 task3 的代码生成一下
- 教程中作者写了一个
reduce_mem_usage()
函数。通过转换数据的存储类型来达到节约内存空间的目的。
代码就不全粘贴了,没什么可说的,后面需要用到的变量是 sample_feature
和 continuous_feature_names
sample_feature = reduce_mem_usage(pd.read_csv('data_for_tree.csv'))
continuous_feature_names = [x for x in sample_feature.columns if x not in ['price','brand','model','brand']]
需要根据所选特征构造训练集。
# 特征处理,删除空的数据并重置数据下标和类型
sample_feature = sample_feature.dropna().replace('-', 0).reset_index(drop=True)
sample_feature['notRepairedDamage'] = sample_feature['notRepairedDamage'].astype(np.float32)
# 构造训练数据
train = sample_feature[continuous_feature_names + ['price']]
train_X = train[continuous_feature_names]
train_y = train['price']
线性回归模型
线性回归模型就是简单的线性关系,类似于 a 1 x 1 + a 2 x 2 + a 3 x 3 + . . . + a n x n + b = y a_1x_1 + a_2x_2 + a_3x_3 + ... + a_nx_n + b = y a1?x1?+a2?x2?+a3?x3?+...+an?xn?+b=y
就是说,每个自变量 x i x_i xi? 的单位变化都会导致因变量 y y y 的成比例的变化( a i x i a_ix_i ai?xi?)
这里使用的是 sklearn
库下的 LinearRegression
函数。
# 简单的线性建模
from sklearn.linear_model import LinearRegression
model = LinearRegression(normalize=True)
model = model.fit(train_X, train_y)
print('intercept:'+ str(model.intercept_))
print(sorted(dict(zip(continuous_feature_names, model.coef_)).items(), key=lambda x:x[1], reverse=True))
运行结果:
intercept:-111820.66151155639
[(‘v_6’, 3372669.6439296836), (‘v_8’, 701432.2110340319), (‘v_9’, 169509.42711357534), (‘v_7’, 32757.63135064817), (‘v_12’, 23807.649529600818), (‘v_3’, 19739.216689565477), (‘v_11’, 13163.940983386958), (‘v_13’, 11963.46498465866), (‘v_10’, 3659.757920045777), (‘gearbox’, 878.481625388411), (‘fuelType’, 372.24870228828286), (‘bodyType’, 185.9175590029288), (‘city’, 46.98275955422143), (‘power’, 30.882991521559063), (‘brand_price_median’, 0.46125248465967494), (‘brand_amount’, 0.14337558626062372), (‘brand_price_std’, 0.13126146237474134), (‘brand_price_max’, 0.01264373865111078), (‘used_time’, 0.0006779416045904323), (‘SaleID’, 5.0161735988244534e-05), (‘train’, 3.982800990343094e-06), (‘seller’, -5.443580448627472e-06), (‘offerType’, -6.388872861862183e-06), (‘brand_price_sum’, -1.969811652902229e-05), (‘name’, -0.00024808160266330667), (‘brand_price_average’, -0.22234169615526606), (‘brand_price_min’, -1.9643720815997738),
(‘power_bin’, -56.46399500590243), (‘v_14’, -340.80766284241344), (‘kilometer’, -372.8800824482724), (‘notRepairedDamage’, -490.61611815679447), (‘v_0’, -2054.722036477167), (‘v_5’, -4343.72864602462), (‘v_4’, -15543.94705998868), (‘v_2’, -29430.03630720955), (‘v_1’, -45074.18455513423)]
这里我得到的结果跟教程稍微不同,猜测特征工程部分做的操作不完全一样。
可视化可以发现,预测目标拟合得并不好。由于很多模型都假设预测目标是正态分布的,因此后面对预测的目标 price
做了一个 log
操作。可视化后发现效果还不错。
对 v_9
单一特征进行预测,不取 log
和 取 log
的对比如下:
模型验证
五折交叉验证
对模型进行五折交叉验证,这里需要说明的是 cross_val_score
函数,它可以将数据分为训练集和测试集,并返回每次验证时模型的得分情况。
# 交叉验证
from sklearn.model_selection import cross_val_score
from sklearn.metrics import mean_absolute_error, make_scorerdef log_transfer(func):def wrapper(y, yhat):result = func(np.log(y), np.nan_to_num(np.log(yhat)))return resultreturn wrapper
# 使用线性模型对未经过 log 变换的数据进行预测
scores = cross_val_score(model, X=train_X, y=train_y_org, verbose=1, cv = 5, scoring=make_scorer(log_transfer(mean_absolute_error)))
print('AVG-org:', np.mean(scores))
# 使用线性模型对 log 变换后的数据进行预测
scores = cross_val_score(model, X=train_X, y=train_y, verbose=1, cv = 5, scoring=make_scorer(log_transfer(mean_absolute_error)))
print('AVG:', np.mean(scores))
运行结果如下:
[Parallel(n_jobs=1)]: Using backend SequentialBackend with 1 concurrent workers.
[Parallel(n_jobs=1)]: Done 5 out of 5 | elapsed: 1.1s finished
AVG-org: 1.3684950216832068
[Parallel(n_jobs=1)]: Using backend SequentialBackend with 1 concurrent workers.
[Parallel(n_jobs=1)]: Done 5 out of 5 | elapsed: 0.7s finished
AVG: 0.02487489588467453
可以看到,做 log 处理后,模型的效果提升了两个数量级。
模拟真实环境
需要说明的一点是,有时数据是存在时序性的,我们不能拥有上帝视角预测未来的数据。因此划分验证集时,需要考虑数据的时序性,模拟真实环境。即前一部分是训练集,后一部分是验证集,验证集后不应再有训练集。这是作者当时直播强调的一点,不过在这里,并没有什么影响。
绘制学习率曲线
这里利用了教程里的 plot_learning_curve
函数来绘制,其中最主要的是调用了 learning_curve
来获得测试和训练时的得分。
plot_learning_curve(LinearRegression(), 'Liner_model', train_X[:1000], train_y[:1000], ylim=(0.0, 0.5), cv=5, n_jobs=1)
plt.show()
运行结果:
嵌入式特征选择
上一次任务中讲的,特征选择一共有三种。前两种是过滤式和包裹式,他们在做特征选择时,可以明显的与训练过程分离开。但嵌入式特征选择是边训练便选择,典型的方法是L1正则化与L2正则化。(以前只知道L1、L2正则化,但并不知道这是属于嵌入式特征选择,。孤陋寡闻了)
线性回归模型加入两种正则化方法后,就分别变成了岭回归与Lasso回归。
代码粘贴一下:
from sklearn.linear_model import LinearRegression
from sklearn.linear_model import Ridge
from sklearn.linear_model import Lassomodels = [LinearRegression(),Ridge(),Lasso()]result = dict()
for model in models:model_name = str(model).split('(')[0]scores = cross_val_score(model, X=train_X, y=train_y_ln, verbose=0, cv = 5, scoring=make_scorer(mean_absolute_error))result[model_name] = scoresprint(model_name + ' is finished')result = pd.DataFrame(result)
result.index = ['cv' + str(x) for x in range(1, 6)]
print(result)
运行结果:
LinearRegression Ridge Lasso
cv1 0.190851 0.194948 0.383810
cv2 0.193835 0.197714 0.382179
cv3 0.194196 0.198252 0.383611
cv4 0.191812 0.195694 0.380331
cv5 0.195868 0.199817 0.383783
这里简单介绍一下 L1 和 L2 的区别,就不粘贴教程里的可视化代码了。
L1 的惩罚项是权值的绝对值之和,因此学到的权值参数小的会尽可能趋向于0,大的会尽可能变大,最后会得到一个稀疏矩阵,0 很多;L2 的惩罚项是权值的平方和,因此权值的绝对值会尽可能小,这样平方求和后的值就会变小,最后得到一个所有权值都向 0 靠近的参数矩阵。
模型对比
这部分教程介绍的比较少,主要是做了一个简单模型性能的对比,包括线性模型、决策树模型、随机森林、梯度提升树、多层感知机、XGBoost 和 LGBM。
运行教程代码的结果:
可以明显看出来,随机森林、XGBoost 和 LGBM的效果比较好。
模型调参
主要有三种方式:贪心、网格和贝叶斯。
需要提前设定好可能的参数集合。
这部分代码就不粘贴了,主要记录一下我的理解。
贪心调参
主要思路是遍历每一个参数,使用其中效果最好的参数继续训练模型,搜索下一个参数。
网格搜索
教程里使用的是 sklearn.model_selection
库里的 GridSearchCV
函数。网格调参是一种穷举搜索方法,遍历所有的参数组合,选出最好的那一组。
贝叶斯调参
教程中使用的是 BayesianOptimization
函数来做的。
贝叶斯调参的方法是一种自动调参的方式。前两种调参的方法运行效率比较低,运行时间很慢。贝叶斯的方法在于,他会建立一个概率模型,基于以前的评估结果,来最小化这个模型值。简而言之,在尝试下一组参数时,会参考以前的参数结果,避免做无用功。
最后
不得不说,还是高估了自己的自控力,拖延症真的得治呀。
不过这次学习还是学到了很多东西,虽然很多东西没有深入了解。最让人兴奋的还是大佬的各种可视化操作,看着各种数据被各种可视化出来,感觉好TM神奇。
好好学习,天天向上。又是瞎捅咕的一天,没干毕设的事内心还是有点愧疚。