机器学习模型的评估

1 回归

评价回归模型,均方误差MSE最常用了:$1/m\sum^m_{i=1}(f(x_i)-y_i)^2$,衡量了回归结果和实际结果之间的整体误差。

# sklearn
metrics.mean_squared_error(y_true, y_pred)

# tensorflow
tf.metrics.mean_squared_error(y_true, y_pred)
# 或手动算
tf.reduce_mean(tf.square(y_true, y_pred))

2 分类问题

2.1 混淆矩阵

对于分类问题,可以通过混淆矩阵计算详细的评估指标

实际 预测
正例 负例
正例 真正例TP 假反例FN
负例 假正例FP 真反例TN

注意上述是二分类时的情况。多分类时,混淆矩阵是一个类别数x类别数的矩阵,列是预测标签,行是实际标签。该混淆矩阵的元素a_ij含义是将本是j类的样本分到了i类里的数量,当i=j时,即对角线上的元素对应的数量才是正确的预测。

sklearn中计算混淆矩阵的API为

from sklearn.metrics import confusion_matrix

# y_pred是预测标签,下同
confusion_matrix(y_true=y_true, y_pred=y_pred)

tensorflow中混淆矩阵的API为

tf.confusion_matrix(
    labels,
    predictions,
    num_classes=None,   # 不指定类别数量就取数据或预测标签中最大值+1
    dtype=tf.int32,
    name=None,
    weights=None
)

有了混淆矩阵,就可以进一步计算详细的评估指标。在实际中,一般不需要特地计算混淆矩阵。

2.2 精度accuracy

精度accuracy是分类正确的数量占样本总数的比例。这个指标其实不需要混淆矩阵就可以计算,用混淆矩阵计算的话,精度就是对角线值的和/总数。对二分类即(TP+TN)/(TP+FP+FN+TN)

精度的问题是对分布不均衡的数据,大类别会主导计算。平均精度是对每个类单独计算精度,然后取平均,以解决大类别主导精度计算的问题。然而因为小类别数据量少,相应的精度的方差会很大。

sklearn中的API为:

from sklearn.metrics import accuracy_score

accuracy_score(y_true=y_true, y_pred=y_pred)

# 平均精度
from sklearn.metrics import average_precision_score

使用tensorflow的API计算精度需要稍微费一点功夫。因为损失计算的原因,softmax会在计算损失的时候和交叉熵一起算,所以一般多分类NN最后一层输出是类别数量长度的序列,其中的最大值对应的index就是所属的分类。

# tf.argmax  取最大值的index,即计算分类结果
# tf.equal  对比预测分类是否和真实分类相同,得到一串布尔值
correct_prediction = tf.equal(tf.argmax(y_conv,1),tf.argmax(y,1))

# tf.cast  将布尔值转化为浮点数
# tf.reduce_mean  计算平均值
# 该平均值就是正确分类所占的百分比,即精度
accuracy = tf.reduce_mean(tf.cast(correct_prediction,tf.float32))

# 除了像上边那样自己写acc,也可以在得到最终预测结果predictions之后直接调用tf.metrics计算,labels是真实结果(下同)
tf.metrics.accuracy(labels, predictions)

2.3 查准率precision

查准率precision:P=TP/(TP+FP),预测出来的正例中有多少是真正的正例,又叫查准率

precision的翻译太多样了,如准确率、精准率等等,甚至和accuracy重复而无法区分。因此这里使用“查准率”这个翻译

2.4 查全率recall

查全率recall:R=TP/(TP+FN),真正的正例有多少被分到了正例里,又叫召回率

2.5 F分数f-score

结合了查准率和查全率的指标,F1-score为二者的调和平均数:2*precision*recall/(precision+recall)

为了更明确评估过程中更看重查准率还是查全率,F-分数更通用的形式为加权调和平均$F_\beta$,其中$\beta$为查全率与查准率的权重比

$$
F_\beta=(1+\beta^2)\frac{precision\times recall}{\beta^2 precision+recall}
$$

2.6 接受者工作特征ROC

  • 真正例比率TPR:TP/(TP+FN),和查全率的计算方法相同
  • 假正例比率FPR:FP/(FP+TN)

ROC曲线描绘了TPR-FPR的变化关系。但混淆矩阵得到之后,只能得到一组TPR-FPR。如何得到多组呢?

在分类时,输出是属于某类的概率(或评分)。一般我们定0.5作为阈值,超过就认为属于该类,否则不属于该类。这个0.5的阈值就叫做截断点。截断点发生变动时,TPR-FPR就可以形成一条曲线了。

考查TPR-FPR平面,可以找到四个特殊点和一条特殊线:

  • (0,0): TP=FP=0,所有数据都预测为负例
  • (0,1): FP=0, FN=0,全对,完美分类
  • (1,0): TN=0, TP=0,全错,最差分类
  • (1,1): TN=0,FN=0,所有数据都预测为正例
  • TPR=FPR: 过原点斜率为1的直线,预测正误一半半,相当于随机猜测

左上角的(0,1)处是完美分类器,所以ROC曲线越接近左上角说明模型性能越好。ROC曲线的好处是通过TPR和FPR减小了样本分布变化对评估指标结果造成的影响。

曲线下方的面积就是AUC,可以根据AUC的值来判断ROC是否更接近左上角。

上述的precision, recall, f-score, ROC都是基于二分类而言的。对于多分类的情况,混淆矩阵更加复杂,有两种策略求得全局的上述指标:

  • 宏平均macro-average。先单独计算每个分类上的各指标,然后取平均
  • 微平均micro-average。先将各混淆矩阵取平均得到一个混淆矩阵,再根据这个混淆矩阵算指标

如果各类样本数量差不多,两种策略的结果差异不大。如果各类样本差异很大,结果就有较大差异。这时根据需求选择使用哪一个:

  • 更关注样本数量多的类:宏平均
  • 更关注样本数量少的类:微平均

查准率、查全率、F分数和ROC都是基于混淆矩阵的评估指标,这里放在一起实现。sklearn中的API:

from sklearn import metrics

# precision
metrics.precision_score(y_true, y_pred)

# recall
metrics.recall_score(y_true, y_pred)

# f1-score
metrics.f1_score(y_true, y_pred)

# ROC曲线
# scores为属于各分类的概率或评分
# i为认为是正例的分类编号(单独求第i类的FPR,TPR和截断点。如果多分类要求全部需要手动指定一遍)
fpr, tpr, thresholds = metrics.roc_curve(y, scores, pos_label=i)

# AUC
metric.auc(fpr,tpr)
# 或直接从scores计算
metrics.roc_auc_score(y_true, scores)

# precision_score, recall_score, f1_score和roc_auc_score都还可以传入参数average,设置多分类下求平均的方式,除了宏平均macro和微平均micro外,加权weighted和采样samples两种方法

# 分类报告
# 返回每个分类的precision、recall、f1-score、数量和平均值
# names是分类名称的列表,显示用
from sklearn.metrics import classification_report

classification_report(y_true, y_pred, target_names=names)

在tensorflow中的API实现:

# precision
tf.metrics.precision(labels, predictions)

# recall
tf.metrics.recall(labels, predictions)

# f1-score
# tf.metric里没有提供f-score的API,需要自己根据P,R手写,或者调用tf.contrib.metric里的API
# contrib里的参数顺序是反过来的
tf.contrib.metrics.f1_score(predictions, labels)

# ROC and AUC
# 没有ROC的API,需要自己根据tf.metrics.true_positives等先计算出TPR, FPR算
tf.metrics.auc(labels, predictions)

3 交叉验证CV

我们知道,一般机器学习的数据会分成3部分,就是所谓的留出法

  • 训练集,用来训练模型
  • 验证集,用来测试训练集训练的结果,作为模型改进的参考
  • 测试集,用来测试最终优化过模型的泛化能力

三份数据的比例对于小数据来说一般是8:1:1,对于大数据是98:1:1。但对于缺乏数据的情况,就需要采取K折交叉验证的方法对数据进行划分,将训练集和验证集轮着交叉训练和验证模型:

  1. 将数据拿出一小部分作为测试集,剩下的数据分成K份
  2. 其中K-1份用作测试集,剩下的1份用作验证集。训练模型
  3. 重复第2步K次,每次都用不同的1份数据作为验证集
  4. 计算K次评估指标均值,得到模型的评估

当K和数据总数相等时,叫做留一法,相当于验证集只有一个样本。训练集就更接近数据原始分布,但计算量翻K倍增加的更多了,缺乏数据时才用。

tensorflow中没有交叉验证的API,一般DL也不需要CV。sklearn中实现交叉验证的API有sklearn.model_selection.KFold(n_splits=k)方法划分数据,但是一般用实现了划分+训练+评价一条龙的另外一个API:

# 仅数据划分,一般不太用,而是直接用下边一条龙的方法
from sklearn.model_selection import KFold

kf = KFold(n_splits=k)

# 划分、训练、评价一条龙
# clf是模型
# scoring是指定的模型评估指标
from sklearn.model_selection import cross_val_score,cross_validate

scores = cross_val_score(clf, data, labels, cv=k, scoring='f1_macro')

# cross_validate可以指定多个评估指标
scores = cross_val_score(clf, data, labels, cv=k, scoring=['precision_macro','recall_macro'])