在机器学习中如何评价一个算法的好坏呢?
对于一个回归问题,可以使用MSE、RMSE、MAE、R方。
对于一个分类问题,可以使用分类精准度。但是实际上,分类精准度是存在陷阱的,有时候我们会需要更加全面的信息。
1.分类算法的评价
1.1 分类准确度够用么?
对于一个癌症预测系统,输入检查指标,判断是否患有癌症,预测准确度99.9%。这个系统是好是坏呢?
如果癌症产生的概率是0.1%,那其实根本不需要任何机器学习算法,只要系统预测所有人都是健康的,即可达到99.9%的准确率。也就是说对于极度偏斜(Skewed Data)的数据,只使用分类准确度是不能衡量。
这是就需要使用混淆矩阵(Confusion Matrix)做进一步分析。
1.2 混淆矩阵
对于二分类问题来说,所有的问题被分为0和1两类,混淆矩阵是2*2的矩阵:
- TN:真实值是0,预测值也是0,即我们预测是negative,预测正确了。
- FP:真实值是0,预测值是1,即我们预测是positive,但是预测错误了。
- FN:真实值是1,预测值是0,即我们预测是negative,但预测错误了。
- TP:真实值是1,预测值是1,即我们预测是positive,预测正确了。
现在假设有1万人进行预测,填入混淆矩阵如下:
对于1万个人中,有9978个人本身并没有癌症,我们的算法也判断他没有癌症;有12个人本身没有癌症,但是我们的算法却错误地预测他有癌症;有2个人确实有癌症,但我们算法预测他没有癌症;有8个人确实有癌症,而且我们也预测对了。
对于1万个人中,有9978个人本身并没有癌症,我们的算法也判断他没有癌症;有12个人本身没有癌症,但是我们的算法却错误地预测他有癌症;有2个人确实有癌症,但我们算法预测他没有癌症;有8个人确实有癌症,而且我们也预测对了。
2.精准率和召回率
精准率:precision=TP/(TP+FP) ,即精准率为8/(8+12)=40%。所谓的精准率是:分母为所有预测为1的个数,分子是其中预测对了的个数,即预测值为1,且预测对了的比例。
为什么管它叫精准率呢?在有偏的数据中,我们通常更关注值为1的特征,比如“患病”,比如“有风险”。在100次结果为患病的预测,平均有40次预测是对的。即精准率为我们关注的那个事件,预测的有多准。
召回率:recall=TP/(TP+NF),即召回率为8/(8+2)=80%。所谓召回率是:所有真实值为1的数据中,预测对了的个数。每当有100个癌症患者,算法可以成功的预测出80个 。也就是我们关注的那个事件真实的发生情况下,我们成功预测的比例是多少。
3. 编程实现
3.1 混淆矩阵
实现一个逻辑回归算法
import numpy as np
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
digits = datasets.load_digits()
X = digits.data
y = digits.target.copy()
# 要构造偏斜数据,将数字9的对应索引的元素设置为1,0~8设置为0
y[digits.target==9]=1
y[digits.target!=9]=0
# 使用逻辑回归做一个分类
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=666)
log_reg = LogisticRegression()
log_reg.fit(X_train,y_train)
# 得到X_test所对应的预测值
y_log_predict = log_reg.predict(X_test)
C:\Users\86139\AppData\Local\Continuum\anaconda3\lib\site-packages\sklearn\linear_model\logistic.py:432: FutureWarning: Default solver will be changed to 'lbfgs' in 0.22. Specify a solver to silence this warning.
FutureWarning)
分类准确度
log_reg.score(X_test, y_test)
0.9755555555555555
定义混淆矩阵的四个指标:
def TN(y_true, y_predict):
assert len(y_true) == len(y_predict)
# (y_true == 0):向量与数值按位比较,得到的是一个布尔向量
# 向量与向量按位与,结果还是布尔向量
# np.sum 计算布尔向量中True的个数(True记为1,False记为0)
return np.sum((y_true == 0) & (y_predict == 0)) # 向量与向量按位与,结果还是向量
TN(y_test, y_log_predict)
403
def FP(y_true, y_predict):
assert len(y_true) == len(y_predict)
# (y_true == 0):向量与数值按位比较,得到的是一个布尔向量
# 向量与向量按位与,结果还是布尔向量
# np.sum 计算布尔向量中True的个数(True记为1,False记为0)
return np.sum((y_true == 0) & (y_predict == 1)) # 向量与向量按位与,结果还是向量
FP(y_test, y_log_predict)
2
def FN(y_true, y_predict):
assert len(y_true) == len(y_predict)
# (y_true == 0):向量与数值按位比较,得到的是一个布尔向量
# 向量与向量按位与,结果还是布尔向量
# np.sum 计算布尔向量中True的个数(True记为1,False记为0)
return np.sum((y_true == 1) & (y_predict == 0)) # 向量与向量按位与,结果还是向量
FN(y_test, y_log_predict)
9
def TP(y_true, y_predict):
assert len(y_true) == len(y_predict)
# (y_true == 0):向量与数值按位比较,得到的是一个布尔向量
# 向量与向量按位与,结果还是布尔向量
# np.sum 计算布尔向量中True的个数(True记为1,False记为0)
return np.sum((y_true == 1) & (y_predict == 1)) # 向量与向量按位与,结果还是向量
TP(y_test, y_log_predict)
36
def confusion_matrix(y_true, y_predict):
return np.array([
[TN(y_true, y_predict), FP(y_true, y_predict)],
[FN(y_true, y_predict), TP(y_true, y_predict)]
])
confusion_matrix(y_test, y_log_predict)
array([[403, 2],
[ 9, 36]])
3.2 精准率
计算精准率
def precision_score(y_true, y_predict):
tp = TP(y_true, y_predict)
fp = FP(y_true, y_predict)
try:
return tp / (tp + fp)
except:
return 0.0
precision_score(y_test, y_log_predict)
0.9473684210526315
3.3 召回率
计算召回率
def recall_score(y_true, y_predict):
tp = TP(y_true, y_predict)
fn = FN(y_true, y_predict)
try:
return tp / (tp + fn)
except:
return 0.0
recall_score(y_test, y_log_predict)
0.8
3.4 scikit-learn中的混淆矩阵,精准率和召回率
混淆矩阵
from sklearn.metrics import confusion_matrix
confusion_matrix(y_test, y_log_predict)
array([[403, 2],
[ 9, 36]], dtype=int64)
精准率
from sklearn.metrics import precision_score
precision_score(y_test, y_log_predict)
0.9473684210526315
召回率
from sklearn.metrics import recall_score
recall_score(y_test, y_log_predict)
0.8
4. F1 Score
4.1 更关注哪个?
精准率(查准率):预测值为1,且预测对了的比例,即:我们关注的那个事件,预测的有多准。
召回率(查全率):所有真实值为1的数据中,预测对了的个数,即:我们关注的那个事件真实的发生情况下,我们成功预测的比例是多少。
其实在衡量机器学习的其他指标中,也需要进行取舍,通常只需要把握一个原则:视场景而定。
比如股票预测系统,未来股票是📈还是📉这样一个二分类问题。很显然“涨”才是我们关注的焦点,那么我们肯定希望:系统预测上涨的股票中,真正上涨的比例越大越好,这就是希望查准率高。那么我们是否关注查全率呢?在大盘中有太多的真实上涨股票,虽然我们漏掉了一些上升周期,但是我们没有买进,也就没有损失。但是如果查准率不高,预测上涨的结果下跌了,那就是实实在在的亏钱了。所以在这个场景中,查准率更重要。
在医疗领域做疾病诊断,如果召回率低,意味着本来有一个病人得病了,但是没有正确预测出来,病情就恶化了。我们希望尽可能地将所有有病的患者都预测出来,而不是在看在预测有病的样例中有多准。
当然也有追求召回率的场景,在医疗领域做疾病诊断,如果召回率低,意味着本来有一个病人得病了,但是没有正确预测出来,病情就恶化了。我们希望尽可能地将所有有病的患者都预测出来,而不是在看在预测有病的样例中有多准。
4.2 二者兼顾 F1 Score
F1 Score 是精准率和召回率的调和平均值。
F1 = 2precisionrecall/(precision+recall)
调和平均值的特点是如果二者极度不平衡,如某一个值特别高、另一个值特别低时,得到的F1 Score值也特别低;只有二者都非常高,F1才会高。这样才符合我们对精准率和召回率的衡量标准。
1/F1 = 1/2*(1/precision+1/recall)
4.3 代码演示
import numpy as np
def f1_score(precision, recall):
try:
return 2 * precision * recall / (precision + recall)
except:
return 0.0
假设精准率和召回率同时为0.5,则二者的算数平均值为0.5,计算F1 Score:
precision = 0.5
recall = 0.5
f1_score(precision, recall)
0.5
假设精准率为0.9,召回率同时为0.1,则二者的算数平均值为0.5,计算F1 Score:
precision = 0.5
recall = 0.5
f1_score(precision, recall)
0.5
因此在数据有偏的情况下,F1 Score的指标更好。
5. ROC曲线
5.1 分类阈值、TPR和FPR
5.1.1 分类阈值
分类阈值,即设置判断样本为正例的阈值thr,
如果某个逻辑回归模型对某封电子邮件进行预测时返回的概率为 0.9995,则表示该模型预测这封邮件非常可能是垃圾邮件。相反,在同一个逻辑回归模型中预测分数为 0.0003 的另一封电子邮件很可能不是垃圾邮件。可如果某封电子邮件的预测分数为 0.6 呢?为了将逻辑回归值映射到二元类别,您必须指定分类阈值(也称为判定阈值)。如果值高于该阈值,则表示“垃圾邮件”;如果值低于该阈值,则表示“非垃圾邮件”。人们往往会认为分类阈值应始终为 0.5,但阈值取决于具体问题,因此您必须对其进行调整。
在sklearn中有一个方法叫:decision_function,即返回分类阈值
decision_scores = log_reg.decision_function(X_test)
y_predict = np.array(decision_scores >= 5, dtype='int')
精准率和召回率这两个指标有内在的联系,并且相互冲突。precision随着threshold的增加而增大,recall随着threshold的增加而减小。如果某些场景需要precision,recall都保持在80%,可以通过这种方式求出threshold
5.1.2 TPR
TPR:预测为1,且预测对了的数量,占真实值为1的数据百分比。很好理解,就是召回率。
TPR = TP/(TP+FN)
5.1.3 FPR
FPR:预测为1,但预测错了的数量,占真实值不为1的数据百分比。与TPR相对应,FPR除以真实值为0的这一行所有的数字和 。
FPR = FP/(FP+TN)
TPR和FPR之间是成正比的,TPR高,FPR也高。ROC曲线就是刻画这两个指标之间的关系。
5.2 什么是ROC曲线
ROC曲线(Receiver Operation Characteristic Cureve),描述TPR和FPR之间的关系。x轴是FPR,y轴是TPR。
TPR就是所有正例中,有多少被正确地判定为正;FPR是所有负例中,有多少被错误地判定为正。 分类阈值取不同值,TPR和FPR的计算结果也不同,最理想情况下,我们希望所有正例 & 负例 都被成功预测 TPR=1,FPR=0,即 所有的正例预测值 > 所有的负例预测值,此时阈值取最小正例预测值与最大负例预测值之间的值即可。
TPR越大越好,FPR越小越好,但这两个指标通常是矛盾的。为了增大TPR,可以预测更多的样本为正例,与此同时也增加了更多负例被误判为正例的情况。
5.2.1 代码展示
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
digits = datasets.load_digits()
X = digits.data
y = digits.target.copy()
# 要构造偏斜数据,将数字9的对应索引的元素设置为1,0~8设置为0
y[digits.target==9]=1
y[digits.target!=9]=0
# 使用逻辑回归做一个分类
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=666)
log_reg = LogisticRegression()
log_reg.fit(X_train,y_train)
# 计算逻辑回归给予X_test样本的决策数据值
# 通过decision_function可以调整精准率和召回率
decision_scores = log_reg.decision_function(X_test)
# TPR
def TPR(y_true, y_predict):
tp = TP(y_true, y_predict)
fn = FN(y_true, y_predict)
try:
return tp / (tp + fn)
except:
return 0.0
# FPR
def FPR(y_true, y_predict):
fp = FP(y_true, y_predict)
tn = TN(y_true, y_predict)
try:
return fp / (fp + tn)
except:
return 0.0
fprs = []
tprs = []
# 以0.1为步长,遍历decision_scores中的最小值到最大值的所有数据点,将其作为阈值集合
thresholds = np.arange(np.min(decision_scores), np.max(decision_scores), 0.1)
for threshold in thresholds:
# decision_scores >= threshold 是布尔型向量,用dtype设置为int
# 大于等于阈值threshold分类为1,小于为0,用这种方法得到预测值
y_predict = np.array(decision_scores >= threshold, dtype=int)
#print(y_predict)
# print(y_test)
#print(FPR(y_test, y_predict))
# 对于每个阈值,所得到的FPR和TPR都添加到相应的队列中
fprs.append(FPR(y_test, y_predict))
tprs.append(TPR(y_test, y_predict))
C:\Users\86139\AppData\Local\Continuum\anaconda3\lib\site-packages\sklearn\linear_model\logistic.py:432: FutureWarning: Default solver will be changed to 'lbfgs' in 0.22. Specify a solver to silence this warning.
FutureWarning)
# 绘制ROC曲线,x轴是fpr的值,y轴是tpr的值
plt.plot(fprs, tprs)
plt.show()
可以看到曲线每次都是一个“爬坡”,遇到正例往上爬一格,错了往右爬一格,显然往上爬对于算法性能来说是最好的。
sklearn中的ROC曲线:
from sklearn.metrics import roc_curve
fprs, tprs, thresholds = roc_curve(y_test, decision_scores)
plt.plot(fprs, tprs)
plt.show()
5.2.2 分析
ROC曲线距离左上角越近,证明分类器效果越好。如果一条算法1的ROC曲线完全包含算法2,则可以断定性能算法1>算法2。这很好理解,此时任做一条 横线(纵线),任意相同TPR(FPR) 时,算法1的FPR更低(TPR更高),故显然更优。
很多时候两个分类器的ROC曲线交叉,无法判断哪个分类器性能更好,这时可以计算曲线下的面积AUC,作为性能度量。
6.AUC
一般在ROC曲线中,关注是曲线下面的面积, 称为AUC(Area Under Curve)。这个AUC是横轴范围(0,1 ),纵轴是(0,1)所以总面积是小于1的。
ROC和AUC的主要应用:比较两个模型哪个好?主要通过AUC能够直观看出来。
ROC曲线下方由梯形组成,矩形可以看成特征的梯形。因此,AUC的面积可以这样算:(上底+下底)* 高 / 2,曲线下面的面积可以由多个梯形面积叠加得到。AUC越大,分类器分类效果越好。
- AUC = 1,是完美分类器,采用这个预测模型时,不管设定什么阈值都能得出完美预测。绝大多数预测的场合,不存在完美分类器。
- 0.5 < AUC < 1,优于随机猜测。这个分类器(模型)妥善设定阈值的话,能有预测价值。
- AUC = 0.5,跟随机猜测一样,模型没有预测价值。
- AUC < 0.5,比随机猜测还差;但只要总是反预测而行,就优于随机猜测。
可以在sklearn中求出AUC值
from sklearn.metrics import roc_auc_score
roc_auc_score(y_test, decision_scores)
0.9830452674897119