分享

机器学习实战3-sklearn使用下载MNIST数据集进行分类项目

 LibraryPKU 2019-07-09

目录

1、MNIST数据集

2、训练一个二元分类器

2.1、随机梯度下降(SGD)分类器

2.2、分类器的性能考核:

2.2.1、混淆矩阵

2.2.2、精度:(我的理解:预测集内,预测正确的比率)

2.2.3、召回率:(我的理解:整个正类样本中,预测正确的样本所占的比率)

2.2.4、精度/召回率权衡

2.2.5、ROC曲线

3、多类别分类器

错误分析

4、多标签分类

5、多输出分类


1、MNIST数据集

      本章将使用MNIST数据集,这是一组由美国高中生和人口调查局员工手写的70000个数字的图片。每张图像都用其代表的数字标记。这个数据集被广为使用,因此也被称作是机器学习领域的“HelloWorld”:但凡有人想到了一个新的分类算法,都会想看看在MNIST上的执行结果。因此只要是学习机器学习的人,早晚都要面对MNIST。

       MNIST数据集需要下载,官方jupyternotebook中使用下列代码进行下载,但是现在已经不能使用,无法下载数据。

  1. from sklearn.datasets import fetch_mldata
  2. mnist = fetch_mldata('MNIST original')
  3. mnist

scikit-learn的函数fetch_mldata()在第一次执行下载mnist数据集的时候会一直报错,因此我们可以把下载好的mnist-original.mat数据集放到数据目录下。

解决方法:

直接下载MNST数据集文件:

https://github.com/amplab/datascience-sp14/raw/master/lab7/mldata/mnist-original.mat

上面的链接下载速度很慢,可以去百度云下载:链接:https://pan.baidu.com/s/1fe60R_ykcdvGp_ZhZGoIIQ 
提取码:o83d 

当前我的ipynb文件的目录是E:\test_jupyter\3classification.ipynb
test_jupyter文件夹中新建datasets\mldata文件夹,把下载好的mnist-original.mat放在datasets\mldata下,如下图:

随后运行下列代码,成功。 

  1. from sklearn.datasets import fetch_mldata
  2. mnist = fetch_mldata('MNIST original',data_home='./datasets')
  3. mnist

 结果:

                           

Scikit-Learn加载的数据集通常具有类似的字典结构,包括:

  1. ·DESCR键,描述数据集

  2. ·data键,包含一个数组,每个实例为一行,每个特征为一列

  3. ·target键,一个标签的数组。

我们来看看这些数组:

  1. X, y = mnist["data"], mnist["target"]
  2. X.shape
  3. out:(70000, 784)
  4. y.shape
  5. out:(70000, )
  6. #随手抓取一个实例的特征向量,重新形成一个28×28数组
  7. import matplotlib
  8. import matplotlib.pyplot as plt
  9. digit_5 = X[36000]#第36000个实例
  10. digit_5_image = digit_5.reshape(28, 28)
  11. plt.imshow(digit_5_image, cmap = matplotlib.cm.binary,interpolation="nearest")
  12. plt.axis("on")
  13. save_fig("some_digit_plot")

     数据X共有7万张图片,每张图片有784个特征。因为图片是28×28像素,每个特征代表了一个像素点的强度,从0(白色)到255(黑色),X[36000]的数字如下,通过“y[36000]”查看其标签为“5”。

                                                  

2、训练一个二元分类器

2.1、随机梯度下降(SGD)分类器

       现在先简化问题,只尝试识别一个数字——比如数字5。那么这个“数字5检测器”就是一个二元分类器的例子,它只能区分两个类别:5和非5。先为此分类任务创建目标向量(将数字标签转换为bool型标签true代表 5,false代表 非5):

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

       接着挑选一个分类器并开始训练。一个好的初始选择是随机梯度下降(SGD)分类器,使用Scikit-Learn的SGDClassifier类即可。这个分类器的优势是,能够有效处理非常大型的数据集。这部分是因为SGD独立处理训练实例,一次一个。此时先创建一个SGDClassifier并在整个训练集上进行训练:

  1. from sklearn.linear_model import SGDClassifier
  2. sgd_clf = SGDClassifier(random_state=42)
  3. sgd_clf.fit(X_train, y_train_5)
  4. sgd_clf.predict([digit_5])

      SGDClassifier在训练时是完全随机的(因此得名“随机”),如果你希望得到可复现的结果,需要设置参数random_state。
用它来检测数字5的图像,输出如下图,分类正确:

                                             

SGDClassifier官方文档:https:///stable/modules/generated/sklearn.linear_model.SGDClassifier.html,原型:

  1. sklearn.linear_model.SGDClassifier(loss=’hinge’, penalty=’l2’, alpha=0.0001, l1_ratio=0.15, fit_intercept=True,
  2. max_iter=None, tol=None, shuffle=True, verbose=0, epsilon=0.1, n_jobs=None, random_state=None,
  3. learning_rate=’optimal’, eta0=0.0, power_t=0.5, early_stopping=False, validation_fraction=0.1, n_iter_no_change=5, class_weight=None, warm_start=False, average=False, n_iter=None)

       梯度下降法(SGD)是一个简单有效的方法,用于判断使用凸loss函数(convex loss function)的分类器(SVM或logistic回归)。SGD被成功地应用在大规模稀疏机器学习问题上(large-scale and sparse machine learning),经常用在文本分类及自然语言处理上。假如数据是稀疏的,该模块的分类器可以轻松地解决这样的问题:超过10^5的训练样本、超过10^5的features。

SGD的优点是:

  • 高效

  • 容易实现(有许多机会进行代码调优)

SGD的缺点是:

  • SGD需要许多超参数:比如正则项参数、迭代数。

  • SGD对于特征归一化(feature scaling)是敏感的。

注意:SGD是一种优化方法,不是分类算法,SGDClassifier函数实际使用的是SVM或logistic回归算法进行分类!!!

介绍SGDClassifier内的几个重要参数:

1.损失函数loss,有三种选择,对应不同分类算法

  • loss=”hinge”: (soft-margin)线性SVM.

  • loss=”modified_huber”: 带平滑的hinge loss.

  • loss=”log”: logistic回归

2.通过penalty参数,可以设置对应的惩罚项。SGD支持下面的罚项:

  • penalty=”l2”: 对coef_的L2范数罚项(岭回归)

  • penalty=”l1”: 对coef_的L1范数罚项(套索回归)

  • penalty=”elasticnet”: L2和L1的convex组合; (1 - l1_ratio) * L2 + l1_ratio * L1 (弹性网络)

正则化器是对损失函数添加的惩罚,其使用平方欧几里德范数L2或绝对范数L1或两者的组合(弹性网络)将模型参数缩减到零向量。更多参考:http:///sklearn/sgd/

2.2、分类器的性能考核:

交叉验证是一个评估模型的好办法:

  1. >>> from sklearn.model_selection import cross_val_score
  2. >>> cross_val_score(sgd_clf, X_train, y_train_5, cv=3, scoring="accuracy")
  3. array([ 0.9502 , 0.96565, 0.96495])

       所有折叠交叉验证的准确率(正确预测的比率)超过95%?看起来挺神奇的,是吗?这是因为只有大约10%的图像是数字
5,所以如果你猜一张图不是5,90%的情况你都是正确的,这说明准确率通常无法成为分类器的首要性能指标,特别是当你
处理偏斜数据集(skewed dataset)的时候(即某些类比其他类更为频繁)。

2.2.1、混淆矩阵

       评估分类器性能的更好方法是混淆矩阵。总体思路就是统计A类别实例被分成为B类别的次数。例如,要想知道分类器将数字3和数字5混淆多少次,只需要通过混淆矩阵的第5行第3列来查看。要计算混淆矩阵,需要先有一组预测才能将其与实际目标进行比较。可以使用cross_val_predict()函数。与cross_val_score()函数一样,cross_val_predict()函数同样执行K-fold交叉验证,但返回的不是评估分数,而是每个折叠的预测。

  1. from sklearn.model_selection import cross_val_predict
  2. y_train_pred = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3)
  3. y_train_pred
  4. output:array([False, False, False, ..., False, False, False])
  5. from sklearn.metrics import confusion_matrix
  6. confusion_matrix(y_train_5, y_train_pred)
  7. output:array([[53272, 1307],
  8. [ 1077, 4344]], dtype=int64

       混淆矩阵中的行表示实际类别,列表示预测类别。本例中第一行表示所有“非5”(负类)的图片中:53272张被正确地分为“非5”类别(真负类),1307张被错误地分类成了“5”(假正类);第二行表示所有“5”(正类)的图片中:1077张被错误地分为“非5”类别(假负类),4344张被正确地分在了“5”这一类别(真正类)。

       混淆矩阵能提供大量信息,但有时你可能希望指标更简洁一些。

2.2.2、精度:(我的理解:预测集内,预测正确的比率)

                                                           

其中: TP是真正类的数量,FP是假正类的数量。

       做一个单独的正类预测,并确保它是正确的,就可以得到完美精度(精度=1/1=100%)。但这没什么意义,因为分类器会忽略这个正类实例之外的所有内容。因此,精度通常与另一个指标一起使用,这个指标就是召回率(recall),也称为灵敏度(sensitivity)或者真正类率(TPR):它是分类器正确检测到的正类实例的比率.

2.2.3、召回率:(我的理解:整个正类样本中,预测正确的样本所占的比率)

                                                        

其中:  FN是假负类的数量。

                                     

       现在再看,这个5-检测器看起来似乎并不像它的准确率那么光鲜亮眼了。当它说一张图片是5时,只有77%的时间是准确的,并且也只有79%的数字5被它检测出来了。

  1. >>> from sklearn.metrics import precision_score, recall_score
  2. >>> precision_score(y_train_5, y_pred) # == 4344 / (4344 + 1307)
  3. 0.76871350203503808
  4. >>> recall_score(y_train_5, y_train_pred) # == 4344 / (4344 + 1077)
  5. 0.79136690647482011

       因此我们可以很方便地将精度和召回率组合成一个单一的指标,称为F1 分数。当你需要一个简单的方法来比较两种分类器时。F1 分数是精度和召回率的谐波平均值。谐波平均值会给予较低的值更高的权重。因此,只有当召回率和精度都很高时,分类器才能得到较高的F1 分数。

                                  

2.2.4、精度/召回率权衡

       你不能同时增加精度并减少召回率,反之亦然。这称为精度/召回率权衡。

       理解这个权衡过程,我们来看看SGDClassifier如何进行分类决策。对于每个实例,它会基于决策函数计算出一个分值,如果该值大于阈值,则将该实例判为正类,否则便将其判为负类。图3-3显示了从左边最低分到右边最高分的几个数字。假设决策阈值位于中间箭头位置(两个5之间):在阈值的右侧可以找到4个真正类(真的5),一个假正类(实际上是6)。因此,在该阈值下,精度为80%(4/5)。但是在6个真正的5中,分类器仅检测到了4个,所以召回率为67%(4/6)。现在,如果提高阈值(将其挪动到右边箭头的位置),假正类(数字6)变成了真负类,因此精度得到提升(本例中提升到100%),但是一个真正类变成一个假负类,召回率降低至50%。反之,降低阈值则会在增加召回率的同时降低精度。

                   

      Scikit-Learn不允许直接设置阈值,但是可以调用decision_function函数访问用于预测的决策分数,这个方法返回每个实例的分数,然后就可以根据这些分数,使用任意阈值进行预测。

     如何决定使用什么阈值呢?首先使用cross_val_predict()函数获取训练集中所有实例的分数,但是这次需要它返回的是决策分数而不是预测结果.

  1. y_scores = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3, method="decision_function")#返回预测分数
  2. y_scores.shape
  3. >>output:(60000, 2)
  4. #计算所有可能的阈值的精度和召回率
  5. from sklearn.metrics import precision_recall_curve
  6. precisions, recalls, thresholds = precision_recall_curve(y_train_5, y_scores[:,1])

画出图像:

                             

       现在,就可以通过轻松选择阈值来实现最佳的 精度/召回率权衡了。还有一种找到好的精度/召回率权衡的方法是直接绘制精度和召回率的函数图:

                                

2.2.5、ROC曲线

         它与精度/召回率曲线非常相似,但绘制的是真 正类率(召回率的另一名称)和假 正类率(FPR)。FPR是被误分为正类的负类实例占所有负类的比例 。也称为特异度。因此,ROC曲线绘制的是灵敏度和(1-特异度)的关系。

                                                         FPR=\frac{FP}{TN+FP}        (虚警概率)

  1. #计算多种阈值的TPR和FPR
  2. from sklearn.metrics import roc_curve
  3. fpr, tpr, thresholds = roc_curve(y_train_5, y_scores[:, 1])

                                          

       虚线表示纯随机分类器的ROC曲线;一个优秀的分类器应该离这条线越远越好(向左上角)。有一种比较分类器的方法是测量曲线下面积(AUC)。完美的分类器的ROC AUC等于1,而纯随机分类器的ROC AUC等于0.5。

       由于ROC曲线与精度/召回率(或PR)曲线非常相似,因此你可能会问如何决定使用哪种曲线。有一个经验法则是,当正类非常少见或者你更关注假正类而不是假负类时,你应该选择PR曲线,反之则是ROC曲线。例如,看前面的ROC曲线图(以及ROC  AUC分数),你可能会觉得分类器真不错。但这主要是因为跟负类(非5)相比,正类(数字5)的数量真得很少。相比之下,PR曲线清楚地说明分类器还有改进的空间(曲线还可以更接近右上角)

3、多类别分类器

               二元分类器在两个类别中区分,而多类别分类器(也称为多项分类器)可以区分两个以上的类别。
随机森林分类器或朴素贝叶斯分类器可以直接处理多个类别支持向量机分类器或线性分类器只用于二元分类器)。

        但是有多种策略可以让你用几个二元分类器实现多类别分类的目的。例如,要创建一个系统将数字图片分为10类(从0到9),一种方法是训练10个二元分类器,每个数字一个(0-检测器、1-检测器、2-检测器,等等,以此类推)。然后,当你对一张图片进行检测分类时,获取每个分类器的决策分数,哪个分类器给分最高,就将其分为哪个类。这称为一对多(OvA)策略

       另一种方法是,为每一对数字训练一个二元分类器:一个用于区分0和1,一个区分0和2,一个区分1和2,以此类推。这称为一对一(OvO)策略。如果存在N个类别,那么这需要训练N×(N-1)÷2个分类器。对于MNIST问题,这意味着要训练45个二元分类器!当需要对一张图片进行分类时,你需要运行45个分类器来对图片进行分类,最后看哪个类别获胜最多。OvO的主要优点在于,每个分类器只需要用到部分训练集对其必须区分的两个类别进行训练。有些算法(例如支持向量机分类器)在数据规模扩大时表现糟糕,因此对于这类算法,OvO是一个优先的选择,因为在较小训练集上分别训练多个分类器比在大型数据集上训练少数分类器要快得多。

                                        但是对大多数二元分类器来说,OvA策略还是更好的选择。

     Scikit-Learn可以检测到你使用二元分类算法进行多类别分类任务,它会自动运行OvA(SVM分类器除外,它会使用OvO)。

  1. >>> sgd_clf.fit(X_train, y_train)
  2. >>> sgd_clf.predict([digit_5])
  3. array([ 5.])

      这段代码使用原始目标类别0到9(y_train)在训练集上对SGDClassifier进行训练,而不是以“5”和“非5”作为目标类别。然后做出预测。而在内部,Scikit-Learn实际上训练了10个二元分类器,获得它们对图片的决策分数,然后选择了分数最高的类别.想要知道是不是这样,可以调用decision_function()方法。它会返回10个分数,每个类别1个,而不再是每个实例返回1个分数

                            

      目标类别的列表会存储在classes_这个属性中,按值的大小排序。在本例里,classes_数组中每个类别的索引正好对应其类别本身(例如,索引上第5个类别正好是数字5这个类别),但是一般来说,不会这么恰巧。

                              

      如果想要强制Scikit-Learn使用一对一或者一对多策略,可以使用OneVsOneClassifier或OneVsRestClassifier类。只需要创建一个实例,然后将二元分类器传给其构造函数。例如,下面这段代码使用OvO策略,基于SGDClassifier创建了一个多类别分类器

  1. >>> from sklearn.multiclass import OneVsOneClassifier
  2. >>> ovo_clf = OneVsOneClassifier(SGDClassifier(random_state=42))
  3. >>> ovo_clf.fit(X_train, y_train)
  4. >>> ovo_clf.predict([some_digit])
  5. array([ 5.])
  6. >>> len(ovo_clf.estimators_)
  7. 45

错误分析

    在这里,假设你已经找到了一个有潜力的模型,现在你希望找到一些方法对其进一步改进。方法之一就是分析其错误类型。
首先,看看混淆矩阵:

  1. y_train_pred = cross_val_predict(sgd_clf, X_train, y_train, cv=3)
  2. conf_mx = confusion_matrix(y_train, y_train_pred)
  3. conf_mx
  4. plt.matshow(conf_mx, cmap=plt.cm.gray)
  5. plt.show()

 

       左上图的混淆矩阵图像看起来很不错,因为大多数图片都在主对角线上,这说明它们被正确分类。数字5看起来比其他数字稍稍暗一些,这可能意味着数据集中数字5的图片较少,也可能是分类器在数字5上的执行效果不如在其他数字上好。所以我们需要将混淆矩阵中的每个值除以相应类别中的图片数量,这样你比较的就是错误率而不是错误的绝对值(后者对图片数量较多的类别不公平),如右上图所示。

     分析:现在可以清晰地看到分类器产生的错误种类了。记住,每行代表实际类别,而每列表示预测类别。第8列整体看起来非常亮,说明有许多图片被错误地分类为数字8了。一些行很暗,比如行1,这意味着大多数数字1都被正确地分类。注意,错误不是完全对称的,比如,数字5被错误分类为数字8的数量比数字8被错误分类为数字5的数量要更多。

通过上面那张图来看,你的精力可以花在改进数字8的分类,以及修正数字8和数字5的混淆上。例如,

  1. 收集更多这些数字的训练数据。

  2. 用一些新特征来改进分类器——举个例子,写一个算法计算闭环数量(例如,数字8有两个,数字6有一个,数字5没有)。

  3. 再或者,还可以对图片进行预处理(使用Scikit-Image、Pillow或OpenCV)让某些模式更为突出。

分析单个的错误也可以为分类器提供洞察:它在做什么?它为什么失败?但这通常更加困难和耗时:

                                                      

        左侧的两个5×5矩阵显示了被分类为数字3的图片,右侧的两个5×5矩阵显示了被分类为数字5的图片。

        分类器弄错的数字(即左下方和右上方的矩阵)里,确实有一些写得非常糟糕,即便是人类也很难做出区分(例如,第8行第1列的数字5看起来真的很像数字3)。然而,对我们来说,大多数错误分类的图片看起来还是非常明显的错误,我们很难理解分类器为什么会弄错。原因在于,我们使用的简单的SGDClassifier模型是一个线性模型。它所做的就是为每个像素分配一个各个类别的权重,当它看到新的图像时,将加权后的像素强度汇总,从而得到一个分数进行分类。而数字3和数字5只在一部分像素位上有区别,所以分类器很容易将其弄混。
     数字3和数字5之间的主要区别是在于连接顶线和下方弧线的中间那段小线条的位置。如果你写的数字3将连接点略往左移,分类器就可能将其分类为数字5,反之亦然。换言之,这个分类器对图像移位和旋转非常敏感。因此,减少数字3和数字5混淆的方法之一,就是对图片进行预处理,确保它们位于中心位置并且没有旋转。这也同样有助于减少其他错误。

4、多标签分类

      在某些情况下,你希望分类器为每个实例产出多个类别。下面的代码会创建一个y_multilabel数组,其中包含两个数字图片的
目标标签:第一个表示数字是否是大数(7、8、9),第二个表示是否为奇数。下一行创建一个KNeighborsClassifier实例(它支持多标签分类,不是所有的分类器都支持),然后使用多个目标数组对它进行训练。现在用它做一个预测,注意它输出的两个标签:

  1. from sklearn.neighbors import KNeighborsClassifier
  2. y_train_large = (y_train >= 7)#创建标签,数字是否大于等于7
  3. y_train_odd = (y_train % 2 == 1)# 是否为奇数
  4. y_multilabel = np.c_[y_train_large, y_train_odd]
  5. knn_clf = KNeighborsClassifier()
  6. knn_clf.fit(X_train, y_multilabel)

输出:

                                           

结果是正确的!数字5确实不大(False),为奇数(True)

5、多输出分类

       我们即将讨论的最后一种分类任务叫作多输出-多类别分类(或简单地称为多输出分类)。简单来说,它是多标签分类的泛化,其标签也可以是多种类别的(比如它可以有两个以上可能的值)。
     为了说明这一点,构建一个系统用来去除图片中的噪声。给它输入一张有噪声的图片,它将输出一张干净的数字图片,跟其他MNIST图片一样,以像素强度的一个数组作为呈现方式。请注意,这个分类器的输出是多个标签(一个像素点一个标签),每个标签可以有多个值(像素强度范围为0到225)。所以这是个多输出分类器系统的例子。

先从创建训练集和测试集开始,使用NumPy的randint()函数为MNIST图片的像素强度增加噪声:

  1. noise = np.random.randint(0, 100, (len(X_train), 784))
  2. X_train_mod = X_train + noise
  3. noise = np.random.randint(0, 100, (len(X_test), 784))
  4. X_test_mod = X_test + noise
  5. y_train_mod = X_train
  6. y_test_mod = X_test
  7. #观察一下其中一个数据
  8. some_index = 5500
  9. plt.subplot(121); plot_digit(X_test_mod[some_index])
  10. plt.subplot(122); plot_digit(y_test_mod[some_index])
  11. save_fig("noisy_digit_example_plot")
  12. plt.show()

                              

左边是有噪声的输入图片,右边是干净的目标图片。现在通过训练分类器,清洗这张图片

  1. knn_clf.fit(X_train_mod, y_train_mod)
  2. clean_digit = knn_clf.predict([X_test_mod[some_index]])
  3. plot_digit(clean_digit)
  4. save_fig("cleaned_digit_example_plot")

                                               

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多