首先引入误差的概念,误差(error)是指:学习器的实际预测输出与样本的真实输出之间的差异。类似地,学习器在训练集上的误差被称之为训练误差(training error)或者经验误差(empirical error),学习器在新样本上的误差称之为泛化误差(generalization error)(泛化误差亦即所学习到的模型的期望风险),学习器在测试集上的样本被称之为测试误差(testing error)。通常,测试误差被用于近似泛化误差。
统计学习方法的目的是学习训练集数据的特征和结构从而进行建模进而完成对新数据某种特征的预测,从这个角度出发,我们希望模型具有良好的预测能力,在现实的训练过程中,通常会观察模型在训练集和测试集上的误差来对模型学习的水平进行评估。
机器学习似乎在某种程度上也恪守了中国传统的中庸之道,过于简单或者过于复杂的模型可能导致所谓的“欠拟合”和“过拟合”。
就我的理解而言,“欠拟合”即模型没有学习到数据的内在结构和本质规律,不论是在训练集还是在测试集上都不能达到较好的效果,而“过拟合”指模型可以非常好甚至于完美的拟合训练集之中的数据,但在过拟合问题中出现的问题是:以特殊代替了一般,模型不仅对数据的一般规律进行了拟合,也对数据中的“个例”进行了拟合。当模型发生过拟合的问题时,其在测试集上的性能往往十分低下。
换言之,当模型出现“欠拟合”或者“过拟合”的问题时,模型的泛化性能通常十分差劲。
在上文中提到,在模型训练过程中,我们通常利用模型在测试集上的误差对模型的泛化误差进行近似。在进行这个操作前,首先要注意的是我们假设测试样本也是从样本的真实分布中独立同分布采样而得且测试集中的样本应该不在训练集中出现。通常,在面对一个包含m个训练样本的数据集,在训练开始之前,我们应该对其进行适当的处理从而产生出训练集和测试集。下述是具体的一些处理方法。
留出法(hold-out):即直接将数据集划分为两个互斥的集合,一个用于训练学习模型,一个用于测试得到测试误差对泛化误差进行估计。在使用留出法的过程中,需要注意的是训练集和测试集的划分要尽可能保证数据分布的一致性。使用留出法时,一般采用若干次随机划分、重复进行试验评估后取平均值作为留出法的评估结果。在sklearn中,完成对数据集划分的函数是:
sklearn.model_selection.train_test_split(*arrays, test_size=None, train_size=None, random_state=None, shuffle=True, stratify=None)
交叉验证法可以简单理解为留出法的进阶版本,交叉验证法是为了尽可能利用现有的数据。交叉验证法先将数据集划分为k个大小相似的互斥子集(每个子集都尽可能保持数据分布的一致性),然后用其中k-1个子集的并集作为训练集,剩下的1个子集作为测试集。交叉验证法通常又被称为“k折验证法”,当k值与数据集中的样本个数相同时,又称交叉验证法为“留一法”。
“留一法”的评估结果往往被认为比较准确,但当数据集比较大时,训练m个模型的计算开销可能是难以忍受的。在sklearn中,完成交叉验证相关任务的函数有:
#返回一个score值
sklearn.model_selection.cross_val_score(estimator, X, y=None, *, groups=None, scoring=None, cv=None, n_jobs=None, verbose=0, fit_params=None, pre_dispatch='2*n_jobs', error_score=nan)
#返回预测标签
sklearn.model_selection.cross_val_predict(estimator, X, y=None, *, groups=None, cv=None, n_jobs=None, verbose=0, fit_params=None, pre_dispatch='2*n_jobs', method='predict')
#k折切割数据
sklearn.model_selection.KFold(n_splits=5, *, shuffle=False, random_state=None)
自助法以“自助采样”为基础,在给定包含m个样本的数据集d中:每次随机从d中挑选一个样本,拷贝放入d’中,再将该样本放回d中。该操作重复m次后,d‘中样本数量达到m个。最后将d’用作于训练集,d\d‘用作测试集。
自助法在测试集较小、难以有效划分训练和测试集时比较有用。
机器学习常涉及两类参数:一类是算法的参数,一类是模型的参数,所谓调参,就是对算法参数进行调节以达到满意的效果。在此处引入“验证集”(validation set)的概念,模型评估与选择中用于评估测试的数据集常称之为验证集。在研究对比不同算法的泛化性能时,用测试集上的判别效果估计模型在实际使用时的泛化能力,而把训练数据另外划分为训练集和验证集,基于验证集上的性能来进行模型选择和调参。
推荐关于调参写得比较好的一篇blog:
https://www.kaggle.com/kevinarvai/fine-tuning-a-classifier-in-scikit-learn
为了对学习器的泛化性能进行评估,不仅需要实验估计方法还需要衡量标准,这就引入了性能度量。性能度量反映的是任务需求,模型的“好坏”是相对的,其不仅取决于算法和数据,还决定于任务需求。常用的性能度量如下:
错误率即分类错误的样本占全体样本的比例,精度即分类正确的样本占样本总数的比例。
首先引入混淆矩阵:
预测结果 | ||
---|---|---|
真实情况 | 正例 | 反例 |
正例 | TP | FN |
反例 | FP | TN |
理解混淆矩阵时可以简单地从首字母缩写的角度来记忆,TP即True Positive:实际为真预测也为真,FP即False Positive:实际为假但预测为真,但对于FN和TN要注意:FN即False Negative:实际为真预测为假,TN即True Negative:实际为假预测也为假。后两者是需要留意的。
进而引出查准率和查全率,对于二者的理解,西瓜书上结合实际给出了狠好的说明,结合信息检索,查准率指的是检索出来的信息有多少是用户所感兴趣的,查全率指的是有多少用户感兴趣的信息被检索出来了。二者的计算公式如下:
同时,Precision和Recall是一对矛盾的值,二者一般呈现反比的关系,再次引用西瓜书中的一个例子,即若想使得挑出来的西瓜都是好瓜,那么可以提高挑西瓜的总数来达到,但是这样Precision值就会比较低。若是想要使挑出来是好瓜的比例尽可能的高,就要尽可能选择自己更有把握的瓜,但这样会漏掉不少好瓜,使得Recall值比较低。
PR曲线即查准率-查全率曲线(Precision-Recall Curve),P-R曲线以查全率为横轴,以查准率为纵轴,直观地显示了学习器在样本总体上的查全率和查准率。通过PR曲线来对学习器性能进行比较的方法有:
通过BEP来进行性能比较过于简单,更加常用的时F1度量:
F 1 = 2 ∗ p ∗ R P + R = 2 ∗ T P 样 例 总 数 + T P − F N F1=\frac{2*p*R}{P+R}=\frac{2*TP}{样例总数+TP-FN} F1=P+R2∗p∗R=样例总数+TP−FN2∗TP
更一般的:
F β = ( 1 + β 2 ) ∗ P ∗ R ( β 2 ∗ P ) + R F\beta=\frac{(1+\beta^2)*P*R}{(\beta^2*P)+R} Fβ=(β2∗P)+R(1+β2)∗P∗R
其中, β \beta β度量了查全率对查准率的相对重要性, β \beta β=1时退化为标准的F1; β \beta β>1查全率影响更大, β \beta β<1时查准率影响更大。
precision, recall, thresholds1 = precision_recall_curve(y_test, score)
def plot_PR(precision, recall):
## PR曲线
lw = 2
plt.figure(figsize=(10,10))
plt.plot(recall, precision, color='darkorange', lw=lw, label='PR_Curve')
plt.grid()
plt.plot([0,1], [0,1], color='navy', lw=lw, linestyle='--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('Recall')
plt.ylabel('Precision')
plt.title('PR')
plt.legend(loc='lower right')
plt.show()
ROC曲线即接收者操作特性曲线(receiver operating characteristic),ROC曲线以假正例率(FPR)作为横轴, 以真正例率(TPR)作为纵轴,二者定义如下:
在利用ROC曲线图对学习器进行比较时,类似的,如果一个学习器的ROC曲线完全包住另一个学习器的ROC曲线,那么认为前一个学习器优于后一个学习器。通过ROC曲线下面积即AUC也可以对学习器的优劣进行判断。
现实任务中通常利用有限个测试样例来绘制ROC图,此时仅能获得有限个坐标对,绘制过程如下:
还有一种更为直观的绘制ROC曲线的方法:
fpr, tpr, thresholds = metrics.roc_curve(y_test, score, pos_label=1)
roc_auc = metrics.auc(fpr, tpr)
def plot_ROC(fpr, tpr):
lw = 2
plt.figure(figsize=(10,10))
plt.plot(fpr, tpr, color='darkorange', lw=lw, label='ROC_Curve (area = % 0.2f)' % roc_auc,)
plt.grid()
plt.plot([0,1], [0,1], color='navy', lw=lw, linestyle='--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC')
plt.legend(loc='lower right')
plt.show()