chapter 3 Classification

OReilly.Hands-On Machine Learning with Scikit-Learn and TensorFlow读书笔记

ch3 Classification

3.1 MNIST数据集

MNIST数据集:手写数字图片数据集。单标签多分类(0-9)。

获取MNIST:

from sklearn.datasets import fetch_mldata
#从下面地址
#https://raw.githubusercontent.com/amplab/datascience-sp14/master/lab7/mldata/mnist-original.mat
#下载mnist-original.mat文件放在./datasets/mnist/mldata目录中
mnist = fetch_mldata('MNIST original',data_home='./datasets/mnist')
mnist

结果

{'COL_NAMES': ['label', 'data'],
'DESCR': 'mldata.org dataset: mnist-original',
'data': array([[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
...,
[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0]], dtype=uint8),
'target': array([ 0., 0., 0., ..., 9., 9., 9.])}

结果显示,mnist是一个字典,包含下列键(key)值(value)对:

  • COL_NAMES:值为列名列表
  • DESCR:数据集的描述
  • data:值为数组,数组的每行代表一个样本,每列代表样本的一个特征
  • target:值为标签数组
>>> X, y = mnist["data"], mnist["target"]
>>> X.shape
(70000, 784)
>>> y.shape
(70000,)

可以看出,MNIST包含70000张图片,每张图片包含784个特征。这是因为每张图片的大小是 28 ∗ 28 = 784 28*28=784 2828=784像素。将其中某张图片reshape为 28 ∗ 28 28*28 2828的数组,然后用matplotlib的imshow函数显示出来,其中im代表image。

%matplotlib inline #使用了该magic命令,就可以省略plt.show()了
import matplotlib
import matplotlib.pyplot as plt

some_digit=X[36000]
some_digit_image=some_digit.reshape(28,28)
#cmap表示配色方案,interpolation表示插值方案
plt.imshow(some_digit_image,cmap=matplotlib.cm.binary,
          interpolation="nearest")
plt.axis("off")
#plt.show();

该图片的标签是5

>>> y[36000]
5.0

将数据集切分为训练集和测试集

X_train, X_test, y_train, y_test = X[:60000], X[60000:], y[:60000], y[60000:]

打乱数据集,以保证交叉验证的每一折(fold)中数据分布相差不多。

import numpy as np
shuffle_index=np.random.permutation(60000)
X_train,y_train=X_train[shuffle_index],y_train[shuffle_index]

3.2 训练一个二分类器

先从简化问题出发,训练一个二分类器,再推广到多分类器。例如,“数字5检测器”就是一个二分类器。

首先,需要将包含多个分类的标签向量y_train和y_test处理为包含两个分类的标签向量。

y_train_5 = (y_train == 5) # True for all 5s, False for all other digits.
y_test_5 = (y_test == 5)

然后,选择一个分类器。这里选用SGDClassifier,即随机梯度下降分类器(Stochastic Gradient Descent Classifier)。SGDClassifier的优点是可以有效地处理大规模数据集,原因在于其每次只随机选取一个实例来计算梯度,深度学习中CNN的训练也采用SGD。

from sklearn.linear_model import SGDClassifier
#加入参数random_state=42的目的是可以重现与教材中相同的结果
sgd_clf=SGDClassifier(random_state=42)
sgd_clf.fit(X_train,y_train_5)

预测结果是True,表明预测结果是数字5。

sgd_clf.predict([some_digit])
#array([ True], dtype=bool)

3.3 性能评价

3.3.1 利用交叉验证度量正确性

from sklearn.model_selection import cross_val_score
cross_val_score(sgd_clf, X_train, y_train_5, cv=3, scoring="accuracy")

结果

array([ 0.9502 , 0.96565, 0.96495]

精度通常来说只是对性能的粗略估计,并不是一个好的性能度量指标,特别是当你处理有偏差的数据集,比方说其中一些类(例如,本例中非5的实例)比其他类(标签为5的实例)频繁得多。

3.3.2 混淆矩阵

对于分类器来说,混淆矩阵(Confusion Matrix)是更好的性能评估指标。

获得预测值使用(method不指定,则为缺省参数"predict")cross_val_predict()函数,再利用confusion_matrix()函数计算混淆矩阵。

from sklearn.model_selection import cross_val_predict
y_train_pred = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3)
from sklearn.metrics import confusion_matrix
confusion_matrix(y_train_5, y_train_pred)

结果

array([[52572,  2007],
       [  941,  4480]], dtype=int64)

混淆矩阵的每一行代表一个实际的分类(例如本例中的非5和5),每一列代表预测的分类。

本例中,

实际分类\预测分类非5(Negative)5(Positive)
非552572(True negatives,TN2007(False positives,FP
5941(False positives,FN4480(True positives,TP

precision = T P T P + F P \textrm{precision}=\frac{TP}{TP+FP} precision=TP+FPTP

recall = T P T P + F N \textrm{recall}=\frac{TP}{TP+FN} recall=TP+FNTP

3.3.3 Precision and Recall

from sklearn.metrics import precision_score, recall_score,f1_score
precision_score(y_train_5,y_train_pred)#4480/(4480+2007)
recall_score(y_train_5,y_train_pred)#4480/(4480+941)

F 1 = 2 1 precision + 1 recall = 2 × precision × recall precision + recall = T P T P + F N + F P 2 F_1=\frac{2}{\frac{1}{\textrm{precision}}+\frac{1}{\textrm{recall}}}=2\times\frac{\textrm{precision}\times\textrm{recall}}{\textrm{precision}+\textrm{recall}}=\frac{TP}{TP+\frac{FN+FP}{2}} F1=precision1+recall12=2×precision+recallprecision×recall=TP+2FN+FPTP

from sklearn.metrics import f1_score
f1_score(y_train_5, y_pred)

F1青睐那些有着相近准确率(precision)和召回率(recall)的分类器。 但不能同时提高两者,增加准确率会降低召回率。这称为准确率和召回率之间的折中。

3.3.4 准确率和召回率之间的折中

要弄清这个折中,需要了解SGDClassifier如何工作。对于每个实例,根据决策函数(decision function)计算出一个分数(score)。如果这个分数对于一个给定的阈值(threshold),则将该实例分类为正(positive)类,否则为负(negative)类。

Scikit-Learn不提供直接设置阈值的功能,但提供访问决策函数decision_function()以取得分数。此时,可以使用任意阈值与分数相比较,做出预测。

y_scores=sgd_clf.decision_function([some_digit])
threshold = 200000
y_some_digit_pred = (y_scores > threshold)

分数由不同分类器的决策函数决定。对于同一分类器,决策函数固定,分数也固定。如何选择阈值,达到理想的准确率或召回率?

根据Scikit-Learn文档3.1,cross_val_predict()函数的返回值也可用于评价分类器。

再次使用cross_val_predict()函数,但这次通过指定method="decision_function"选项使得返回分数而不是预测值。

y_scores = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3,
method="decision_function")

利用得到的分数,可以计算出不同阈值下的准确率和召回率。

这里y_train_5是Boolean数组,而y_scores是分数,分数结合了不同阈值之后,可以得到Boolean数组,例如前面提到的y_some_digit_pred = (y_scores > threshold)。然后,结合两个Boolean数组可以得到准确率和召回率。

from sklearn.metrics import precision_recall_curve
precisions,recalls,thresholds = precision_recall_curve(y_train_5,y_scores)

thresholds的个数比precisions和recalls少一个。例如,三个thresholds[0.3,0.6,0.9]构成了三个区间, , , [ 0.3 , 0.6 ) , [ 0.6 , 0.9 ) , [ 0.9 , + ∞ ] ,,[0.3,0.6),[0.6,0.9),[0.9,+\infin] [0.3,0.6)[0.6,0.9)[0.9,+],分别对应于precisions [ : − 1 ] [:-1] [:1]和recalls [ : − 1 ] [:-1] [:1],最后一个precision和recall是1和0,没有对应的threshold。

作图,把准确率和召回率作为阈值的函数。通过图像可以直观地选择心仪的准确率或召回率。

def plot_precision_recall_vs_threshold(precisions,recalls,thresholds):
    plt.plot(thresholds,precisions[:-1],"b--",label="Precision")
    plt.plot(thresholds,recalls[:-1],"g-",label="Recall")
    plt.xlabel("Threshold")
    plt.legend(loc="upper left")
    plt.ylim([0,1])
plot_precision_recall_vs_threshold(precisions,recalls,thresholds)
plt.show();

3.3.5 ROC曲线

ROC(Receiver Operating Characteristic)曲线是另一个评价二分类器的工具。ROC曲线是TPR(true positive rate,等价于recall)对FPR(false positive rate)。FPR等于 1 − T N R 1-TNR 1TNR,TNR又称为specificity。
F P R = F P F P + T N = 1 − T N F P + T N = 1 − T N R FPR=\frac{FP}{FP+TN}=1-\frac{TN}{FP+TN}=1-TNR FPR=FP+TNFP=1FP+TNTN=1TNR
绘制ROC曲线:先用roc_curve()函数计算不同阈值下的TPR和FPR。

from sklearn.metrics import roc_curve
fpr,tpr,thresholds =roc_curve(y_train_5,y_scores)

然后,绘制ROC曲线。

def plot_roc_curve(fpr,tpr,label=None):
    plt.plot(fpr,tpr,linewidth=2,label=label)
    plt.plot([0,1],[0,1],'k--')
    plt.axis([0,1,0,1])
    plt.xlabel("False Positive Rate")
    plt.ylabel("True Positive Rate")
plot_roc_curve(fpr,tpr)
plt.show;

同样存在折中:召回率(TPR)越高,则分类器会产生更多false positives(FPR)。好的分类器应该向左上角靠拢。

一个比较分类器优劣的方法是:测量ROC曲线下的面积(AUC)。一个完美的分类器的
ROC AUC 等于 1,而一个纯随机分类器的 ROC AUC 等于 0.5。Scikit-Learn 提供了一个函
数来计算 ROC AUC:

from sklearn.metrics import roc_auc_score
roc_auc_score(y_train_5,y_scores)

选择ROC曲线或准确率/召回率曲线(PR曲线)的经验法则(a rule of thumb)是:当正例稀少,或者对false positive比false negative更在意时,选择PR曲线,其他时候选择ROC曲线。

举个例子,利用ROC曲线和ROC AUC值比较RandomForestClassifier和SGDClassifier。

首先,需要利用决策函数decision_function()以取得RandomForestClassifier在训练集上的分数。但是,RandomForestClassifier不提供decision_function()方法,取而代之的是predict_proba()方法。Scikit-Learn的分类器通常都有这两个方法中的一个。predict_proba() 方法返回一个二维数组,数组的每一行代表一个实例,每一列代表一个类。数组第 i i i行第 j j j列的值的表示“实例 i i i属于类 j j j的概率”。 本例中,只有两个类,第0列“非5”类,第1列“5”类。

from sklearn.ensemble import RandomForestClassifier
forest_clf = RandomForestClassifier(random_state=42)
y_probas_forest=cross_val_predict(forest_clf,X_train,y_train_5,cv=3,
                                 method="predict_proba")

将第1列(实例属于正例的概率)作为分数。画出两个分类器的ROC曲线,其中fpr和tpr是用y_train_5和y_scores计算出来的,而y_scores是sgd_clf分类器计算出来的。

y_scores_forest=y_probas_forest[:,1]
fpr_forest,tpr_forest,thresholds_forest =roc_curve(y_train_5,y_scores_forest)
plt.plot(fpr,tpr,"b:",label="SGD")
plot_roc_curve(fpr_forest,tpr_forest,"Random Forest")
plt.legend(loc="lower right")
plt.show();

计算ROC AUC

roc_auc_score(y_train_5,y_scores_forest)

如果计算precision和recall,需要y_train_5和y_scores_forest具有相同的数据分布,前者是离散值,只有0和1,后者是[0,1]区间内的连续值。

y_scores_forest_binary=(y_scores_forest>0.5)
precision_score(y_train_5,y_scores_forest_binary)
recall_score(y_train_5,y_scores_forest_binary)

3.4 多分类

随机森林或朴素贝叶斯分类器可以直接处理多分类问题,而SVM和线性分类器是严格的二分类器。可以用二分类器实现多分类的策略包括:

  • OvA(One vs. All):对于n分类问题,训练n个分类器,其中第i个将实例划分为“非i类”和“i类”。
  • OvO(One vs. One):对于n分类问题,训练n*(n-1)/2个分类器,每个分类器将实例分为“i类”和“j类”。OvO策略的优点是,只需在部分数据上训练。

当Scikit-Learn侦测到你想使用二分类器来处理多分类任务,它会自动采取OvA策略(SVM是例外,采用OvO策略)。例如,

sgd_clf.fit(X_train,y_train)
sgd_clf.predict([some_digit])

结果是

array([5.])

得到了一个多分类器。为了证明,可以调用decision_function() 方法。可以看到,输入一个实例,函数返回 10 个数值,一个数值对应于一个类,而不是二分类时的一个数值。

some_digit_scores =sgd_clf.decision_function([some_digit])
some_digit_scores

结果是

array([[-166010.25384852, -382404.03459972, -342825.36264187,
        -163214.57470472, -313433.81848567,   76316.70682816,
        -695068.73595379, -336627.43277419, -775970.84741497,
        -824739.99872098]])

最高数值对应于类别 5 :

np.argmax(some_digit_scores)#5
sgd_clf.classes_ #array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9.])
sgd_clf.classes_[5]#5.0 

如果想强制 Scikit-Learn 使用 OvO 策略或者 OvA 策略,可以使用 OneVsOneClassifier 类或者 OneVsRestClassifier 类。创建一个类的实例,并向其构造函数传入一个二分类器。例如,下面的代码基于二分类器基于 SGDClassifier创建一个多类分类器,使用 OvO 策略。

from sklearn.multiclass import OneVsOneClassifier
ovo_clf=OneVsOneClassifier(SGDClassifier(random_state=42))
ovo_clf.fit(X_train,y_train)
ovo_clf.predict([some_digit])#array([5.])
len(ovo_clf.estimators_)#45

随机森林分类器RandomForestClassifier 可以直接将一个实例分到多个类别。

forest_clf.fit(X_train, y_train)
forest_clf.predict([some_digit])#array([ 5.])

调用 predict_proba() ,可以得到实例对应类别的概率值列表。

forest_clf.predict_proba([some_digit])
#array([[0.2, 0. , 0. , 0.2, 0. , 0.6, 0. , 0. , 0. , 0. ]])

用cross_val_score粗略评价分类器。

cross_val_score(sgd_clf, X_train, y_train, cv=3, scoring="accuracy")
#array([0.86907618, 0.83494175, 0.85412812])

简单缩放输入,会将精度提高到 90% 以上。

from sklearn.preprocessing import StandardScaler
scaler=StandardScaler()
X_train_scaled=scaler.fit_transform(X_train.astype(np.float64))
cross_val_score(sgd_clf,X_train_scaled,y_train,cv=3,scoring="accuracy")
#array([0.91246751, 0.90829541, 0.90703606])

与随机二分类器不同,使用随机多分类器,只能得到10%的精度(10分类)。

3.5 误差分析

通过分析误差的类型来改进模型而不是选择模型,选择模型需要尝试多个模型,而确定模型后选择模型超参数则使用GridSearchCV()函数来调试。

首先,使用混淆矩阵的方法。先用cross_val_predict()函数得到预测值,再用confusion_matrix()函数得到混淆矩阵。假设已选定SGDClassifier模型。

y_train_pred=cross_val_predict(sgd_clf,X_train_scaled,y_train,cv=3)
conf_mx=confusion_matrix(y_train,y_train_pred)
conf_mx

使用Matplotlib的matshow()方法绘制混淆矩阵,mat代表matrix。

plt.matshow(conf_mx,cmap=plt.cm.gray)
plt.show();

其中,代表5的对角线上图片看起来颜色更深,意味着数据集中5的图片较少或者分类较差。

下面关注误差。首先,将混淆矩阵中每个值除以对应类别的图片总数,以比较误差率而不是误差的绝对数量。axis=1表示按行求和。如果选择keepdims=True,则意味着保留维度,得到的是一个 n ∗ 1 n*1 n1维数组。如果keepdims=False(缺省值),则不保留维度,得到 1 ∗ n 1*n 1n维数组。

row_sums=conf_mx.sum(axis=1,keepdims=True)
nor_conf_mx=conf_mx/row_sums

然后,将对角线置为0,只保留误差,并绘出结果。

np.fill_diagonal(norm_conf_mx,0)
plt.matshow(norm_conf_mx,cmap=plt.cm.gray)
plt.show();

黑的图片代表分类正确,亮的图片代表误分类。例如,很多3被误分为5。

将涉及3和5的图片绘制出来。

cl_a,cl_b=3,5
X_aa=X_train[(y_train==cl_a)&(y_train_pred==cl_a)]
X_ab=X_train[(y_train==cl_a)&(y_train_pred==cl_b)]
X_ba=X_train[(y_train==cl_b)&(y_train_pred==cl_a)]
X_bb=X_train[(y_train==cl_b)&(y_train_pred==cl_b)]

plt.figure(figsize=(8,8))
plt.subplot(221);plot_digits(X_aa[:25],images_per_row=5)
plt.subplot(222);plot_digits(X_ab[:25],images_per_row=5)
plt.subplot(223);plot_digits(X_ba[:25],images_per_row=5)
plt.subplot(224);plot_digits(X_bb[:25],images_per_row=5)
plt.show();

其中,plot_digits()函数定义如下:

def plot_digits(instances, images_per_row=10, **options):
    size = 28
    images_per_row = min(len(instances), images_per_row)
    images = [instance.reshape(size,size) for instance in instances]
    n_rows = (len(instances) - 1) // images_per_row + 1
    row_images = []
    n_empty = n_rows * images_per_row - len(instances)
    images.append(np.zeros((size, size * n_empty)))
    for row in range(n_rows):
        rimages = images[row * images_per_row : (row + 1) * images_per_row]
        row_images.append(np.concatenate(rimages, axis=1))
    image = np.concatenate(row_images, axis=0)
    plt.imshow(image, cmap = matplotlib.cm.binary, **options)
    plt.axis("off")

最后,给出的解决方案是对图片预处理。

3.6 多标签分类

输出多个二元标签的分类系统称为多标签分类系统。多标签分类系统一定是一个多分类系统。例如,数据集有三个分类A、B、C,每个实例可能属于1-3个分类,则输出标签为[1,0,1]代表该实例属于A、C,不属于B。

包含两个标签的多标签例子,第一个标签表示图片中数字是否>=7,第二个标签表示图片中数字是否为奇数。

from sklearn.neighbors import KNeighborsClassifier
y_train_large=(y_train>=7)
y_train_odd=(y_train%2==1)
y_multilabel=np.c_[y_train_large,y_train_odd]

knn_clf=KNeighborsClassifier()
knn_clf.fit(X_train,y_multilabel)
knn_clf.predict([some_digit])
#结果 array([[False, True]], dtype=bool)

评价多标签分类器和选择正确的度量标准,取决于具体项目。下面是计算单个标签的F1分数的代码,多个标签则单独计算后求平均值。

y_train_knn_pred=cross_val_predict(knn_clf,X_train,y_train,cv=3)
f1_score(y_train,y_train_knn_pred,average="macro")

如果认为不同标签重要性不同,可以通过设置average="weighted"来实现,其中权重与具有该标签的实例数目相关。

3.7 多输出分类

又称多输出多分类(multioutput-multiclass classification)。与多标签分类相比,每个标签可以是多个类别(不只是二值的)。举例来说,如果一个分类器(已经与传统意义上的分类器相去甚远)的输入是一个带噪声的图片,输出一个去噪的图片,这是一个多标签输出(每个像素一个标签),每个标签有多个值(介于0到255的像素值)。

至此,区分一下二分类、多分类、多标签、多输出。

二分类:实例属于两个类别中的一个,每个实例的标签是单值,True或False。

多分类(单标签):实例属于n个类别中的一个(n>2),每个实例的只有一个标签i,表示该实例属于类别i。

多标签:实例属于n个类别中的m个类别(n>=2,m>=2),每个实例的标签是一个一维数组,具有n个元素,如果实例属于第i类和第j类,则第i和第j个元素为True,其余为False。有时也可以表示为标签数组的形式,如[i,j]。

多输出:是多标签分类的泛化,其中每个标签不只有两个可能值True和False,可以有多个可能值。事实上,多输出不只限于分类,上面的关于去噪的例子,输出像素标签更类似于回归。

例子:为像素添加噪声,再去噪。

#添加噪声
noise = np.random.randint(0, 100, (len(X_train), 784))
X_train_mod = X_train + noise
noise = np.random.randint(0, 100, (len(X_test), 784))
X_test_mod = X_test + noise
y_train_mod = X_train
y_test_mod = X_test

训练模型,分别绘出带噪声和去噪后的图片。

knn_clf.fit(X_train_mod, y_train_mod)
clean_digit = knn_clf.predict([X_test_mod[100]])
plot_digits(X_test_mod[100].reshape(1,784))
plot_digits(clean_digit)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值