本篇文章详细阐述机器学习模型评估和参数调优。将主要围绕两个问题来阐述:
- “知其所以然”:当你选择的一个机器学习模型运行时,你要知道它是如何工作的;
- “青出于蓝”:更进一步,你得知道如何让此机器学习模型工作的更优。
模型评估的方法
一般情况来说,F1评分或者R平方(R-Squared value)等数值评分可以告诉我们训练的机器学习模型的好坏。也有其它许多度量方式来评估拟合模型。
你应该猜出来,我将提出使用可视化的方法结合数值评分来更直观的评判机器学习模型。接下来的几个部分将分享一些有用的工具。
首先想声明的,单单一个评分或者一条线,是无法完全评估一个机器学习模型。偏离真实场景来评估机器学习模型(’good’ or ‘bad’)都是“耍流氓”。某个机器学习模型若可“驾驭”小样本数据集生成最多预测模型(即,命中更多预测数据集)。如果一个拟合模型比其它拟合过的模型形式或者你昨天的预测模型能够得到更好的结果,那即是好(’good’)。
下面是一些标准指标: confusion_matrix,mean_squared_error, r2_score,这些可以用来评判分类器或者回归的好坏。表格中给出的是Scikit-Learn中的函数以及描述:
评估分类模型:
指标 | 描述 | Scikit-learn函数 |
---|
Precision | 精准度 | from sklearn.metrics import precision_score |
Recall | 召回率 | from sklearn.metrics import recall_score |
F1 | F1值 | from sklearn.metrics import f1_score |
Confusion Matrix | 混淆矩阵 | from sklearn.metrics import confusion_matrix |
ROC | ROC曲线 | from sklearn.metrics import roc |
AUC | ROC曲线下的面积 | from sklearn.metrics import auc |
评估回归模型:
指标 | 描述 | Scikit-learn函数 |
---|
Mean Square Error (MSE, RMSE) | 平均方差 | from sklearn.metrics import mean_squared_error |
Absolute Error (MAE, RAE) | 绝对误差 | from sklearn.metrics import mean_absolute_error, median_absolute_error |
R-Squared | R平方值 | from sklearn.metrics import r2_score |
下面开始使用Scikit-Learn的可视化工具来更直观的展现模型的好坏。
评估分类模型
我们评估分类器是判断预测值时否很好的与实际标记值相匹配。正确的鉴别出正样本(True Positives)或者负样本(True Negatives)都是True。同理,错误的判断正样本(False Positive,即一类错误)或者负样本(False Negative,即二类错误)。
注意:True和False是对于评价预测结果而言,也就是评价预测结果是正确的(True)还是错误的(False)。而Positive和Negative则是样本分类的标记。
通常,我们希望通过一些参数来告知模型评估如何。为此,我们使用混淆矩阵。
混淆矩阵
![](https://www.52ml.net/wp-content/uploads/2016/10/mlsummary.1.png)
幸运的是,Scikit-Learn提供内建函数(sklearn.metrics.confusion_matrix)来计算混淆矩阵。输入数据集实际值和模型预测值作为参数,输出即为混淆矩阵,结果类似这样:
分类报告
分类报告除了包括混淆矩阵,也增加了其它优势,比如,混淆矩阵会标示样例是否被正确鉴别,同时也提供precision,recall和 F1 值三种评估指标。
-
from sklearn.
metrics
import classification_report
-
print
(classification_report
(y_true
, y_pred
, target_names
=target_names
)
)
|
更进一步,可以对Scikit-Learn的内建函数做些加强,比如,使用带颜色区分的热力图,它将帮助我们的眼睛更容易的辨别预测成功(橘黄色)和失败(灰色)。代码如下:
-
from matplotlib
import colors
-
from matplotlib.
colors
import ListedColormap
-
ddl_heat
=
[
'#DBDBDB'
,
'#DCD5CC'
,
'#DCCEBE'
,
'#DDC8AF'
,
'#DEC2A0'
,
'#DEBB91'
,\
-
'#DFB583'
,
'#DFAE74'
,
'#E0A865'
,
'#E1A256'
,
'#E19B48'
,
'#E29539'
]
-
ddlheatmap
= colors.
ListedColormap
(ddl_heat
)
-
def
plot_classification_report
(
cr
,
title
=
None
,
cmap
=
ddlheatmap
)
:
-
title
= title
or
'Classification report'
-
lines
= cr.
split
(
'\n'
)
-
-
-
for line
in lines
[
2:
(
len
(lines
)
-
3
)
]:
-
-
classes.
append
(s
[
0
]
)
-
value
=
[
float
(x
)
for x
in s
[
1:
len
(s
) -
1
]
]
-
-
fig
, ax
= plt.
subplots
(
1
)
-
for column
in
range
(
len
(matrix
)
1
):
-
for row
in
range
(
len
(classes
)
):
-
txt
= matrix
[row
]
[column
]
-
ax.
text
(column
,row
,matrix
[row
]
[column
]
,va
=
'center'
,ha
=
'center'
)
-
fig
= plt.
imshow
(matrix
, interpolation
=
'nearest'
, cmap
=cmap
)
-
-
-
x_tick_marks
= np.
arange
(
len
(classes
)
1
)
-
y_tick_marks
= np.
arange
(
len
(classes
)
)
-
plt.
xticks
(x_tick_marks
,
[
'precision'
,
'recall'
,
'f1-score'
]
, rotation
=
45
)
-
plt.
yticks
(y_tick_marks
, classes
)
-
plt.
ylabel
(
'Classes'
)
-
plt.
xlabel
(
'Measures'
)
-
-
cr
= classification_report
(y_true
, y_pred
)
-
plot_classification_report
(cr
)
|
![](https://www.52ml.net/wp-content/uploads/2016/10/mlsummary.2.png)
看起来挺容易,对不?发现分类热力图的另外一个好处,它可以让我们看出一类错误 VS 二类错误。但有一个缺陷,它并不能垮模型进行比较,而这对评估拟合模型是相当重要的。因为这个原因,接下来将使用第二篇文章中的classify和regress代码。
下面的get_preds函数将输出一个实际标记值和预测值的二元组,这个二元组将会使得后续的跨模型的可视化比较变得容易:
-
def
get_preds
(
attributes
,
targets
,
model
)
:
-
-
Executes classification or regression using the specified model
-
and returns expected and predicted values.
-
Useful for comparison plotting!
-
-
splits
= cv.
train_test_split
(attributes
, targets
, test_size
=
0.2
)
-
X_train
, X_test
, y_train
, y_test
= splits
-
model.
fit
(X_train
, y_train
)
-
-
y_pred
= model.
predict
(X_test
)
-
|
ROC曲线
另一种评估分类模型的方法是ROC(Receiver Operating Characteristic)曲线。我们能从Scikit-Learn 指标模块中import roc_curve,计算 true positive率和false positive 率的数值。我们也可以画出ROC曲线来权衡模型的敏感性和特异性。
下面的代码将画出ROC,Y轴代表true positive率,X轴代表false positive 率。同时,我们也可以增加同时比较两种不同的拟合模型,这里看到的是 KNeighborsClassifier 分类器远胜 LinearSVC 分类器:
-
def
roc_compare_two
(
y
,
yhats
,
models
)
:
-
f
,
(ax1
, ax2
)
= plt.
subplots
(
1
,
2
, sharey
=
True
)
-
for yhat
, m
, ax
in
(
(yhats
[
0
]
, models
[
0
]
, ax1
)
,
(yhats
[
1
]
, models
[
1
]
, ax2
)
):
-
false_positive_rate
, true_positive_rate
, thresholds
= roc_curve
(y
,yhat
)
-
roc_auc
= auc
(false_positive_rate
, true_positive_rate
)
-
ax.
set_title
(
'ROC for %s' % m
)
-
ax.
plot
(false_positive_rate
, true_positive_rate
, \
-
c
=
'#2B94E9'
, label
=
'AUC = %0.2f'% roc_auc
)
-
ax.
legend
(loc
=
'lower right'
)
-
ax.
plot
(
[
0
,
1
]
,
[
0
,
1
]
,
'm--'
,c
=
'#666666'
)
-
-
plt.
ylim
(
[
0
,
1.1
]
)
-
-
y_true_svc
, y_pred_svc
= get_preds
(stdfeatures
, labels
, LinearSVC
(
)
)
-
y_true_knn
, y_pred_knn
= get_preds
(stdfeatures
, labels
, KNeighborsClassifier
(
)
)
-
actuals
= np.
array
(
[y_true_svc
,y_true_knn
]
)
-
predictions
= np.
array
(
[y_pred_svc
,y_pred_knn
]
)
-
models
=
[
'LinearSVC'
,
'KNeighborsClassifier'
]
-
roc_compare_two
(actuals
, predictions
, models
)
|
![](https://www.52ml.net/wp-content/uploads/2016/10/mlsummary.3.png)
在ROC空间,ROC曲线越凸向左上方向效果越好;越靠近对角线,分类器越趋向于随机分类器。
同时,我们也会计算曲线下的面积(AUC),可以结合上图。如果AUC的值达到0.80,那说明分类器分类非常准确;如果AUC值在0.60~0.80之间,那分类器还算好,但是我们调调参数可能会得到更好的性能;如果AUC值小于0.60,那就惨不忍睹了,你得好好分析下咯。
评估回归模型
对于混凝土数据集试验一些不同的机器学习模型,然后评判哪种更好。在第二篇文章中,我们使用的平均方差和 R 平方值,比如:
-
Mean squared error
=
116.268
-
|
这些数值是有用的,特别是对不同的拟合模型比较平均方差和 R 平方值。但是,这是不够的,它不能告诉我们为什么一个模型远胜于另外一个;也不能告诉我们如何对模型调参数提高评分。接下来,我们将看到两种可视化的评估技术来帮助诊断模型有效性:预测错误曲线 和 残差曲线。
预测错误曲线
为了知道我们的模型预测值与期望值到底有多接近,我们将拿混凝土数据集(混凝土强度)做例子,画出其期望值和模型预测值曲线。下面是不同回归模型的错误曲线:Ridge, SVR 和RANSACRegressor。
-
def
error_compare_three
(
mods
,
X
,
y
)
:
-
f
,
(ax1
, ax2
, ax3
)
= plt.
subplots
(
3
, sharex
=
True
, sharey
=
True
)
-
for mod
, ax
in
(
(mods
[
0
]
, ax1
)
,
(mods
[
1
]
, ax2
)
,
(mods
[
2
]
, ax3
)
):
-
predicted
= cv.
cross_val_predict
(mod
[
0
]
, X
, y
, cv
=
12
)
-
ax.
scatter
(y
, predicted
, c
=
'#F2BE2C'
)
-
ax.
set_title
(
'Prediction Error for %s' % mod
[
1
]
)
-
ax.
plot
(
[y.
min
(
)
, y.
max
(
)
]
,
[y.
min
(
)
, y.
max
(
)
]
,
'k--'
, lw
=
4
, c
=
'#2B94E9'
)
-
ax.
set_ylabel
(
'Predicted'
)
-
plt.
xlabel
(
'Measured'
)
-
-
models
= np.
array
(
[
(Ridge
(
)
,
'Ridge'
)
,
(SVR
(
)
,
'SVR'
)
,
(RANSACRegressor
(
)
,
'RANSAC'
)
]
)
-
error_compare_three
(models
, features
, labels
)
|
![](https://www.52ml.net/wp-content/uploads/2016/10/mlsummary.4.png)
从这里可以很清晰的看出预测值和期望值的关系。同时也发现线性回归模型效果好。
残差曲线
残差是数据集每个实例的实际标记值和预测值之间的差值。通过画出一系列实例的残差,可以帮助我们检测它们是否随机错误。
-
def
resids_compare_three
(
mods
,
X
,
y
)
:
-
f
,
(ax1
, ax2
, ax3
)
= plt.
subplots
(
3
, sharex
=
True
, sharey
=
True
)
-
plt.
title
(
'Plotting residuals using training (blue) and test (green) data'
)
-
for m
, ax
in
(
(mods
[
0
]
, ax1
)
,
(mods
[
1
]
, ax2
)
,
(mods
[
2
]
, ax3
)
):
-
for feature
in
list
(X
):
-
splits
= cv.
train_test_split
(X
[
[feature
]
]
, y
, test_size
=
0.2
)
-
X_tn
, X_tt
, y_tn
, y_tt
= splits
-
m
[
0
].
fit
(X_tn
, y_tn
)
-
ax.
scatter
(m
[
0
].
predict
(X_tn
)
,m
[
0
].
predict
(X_tn
)-y_tn
,c
=
'#2B94E9'
,s
=
40
,alpha
=
0.5
)
-
ax.
scatter
(m
[
0
].
predict
(X_tt
)
, m
[
0
].
predict
(X_tt
)-y_tt
,c
=
'#94BA65'
,s
=
40
)
-
ax.
hlines
(y
=
0
, xmin
=
0
, xmax
=
100
)
-
-
ax.
set_ylabel
(
'Residuals'
)
-
plt.
xlim
(
[
20
,
70
]
)
-
plt.
ylim
(
[
-
50
,
50
]
)
-
-
models
= np.
array
(
[
(Ridge
(
)
,
'Ridge'
)
,
(LinearRegression
(
)
,
'Linear Regression'
)
,
(SVR
(
)
,
'SVR'
)
]
)
-
resids_compare_three
(models
, features
, labels
)
|
![](https://www.52ml.net/wp-content/uploads/2016/10/mlsummary.5.png)
Bias VS Variance
每种评估器都有是有利有弊。
首先 Error = Bias Variance。Error反映的是整个模型的准确度,Bias反映的是模型在样本上的输出与真实值之间的误差,即模型本身的精准度,Variance反映的是模型每一次输出结果与模型输出期望之间的误差,即模型的稳定性。
机器学习可视化调参
在文章开篇,我们提出了两个问题:我们如何知道一个机器学习模型可以工作?我们如何让这个模型工作(运行)的更好?
接下来,我们将回答第二个问题。如果你有注意,我们用的模型都是使用Scikit-Learn 默认的参数。对于我们的大部分拟合模型来讲,评分已经相当好了。但有时并没有那么幸运,这时我们就得自己调参数。
可视化训练和验证模型
如何选择最好的模型参数呢?一种方法是,用单一参数的不同值去验证一个模型的评估分数。让我们拿SVC 分类器来试验,通过调不同的gama值来画出训练值和测试纸的曲线。
我们的关注点是训练值和测试值都高的点。如果两者都低,那是欠拟合(underfit);如果训练值高但是测试值低,那说明是过拟合(overfit)。
下面的代码画出来的曲线是拿信用卡数据集来做例子,这里用的 6折交叉验证。
-
def
plot_val_curve
(
features
,
labels
,
model
)
:
-
p_range
= np.
logspace
(
-
5
,
5
,
5
)
-
train_scores
, test_scores
= validation_curve
(
-
model
, features
, labels
, param_name
=
'gamma'
, param_range
=p_range
,
-
cv
=
6
, scoring
=
'accuracy'
, n_jobs
=
1
-
-
train_scores_mean
= np.
mean
(train_scores
, axis
=
1
)
-
train_scores_std
= np.
std
(train_scores
, axis
=
1
)
-
test_scores_mean
= np.
mean
(test_scores
, axis
=
1
)
-
test_scores_std
= np.
std
(test_scores
, axis
=
1
)
-
plt.
title
(
'Validation Curve'
)
-
plt.
xlabel
(
'$\gamma$'
)
-
-
plt.
semilogx
(p_range
, train_scores_mean
, label
=
'Training score'
, color
=
'#E29539'
)
-
plt.
semilogx
(p_range
, test_scores_mean
, label
=
'Cross-validation score'
, color
=
'#94BA65'
)
-
plt.
legend
(loc
=
'best'
)
-
-
X
= scale
(credit
[
[
'limit'
,
'sex'
,
'edu'
,
'married'
,
'age'
,
'apr_delay'
]
]
)
-
-
plot_val_curve
(X
, y
, SVC
(
)
)
|
![](https://www.52ml.net/wp-content/uploads/2016/10/mlsummary.6.png)
Grid Search
对于超参数调优,大部分人使用的grid search。Grid search是一种暴力调参方法,即遍历所有可能的参数值。
对于信用卡数据集使用 SVC模型,我们通过试验不同内核系数gama来提高预测准确性:
-
from sklearn.
grid_search
import GridSearchCV
-
def
blind_gridsearch
(
model
,
X
,
y
)
:
-
C_range
= np.
logspace
(
-
2
,
10
,
5
)
-
gamma_range
= np.
logspace
(
-
5
,
5
,
5
)
-
param_grid
=
dict
(gamma
=gamma_range
, C
=C_range
)
-
grid
= GridSearchCV
(SVC
(
)
, param_grid
=param_grid
)
-
-
-
'The best parameters are {} with a score of {:0.2f}.'.
format
(
-
grid.
best_params_
, grid.
best_score_
-
-
-
features
= credit
[
[
'limit'
,
'sex'
,
'edu'
,
'married'
,
'age'
,
'apr_delay'
]
]
-
labels
= credit
[
'default'
]
-
blind_gridsearch
(SVC
(
)
, features
, labels
)
|
但是,grid search需要我们理解哪些参数是合适的,参数的意义,参数是如何影响模型的以及参数的合理的搜索范围来初始化搜索。
这里,我们使用 visual_gridsearch 代替 blind_gridsearch 函数:
-
def
visual_gridsearch
(
model
,
X
,
y
)
:
-
C_range
= np.
logspace
(
-
2
,
10
,
5
)
-
gamma_range
= np.
logspace
(
-
5
,
5
,
5
)
-
param_grid
=
dict
(gamma
=gamma_range
, C
=C_range
)
-
grid
= GridSearchCV
(SVC
(
)
, param_grid
=param_grid
)
-
-
scores
=
[x
[
1
]
for x
in grid.
grid_scores_
]
-
scores
= np.
array
(scores
).
reshape
(
len
(C_range
)
,
len
(gamma_range
)
)
-
plt.
figure
(figsize
=
(
8
,
6
)
)
-
plt.
subplots_adjust
(left
=
.2
, right
=
0.95
, bottom
=
0.15
, top
=
0.95
)
-
plt.
imshow
(scores
, interpolation
=
'nearest'
, cmap
=ddlheatmap
)
-
-
-
-
plt.
xticks
(np.
arange
(
len
(gamma_range
)
)
, gamma_range
, rotation
=
45
)
-
plt.
yticks
(np.
arange
(
len
(C_range
)
)
, C_range
)
-
-
"The best parameters are {} with a score of {:0.2f}.".
format
(
-
grid.
best_params_
, grid.
best_score_
)
-
-
-
visual_gridsearch
(SVC
(
)
, features
, labels
)
|
![](https://www.52ml.net/wp-content/uploads/2016/10/mlsummary.7.png)
visual_gridsearch 的方法可以帮助我们理解不同的模型参数下的精确值。但是超参数调优的路程很长,好些人为此研究了几十年。