机器学习模型评估选择与验证
Posted ZSYL
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了机器学习模型评估选择与验证相关的知识,希望对你有一定的参考价值。
模型评估、选择与验证
1. 为什么要有训练集与测试集?
为什么要有训练集与测试集?
我们想要利用收集的西瓜数据构建一个机器学习模型,用来预测新的西瓜的好坏,但在将模型用于新的测量数据之前,我们需要知道模型是否有效,也就是说,我们是否应该相信它的预测结果。
不幸的是,我们不能将用于构建模型的数据用于评估模型的性能。
因为我们的模型会一直记住整个训练集,所以,对于训练集中的任何数据点总会预测成正确的标签。
这种记忆无法告诉我们模型的泛化能力如何,即预测新样本的能力如何。
我们要用新数据来评估模型的性能。新数据是指模型之前没见过的数据,而我们有这些新数据的标签。通常的做法是,我们把手头上的数据分为两部分,训练集与测试集。
训练集用来构建机器学习模型,测试集用来评估模型性能。
2. 欠拟合与过拟合
什么是欠拟合与欠拟合的原因?
-
欠拟合:模型在训练集上误差很高;
-
欠拟合原因:模型过于简单,没有很好的捕捉到数据特征,不能很好的拟合数据。
如上面例子,我们的数据是一份非线性数据,如果你想要用线性回归来拟合这份数据,由于数据是非线性的,模型是线性,则过于简单。
所以,无论模型怎么训练,最终都不能很好的拟合数据。
什么是过拟合与过拟合的原因?
- 过拟合:在训练集上误差低,测试集上误差高;
- 过拟合原因:模型把数据学习的太彻底,以至于把噪声数据的特征也学习到了,这样就会导致在后期测试的时候不能够很好地识别数据,模型泛化能力太差。
如上面例子,在训练集上,模型为了拟合数据,添加了更多的多次项,使模型过于复杂,对噪音数据也能很好的拟合,所以在训练集上正确率很高,而在测试集上没有这些噪音数据,所以正确率很低。
在分类的问题中,如下例子:
欠拟合:由于模型过于简单,只学习到绿色这个特征,只要是绿色就都判断为树叶,结果将树当做了树叶。
过拟合:模型过于复杂,将锯齿这个普通特征,看的过于重要,认为必须有锯齿才是树叶,结果将树叶误判为不是树叶。
3. 偏差与方差
模型误差来源
在上一部分,我们知道了欠拟合是模型在训练集上误差过高,过拟合模型是在训练集上误差低,在测试集上误差高。
那么模型误差的来源是什么呢?
上图表示:
-
如果一个模型在训练集上正确率为 80%,测试集上正确率为 79% ,则模型欠拟合,其中 20% 的误差来自于偏差,1% 的误差来自于方差。
-
如果一个模型在训练集上正确率为 99%,测试集上正确率为 80% ,则模型过拟合,其中 1% 的误差来自于偏差,19% 的误差来自于方差。
可以看出,欠拟合是一种高偏差的情况。过拟合是一种低偏差,高方差的情况。
偏差与方差
-
偏差:预计值的期望与真实值之间的差距;
-
方差:预测值的离散程度,也就是离其期望值的距离。
以射击打靶为例,蓝色的小点是我们在靶子上的射击记录,蓝色点的质心(黑色点)到靶心的距离为偏差,某个点到质心的距离为方差。
所以,某个点到质心的误差就是由偏差与方差所组成。
那么,为什么欠拟合是一直高偏差情况,过拟合是一种低偏差高方差情况呢?
我们知道,欠拟合是因为模型过于简单,模型过于简单我们可以当做是我们射击时射击的范围比较小,它所涵盖的范围不包括靶心,所以无论怎么射击,射击点的质心里靶心的距离都很远,所以偏差很高。但是因为射击范围很小,所以所有射击点相互离的比较紧密,则方差低。
而过拟合是因为模型过于复杂,我们可以理解为这个时候射击的范围很大了,经过不断的训练射击的点的质心离靶心的距离很近了,但是由于数据量有限,而射击范围很大,所以所有射击点之间非常离散,也就是方差很大。
4. 验证集与交叉验证
为什么需要验证集?
在机器学习中,通常需要评估若⼲候选模型的表现并从中选择模型。
这⼀过程称为模型选择。可供选择的候选模型可以是有着不同超参数的同类模型。
以神经网络为例,我们可以选择隐藏层的个数,学习率大小和激活函数。为了得到有效的模型,我们通常要在模型选择上下⼀番功夫。
从严格意义上讲,测试集只能在所有超参数和模型参数选定后使用⼀次。不可以使用测试数据选择模型,如调参。由于无法从训练误差估计泛化误差,因此也不应只依赖训练数据选择模型。鉴于此,我们可以预留⼀部分在训练数据集和测试数据集以外的数据来进行模型选择。
这部分数据被称为验证数据集,简称验证集。
为了方便大家理解,举一个生活中的案例进行类比,我们一般是通过考试衡量学生的学习情况。
老师上完课后,给学生布置的作业相当于训练数据集,中期的测试题相当于验证集,期末考试题相当于测试数据集。为了更加客观的衡量学生学习情况,期末考试题的内容不应该出现在平常的作业题和中期的测试题中,因为之前做过的题,对于计算机而言,相当于已经记住了,如果再次做同样的题,准确率就会很高。
同样的道理,平常的作业题也不应该出现在中期的测试题里。中期的测试题,是为了掌握学生的学习情况,了解自己哪些方面内容没掌握,从而调整下一步学习的方向,为期末考试做好准备。
4.1 k折交叉验证
由于验证数据集不参与模型训练,当训练数据不够⽤时,预留大量的验证数据显得太奢侈。⼀种改善的方法是 K 折交叉验证。
在 K 折交叉验证中,我们把原始训练数据集分割成 K 个不重合的子数据集,然后我们做K次模型训练和验证。
每⼀次,我们使用⼀个子数据集验证模型,并使用其它 K−1 个子数据集来训练模型。在这 K 次训练和验证中,每次用来验证模型的子数据集都不同。
最后,我们对这 K 次训练误差和验证误差分别求平均。
k 的值由我们自己来指定,如以下为 5 折交叉验证。
还是以考试为例,解释上图内容。
交叉验证,相当于把平常的作业题和中期的测试题合并成一个题库,然后等分成几份。
图中所示,将题库分成了五份,第一行的意思是,先让学生做后面的四份训练题,再用第一份题进行测试。以此类推,再重复四次,每一次相当于重新进行学习。
最后,取五次的平均成绩,平均成绩高,说明老师的教学方法好,对应到模型,就是超参数更好。
4.2 集成学习
在机器学习的有监督学习算法中,我们的目标是学习出一个稳定的且在各个方面表现都较好的模型,但实际情况往往不这么理想,有时我们只能得到多个有偏好的模型(弱监督模型,在某些方面表现的比较好)。
集成学习就是组合这里的多个弱监督模型以期得到一个更好更全面的强监督模型。
集成学习潜在的思想是即便某一个弱分类器得到了错误的预测,其他的弱分类器也可以将错误纠正回来。
集成方法是将几种机器学习技术组合成一个预测模型的元算法,以达到减小方差、偏差或改进预测的效果。
4.3 自助法
在统计学中,自助法是一种从给定训练集中有放回的均匀抽样,也就是说,每当选中一个样本,它等可能地被再次选中并被再次添加到训练集中。
自助法以自助采样法为基础,给定包含 m 个样本的数据集 D,我们对它进行采样产生数据集 D’;每次随机从 D 中挑选一个赝本,将其拷贝放入 D’,然后再将该样本放回初始数据集 D 中,使得该样本在下次采样时仍有可能被采到;
这个过程重复执行 m 次后,就得到了包含m个样本的数据集 D’,这就是自助采样的结果。自助法在数据集较小、难以有效划分训练/测试集时很有用;
此外,自助法能从初始数据集中产生多个不同的训练集,这对集成学习等方法有很大的好处。然而,自助法产生的数据集改变了初始数据集的分布,这会引入估计偏差。
5. 衡量回归的性能指标
大家知道已经,机器学习通常都是将训练集上的数据对模型进行训练,然后再将测试集上的数据给训练好的模型进行预测,最后根据模型性能的好坏选择模型,对于分类问题,大家很容易想到,可以使用正确率来评估模型的性能,那么回归问题可以使用哪些指标用来评估呢?
5.1 MSE
MSE (Mean Squared Error)叫做均方误差,公式如下:
5.2 RMSE
RMSE(Root Mean Squard Error)均方根误差,公式如下:
RMSE 其实就是 MSE 开个根号。有什么意义呢?
其实实质是一样的。只不过用于数据更好的描述。
例如:要做房价预测,每平方是万元,我们预测结果也是万元。那么差值的平方单位应该是千万级别的。
那我们不太好描述自己做的模型效果。怎么说呢?我们的模型误差是多少千万?于是干脆就开个根号就好了。我们误差的结果就跟我们数据是一个级别的了,在描述模型的时候就说,我们模型的误差是多少万元。
5.3 MAE
MAE (平均绝对误差),公式如下:
MAE 虽然不作为损失函数,确是一个非常直观的评估指标,它表示每个样本的预测标签值与真实标签值的 L1 距离。
5.4 R-Squared
上面的几种衡量标准针对不同的模型会有不同的值。
比如说预测房价那么误差单位就是万元。数子可能是 3,4 ,5 之类的。那么预测身高就可能是 0.1,0.6 之类的。
没有什么可读性,到底多少才算好呢?
不知道,那要根据模型的应用场景来。 看看分类算法的衡量标准就是正确率,而正确率又在 0~1 之间,最高百分之百。最低 0 。如果是负数,则考虑非线性相关。很直观,而且不同模型一样的。那么线性回归有没有这样的衡量标准呢?
R-Squared 就是这么一个指标,公式如下:
其实分子表示的是模型预测时产生的误差,分母表示的是对任意样本都预测为所有标签均值时产生的误差,由此可知:
6. 准确度的陷阱与混淆矩阵
6.1 准确度的缺陷
准确度这个概念相信对于大家来说肯定并不陌生,就是正确率。
例如模型的预测结果与数据真实结果如下表所示:
很明显,连小朋友都能算出来该模型的准确度为 3/5。
那么准确对越高就能说明模型的分类性能越好吗?
非也!
举个例子,现在我开发了一套癌症检测系统,只要输入你的一些基本健康信息,就能预测出你现在是否患有癌症,并且分类的准确度为 0.999。您认为这样的系统的预测性能好不好呢?
您可能会觉得,哇,这么高的准确度!这个系统肯定很牛逼!但是我们知道,一般年轻人患癌症的概率非常低,假设患癌症的概率为 0.001,那么其实我这个癌症检测系统只要一直输出您没有患癌症,准确度也可能能够达到 0.999。
假如现在有一个人本身已经患有癌症,但是他自己不知道自己患有癌症。这个时候用我的癌症检测系统检测发现他没有得癌症,那很显然我这个系统已经把他给坑了(耽误了治疗)。
看到这里您应该已经体会到了,一个分类模型如果光看准确度是不够的,尤其是对这种样本极度不平衡的情况( 10000 条健康信息数据中,只有 1 条的类别是患有癌症,其他的类别都是健康)。
6.2 混淆矩阵
想进一步的考量分类模型的性能如何,可以使用其他的一些性能指标,例如精准率和召回率。但这些指标计算的基础是混淆矩阵。
继续以癌症检测系统为例,癌症检测系统的输出不是有癌症就是健康,这里为了方便,就用 1 表示患有癌症,0 表示健康。
假设现在拿 10000 条数据来进行测试,其中有 9978 条数据的真实类别是 0,系统预测的类别也是 0,有 2 条数据的真实类别是 1 却预测成了 0,有 12 条数据的真实类别是 0 但预测成了 1,有 8 条数据的真实类别是 1,预测结果也是 1。
如果我们把这些结果组成如下矩阵,则该矩阵就成为混淆矩阵。
混淆矩阵中每个格子所代表的的意义也很明显,意义如下:
如果将正确看成是 True,错误看成是 False, 0 看成是 Negtive, 1 看成是 Positive。然后将上表中的文字替换掉,混淆矩阵如下:
因此 TN 表示真实类别是 Negtive,预测结果也是 Negtive 的数量; FP 表示真实类别是 Negtive,预测结果是 Positive 的数量; FN 表示真实类别是 Positive,预测结果是 Negtive 的数量;TP 表示真实类别是 Positive,预测结果也是 Positive 的数量。
很明显,当 FN 和 FP 都等于 0 时,模型的性能应该是最好的,因为模型并没有在预测的时候犯错误。即如下混淆矩阵:
所以模型分类性能越好,混淆矩阵中非对角线上的数值越小。
代码示例:
import numpy as np
def confusion_matrix(y_true, y_predict):
'''
构建二分类的混淆矩阵,并将其返回
:param y_true: 真实类别,类型为ndarray
:param y_predict: 预测类别,类型为ndarray
:return: shape为(2, 2)的ndarray
'''
#********* Begin *********#
def TN(y_true, y_predict):
return np.sum((y_true == 0) & (y_predict == 0))
def FP(y_true, y_predict):
return np.sum((y_true == 0) & (y_predict == 1))
def FN(y_true, y_predict):
return np.sum((y_true == 1) & (y_predict == 0))
def TP(y_true, y_predict):
return np.sum((y_true == 1) & (y_predict == 1))
return np.array([
[TN(y_true, y_predict), FP(y_true, y_predict)],
[FN(y_true, y_predict), TP(y_true, y_predict)]
])
#********* End *********#
7. 精准率与召回率
7.1 精准率
精准率(Precision) 指的是模型预测为 Positive 时的预测准确度,其计算公式如下:
假如癌症检测系统的混淆矩阵如下:
则该系统的精准率=8/(8+12)=0.4。
0.4 这个值表示癌症检测系统的预测结果中如果有 100 个人被预测成患有癌症,那么其中有 40 人是真的患有癌症。也就是说,精准率越高,那么癌症检测系统预测某人患有癌症的可信度就越高。
7.2 召回率
召回率(Recall) 指的是我们关注的事件发生了,并且模型预测正确了的比值,其计算公式如下:
假如癌症检测系统的混淆矩阵如下:
则该系统的召回率 =8/(8+2)=0.8。
从计算出的召回率可以看出,假设有 100 个患有癌症的病人使用这个系统进行癌症检测,系统能够检测出 80 人是患有癌症的。也就是说,召回率越高,那么我们感兴趣的对象成为漏网之鱼的可能性越低。
精准率与召回率之间的关系
假设有这么一组数据,菱形代表 Positive,圆形代表 Negtive 。
现在需要训练一个模型对数据进行分类,假如该模型非常简单,就是在数据上画一条线作为分类边界。模型认为边界的左边是 Negtive,右边是 Positive。如果该模型的分类边界向左或者向右移动的话,模型所对应的精准率和召回率如下图所示:
从上图可知,模型的精准率变高,召回率会变低,精准率变低,召回率会变高。
应该选精准率还是召回率作为性能指标?
到底应该使用精准率还是召回率作为性能指标,其实是根据具体业务来决定的。
比如我现在想要训练一个模型来预测我关心的股票是涨( Positive )还是跌( Negtive ),那么我们应该主要使用精准率作为性能指标。
因为精准率高的话,则模型预测该股票要涨的可信度就高(很有可能赚钱!)。
比如现在需要训练一个模型来预测人是( Positive )否( Negtive )患有艾滋病,那么我们应该主要使用召回率作为性能指标。因为召回率太低的话,很有可能存在漏网之鱼(可能一个人本身患有艾滋病,但预测成了健康),这样就很可能导致病人错过了最佳的治疗时间,这是非常致命的。
import numpy as np
def precision_score(y_true, y_predict):
'''
计算精准率并返回
:param y_true: 真实类别,类型为ndarray
:param y_predict: 预测类别,类型为ndarray
:return: 精准率,类型为float
'''
#********* Begin *********#
def TP(y_true, y_predict):
return np.sum((y_true ==1)&(y_predict == 1))
def FP(y_true,y_predict):
return np.sum((y_true ==0)&(y_predict==1))
tp =TP(y_true, y_predict)
fp =FP(y_true, y_predict)
try:
return tp /(tp+fp)
except:
return 0.0
#********* End *********#
def recall_score(y_true, y_predict):
'''
计算召回率并召回
:param y_true: 真实类别,类型为ndarray
:param y_predict: 预测类别,类型为ndarray
:return: 召回率,类型为float
'''
#********* Begin *********#
def FN(y_true, y_predict):
return np.sum((y_true ==1)&(y_predict == 0))
def TP(y_true,y_predict):
return np.sum((y_true ==1)&(y_predict==1))
fn =FN(y_true, y_predict)
tp =TP(y_true, y_predict)
try:
return tp /(tp+fn)
except:
return 0.0
#********* End *********#
8. F1 Score
上一节中提到了精准率变高,召回率会变低,精准率变低,召回率会变高。那如果想要同时兼顾精准率和召回率,这个时候就可以使用 F1 Score 来作为性能度量指标了。
F1 Score 是统计学中用来衡量二分类模型精确度的一种指标。
它同时兼顾了分类模型的准确率和召回率。 F1 Score 可以看作是模型准确率和召回率的一种加权平均,它的最大值是 1,最小值是 0。其公式如下:
- 假设模型 A 的精准率为 0.2,召回率为 0.7,那么模型 A 的 F1 Score 为 0.31111。
- 假设模型 B 的精准率为 0.7,召回率为 0.2,那么模型 B 的 F1 Score 为 0.31111。
- 假设模型 C 的精准率为 0.8,召回率为 0.7,那么模型 C 的 F1 Score 为 0.74667。
- 假设模型 D 的精准率为 0.2,召回率为 0.3,那么模型 D 的 F1 Score 为 0.24。
从上述 4 个模型的各种性能可以看出,模型C的精准率和召回率都比较高,因此它的 F1 Score 也比较高。而其他模型的精准率和召回率要么都比较低,要么一个低一个高,所以它们的 F1 Score 比较低。
这也说明了只有当模型的精准率和召回率都比较高时 F1 Score 才会比较高。
这也是 F1 Score 能够同时兼顾精准率和召回率的原因。
import numpy as np
def f1_score(precision, recall):
'''
计算f1 score并返回
:param precision: 模型的精准率,类型为float
:param recall: 模型的召回率,类型为float
:return: 模型的f1 score,类型为float
'''
#********* Begin *********#
try:
return 2*precision*recall / (precision+recall)
except:
return 0.0
#********* End ***********#
9. ROC曲线与AUC
9.1 ROC曲线
ROC曲线( Receiver Operating Cha\\fracteristic Curve )描述的 TPR ( True Positive Rate )与 FPR ( False Positive Rate )之间关系的曲线。
TPR 与 FPR 的计算公式如下:
其中 TPR 的计算公式您可能有点眼熟,没错!就是召回率的计算公式。
也就是说 TPR 就是召回率。
所以 TPR 描述的是模型预测 Positive 并且预测正确的数量占真实类别为 Positive 样本的比例。而 FPR 描述的模型预测 Positive 并且预测错了的数量占真实类别为 Negtive 样本的比例。
和精准率与召回率一样, TPR 与 FPR 之间也存在关系。假设有这么一组数据,菱形代表 Positive,圆形代表 Negtive。
现在需要训练一个逻辑回归的模型对数据进行分类,假如将从 0 到 1 中的一些值作为模型的分类阈值。
若模型认为当前数据是 Positive 的概率小于分类阈值则分类为 Negtive ,否则就分类为 Positive (假设分类阈值为 0.8,模型认为这条数据是 Positive 的概率为 0.7, 0.7 小于 0.8,那么模型就认为这条数据是 Negtive)。
在不同的分类阈值下,模型所对应的 TPR 与 FPR 如下图所示(竖线代表分类阈值,模型会将竖线左边的数据分类成 Negtive,竖线右边的分类成 Positive ):
从图中可以看出,当模型的 TPR 越高 FPR 也会越高, TPR 越低 FPR 也会越低。这与精准率和召回率之间的关系刚好相反。
并且,模型的分类阈值一但改变,就有一组对应的 TPR 与 FPR 。假设该模型在不同的分类阈值下其对应的 TPR 与 FPR 如下表所示:
若将 FPR 作为横轴, TPR 作为纵轴,将上面的表格以折线图的形式画出来就是 ROC曲线 。
假设现在有模型 A 和模型 B ,它们的 ROC 曲线如下图所示(其中模型 A 的 ROC曲线 为黄色,模型 B 的 ROC 曲线 为蓝色):
那么模型 A 的性能比模型 B 的性能好,因为模型 A 当 FPR 较低时所对应的 TPR 比模型 B 的低 FPR 所对应的 TPR 更高。
由于随着 FPR 的增大, TPR 也会增大。所以 ROC 曲线与横轴所围成的面积越大,模型的分类性能就越高。而 ROC曲线的面积称为AUC。
9.2 AUC
很明显模型的 AUC 越高,模型的二分类性能就越强。
AUC 的计算公式如下:
其中 M 为真实类别为 Positive 的样本数量,N 为真实类别为 Negtive 的样本数量。ranki 代表了真实类别为 Positive 的样本点额预测概率从小到大排序后,该预测概率排在第几。
举个例子,现有预测概率与真实类别的表格如下所示(其中 0 表示 Negtive, 1 表示 Positive ):
想要得到公式中的 rank,就需要将预测概率从小到大排序,排序后如下:
排序后的表格中,真实类别为 Positive 只有编号为 3 和编号为 4 的数据,并且编号为 3 的数据排在第 2 ,编号为 4 的数据排在第 4。所以 rank=[2, 4] 。又因表格中真是类别为 Positive 的数据有 2 条, Negtive 的数据有 2 条。因此 M 为2, N 为2。所以根据 AUC 的计算公式可知:
import numpy as np
def calAUC(prob, labels):
'''
计算AUC并返回
:param prob: 模型预测样本为Positive的概率列表,类型为ndarray
:param labels: 样本的真实类别列表,其中1表示Positive,0表示Negtive,类型为ndarray
:return: AUC,类型为float
'''
#********* Begin *********#
a= list(zip(prob,labels))
rank =[values2 for values1,values2 in sorted(a, key=lambda x:x[0])]
rankList=[i+1 for i in range(len(rank))if rank[i] ==1]
posNum =0
negNum =0
for i in range(len(labels)):
if(labels[i]==1):
posNum+=1
else:
negNum+=1
auc= (sum(rankList)-(posNum*(posNum+1))/2)/(posNum*negNum)
return auc
#********* End *********#
10. sklearn中的分类性能指标
10.1 accu\\fracy_score
sklearn 提供了计算准确度的接口 accu\\fracy_score
。其中参数如下:
- y_true:为样本真实标签,类型为一维的 ndarray 或者 list;
- y_pred:为模型预测标签,类型为一维的 ndarray 或者 list。
示例代码如下:
from sklearn.metrics import accu\\fracy_score
10.2 precision_score
sklearn 提供了计算精准率的接口 precision_score 。其中参数如下:
- y_true:为样本真实标签,类型为一维的 ndarray 或者 list;
- y_pred:为模型预测标签,类型为一维的 ndarray 或者 list;
- pos_label:用什么值表示 Positive,默认为 1。
示例代码如下:
from sklearn.metrics import precision_score
10.3 recall_score
sklearn 提供了计算召回率的接口 recall_score 。其中参数如下:
- y_true:为样本真实标签,类型为一维的 ndarray 或者 list;
- y_pred:为模型预测标签,类型为一维的 ndarray 或者 list;
- pos_label:用什么值表示 Positive ,默认为 1。
示例代码如下:
rom sklearn.metrics import recall_score
from sklearn.metrics import f1_score
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, roc_auc_score
def classification_performance(y_true, y_pred, y_prob):
'''
返回准确度、精准率、召回率、f1 Score和AUC
:param y_true:样本的真实类别,类型为`ndarray`
:param y_pred:模型预测出的类别,类型为`ndarray`
:param y_prob:模型预测样本为`Positive`的概率,类型为`ndarray`
:return:
'''
#********* Begin *********#
return accuracy_score(y_true, y_pred),precision_score(y_true, y_pred),recall_score(y_true, y_pred),f1_score(y_true, y_pred),roc_auc_score(y_true, y_prob)
#********* End *********#
以上是关于机器学习模型评估选择与验证的主要内容,如果未能解决你的问题,请参考以下文章