分类 手写体数字识别

  • 1.数据集
    • 分离训练集和测试集
  • 2.训练一个二分类器
  • 3.评价分类器的性能
    • 使用交叉验证分类准确率
    • 精准率和召回率
      • 混淆矩阵
      • 精准率和召回率的折衷
    • ROC 曲线
  • 4.多分类器
  • 5.误差分析
  • 6.多标签分类
  • 7.多输出分类
  • 8.参考链接

1.数据集

我们将会使用 MNIST 这个数据集,它有着 70000 张规格较小的手写数字图片,由美国的高中生和美国人口调查局的职员手写而成。这相当于机器学习当中的“Hello World”。

Scikit-Learn 提供了许多辅助函数,以便于下载流行的数据集。MNIST 是其中一个。下面的代码获取 MNIST。

import os
import scipy.io as sio
mat_path = os.path.join('mnist-original.mat')
mnist = sio.loadmat(mat_path)

数据格式如下

{'__header__': b'MATLAB 5.0 MAT-file Platform: posix, Created on: Sun Mar 30 03:19:02 2014', '__version__': '1.0', '__globals__': [], 'mldata_descr_ordering': array([[array(['label'], dtype='<U5'), array(['data'], dtype='<U4')]],dtype=object), '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), 'label': array([[0., 0., 0., ..., 9., 9., 9.]])}
  • data 存放一个数组,数组的一行表示一个特征,一列表示一个数据
  • label 存放一个标签数组
X, y = mnist["data"].T, mnist["label"].T
X.shape
y.shape
(70000, 784)
(70000, 1)

可以看出,MNIST 有 70000 张图片,每张图片有 784 个特征。这是因为每个图片都是28*28像素的,并且每个像素的值介于 0~255 之间。

让我们看一看数据集的某一个数字。只需要将某个实例的特征向量,reshape为28*28的数组,然后使用 Matplotlib 的imshow()函数展示出来。

import matplotlib
import matplotlib.pyplot as plt
some_digit = X[9]
some_digit_image = some_digit.reshape(28, 28)
plt.imshow(some_digit_image, cmap = matplotlib.cm.binary)
plt.show()


比较容易辨识,是数字0,显示一下它的标签。

print(y[9])
[0.]

分离训练集和测试集

将数据集分为训练集与测试集

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

将训练数据集打算顺序
这可以保证交叉验证的每一折都是相似(某一折不会缺少某类数字)。而且,一些学习算法对训练样例的顺序敏感,当它们在一行当中得到许多相似的样例,这些算法将会表现得非常差。打乱数据集将保证这种情况不会发生。

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

2.训练一个二分类器

现在我们简化一下问题,只尝试去识别一个数字,比如说,数字 5。这个“数字 5 检测器”就是一个二分类器,能够识别两类别,“是 5”和“非 5”。让我们为这个分类任务创建目标向量:

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

现在让我们挑选一个分类器去训练它。用随机梯度下降分类器是一个不错的开始。使用 Scikit-Learn 的SGDClassifier 类。这个分类器有一个好处是能够高效地处理非常大的数据集。这部分原因在于SGD一次只处理一条数据,这也使得 SGD 适合在线学习(online learning)。我们在稍后会看到它。让我们创建一个SGDClassifier和在整个数据集上训练它。

from sklearn.linear_model import SGDClassifier
sgd_clf = SGDClassifier(random_state=42)
sgd_clf.fit(X_train, y_train_5)

如果分类器想预测这个数字是否是5

sgd_clf.predict([some_digit])

3.评价分类器的性能

评估一个分类器,有许多量度性能的方法。

使用交叉验证分类准确率

以下代码粗略地做了和cross_val_score()相同的事情,并且输出相同的结果。

from sklearn.model_selection import StratifiedKFold #K折交叉切分
#StratifiedKFold 分层采样交叉切分,确保训练集,测试集中各类别样本的比例与原始数据集中相同。
from sklearn.base import clone # 复制模型及其参数。属于深层复制。
skfolds = StratifiedKFold(n_splits=3, random_state=42)for train_index, test_index in skfolds.split(X_train, y_train_5): #训练、测试clone_clf = clone(sgd_clf)X_train_folds = X_train[train_index]y_train_folds = (y_train_5[train_index])X_test_fold = X_train[test_index]y_test_fold = (y_train_5[test_index])clone_clf.fit(X_train_folds, y_train_folds)y_pred = clone_clf.predict(X_test_fold)n_correct = sum(y_pred == y_test_fold)print(n_correct / len(y_pred))

StratifiedKFold类实现了分层采样,生成的折(fold)包含了各类相应比例的样例。在每一次迭代,上述代码生成分类器的一个克隆版本,在训练折(training folds)的克隆版本上进行训,在测试折(test folds)上进行预测。然后它计算出被正确预测的数目和输出正确预测的比例。
KFold与StratifiedKFold 的区别
sklearn.bash clone的作用
让我们使用cross_val_score()函数来评估SGDClassifier模型,同时使用 K 折交叉验证,此处让k=3。记住:K 折交叉验证意味着把训练集分成 K 折(此处 3 折),然后使用一个模型对其中一折进行预测,对其他折进行训练。

from sklearn.model_selection import cross_val_score
cross_val_score(sgd_clf, X_train, y_train_5, cv=3, scoring="accuracy")
[0.9535  0.96605 0.9631 ]

在交叉验证上有大于 95% 的精度(accuracy)?这看起来很令人吃惊。先别高兴,让我们来看一个非常笨的分类器去分类,看看其在“非 5”这个类上的表现。

from sklearn.base import BaseEstimator #估计器
class Never5Classifier(BaseEstimator): #BaseEstimator作为基类def fit(self, X, y=None):passdef predict(self, X):return np.zeros((len(X), 1), dtype=bool) #全部预测为False

分类准确率(accuracy)为:

never_5_clf = Never5Classifier()
cross_val_score(never_5_clf, X_train, y_train_5, cv=3, scoring="accuracy")

全部预测为“False”分类器也有 90% 的准确率。这是因为只有 10% 的图片是数字 5,所以你总是猜测某张图片不是 5,你也会有90%的可能性是对的。

这证明了准确率通常来说不是一个好的性能度量指标,特别是处理有偏差的数据集,比方说其中一些类比其他类频繁得多。

精准率和召回率

混淆矩阵

对分类器来说,一个好得多的性能评估指标是混淆矩阵。

真正例(true positive)、假正例(false positive)、真反例(true negative)、假反例(false negative)四种情形。分类结果的 混淆矩阵 如下表所示:

预测反例 预测正例
真实反例 TN (真反例) FP (假正例)
真实正例 FN (假反例) TP (真正例)

为了计算混淆矩阵,首先需要有一系列的预测值,应该使用cross_val_predict()函数,返回的是estimator 的分类结果(或回归值)。

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

就像 cross_val_score()cross_val_predict()也使用 K 折交叉验证。它不是返回一个评估分数(准确率),而是返回基于每一个折做出的一个预测值。

为了计算混淆矩阵,首先你需要有一系列的预测值,这样才能将预测值与真实值做比较。注意这里使用的是训练集的预测值。(只有处于项目的尾声,准备上线一个分类器的时候,你才应该使用测试集)
现在使用 confusion_matrix()函数,将会得到一个混淆矩阵。传递目标类(y_train_5)和预测类(y_train_pred)给它。

from sklearn.metrics import confusion_matrix
confusion_matrix(y_train_5, y_train_pred)
[[51496  3083][  954  4467]]TN = 53272      FP = 1307
FN = 1077      TP = 4344

分类器常用评价指标是精准率(precision)和召回率(recall):

P=TPTP+FPP=\frac{TP}{TP+FP} P=TP+FPTP​
R=TPTP+FNR=\frac{TP}{TP+FN} R=TP+FNTP​
Scikit-Learn 提供了一些函数去计算分类器的指标,包括准确率和召回率。

from sklearn.metrics import precision_score, recall_score
precision_score(y_train_5, y_train_pred) # == 4344 / (4344 + 1307)
recall_score(y_train_5, y_train_pred) # == 4344 / (4344 + 1077)
0.7687135020350381
0.801328168234643

“数字 5 探测器”看起来还不够好。当它声明某张图片是 5 的时候,它只有 77% 的可能性是正确的。而且,它也只检测出“是 5”类图片当中的 80%。

精准率和召回率的折衷

通常结合准确率和召回率会更加方便,这个指标叫做“F1 值”,特别是当你需要一个简单的方法去比较两个分类器的优劣的时候。F1 值是准确率和召回率的调和平均。
2F1=1P+1R\frac{2}{F_{1}} = \frac{1}{P} + \frac{1}{R} F1​2​=P1​+R1​
F1=2TP2TP+FP+FNF_{1} = \frac{2TP}{2TP+FP+FN} F1​=2TP+FP+FN2TP​
通过调用f1_score()计算 F1 值:

from sklearn.metrics import f1_score
f1_score(y_train_5, y_train_pred)
0.78468208092485547

F1 支持那些有着相近准确率和召回率的分类器。这不会总是你想要的。有的场景你会绝大程度地关心准确率,而另外一些场景你会更关心召回率。举例子,如果你训练一个分类器去检测视频是否适合儿童观看,你会倾向选择那种即便拒绝了很多好视频、但保证所保留的视频都是好(高准确率)的分类器,而不是那种高召回率、但让坏视频混入的分类器(这种情况下你或许想增加人工去检测分类器选择出来的视频)。另一方面,加入你训练一个分类器去检测监控图像当中的窃贼,有着 30% 准确率、99% 召回率的分类器或许是合适的(当然,警卫会得到一些错误的报警,但是几乎所有的窃贼都会被抓到)。

不幸的是,你不能同时拥有两者。增加准确率会降低召回率,反之亦然。这叫做准确率与召回率之间的折衷。

为了弄懂这个折衷,我们看一下SGDClassifier是如何做分类决策的。对于每个样例,它根据决策函数计算分数,如果这个分数大于一个阈值,它会将样例分配给正例,否则它将分配给反例。

下图显示了几个数字从左边的最低分数排到右边的最高分。

假设决策阈值位于中间的箭头(介于两个 5 之间):您将发现4个真正例(数字 5)和一个假正例(数字 6)在该阈值的右侧。因此,使用该阈值,准确率为 80%(TP = 4, FP = 1 P = 4/5)。但实际有 6 个数字 5,分类器只检测 4 个, 所以召回是 67% (4/6)。现在,如果你 提高阈值(移动到右侧的箭头),假正例(数字 6)成为一个真反例,从而提高准确率(在这种情况下高达 100%),但一个真正例 变成假反例,召回率降低到 50%。相反,降低阈值可提高召回率、降低准确率。

[图3-3 决策阈值与准确度/召回率折衷][…/images/chapter_3/chapter3.3.jpeg]

Scikit-Learn 不让你直接设置阈值,但是它给你提供了设置决策分数的方法,这个决策分数可以用来产生预测。它不是调用分类器的predict()方法,而是调用decision_function()方法。这个方法返回每一个样例的分数值,然后基于这个分数值,使用你想要的任何阈值做出预测。

some_digit = X[36000] #X[36000] 是 数字5
y_scores = sgd_clf.decision_function([some_digit])
[161855.74572176]
threshold = 0
y_some_digit_pred = (y_scores > threshold)
[True]

SGDClassifier用了一个等于 0 的阈值,所以前面的代码返回了跟predict()方法一样的结果(都返回了true)。让我们提高这个阈值:

threshold = 200000
y_some_digit_pred = (y_scores > threshold)
[False]

这证明了提高阈值会降调召回率。这个图片实际就是数字 5,当阈值等于 0 的时候,分类器可以探测到这是一个 5,当阈值提高到 20000 的时候,分类器将不能探测到这是数字 5。

那么,你应该如何使用哪个阈值呢?首先,你需要再次使用cross_val_predict()得到每一个样例的分数值,但是这一次指定返回一个决策分数,而不是预测值。

y_scores = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3, method="decision_function")
[ -434076.49813641 -1825667.15281624  -767086.76186905 ...-867191.25267993  -565357.11420164  -366599.16018198]

???这里的scores是什么意思呢?这个得分是怎样计算的。

现在有了这些分数值。对于任何可能的阈值,使用precision_recall_curve(),你都可以计算准确率和召回率:

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

最后,可以使用 Matplotlib 画出准确率和召回率如下图所示,这里把准确率和召回率当作是关于阈值的一个函数。

你也许会好奇为什么准确率曲线比召回率曲线更加起伏不平。原因是准确率有时候会降低,尽管当你提高阈值的时候,通常来说准确率会随之提高。回头看图2 决策阈值和精确/召回率折中,留意当你从中间箭头开始然后向右移动一个数字会发生什么: 准确率会由 4/5(80%)降到 3/4(75%)。另一方面,当阈值提高时候,召回率只会降低。这也就说明了为什么召回率的曲线更加平滑。

现在你可以选择适合你任务的最佳阈值。另一个选出好的准确率/召回率折衷的方法是直接画出准确率对召回率的曲线:

plt.plot(recalls[:-1], precisions[:-1])
plt.xlabel("Recall")
plt.ylabel("Precision")
plt.xlim([0, 1])
plt.ylim([0, 1])
plt.show()

如下图所示,成为“精准率——召回率曲线”,简称“P-R曲线”。

若一个学习器的P-R曲线被另一个学习器的曲线完全“包住”,则可断言后者的性能优于前者。如果两个学习器的P-R曲线发生了交叉,想要比较两个学习器的好坏,一个比较合理的判据是比较P-R曲线下的面积大小。
还可以通过“平衡点”度量。(学习器的P-R曲线与“精准率 = 召回率”时的交点。)
除了F1值之外,还有Fβ。


可以看到,在召回率在 80% 左右的时候,精准率急剧下降。你可能会想选择在急剧下降之前选择出一个精准率/召回率折衷点。比如说,在召回率 60% 左右的点。当然,这取决于你的项目需求。

我们假设你决定达到 80% 的准确率。你查阅第一幅图(放大一些),在 40000 附近找到一个阈值。为了作出预测(目前为止只在训练集上预测),你可以运行以下代码。

y_train_pred_90 = (y_scores > 40000)

注意:不是运行分类器的predict()方法。

现在查看精准率和召回率:

precision_score(y_train_5, y_train_pred_80)
recall_score(y_train_5, y_train_pred_80)
0.830414176797857
0.7434052757793765

我们可根据学习器的预测结果对样例进行排序,排在前面的是学习器认为“最可能”是正例的样本

ROC 曲线

ROC全称是“受试者工作特征”(Receiver Operating Characteristic)曲线。
根据学习器的预测结果对样例进行排序,按此排序逐个把样本作为正例进行预测,每次计算出两个重要量的值,分别以它们为横、纵坐标作图,就得到了“ROC”曲线。
ROC曲线的纵轴是真正例率(True Positive Rate , TPR),横轴是“假正例率” (False Positive Rate,FPR)。

TPR=TPTP+FNTPR = \frac{TP}{TP+FN} TPR=TP+FNTP​
FPR=FPTN+FPFPR = \frac{FP}{TN+FP} FPR=TN+FPFP​
为了画出 ROC 曲线,你首先需要计算各种不同阈值下的 TPR、FPR,使用roc_curve()函数:

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

然后你可以使用 matplotlib,画出 FPR 对 TPR 的曲线。

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)越高,分类器就会产生越多的假正例(FPR)例如极端的都预测为正例的情况。图中的点线是一个完全随机的分类器生成的 ROC 曲线(这条对角线上的点其实表示的是一个采用随机猜测策略的分类器的结果,例如(0.5,0.5),表示该分类器随机对于一半的样本猜测其为正样本,另外一半的样本为负样本。);一个好的分类器的 ROC 曲线应该尽可能远离这条线(即向左上角方向靠拢)。

一个比较分类器之间优劣的方法是:测量ROC曲线下的面积(AUC)。

Scikit-Learn 提供了一个函数来计算 ROC AUC:

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

因为 ROC 曲线跟精准率-召回率曲线(或者叫 PR)很类似,你或许会好奇如何决定使用哪一个曲线呢?
比较简单的规则是:当正例很少时,优先使用 PR 曲线,或者当你关注假正例多于假反例的时候。其他情况使用 ROC 曲线。举例子,回顾前面的 ROC 曲线和 ROC AUC 数值,你或许认为这个分类器很棒。但是这几乎全是因为只有少数正例(“是 5”),而大部分是反例(“非 5”)。相反,PR 曲线清楚显示出这个分类器还有很大的改善空间(PR 曲线应该尽可能地靠近右上角)。

让我们训练一个RandomForestClassifier,然后拿它的的ROC曲线和ROC AUC数值去跟SGDClassifier的比较。首先你需要得到训练集每个样例的数值。但是由于随机森林分类器的工作方式,RandomForestClassifier不提供decision_function()方法。相反,它提供了predict_proba()方法。predict_proba()方法返回一个数组,数组的每一行代表一个样例,每一列代表一个类。数组当中的值的意思是:给定一个样例属于给定类的概率。比如,70%的概率这幅图是数字 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")

要画 ROC 曲线,需要的是样例的分数,而不是概率。一个简单的解决方法是使用正例的概率当作样例的分数。

y_scores_forest = y_probas_forest[:, 1] # score = proba of positive class
fpr_forest, tpr_forest, thresholds_forest = roc_curve(y_train_5,y_scores_forest)

现在你即将得到 ROC 曲线。将前面一个分类器的 ROC 曲线一并画出来是很有用的,可以清楚地进行比较。

plt.plot(fpr, tpr, "b:", label="SGD")
plot_roc_curve(fpr_forest, tpr_forest, "Random Forest")
plt.legend(loc="bottom right")
plt.show()


如图所示,RandomForestClassifier的 ROC 曲线比SGDClassifier的好得多:它更靠近左上角。所以,它的 AUC 也会更大。

roc_auc_score(y_train_5, y_scores_forest)
0.9931243366003829

计算一下准确率和召回率,混淆矩阵如下:

y_train_rf_pred = (y_probas_forest[:, 0] <= y_probas_forest[:, 1])
from sklearn.metrics import confusion_matrix
confusion_matrix(y_train_5, y_train_rf_pred)
[[54412   167][  637  4784]]
precision_score(y_train_5, y_train_rf_pred) #4784 / (1484 + 167)
recall_score(y_train_5, y_train_rf_pred) #4784 / (1484 + 637)
0.9662694405170673
0.882494004796163

98.5% 的准确率,82.8% 的召回率。

4.多分类器

二分类器只能区分两个类,而多类分类器(也被叫做多项式分类器)可以区分多个类。

一些算法(比如随机森林分类器或者朴素贝叶斯分类器)可以直接处理多类分类问题。其他一些算法(比如 SVM 分类器或者线性分类器)则是严格的二分类器。然而,有许多策略可以让二分类器去执行多类分类。

例如,创建一个可以将图片分成 10 类(从 0 到 9)的系统的一个方法是:训练10个二分类器,每一个对应一个数字(分类器 0,分类器 1,分类器 2,以此类推)。然后当你想对某张图片进行分类的时候,让每一个分类器对这个图片进行分类,选出决策分数最高的那个分类器。这叫做“一对所有”(OvA)策略(也被叫做“一对其他”)。

另一个策略是对每一对数字都训练一个二分类器:一个分类器用来处理数字 0 和数字 1,一个用来处理数字 0 和数字 2,一个用来处理数字 1 和 2,以此类推。这叫做“一对一”(OvO)策略。如果有 N 个类。你需要训练N*(N-1)/2个分类器。对于 MNIST 问题,需要训练 45 个二分类器!当你想对一张图片进行分类,你必须将这张图片跑在全部45个二分类器上。然后看哪个类胜出。OvO 策略的主要有点是:每个分类器只需要在训练集的部分数据上面进行训练。这部分数据是它所需要区分的那两个类对应的数据。

一些算法(比如 SVM 分类器)在训练集的大小上很难扩展,所以对于这些算法,OvO 是比较好的,因为它可以在小的数据集上面可以更多地训练,较之于巨大的数据集而言。但是,对于大部分的二分类器来说,OvA 是更好的选择。

Scikit-Learn 可以探测出你想使用一个二分类器去完成多分类的任务,它会自动地执行 OvA(除了 SVM 分类器,它使用 OvO)。让我们试一下SGDClassifier.

sgd_clf.fit(X_train, y_train) # y_train, not y_train_5
sgd_clf.predict([some_digit])
array([ 5.])

很容易。上面的代码在训练集上训练了一个SGDClassifier。这个分类器处理原始的目标class,从 0 到 9(y_train),而不是仅仅探测是否为 5 (y_train_5)。然后它做出一个判断(在这个案例下只有一个正确的数字)。在幕后,Scikit-Learn 实际上训练了 10 个二分类器,每个分类器都产到一张图片的决策数值,选择数值最高的那个类。

为了证明这是真实的,你可以调用decision_function()方法。不是返回每个样例的一个数值,而是返回 10 个数值,一个数值对应于一个类。

some_digit_scores = sgd_clf.decision_function([some_digit])
some_digit_scores
array([[-311402.62954431, -363517.28355739, -446449.5306454 ,-183226.61023518, -414337.15339485, 161855.74572176,-452576.39616343, -471957.14962573, -518542.33997148,-536774.63961222]])

最高数值是对应于类别 5 :

np.argmax(some_digit_scores)
5

如果你想强制 Scikit-Learn 使用 OvO 策略或者 OvA 策略,你可以使用OneVsOneClassifier类或者OneVsRestClassifier类。创建一个样例,传递一个二分类器给它的构造函数。举例子,下面的代码会创建一个多类分类器,使用 OvO 策略,基于SGDClassifier

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.])

这次 Scikit-Learn 没有必要去运行 OvO 或者 OvA,因为随机森林分类器能够直接将一个样例分到多个类别。你可以调用predict_proba(),得到样例对应的类别的概率值的列表:

forest_clf.predict_proba([some_digit])
array([[ 0.1, 0. , 0. , 0.1, 0. , 0.8, 0. , 0. , 0. , 0. ]])

你可以看到这个分类器相当确信它的预测:在数组的索引 5 上的 0.8,意味着这个模型以 80% 的概率估算这张图片代表数字 5。它也认为这个图片可能是数字 0 或者数字 3,分别都是 10% 的几率。

现在当然你想评估这些分类器。像平常一样,你想使用交叉验证。让我们用cross_val_score()来评估SGDClassifier的精度。

cross_val_score(sgd_clf, X_train, y_train, cv=3, scoring="accuracy")
[ 0.84063187, 0.84899245, 0.86652998]

在所有测试折(test fold)上,它有 84% 的准确率。但是你可以做的更好。举例子,简单将输入正则化,将会提高精度到 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")
[ 0.91011798, 0.90874544, 0.906636 ]

5.误差分析

当然,如果这是一个实际的项目,你会在你的机器学习项目当中,继续以下步骤:探索准备数据的候选方案,尝试多种模型,把最好的几个模型列为候选名单,用GridSearchCV调试超参数,尽可能地自动化。在这里,我们假设你已经找到一个不错的模型,你试图找到方法去改善它。一个方式是分析模型产生的误差的类型。

首先,你可以检查混淆矩阵。你需要使用cross_val_predict()做出预测,然后调用confusion_matrix()函数,像你早前做的那样。

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
array([[5725, 3, 24, 9, 10, 49, 50, 10, 39, 4],[ 2, 6493, 43, 25, 7, 40, 5, 10, 109, 8],[ 51, 41, 5321, 104, 89, 26, 87, 60, 166, 13],[ 47, 46, 141, 5342, 1, 231, 40, 50, 141, 92],[ 19, 29, 41, 10, 5366, 9, 56, 37, 86, 189],[ 73, 45, 36, 193, 64, 4582, 111, 30, 193, 94],[ 29, 34, 44, 2, 42, 85, 5627, 10, 45, 0],[ 25, 24, 74, 32, 54, 12, 6, 5787, 15, 236],[ 52, 161, 73, 156, 10, 163, 61, 25, 5027, 123],[ 43, 35, 26, 92, 178, 28, 2, 223, 82, 5240]])

使用 Matplotlib 的matshow()函数,将混淆矩阵以图像的方式呈现,将会更加方便。

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


这个混淆矩阵看起来相当好,因为大多数的图片在主对角线上。在主对角线上意味着被分类正确。数字 5 对应的格子看起来比其他数字要暗淡许多。这可能是数据集当中数字 5 的图片比较少,又或者是分类器对于数字 5 的表现不如其他数字那么好。

让我们关注仅包含误差数据的图像呈现。首先你需要将混淆矩阵的每一个值除以相应类别的图片的总数目。这样子,你可以比较错误率,而不是绝对的错误数(这对大的类别不公平)。

row_sums = conf_mx.sum(axis=1, keepdims=True) #按行求和
norm_conf_mx = conf_mx / row_sums

现在让我们用 0 来填充对角线。这样子就只保留了被错误分类的数据。让我们画出这个结果。

np.fill_diagonal(norm_conf_mx, 0) #用0填充对角线元素
plt.matshow(norm_conf_mx, cmap=plt.cm.gray)
plt.show()


现在你可以清楚看出分类器制造出来的各类误差。
注意:行代表实际类别,列代表预测的类别。第 8、9 列相当亮,这告诉你许多图片被误分成数字 8 或者数字 9。相似的,第 8、9 行也相当亮,告诉你数字 8、数字 9 经常被误以为是其他数字。相反,一些行相当黑,比如第一行:这意味着大部分的数字 1 被正确分类(一些被误分类为数字 8 )。留意到误差图不是严格对称的。举例子,比起将数字 8 误分类为数字 5 的数量,有更多的数字 5 被误分类为数字 8。

分析混淆矩阵通常可以给你提供深刻的见解去改善你的分类器。回顾这幅图,看样子你应该努力改善分类器在数字 8 和数字 9 上的表现,和纠正 3/5 的混淆(3被分到5,5被预测为3的样例数很多,因为这部分很亮)。举例子,你可以尝试去收集更多的数据,或者你可以构造新的、有助于分类器的特征。举例子,写一个算法去数闭合的环(比如,数字 8 有两个环,数字 6 有一个, 5 没有)。又或者你可以预处理图片(比如,使用 Scikit-Learn,Pillow, OpenCV)去构造一个模式,比如闭合的环。

分析独特的误差,是获得关于你的分类器是如何工作及其为什么失败的洞见的一个好途径。但是这相对难和耗时。举例子,我们可以画出数字 3 和 5 的例子

左边两个5*5的块将数字识别为 3,右边的将数字识别为 5。一些被分类器错误分类的数字(比如左下角和右上角的块)是书写地相当差,甚至让人类分类都会觉得很困难(比如第 8 行第 1 列的数字 5,看起来非常像数字 3 )。但是,大部分被误分类的数字,在我们看来都是显而易见的错误。很难明白为什么分类器会分错。原因是我们使用的简单的SGDClassifier,这是一个线性模型。它所做的全部工作就是分配一个类权重给每一个像素,然后当它看到一张新的图片,它就将加权的像素强度相加,每个类得到一个新的值。所以,因为 3 和 5 只有一小部分的像素有差异,这个模型很容易混淆它们。

3 和 5 之间的主要差异是连接顶部的线和底部的线的细线的位置。如果你画一个 3,连接处稍微向左偏移,分类器很可能将它分类成 5。反之亦然。换一个说法,这个分类器对于图片的位移和旋转相当敏感。所以,减轻 3/5 混淆的一个方法是对图片进行预处理,确保它们都很好地中心化和不过度旋转。这同样很可能帮助减轻其他类型的错误。

6.多标签分类

到目前为止,所有的样例都总是被分配到仅一个类。有些情况下,你也许想让你的分类器给一个样例输出多个类别。比如说,思考一个人脸识别器。如果对于同一张图片,它识别出几个人,它应该做什么?当然它应该给每一个它识别出的人贴上一个标签。比方说,这个分类器被训练成识别三个人脸,Alice,Bob,Charlie;然后当它被输入一张含有 Alice 和 Bob 的图片,它应该输出[1, 0, 1](意思是:Alice 是,Bob 不是,Charlie 是)。这种输出多个二值标签的分类系统被叫做多标签分类系统。

目前我们不打算深入脸部识别。我们可以先看一个简单点的例子,仅仅是为了阐明的目的。

from sklearn.neighbors import KNeighborsClassifier
y_train_large = (y_train >= 7) #7 8 9
y_train_odd = (y_train % 2 == 1) #奇数
y_multilabel = np.c_[y_train_large, y_train_odd] #按行连接两个矩阵
knn_clf = KNeighborsClassifier() #K近邻
knn_clf.fit(X_train, y_multilabel)

这段代码创造了一个y_multilabel数组,里面包含两个目标标签。第一个标签指出这个数字是否为大数字(7,8 或者 9),第二个标签指出这个数字是否是奇数。接下来几行代码会创建一个KNeighborsClassifier样例(它支持多标签分类,但不是所有分类器都可以),然后我们使用多目标数组来训练它。现在你可以生成一个预测,然后它输出两个标签:

knn_clf.predict([some_digit])
array([[False, True]], dtype=bool)

它工作正确。数字 5 不是大数(False),同时是一个奇数(True)。

有许多方法去评估一个多标签分类器,和选择正确的量度标准,这取决于你的项目。举个例子,一个方法是对每个个体标签去量度 F1 值(或者前面讨论过的其他任意的二分类器的量度标准),然后计算平均值。下面的代码计算全部标签的平均 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")
0.96845540180280221

这里假设所有标签有着同等的重要性,但可能不是这样。特别是,如果你的 Alice 的照片比 Bob 或者 Charlie 更多的时候,也许你想让分类器在 Alice 的照片上具有更大的权重。一个简单的选项是:给每一个标签的权重等于它的支持度(比如,那个标签的样例的数目)。为了做到这点,简单地在上面代码中设置average=“weighted”。

7.多输出分类

我们即将讨论的最后一种分类任务被叫做“多输出-多类分类”(或者简称为多输出分类)。它是多标签分类的简单泛化,在这里每一个标签可以是多类别的(比如说,它可以有多于两个可能值)。

为了说明这点,我们建立一个系统,它可以去除图片当中的噪音。它将一张混有噪音的图片作为输入,期待它输出一张干净的数字图片,用一个像素强度的数组表示,就像 MNIST 图片那样。注意到这个分类器的输出是多标签的(一个像素一个标签)和每个标签可以有多个值(像素强度取值范围从 0 到 255)。所以它是一个多输出分类系统的例子。

分类与回归之间的界限是模糊的,比如这个例子。按理说,预测一个像素的强度更类似于一个回归任务,而不是一个分类任务。而且,多输出系统不限于分类任务。你甚至可以让你一个系统给每一个样例都输出多个标签,包括类标签和值标签。

让我们从 MNIST 的图片创建训练集和测试集开始,然后给图片的像素强度添加噪声,这里是用 NumPy 的randint()函数。目标图像是原始图像。

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[some_index]])
plot_digit(clean_digit)

这里的输出是一张图像的每个像素点。

8.参考链接

1.sklearn中的交叉验证(Cross-Validation)

分类 手写体数字识别相关推荐

  1. 基于matlab支持向量机SVM多分类手写体数字识别

    此程序为本人模式识别大作业,参考了网上的代码,并进行了一定的修改,希望对大家有所帮助! 此代码主要参考了以下文章: https://blog.csdn.net/Einperson/article/de ...

  2. Tensorflow解决MNIST手写体数字识别

    这里给出的代码是来自<Tensorflow实战Google深度学习框架>,以供参考和学习. 首先这个示例应用了几个基本的方法: 使用随机梯度下降(batch) 使用Relu激活函数去线性化 ...

  3. 【机器学习实验二】k-NN算法—改进约会网站以及手写体数字识别

    目录 一.改进约会网站 1.项目背景 2.数据收集 3.在约会网站中使用k-近邻算法的流程 4.代码实现 二.手写体数字识别 1.了解手写体数字识别 2.手写体数字识别思路 3.1.导入模块 3.2. ...

  4. 基于MATLAB手写体数字识别程序设计

    基于MATLAB手写体数字识别程序设计 手写体识别由于其实用性,一直处于研究进步的阶段,本文主要针对的是对0-9十个手写数字体脱机识别,在Matlab中对样本部分为进行16特征的提取,分别采用最小距离 ...

  5. 基于matlab的手写体数字识别系统

    摘要:随着科学技术的发展,机器学习成为一大学科热门领域,是一门专门研究计算机怎样模拟或实现人类的学习行为的交叉学科.文章在matlab软件的基础上,利用BP神经网络算法完成手写体数字的识别. 机器学习 ...

  6. 基于MATLAB的手写体数字识别算法的实现

    基于MATLAB的手写体数字识别 一.课题介绍 手写数字识别是模式识别领域的一个重要分支,它研究的核心问题是:如何利用计算机自动识别人手写在纸张上的阿拉伯数字.手写体数字识别问题,简而言之就是识别出1 ...

  7. 基于KNN算法的手写体数字识别

    基于KNN算法的手写体数字识别 KNN分类算法是一种经典的分类算法,属于懒惰学习算法的一种. 1.算法原理 工作原理:存在一个样本数据集合,也称作训练样本集,并且样本集中每个数据都存在标签,即我们知道 ...

  8. keras框架下的深度学习(一)手写体数字识别

    文章目录 前言 一.keras的介绍及其操作使用 二.手写题数字识别 1.介绍 2.对数据的预处理 3.搭建网络框架 4.编译 5.循环训练 6.测试训练的网络模 7.总代码 三.附:梯度下降算法 1 ...

  9. 基于AlexNet卷积神经网络的手写体数字识别系统研究-附Matlab代码

    ⭕⭕ 目 录 ⭕⭕ ✳️ 一.引言 ✳️ 二.手写体数字识别系统 ✳️ 2.1 MNIST 数据集 ✳️ 2.2 CNN ✳️ 2.3 网络训练 ✳️ 三.手写体数字识别结果 ✳️ 四.参考文献 ✳️ ...

最新文章

  1. 解决Oracle数据库不能导出空表的问题
  2. 600度近视眼恢复方法_近视眼了怎么办?试试这几种方法,或许能奏效
  3. Gut: 孕期健康对孩子至关重要!
  4. Linux + RIL.pdf
  5. 【OpenPose-Windows】OpenPose+VS2015+Windows+CUDA8+cuDNN5.1 官方配置教程
  6. matlab trendsurface,MATLAB 添加新的预测性维护产品
  7. 《天天数学》连载28:一月二十八日
  8. vm虚拟机win10无法复制文件_远程桌面无法复制粘贴传输文件解决办法
  9. 中序遍历+后序/先序遍历构建二叉树
  10. kafka从头消费信息
  11. 线性代数 前五章知识点梳理总结
  12. 幻想西游php源码,如何搭建幻想西游服务器
  13. 从session里面取得值为null
  14. 淘气的小丁-将图片切换成背景
  15. 苹果电脑合并pdf文件最简单的方法?苹果电脑怎么把多个pdf文件合并成一个?
  16. 论文阅读:Generating Abstractive Summaries with Finetuned Language Models
  17. 固定资产管理流程和技巧
  18. php 数组函数特点,php常见数组函数
  19. 2016即将结束,你的目标实现了吗?
  20. 特征多项式、代数重数与几何重数

热门文章

  1. sqluldr2导出
  2. 淑芬 四川方言.非常搞笑
  3. 机器学习的 50 个最佳免费数据集
  4. 记录集 执行mysql_mysql 多次查询后再执行记录集
  5. Ubuntu配置简易C++环境(make: /bin/csh: Command not found)
  6. 西电操作系统上机实验6
  7. 用c语言简单实现通讯录(详解和具体代码)
  8. 11 种加密 哈希算法的原理及其 Java 实现
  9. EfficientDet(EfficientNet+BiFPN)论文超详细解读(翻译+学习笔记+代码实现)
  10. 浅谈python函数