机器学习(2)

10 篇文章 1 订阅
10 篇文章 1 订阅

机器学习(二)

本章介绍分类系统

1,MNIST

2,训练一个二元分类器

3,性能考核

4,多类别分类器

5,错误分析

6,多标签分类

7,多输出分类

一,MNIST

MNIST数据集是一组由美国高中生和人口调查局员工手写的70000个数字的图片,每张图片都用其代表的数字表示。这个数据集被广为使用,因此也被称作是机器学习领域的“HelloWorld”

1,数据获取

Scikit提供了许多助手功能来下载数据集。

from sklearn.datasets import fetch_mldata
mnist = fetch_mldata('MNIST original')
print(mnist)

这段代码已经不能使用,会提示错误,所以将下载的数据集放在百度云,供下载:

链接:https://pan.baidu.com/s/1ANlUjmZpZMNgCttRVjwfiA 提取码:rqbz

把下载好的数据放在项目中,目录如下:

在这里插入图片描述

再运行下面代码:

from sklearn.datasets import fetch_mldata
mnist = fetch_mldata('MNIST original',data_home='C:\\Users\\aqiu\\PycharmProjects\\sklearn\\datasets')
print(mnist)

结果:

在这里插入图片描述

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

  1. ·DESCR键,描述数据集
  2. ·data键,包含一个数组,每个实例为一行,每个特征为一列
  3. ·target键,一个标签的数组。

2,查看数据集

X,y = mnist["data"],mnist["target"]
print(X.shape)
print(y.shape)

结果:

在这里插入图片描述

数据X共有7万张图片,每张图片有784个特征。因为图片是28×28像素,每个特征代表了一个像素点的强度,从0(白色)到255(黑色)

import matplotlib
import matplotlib.pyplot as plt
some_digit = X[36000]
some_digit_image = some_digit.reshape(28, 28)
plt.imshow(some_digit_image, cmap = matplotlib.cm.binary,
           interpolation="nearest")
plt.axis("off")
plt.show()

结果:

在这里插入图片描述

再来看一下y[36000]

print(y[36000])

在这里插入图片描述
imshow是设置图片的展示,cmap意思是color map,颜色方案,binary代表是白底黑字
interpolation ='nearest’如果显示分辨率与显示分辨率不同,则只显示图像而不尝试在像素之间进行插值图像分辨率(最常见的情况)。这将产生一个图像,其中像素显示为多个像素的正方形。

3,拆分训练集和测试集

MNIST数据集已经分成训练集和(前60000张图像)和测试集(后10000张图像):

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

然后对训练集进行打乱洗牌,这样能够保证交叉验证时所有的折叠都差不多,此外,有些机器学习算法对训练的实例的顺序非常敏感。

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

二,训练一个二元分类器

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

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

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

from sklearn.linear_model import SGDClassifier
sgd_clf = SGDClassifier(random_state=42)
sgd_clf.fit(X_train, y_train_5)
y_pre = sgd_clf.predict([X_train[5]])
print(y_pre)
print(y_train[5])

结果:

在这里插入图片描述

好像猜对了啊,下面我们评估一下这个模型

三,性能考核

评估分类器比评估回归器困难的多。

1,使用交叉验证测量精度

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

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

结果:

在这里插入图片描述

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

2,混淆矩阵

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

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
cm = confusion_matrix(y_train_5, y_train_pred)
print(cm)

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

在这里插入图片描述

在这里插入图片描述

3,精度和召回率

在这里插入图片描述

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

在这里插入图片描述

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

from sklearn.metrics import precision_score, recall_score
print(precision_score(y_train_5, y_train_pred))
print(recall_score(y_train_5, y_train_pred))

结果:

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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()函数获取训练集中所有实例的分数,但是这次需要它返回的是决策分数而不是预测结果.

import matplotlib.pyplot as plt
y_scores = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3, method="decision_function")  # 返回预测分数
print(y_scores.shape)
if y_scores.ndim == 2:
    y_scores = y_scores[:, 1]
# 计算所有可能的阈值的精度和召回率
from sklearn.metrics import precision_recall_curve
precisions, recalls, thresholds = precision_recall_curve(y_train_5, y_scores)
def plot_precision_recall_vs_threshold(precisions, recalls, thresholds):
    plt.plot(thresholds, precisions[:-1], "b--", label="Precision", linewidth=2)
    plt.plot(thresholds, recalls[:-1], "g-", label="Recall", linewidth=2)
    plt.xlabel("Threshold", fontsize=16)
    plt.legend(loc="upper left", fontsize=16)
    plt.ylim([0, 1])

plt.figure(figsize=(8, 4))
plot_precision_recall_vs_threshold(precisions, recalls, thresholds)
plt.xlim([-700000, 700000])
plt.show()

结果:

在这里插入图片描述

在这里插入图片描述

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

def plot_precision_vs_recall(precisions, recalls):
    plt.plot(recalls, precisions, "b-", linewidth=2)
    plt.xlabel("Recall", fontsize=16)
    plt.ylabel("Precision", fontsize=16)
    plt.axis([0, 1, 0, 1])

plt.figure(figsize=(8, 6))
plot_precision_vs_recall(precisions, recalls)
plt.show()

结果:

在这里插入图片描述

5,ROC曲线

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

在这里插入图片描述

#计算多种阈值的TPR和FPR
from sklearn.metrics import roc_curve
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曲线清楚地说明分类器还有改进的空间(曲线还可以更接近右上角)。

四,多类别分类器

二元分类器在两个类别中区分,而多类别分类器(也称为多项分类器)可以区分两个以上的类别。
(随机森林分类器或朴素贝叶斯分类器可以直接处理多个类别。支持向量机分类器或线性分类器只用于二元分类器)。
但是有多种策略可以让你用几个二元分类器实现多类别分类的目的。例如,要创建一个系统将数字图片分为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)。

sgd_clf.fit(X_train, y_train)
sgd_clf.predict([X_train[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创建了一个多类别分类器.

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

五,错误分析

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

y_train_pred = cross_val_predict(sgd_clf, X_train, y_train, cv=3)
conf_mx = confusion_matrix(y_train, y_train_pred)
conf_mx
 
plt.matshow(conf_mx, cmap=plt.cm.gray)
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混淆的方法之一,就是对图片进行预处理,确保它们位于中心位置并且没有旋转。这也同样有助于减少其他错误。

六,多标签分类

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

from sklearn.neighbors import KNeighborsClassifier
 
y_train_large = (y_train >= 7)#创建标签,数字是否大于等于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)

结果:

在这里插入图片描述

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

7,多标签输出

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

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
 
#观察一下其中一个数据
some_index = 5500
plt.subplot(121); plot_digit(X_test_mod[some_index])
plt.subplot(122); plot_digit(y_test_mod[some_index])
save_fig("noisy_digit_example_plot")
plt.show()

在这里插入图片描述

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

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

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

aqiu12316

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值