-
本文对机器学习模型评估指标进行了完整总结。机器学习的数据集一<
/p>
般被划分为训练集和测试集,训练集用于训练模型,测试集则用于评估模
< br>型。针对不同的机器学习问题(分类、排序、回归、序列预测等),评估
指标决定
了我们如何衡量模型的好坏。
一、
Accuracy
准确率是最简单的评价指标,公式如下:
但是存在明显的缺陷:
?
当样本分布不均匀时,指标的结果
由占比大的类别决定。比如正样本占
99%
< br>,只要分类器将所有样本都预测为正样本就能获得
99%
的准确率。
?
结果太笼统,实际应用中,我们可
能更加关注某一类别样本的情况。比如
搜索时会关心
“检索出的信息有多少是用户感兴趣的”,“用户感兴趣的
信息有多少被
检测出来了”
等等。
相应地还有
错误率
:分类错误的样本占总样本的比例
。
from
s
import
accuracy_score
y_pred = [0, 0, 1, 1]
y_true = [1, 0, 1, 0]
accuracy_score(y_true, y_pred)
# 0.5
二、
Precision Recall
和
F1
精准率
(
Precision
)也叫查准率,衡量的是所有预测为
正例的结果中,
预测正确的(为真正例)比例。
召回率
(
Recall
)也
叫查全率,衡量的是实际的正例有多少被模型预
测为正例。
在排序问题中,一般以
TopN
的结果作为正例,然后计算前
N
个位置
上的精准率
Precision@N
和召回率
Recall@N
。
精确率和召回率
是一对相互矛盾的指标,一般来说高精准往往低召回,
相反亦然。其实这个是比较直观的
,比如我们想要一个模型准确率达到
100%
,那就意味着要保证每一个结果都是真正例,这就会导致有些正例被
放弃;相反
,要保证模型能将所有正例都预测为正例,意味着有些反例也
会混进来。这背后的根本原
因就在于我们的数据往往是随机、且充满噪声
的,并不是非黑即白。
精准率和召回率与
混淆矩阵
密切
相关,混淆矩阵是将分类(二分类)
结果通过矩阵的形式直观展现出来:
真实情况
正例
预测结果正例
TP(
真正例
)
预测结果反例
FN(
假反例
)
真实情况
反例
预测结果正例
FP(
假正例
)
预测结果反例
TN(
真反例
)
然后,很容易就得到精准率(
P
)和召回率(
R
)的计算公式:
得到
P
和
R
后就可以画出更加直观的
P-R
图(
P-R
曲线)
< br>,横坐标
为召回率,纵坐标是精准率。绘制方法如下:
?
对模型的学习结果进行排序(一般都有一个概率值)
?
按照上面的顺序逐个把样本作为正
例进行预测,每次都可以得到一个
P R
值
?
将得到的
P R
值按照
R
为横坐标,
P
为纵坐标绘制曲线图。
from
typing
import
List, Tuple
import
as
plt
def
get_confusion_matrix
(
y_pred: List[int],
y_true: List[int]
) -> Tuple[int,
int, int, int]:
length = len(y_pred)
assert
length == len(y_true)
tp, fp, fn, tn = 0, 0, 0, 0
for
i
in
range(length):
if
y_pred[i] == y_true[i]
and
y_pred[i] == 1:
tp += 1
elif
y_pred[i] == y_true[i]
and
y_pred[i] == 0:
tn += 1
elif
y_pred[i] == 1
and
y_true[i]
== 0:
fp += 1
elif
y_pred[i] == 0
and
y_true[i] == 1:
fn += 1
return
(tp, fp, tn, fn)
def
calc_p
(tp: int, fp: int) ->
float:
return
tp / (tp + fp)
def
calc_r
(tp: int, fn: int) ->
float:
return
tp / (tp + fn)
def
get_pr_pairs
(
y_pred_prob: List[float],
y_true:
List[int]
) ->
Tuple[List[int], List[int]]:
ps =
[1]
rs = [0]
for
prob1
in
y_pred_prob:
y_pred_i = []
for
prob2
in
y_pred_prob:
if
prob2 < prob1:
y_pred_(0)
else
:
y_pred_(1)
tp, fp, tn, fn = get_confusion_matrix(y_pred_i,
y_true)
p = calc_p(tp, fp)
r = calc_r(tp, fn)
(p)
(r)
(0)
(1)
return
ps, rs
y_pred_prob = [0.9, 0.8, 0.7, 0.6,
0.55, 0.54, 0.53, 0.52, 0.51, 0.505,
0.4, 0.39, 0.38, 0.37, 0.36, 0.35, 0.34, 0.33,
0.3, 0.1]
y_true = [1, 1, 0, 1, 1, 1,
0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0]
y_pred = [1] * 10 + [0] * 10
ps, rs = get_pr_pairs(y_pred_prob,
y_true)
fig, ax = ts(nrows=1, ncols=1,
figsize=(12, 5))
(rs, ps);
如果有多个模型就可以绘制多条
P-R
曲线:
?
如果某个模型的曲线完全被另外一个模型
“包住”(即后者更加凹向原
点),那么后者的性能一定优于前者。
?
如果多个模型的曲线发
生交叉,此时不好判断哪个模型较优,一个较为合
理的方法是计算曲线下面积,但这个值
不太好估算。
为了获得模型优劣,需要综合
P
和
R
,平衡点
BEP
(
Break-Even
Point
)
就是这样一个度量,它是
P=R
时的取值,
BPE
越远离原点,说明模型效果
越好。由于
BPE
过于简单,实际中常用
F1
值衡量:
F1
有更一般的形式:
当
β
> 1
时,更偏好召回
?
当
β
< 1
时,更偏好精准
?
当
β
= 1
时,平衡精准和召回,即为
F1
F1
其实来自精准和召回的加权调和平均:
?
p>
当有多个混淆矩阵(多次训练、多个数据集、多分类任务)时,有两
种方式估算
“全局”
性能:
?
macro
方法:先计算每个
PR
,取平均后,再计算
F1
?
micro
方法:先计算混淆矩阵元素的平均,再计算
PR
和
F1
三、
RMSE
均方根误差
RMSE
(
Root Mearn Square Error
)主要用在回归模型,
也就是俗称的
R
方。计算公式为:
但是如果有非常严重的离群点时,那些点会影响
RMSE
的结果,针对
这个问题:
?
如果离群点为噪声,则去除这些点
?
如果离群点为正常样本,可以重新建模
?
换一个评估指标,比如平均绝对百分比误差
MAPE
(
Mean Absolute
Percent
Error
),
MAPE
对每个误差
进行了归一化,一定程度上降低了离
群点的影响。
四、
ROC
和
AUC
受试者工作特征
ROC
(
Receiver
Operating Characteristic
)曲线
是
另一个重要的二分类指标。它的横坐标是
“假正例率”
FPR
(
False
Positive
Rate
),纵坐标是
“真正例率”
TPR
(
True Positive
Rate
),
计算公式如下:
绘制方法和上面的
P-R
曲线类似,不再赘述。
def
calc_fpr
(fp: int, tn: int)
-> float:
return
fp / (fp + tn)
def
calc_tpr
(tp: int, fn: int)
-> float:
return
tp / (tp + fn)
def
get_ftpr_pairs
(
y_pred_prob: List[float],
y_true:
List[int]
) ->
Tuple[List[int], List[int]]:
fprs =
[0]
tprs = [0]
for
prob1
in
y_pred_prob:
y_pred_i = []
for
prob2
in
y_pred_prob:
if
prob2 < prob1:
y_pred_(0)
else
:
y_pred_(1)
tp, fp, tn, fn =
get_confusion_matrix(y_pred_i, y_true)
fpr = calc_fpr(fp, tn)
tpr =
calc_tpr(tp, fn)
(fpr)
(tpr)
(1)
(1)
return
fprs, tprs
fprs, tprs =
get_ftpr_pairs(y_pred_prob, y_true)
fig, ax = ts(nrows=1, ncols=1,
figsize=(12, 5))
(fprs, tprs);
除此之外,还有一种绘制
ROC
曲线的方法:
?
假设有
m+
个正例,
m-
个负例,对模型输出的预测概率按从高到低排序
?
然后依次将每个样本的预测值作为
阈值(即将该样本作为正例),假设前
一个坐标为(
x,
y
),若当前为真正例,对应标记点为(
x, y+1/m+<
/p>
),若
当前为假正例,则对应标记点为(
x+1/m-, y
)
?
将所有点相连即可得到
ROC
曲线
该方法和这种做法是一样的:将纵坐标的刻度间隔设为
1/m+
,横坐标的刻
度间隔设为
1
/m-
,从(
0,0
)开始,每遇到一
个真正例就沿着纵轴绘制一
个刻度间隔的曲线,假正例就沿着横轴绘制一个刻度间隔的曲
线,最终就
可以得到
ROC
曲线。
def
get_ftpr_pairs2
(
y_pred_prob: List[float],
y_true:
List[int]
) -> Tuple[List[int],
List[int]]:
mplus = sum(y_true)
msub = len(y_true) - mplus
pairs = [(0, 0)]
prev = (0, 0)
length =
len(y_pred_prob)
assert
length == len(y_true)