拟合模型
Liner Regression-线性回归
f
(
x
)
=
w
x
+
b
f(x) = wx+b
f(x)=wx+b
这是对输入和输出一个最简单的拟合,认为输入和输出之间是一个线性关系,每个输入分量在输出结果中起到一定比例的作用。
其中需要拟合的参数有
w
,
b
w,b
w,b,目前有很多方法解决这个问题,比如最小二乘法,梯度下降法等等
但需要注意的是,线性回归模型有5个前提假设
- 线性性 & 可加性
- 误差项(ε)之间应相互独立
- 自变量(X1,X2)之间应相互独立
- 误差项(ε)的方差应为常数
- 误差项(ε)应呈正态分布
尤其是最后一条,在一些情况下是不满足的,需要对输出数据进行一些变形。
决策树模型
GBDT模型
XGBoost模型
LightGBM模型
这里直接附上python调用sklearn进行拟合的代码
from sklearn.linear_model import LinearRegression
from sklearn.svm import SVC
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import RandomForestRegressor
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.neural_network import MLPRegressor
from xgboost.sklearn import XGBRegressor
from lightgbm.sklearn import LGBMRegressor
models = [LinearRegression(),
DecisionTreeRegressor(),
RandomForestRegressor(),
GradientBoostingRegressor(),
MLPRegressor(solver='lbfgs', max_iter=100),
XGBRegressor(n_estimators = 100, objective='reg:squarederror'),
LGBMRegressor(n_estimators = 100)]
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] = scores
print(model_name + ' is finished')
嵌入式特征选择
嵌入式特征选择也就是不人工干预特征的选取,在优化时加入关于特征的选择项,让算法自己选择使用哪些特征。
最常见的是在线性回归模型里加入的系数惩罚项,L1正则化与L2正则化
在将线性回归看为一个优化问题,目标是最小化 ∑ ( y i − ( w x i + b ) ) 2 \sum (y_i-(wx_i+b))^2 ∑(yi−(wxi+b))2
而对于lasso回归和岭回归,它们在这个能量函数里加入了对系数的惩罚项
lasso要求 ∑ ( y i − ( w x i + b ) ) 2 + α ∣ ∣ w ∣ ∣ 1 1 \sum (y_i-(wx_i+b))^2 + \alpha ||w||_1^1 ∑(yi−(wxi+b))2+α∣∣w∣∣11
岭回归要求 ∑ ( y i − ( w x i + b ) ) 2 + α ∣ ∣ w ∣ ∣ 2 2 \sum (y_i-(wx_i+b))^2 + \alpha ||w||_2^2 ∑(yi−(wxi+b))2+α∣∣w∣∣22
这也就是所说的L1、L2正则化
可以在直观的来看,这两个能量函数都要求模型的系数尽可能的小,而在数学上可以证明,Lasso会是模型系数变得稀疏。一个直观的解释如图
这里正方形就是L1模下的单位圆
from sklearn.linear_model import LinearRegression
from sklearn.linear_model import Ridge
from sklearn.linear_model import Lasso
models = [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] = scores
print(model_name + ' is finished')
线性回归的系数如下
岭回归的系数如下
L2正则化在拟合过程中通常都倾向于让权值尽可能小,最后构造一个所有参数都比较小的模型。因为一般认为参数值小的模型比较简单,能适应不同的数据集,也在一定程度上避免了过拟合现象。可以设想一下对于一个线性回归方程,若参数很大,那么只要数据偏移一点点,就会对结果造成很大的影响;但如果参数足够小,数据偏移得多一点也不会对结果造成什么影响,专业一点的说法是『抗扰动能力强』
Lasso回归的系数如下
L1正则化有助于生成一个稀疏权值矩阵,进而可以用于特征选择。如下图,我们发现power与userd_time特征非常重要。
模型性能验证
模型的性能验证和过拟合问题是紧密联系在一起的。
过拟合一个直观的解释就是模型忽视了数据的规律,将训练数据背了下来。
在模型训练里测试集的作用就是检测最后的结果达到什么效果,在训练集上效果很好是不是因为过拟合了
但一个致命的问题是,理论上测试集要与训练集无关。假设训练了一个模型,在训练集上结果很好,高高兴兴地去测试集上跑一跑,发现结果不好,于是修改模型参数啦什么的,得到一个新的结果,继续去测试集上试一下,发现结果还不好,你继续去修改模型。如此循环往复,得到了一个很好的结果,试问选择模型是不是过拟合了?
我认为这也算一种过拟合,因为在训练时利用了测试集的信息(结果不好)。
于是一个直观的想法是再分一个集合出来,用来检测模型是不是过拟合了,这就是验证集。
在使用训练集对参数进行训练的时候,经常会发现人们通常会将一整个训练集分为三个部分(比如mnist手写训练集)。一般分为:训练集(train_set),评估集(valid_set),测试集(test_set)这三个部分。这其实是为了保证训练效果而特意设置的。其中测试集很好理解,其实就是完全不参与训练的数据,仅仅用来观测测试效果的数据。而训练集和评估集则牵涉到下面的知识了。
因为在实际的训练中,训练的结果对于训练集的拟合程度通常还是挺好的(初始条件敏感),但是对于训练集之外的数据的拟合程度通常就不那么令人满意了。因此我们通常并不会把所有的数据集都拿来训练,而是分出一部分来(这一部分不参加训练)对训练集生成的参数进行测试,相对客观的判断这些参数对训练集之外的数据的符合程度。这种思想就称为交叉验证(Cross Validation)
from sklearn.model_selection import cross_val_score
from sklearn.metrics import mean_absolute_error, make_scorer
def log_transfer(func):
def wrapper(y, yhat):
result = func(np.log(y), np.nan_to_num(np.log(yhat)))
return result
return wrapper
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))
scores = pd.DataFrame(scores.reshape(1,-1))
scores.columns = ['cv' + str(x) for x in range(1, 6)]
scores.index = ['MAE']
scores
模型调参
贪心调参方法
贪心法也就是将一个多步问题一步步求解,每次找一个当前最优解。在调参时就是每次固定其他地方,调整一个参数,调整到当前最优后固定这个参数,继续调整下一个参数
best_obj = dict()
for obj in objective:
model = LGBMRegressor(objective=obj)
score = np.mean(cross_val_score(model, X=train_X, y=train_y_ln, verbose=0, cv = 5, scoring=make_scorer(mean_absolute_error)))
best_obj[obj] = score
best_leaves = dict()
for leaves in num_leaves:
model = LGBMRegressor(objective=min(best_obj.items(), key=lambda x:x[1])[0], num_leaves=leaves)
score = np.mean(cross_val_score(model, X=train_X, y=train_y_ln, verbose=0, cv = 5, scoring=make_scorer(mean_absolute_error)))
best_leaves[leaves] = score
best_depth = dict()
for depth in max_depth:
model = LGBMRegressor(objective=min(best_obj.items(), key=lambda x:x[1])[0],
num_leaves=min(best_leaves.items(), key=lambda x:x[1])[0],
max_depth=depth)
score = np.mean(cross_val_score(model, X=train_X, y=train_y_ln, verbose=0, cv = 5, scoring=make_scorer(mean_absolute_error)))
best_depth[depth] = score
sns.lineplot(x=['0_initial','1_turning_obj','2_turning_leaves','3_turning_depth'], y=[0.143 ,min(best_obj.values()), min(best_leaves.values()), min(best_depth.values())])
网格调参方法
其实网格调参法就是个枚举,将参数域划分为网格,尝试枚举每个格点参数的模型优劣。
from sklearn.model_selection import GridSearchCV
parameters = {'objective': objective , 'num_leaves': num_leaves, 'max_depth': max_depth}
model = LGBMRegressor()
clf = GridSearchCV(model, parameters, cv=5)
clf = clf.fit(train_X, train_y)
clf.best_params_
{'max_depth': 15, 'num_leaves': 55, 'objective': 'regression'}
model = LGBMRegressor(objective='regression',
num_leaves=55,
max_depth=15)
np.mean(cross_val_score(model, X=train_X, y=train_y_ln, verbose=0, cv = 5, scoring=make_scorer(mean_absolute_error)))
贝叶斯调参方法
贝叶斯方法也是在参数域里寻找一个最优解,但网格法和随机法是漫无目的的寻找,贝叶斯方法是根据之前试过的参数配置估计可能出现优质解的位置,有目的试验参数。
from bayes_opt import BayesianOptimization
def rf_cv(num_leaves, max_depth, subsample, min_child_samples):
val = cross_val_score(
LGBMRegressor(objective = 'regression_l1',
num_leaves=int(num_leaves),
max_depth=int(max_depth),
subsample = subsample,
min_child_samples = int(min_child_samples)
),
X=train_X, y=train_y_ln, verbose=0, cv = 5, scoring=make_scorer(mean_absolute_error)
).mean()
return 1 - val
rf_bo = BayesianOptimization(
rf_cv,
{
'num_leaves': (2, 100),
'max_depth': (2, 100),
'subsample': (0.1, 1),
'min_child_samples' : (2, 100)
}
)
rf_bo.maximize()
| iter | target | max_depth | min_ch... | num_le... | subsample |
-------------------------------------------------------------------------
| 1 | 0.8591 | 45.17 | 28.53 | 34.28 | 0.5116 |
| 2 | 0.8662 | 72.17 | 94.75 | 68.84 | 0.6485 |
| 3 | 0.863 | 8.271 | 88.89 | 55.83 | 0.5158 |
| 4 | 0.8651 | 8.242 | 46.95 | 78.31 | 0.2923 |
| 5 | 0.8676 | 57.41 | 62.01 | 79.51 | 0.9199 |
| 6 | 0.8692 | 97.09 | 96.4 | 98.48 | 0.1618 |
| 7 | 0.869 | 90.15 | 2.932 | 99.83 | 0.9159 |
| 8 | 0.8695 | 91.79 | 96.49 | 99.6 | 0.8113 |
| 9 | 0.8504 | 5.098 | 87.54 | 99.91 | 0.4858 |
| 10 | 0.7719 | 2.276 | 90.83 | 2.803 | 0.8641 |
| 11 | 0.869 | 95.79 | 99.28 | 96.3 | 0.5634 |
| 12 | 0.8555 | 98.19 | 3.927 | 26.92 | 0.8837 |
| 13 | 0.8063 | 2.775 | 2.932 | 43.19 | 0.8099 |
| 14 | 0.7719 | 99.81 | 99.21 | 2.276 | 0.9053 |
| 15 | 0.8669 | 98.83 | 31.76 | 72.88 | 0.8974 |
| 16 | 0.867 | 64.5 | 2.082 | 73.23 | 0.2481 |
| 17 | 0.8667 | 97.24 | 4.207 | 70.31 | 0.8119 |
| 18 | 0.8507 | 5.837 | 2.611 | 99.76 | 0.4554 |
| 19 | 0.8669 | 33.84 | 97.23 | 74.94 | 0.1564 |
| 20 | 0.8644 | 38.38 | 66.84 | 54.39 | 0.9654 |
| 21 | 0.8693 | 45.07 | 23.99 | 98.81 | 0.3903 |
| 22 | 0.8694 | 98.17 | 53.53 | 99.11 | 0.3457 |
| 23 | 0.865 | 72.71 | 32.72 | 59.46 | 0.1247 |
| 24 | 0.8673 | 41.96 | 22.85 | 75.48 | 0.9121 |
| 25 | 0.8681 | 97.57 | 76.33 | 81.99 | 0.677 |
| 26 | 0.8691 | 80.88 | 22.38 | 97.99 | 0.9399 |
| 27 | 0.869 | 58.01 | 3.743 | 99.88 | 0.918 |
| 28 | 0.8685 | 68.81 | 98.43 | 89.11 | 0.1334 |
| 29 | 0.867 | 21.72 | 70.88 | 74.84 | 0.1054 |
| 30 | 0.8691 | 99.92 | 15.64 | 99.21 | 0.872 |
=========================================================================
小结
在本章中,我们完成了建模与调参的工作,并对我们的模型进行了验证。此外,我们还采用了一些基本方法来提高预测的精度,提升如下图所示。
plt.figure(figsize=(13,5))
sns.lineplot(x=['0_origin','1_log_transfer','2_L1_&_L2','3_change_model','4_parameter_turning'], y=[1.36 ,0.19, 0.19, 0.14, 0.13])