机器学习可视化系统完结篇:模型评估和参数调优

机器学习可视化系统完结篇:模型评估和参数调优

写在之前:前两篇讲述了特征分析:《可视化图表让机器学习“biu”的一样简单:特征分析》和模型选择:《机器学习模型选择如此简单》。

本篇文章详细阐述机器学习模型评估和参数调优。将主要围绕两个问题来阐述:

  1. “知其所以然”:当你选择的一个机器学习模型运行时,你要知道它是如何工作的;
  2. “青出于蓝”:更进一步,你得知道如何让此机器学习模型工作的更优。

模型评估的方法

一般情况来说,F1评分或者R平方(R-Squared value)等数值评分可以告诉我们训练的机器学习模型的好坏。也有其它许多度量方式来评估拟合模型。

你应该猜出来,我将提出使用可视化的方法结合数值评分来更直观的评判机器学习模型。接下来的几个部分将分享一些有用的工具。

首先想声明的,单单一个评分或者一条线,是无法完全评估一个机器学习模型。偏离真实场景来评估机器学习模型(’good’ or ‘bad’)都是“耍流氓”。某个机器学习模型若可“驾驭”小样本数据集生成最多预测模型(即,命中更多预测数据集)。如果一个拟合模型比其它拟合过的模型形式或者你昨天的预测模型能够得到更好的结果,那即是好(’good’)。

下面是一些标准指标: confusion_matrixmean_squared_errorr2_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则是样本分类的标记。

通常,我们希望通过一些参数来告知模型评估如何。为此,我们使用混淆矩阵。

混淆矩阵

Confusion Matrix

幸运的是,Scikit-Learn提供内建函数(sklearn.metrics.confusion_matrix)来计算混淆矩阵。输入数据集实际值和模型预测值作为参数,输出即为混淆矩阵,结果类似这样:

1
2
[[1238 19] # True Positives = 1238, False Negatives = 19
[ 2 370]] # False Positives = 2, True Negatives = 370
分类报告

分类报告除了包括混淆矩阵,也增加了其它优势,比如,混淆矩阵会标示样例是否被正确鉴别,同时也提供precision,recall和 F1 值三种评估指标。

1
2
3
from sklearn.metrics import classification_report
print(classification_report(y_true, y_pred, target_names=target_names))

更进一步,可以对Scikit-Learn的内建函数做些加强,比如,使用带颜色区分的热力图,它将帮助我们的眼睛更容易的辨别预测成功(橘黄色)和失败(灰色)。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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')
classes = []
matrix = []
for line in lines[2:(len(lines)-3)]:
s = line.split()
classes.append(s[0])
value = [float(x) for x in s[1: len(s) - 1]]
matrix.append(value)
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)
plt.title(title)
plt.colorbar()
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')
plt.show()
cr = classification_report(y_true, y_pred)
plot_classification_report(cr)

Classification Report

看起来挺容易,对不?发现分类热力图的另外一个好处,它可以让我们看出一类错误 VS 二类错误。但有一个缺陷,它并不能垮模型进行比较,而这对评估拟合模型是相当重要的。因为这个原因,接下来将使用第二篇文章中的classifyregress代码。

下面的get_preds函数将输出一个实际标记值和预测值的二元组,这个二元组将会使得后续的跨模型的可视化比较变得容易:

1
2
3
4
5
6
7
8
9
10
11
12
13
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_true = y_test
y_pred = model.predict(X_test)
return (y_true,y_pred)
ROC曲线

另一种评估分类模型的方法是ROC(Receiver Operating Characteristic)曲线。我们能从Scikit-Learn 指标模块中import roc_curve,计算 true positive率和false positive 率的数值。我们也可以画出ROC曲线来权衡模型的敏感性和特异性。

下面的代码将画出ROC,Y轴代表true positive率,X轴代表false positive 率。同时,我们也可以增加同时比较两种不同的拟合模型,这里看到的是 KNeighborsClassifier 分类器远胜 LinearSVC 分类器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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.xlim([0,1])
plt.ylim([0,1.1])
plt.show()
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)

ROC_AUC Curve

在ROC空间,ROC曲线越凸向左上方向效果越好;越靠近对角线,分类器越趋向于随机分类器。

同时,我们也会计算曲线下的面积(AUC),可以结合上图。如果AUC的值达到0.80,那说明分类器分类非常准确;如果AUC值在0.60~0.80之间,那分类器还算好,但是我们调调参数可能会得到更好的性能;如果AUC值小于0.60,那就惨不忍睹了,你得好好分析下咯。

评估回归模型

对于混凝土数据集试验一些不同的机器学习模型,然后评判哪种更好。在第二篇文章中,我们使用的平均方差和 R 平方值,比如:

1
2
Mean squared error = 116.268
R2 score = 0.606

这些数值是有用的,特别是对不同的拟合模型比较平均方差和 R 平方值。但是,这是不够的,它不能告诉我们为什么一个模型远胜于另外一个;也不能告诉我们如何对模型调参数提高评分。接下来,我们将看到两种可视化的评估技术来帮助诊断模型有效性:预测错误曲线 和 残差曲线。

预测错误曲线

为了知道我们的模型预测值与期望值到底有多接近,我们将拿混凝土数据集(混凝土强度)做例子,画出其期望值和模型预测值曲线。下面是不同回归模型的错误曲线:RidgeSVRRANSACRegressor

1
2
3
4
5
6
7
8
9
10
11
12
13
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')
plt.show()
models = np.array([(Ridge(),'Ridge'), (SVR(),'SVR'), (RANSACRegressor(),'RANSAC')])
error_compare_three(models, features, labels)

Visualizing error in regression models

从这里可以很清晰的看出预测值和期望值的关系。同时也发现线性回归模型效果好。

残差曲线

残差是数据集每个实例的实际标记值和预测值之间的差值。通过画出一系列实例的残差,可以帮助我们检测它们是否随机错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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_title(m[1])
ax.set_ylabel('Residuals')
plt.xlim([20,70]) # Adjust according to your dataset
plt.ylim([-50,50])
plt.show()
models = np.array([(Ridge(),'Ridge'), (LinearRegression(),'Linear Regression'), (SVR(),'SVR')])
resids_compare_three(models, features, labels)

Plotting residuals in regression models

Bias VS Variance

每种评估器都有是有利有弊。

首先 Error = Bias + Variance。Error反映的是整个模型的准确度,Bias反映的是模型在样本上的输出与真实值之间的误差,即模型本身的精准度,Variance反映的是模型每一次输出结果与模型输出期望之间的误差,即模型的稳定性。

机器学习可视化调参

在文章开篇,我们提出了两个问题:我们如何知道一个机器学习模型可以工作?我们如何让这个模型工作(运行)的更好?

接下来,我们将回答第二个问题。如果你有注意,我们用的模型都是使用Scikit-Learn 默认的参数。对于我们的大部分拟合模型来讲,评分已经相当好了。但有时并没有那么幸运,这时我们就得自己调参数。

# 可视化训练和验证模型

如何选择最好的模型参数呢?一种方法是,用单一参数的不同值去验证一个模型的评估分数。让我们拿SVC 分类器来试验,通过调不同的gama值来画出训练值和测试纸的曲线。

我们的关注点是训练值和测试值都高的点。如果两者都低,那是欠拟合(underfit);如果训练值高但是测试值低,那说明是过拟合(overfit)。

下面的代码画出来的曲线是拿信用卡数据集来做例子,这里用的 6折交叉验证。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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.ylabel('Score')
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')
plt.show()
X = scale(credit[['limit','sex','edu','married','age','apr_delay']])
y = credit['default']
plot_val_curve(X, y, SVC())

Validation curve

对于超参数调优,大部分人使用的grid search。Grid search是一种暴力调参方法,即遍历所有可能的参数值。

对于信用卡数据集使用 SVC模型,我们通过试验不同内核系数gama来提高预测准确性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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)
grid.fit(X, y)
print(
'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 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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)
grid.fit(X, y)
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.xlabel('gamma')
plt.ylabel('C')
plt.colorbar()
plt.xticks(np.arange(len(gamma_range)), gamma_range, rotation=45)
plt.yticks(np.arange(len(C_range)), C_range)
plt.title(
"The best parameters are {} with a score of {:0.2f}.".format(
grid.best_params_, grid.best_score_)
)
plt.show()
visual_gridsearch(SVC(), features, labels)

Validation accuracy as a function of gamma and C

visual_gridsearch 的方法可以帮助我们理解不同的模型参数下的精确值。但是超参数调优的路程很长,好些人为此研究了几十年。

结论

这是可视化机器学习部分的最后一篇,可视化在机器学习的过程占用重要的角色。许多工具都提供这个功能,比如, Scikit-LearnMatplotlibPandasBokehSeaborn

希望我写的对部分人有用,如果是这样,请让我知道,谢谢。

Enjoy!


侠天,专注于大数据、机器学习和数学相关的内容,并有个人公众号:bigdata_ny分享相关技术文章。

若发现以上文章有任何不妥,请联系我。

image