在本篇文章中,我们使用著名的手写数字数据集mnist做为例子.使用sklearn.datasets
的fetch_openml()
方法可以下载该数据集:
from sklearn.datasets import fetch_openml
# 下载minst数据集,data_home属性指定存储数据集的路径
mnist = fetch_openml('MNIST original', data_home='dataset_home')
X, y = mnist['data'], mnist['target'] # X每一行存储一张28*28的点阵图,y为对应的数字
# 展示一张图片
some_digit_image = X[36000].reshape(28, 28)
plt.imshow(some_digit_image, cmap=matplotlib.cm.binary, interpolation='nearest')
plt.show()
交叉验证
在训练时,我们常使用k折交叉验证扩充数据集.
我们将给定样本分为 k k k份,每次拿出 k − 1 k-1 k−1份进行训练,留下 1 1 1份用于测试,重复 k k k次.取每一次误差的平方和作为最终的误差.
使用sklearn.model_selection
模块的cross_val_predict()
方法可以使用k折交叉验证进行预测,cross_val_score()
方法可以使用k折交叉验证对计算某指标.
函数各参数意义:
estimator
: 待训练的模型.X
和y
: 训练数据.cv
: 交叉验证的折数.scoring
:cross_val_score()
方法计算的指标.method
:cross_val_predict()
方法调用的模型的方法.
sgd_clf = SGDClassifier(loss='log') # 创建一个逻辑回归分类器
# 进行3折交叉验证,评估其准确率
cross_val_score(sgd_clf, X_train, y_train, cv=3, scoring='accuracy')
# 进行3折交叉验证,调用分类器的predict方法进行预测
cross_val_predict(sgd_clf, X_train, y_train, cv=3, method='predict')
# 进行3折交叉验证,调用分类器的predict_proba方法得到概率值
cross_val_predict(sgd_clf, X_train, y_train, cv=3, method='predict_proba')
评估指标
混淆矩阵
混淆矩阵是一张分类模型预测结果的情形分析表,其行索引为列表为样本的真实类别,列索引为样本的预测结果,表格中每一项表示对应真实类别与预测结果的样本的数量.
在主对角线上的样本预测正确,非主对角线上的样本预测错误.
当我们进行二分类时,混淆矩阵退化成为一个2×2的表格.
预测值 | |||
---|---|---|---|
负例(Negative) | 正例(Positive) | ||
真 实 值 | 负例(Negative) | 真阴性(True Negative,TN) | 假阳性(False Positive,FP) 第一类错误 |
正例(Positive) | 假阴性(False Negative,FP) 第二类错误 | 真阳性(True Positive,TP) |
调用sklearn.metrics
模块的confusion_matrix(y_true, y_pred)
方法可以计算混淆矩阵,其中y_true
为真实值数组,y_pred
为预测值数组.
print(y_true) # array([False, False, False, ..., False, False, False])
print(y_pred) # array([False, False, False, ..., False, False, False])
print(confusion_matrix(y_labeled, y_predict))
# 得到 array([[61413, 2274], [ 6093, 220]], dtype=int64)
准确率(Accuracy),精确率(Precision),召回率(Recall)
根据二分类的混淆矩阵,我们分别定义三个指标
预测值 | |||
---|---|---|---|
负例(Negative) | 正例(Positive) | ||
真 实 值 | 负例(Negative) | 真阴性(True Negative,TN) | 假阳性(False Positive,FP) 第一类错误 |
正例(Positive) | 假阴性(False Negative,FN) 第二类错误 | 真阳性(True Positive,TP) |
-
准确率(Accuracy): 所有样本中被正确预测的比例.
A c c u r a c y = T P + T N T P + F P + T N + F N Accuracy = \frac{TP + TN}{TP + FP + TN + FN} Accuracy=TP+FP+TN+FNTP+TN
准确率Accuracy关心的是整体的正确率,在样本正负例个数不平衡时不是一个好的评估指标.考虑如下情况: 当样本中绝大多数都是负例时,我们的分类器只要无脑判负即可使得准确率Accuracy很高,但这并不是一个好的模型.
下面例子判断minst数据集中手写图片的数字是否为5,我们创建一个无脑判负的分类器,并将其和逻辑回归模型相对比.
import numpy as np
from sklearn.base import BaseEstimator
from sklearn.datasets import fetch_openml
from sklearn.linear_model import SGDClassifier
from sklearn.model_selection import cross_val_score
# 获取数据集并打乱原始数据集
mnist = fetch_openml('MNIST original', data_home='dataset_home')
X, y = mnist['data'], mnist['target']
shuffle_index = np.random.permutation(X.shape[0])
X_train, y_train = X[shuffle_index], y[shuffle_index]
y_train_5 = (y_train == 5)
# 创建一个逻辑回归分类器并进行交叉验证
sgd_clf = SGDClassifier(loss='log', max_iter=1000, tol=1e-4)
cross_val_score(sgd_clf, X_train, y_train_5, cv=3, scoring='accuracy')
# 进行三折交叉验证得到的准确率为 array([0.96532956, 0.96897099, 0.96018515])
cross_val_score(sgd_clf, X_train, y_train_5, cv=3, scoring='precision')
# 进行三折交叉验证得到的精确率为 array([0.95501022, 0.67672576, 0.87068966])
cross_val_score(sgd_clf, X_train, y_train_5, cv=3, scoring='recall')
# 进行三折交叉验证得到的召回率为 array([0.89643705, 0.87262357, 0.70722433])
# 创建一个无脑判负的分类器并进行交叉验证
class Never5Classifier(BaseEstimator):
def fit(self, X, y=None):
pass
def predict(self, X):
return np.zeros((len(X), 1), dtype=bool)
never_5_clf = Never5Classifier()
cross_val_score(never_5_clf, X_train, y_train_5, cv=3, scoring='accuracy')
# 进行三折交叉验证得到的准确率为 array([0.91188823, 0.90957014, 0.9079844 ])
cross_val_score(never_5_clf, X_train, y_train_5, cv=3, scoring='precision')
# 进行三折交叉验证得到的精确率为 array([0., 0., 0.])
cross_val_score(never_5_clf, X_train, y_train_5, cv=3, scoring='recall')
# 进行三折交叉验证得到的召回率为 array([0., 0., 0.])
可以看到,无脑判负的Never5Classifier
虽然准确率很高,但是精确率和召回率都很低.
-
精确率(Precision): 所有预测为正例的样本中被正确预测的比例.
P r e c i s i o n = T P T P + F P Precision = \frac{TP}{TP + FP} Precision=TP+FPTP
-
召回率(Recall): 所有真实值为正例的样本中被正确预测的比例.
R e c a l l = T P T P + F N Recall = \frac{TP}{TP+FN} Recall=TP+FNTP
精确率Precision和召回率Recall只关心正例,它们两者是相互抑制(trade off)的关系,考虑如下情况:
对于一个分类系统,我们将用户喜欢视为正例,用户不喜欢视为负例,这时我们只关心推荐的商品中用户喜欢的有多少(TP),因此应考虑精确率Precision和召回率Recall作为评价指标.
- 假设用户实际喜欢的所有商品(TP+FN)有10000个,推荐系统向他推荐(TP+FP)10个商品,用户最终下单(TP)了3个商品.则此时的精确率Precision为30%,召回率Recall为0.003%.
- 为了让用户多下单(增加TP),我们降低阈值(threshold),向用户推荐(TP+FP)1000个商品,用户最终下单(TP)了30个商品.则此时的精确率Precision为3%,召回率Recall为0.03%.召回率Recall上升,而精确率Precision却下降了.(这有点类似于’言多必失’的道理).
调用sklearn.metrics
模块的accuracy_score(y_true, y_pred)
,precision_score(y_true, y_pred)
和recall_score(y_true, y_pred)
分别可以计算预测结果的准确率,精确率和召回率,其中y_true
为真实值数组,y_pred
为预测值数组.
使用[precisions, recalls, thresholds] = precision_recall_curve(y_true, probas_pred)
方法可以生成精确率Precision和召回率Recall曲线数据,示例如下:
# 获取逻辑回归表达式计算正例的概率
y_scores = cross_val_predict(sgd_clf, X_train, y_train, cv=3, method='decision_function')
# 生成 precisions,recalls,thresholds曲线
precisions, recalls, thresholds = precision_recall_curve(y_train, y_scores)
# 绘制 precisions,recalls,thresholds曲线
plt.plot(thresholds, precisions[:-1], 'b--', label='Precision')
plt.plot(thresholds, recalls[:-1], 'r--', label='Recall')
plt.xlabel("Threshold")
plt.legend(loc='upper left')
plt.ylim([0, 1])
plt.show()
F1-score
因为精确率Precision和召回率Recall是一个此消彼长的关系,因此我们有必要将两者综合成一个指标.
F1-Score被定义为精确率Precision和召回率Recall的调和平均数:
F 1 = 2 1 P r e c i s i o n + 1 R e c a l l = 2 ⋅ P r e c i s i o n × R e c a l l P r e c i s i o n + R e c a l l F_1 = \frac{2}{\frac{1}{Precision}+\frac{1}{Recall}} = 2 \cdot \frac{Precision \times Recall}{Precision + Recall} F1=Precision1+Recall12=2⋅Precision+RecallPrecision×Recall
更一般的,有Fβ-Score,β越大,召回率Recall所占权重越高.
F β = 1 + β 2 1 P r e c i s i o n + β 2 R e c a l l = ( 1 + β 2 ) ⋅ P r e c i s i o n × R e c a l l β 2 P r e c i s i o n + R e c a l l F_{\beta} = \frac{1+\beta^2}{\frac{1}{Precision} + \frac{\beta^2}{Recall}} = (1+\beta^2) \cdot \frac{Precision \times Recall}{\beta^2 \, Precision+Recall} Fβ=Precision1+Recallβ21+β2=(1+β2)⋅β2Precision+RecallPrecision×Recall
调用sklearn.metrics
模块的f1_score(y_true, y_pred)
和fbeta_score(y_true, y_pred, beta)
方法分别可以计算预测结果的F1-Score和Fβ-Score,其中y_true
为真实值数组,y_pred
为预测值数组.
AUC
ROC(receiver operating characteristic)曲线.ROC曲线上每个点反映着对同一信号刺激的感受性,即在不同阈值(threshold)下的负正类率(False Positive Rate,FPR)和真正类率(True Positive Rate,TPR).
-
真正类率TPR表示所有真实值为正例的样本中,被正确预测为正例的样本的比例.
T P R = T P T P + F N TPR = \frac{TP}{TP+FN} TPR=TP+FNTP
-
负正类率FPR表示所有真实值为负例的样本中,被错误预测为正例的样本的比例.
F P R = F P F P + T N FPR = \frac{FP}{FP+TN} FPR=FP+TNFP
根据定义,易知ROC曲线为一个过
(
0
,
0
)
(0,0)
(0,0)点和
(
1
,
1
)
(1,1)
(1,1)点的曲线.
AUC(Area under Curve)为ROC曲线下方的面积,即TPR对FPR积分的结果.容易看出AUC更看重正例.
调用sklearn.metrics
模块的roc_curve(y_true, y_score)
方法可以生成ROC曲线数据,调用roc_auc_score(y_true, y_score)
方法可以计算AUC值.
# 使用逻辑回归分类器计算正例的概率
sgd_clf = SGDClassifier(loss='log', max_iter=1000, tol=1e-4)
y_scores_sgd = cross_val_predict(sgd_clf, X_train, y_train, cv=3, method='decision_function')
fpr_sgd, tpr_sgd, thresholds_sgd = roc_curve(y_train, y_scores)
# 使用随机森林分类器计算正例的概率
forest_clf = RandomForestClassifier(random_state=42)
y_probas_forest = cross_val_predict(forest_clf, X_train, y_train, cv=3, method='predict_proba')
y_scores_forest = y_probas_forest[:, 1]
fpr_forest, tpr_forest, thresholds_forest = roc_curve(y_train, y_scores_forest)
# 绘制ROC曲线
plt.plot(fpr_sgd, tpr_sgd, 'b:', label='SGD(AUC=%.5f)' % roc_auc_score(y_train, y_scores_sgd))
plt.plot(fpr_forest, tpr_forest, label='Random Forest(AUC=%.5f)' % roc_auc_score(y_train, y_scores_forest))
plt.legend(loc='lower right')
plt.show()