《机器学习实战:基于Scikit-Learn、Keras和TensorFlow第2版》-学习笔记(3)

第三章 分类

· Hands-On Machine Learning with Scikit-Learn, Keras, and TensorFlow, 2nd Edition, by Aurélien Géron (O’Reilly). Copyright 2019 Aurélien Géron, 978-1-492-03264-9.
· 环境:Anaconda(Python 3.8) + Pycharm
· 学习时间:2022.04.01~2022.04.02

最常见的有监督学习任务包括回归任务(预测值)和分类任务(预测类)。
第2章尝试了一个回归任务——预测住房价格,用到了线性回归、决策树和随机森林等算法。
本章将尝试做一个分类任务。内容大致如下:

3.1 MNIST

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

Scikit-Learn提供了许多助手功能来帮助你下载流行的数据集。MNIST也是其中之一。下面是获取MNIST数据集的代码:

# 获取MNIST数据集
from sklearn.datasets import fetch_openml
mnist = fetch_openml('mnist_784', version=1, as_frame=False)
# 这里加入as_frame=False是因为按书上写法后面步骤出现了问题:“查看数据集中的一个数字”显示不出来。
# 原因可能是:https://www.cnpython.com/qa/1394204
print(mnist.keys())  # 查看所有的键
# 读取速度有点慢,查了一下网上直接下载文件的方法: https://www.jianshu.com/p/d282bce1a999
# 注释写道:“默认情况下,Scikit-Learn将下载的数据集缓存在$HOME/scikit_learn_data目录下。”所以,之后的调用应该都会快一些。

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

  • DESCR键: 描述数据集;
  • data键: 包含一个数组,每个实例为一行,每个特征为一列;
  • target键: 包含一个带有标记的数组.
# 查看MNIST数据集中的数组
X, y = mnist["data"], mnist["target"]
print(X.shape)
print(y.shape)

共有7万张图片(70000),每张图片有784个特征(因为图片是28×28像素(28*28=784),每个特征代表了一个像素点的强度,从0(白色)到255(黑色))。
先来看看数据集中的一个数字,只需要随手抓取一个实例的特征向量,将其重新形成一个28×28数组,然后使用Matplotlib的imshow()函数将其显示出来:

# 查看数据集中的一个数字
import matplotlib.pyplot as plt
some_digit = X[1]
some_digit_image = some_digit.reshape(28, 28)
plt.imshow(some_digit_image, cmap="binary")
plt.axis("off")
plt.show()  # 查看数据集中的一个数字
print(y[1])  # 查看该数字的标签
# 要注意:标签是字符,大部分机器学习算法希望是都是数字,所以要把y转换成整数:
import numpy as np
y = y.astype(np.uint8)

在开始深入研究这些数据之前,你还是应该先创建一个测试集,并将其放在一边。
事实上,MNIST数据集已经分成训练集(前6万张图片)和测试集(最后1万张图片)

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

最后,我们将训练集数据混洗。

  • 这样能保证交叉验证时所有的折叠都差不多(你肯定不希望某个折叠丢失一些数字)

  • 此外,有些机器学习算法对训练实例的顺序敏感,如果连续输入许多相似的实例,可能导致执行性能不佳。给数据集混洗正是为了确保这种情况不会发生

3.2 训练二元分类器

作为初学者,先简化问题,只尝试识别一个数字,比如数字5。
那么这个 “数字5检测器”就是一个二元分类器的示例,它只能区分两个类别:5和非5。先为此分类任务创建目标向量:

y_train_5 = (y_train == 5)  # y_train_5/y_test_5中保存的是一系列True或False值
y_test_5 = (y_test == 5)  # y_train_5/y_test_5中的True表示“是数字5”,False表示“不是数字5”

接着挑选一个分类器并开始训练。一个好的初始选择是随机梯度下降(SGD)分类器,使用Scikit-Learn的SGDClassifier类即可。
这个分类器的优势是能够有效处理非常大型的数据集。这部分是因为SGD独立处理训练实例,一次一个(这也使得SGD非常适合在线学习),稍后我们将会看到。

# 此时先创建一个SGDClassifier并在整个训练集上进行训练:
from sklearn.linear_model import SGDClassifier
sgd_clf = SGDClassifier(random_state=42)  # 定义1个SGDClassifier分类器,并设置参数random_state=42
# 因为SGDClassifier在训练时是完全随机的(因此得名“随机”),如果你希望得到可复现的结果,需要设置参数random_state。
sgd_clf.fit(X_train, y_train_5)  # 用数据集去训练这个分类器

使用它来检测图片是否是数字5:(some_digit是上面查看的X[0]的数字图片)

print(sgd_clf.predict([some_digit]))  # 输出将是“True”

分类器猜这个图像代表5(True)。看起来这次它猜对了!那么,下面评估一下这个模型的性能。

3.3 性能测量

评估分类器比评估回归器要困难得多,因此本章将用很多篇幅来讨论这个主题,同时会涉及许多性能考核的方法。

3.3.1 使用交叉验证测量准确率

正如第2章所述,交叉验证是一个评估模型的好办法。

(1)for循环实现交叉验证

相比于Scikit-Learn提供cross_val_score()这一类交叉验证的函数,有时你可能希望自己能控制得多一些。
在这种情况下,你可以自行实现交叉验证,操作也简单明了。

from sklearn.model_selection import StratifiedKFold
from sklearn.base import clone
# 使用交叉验证测量准确率
skfolds = StratifiedKFold(n_splits=3, shuffle=True, random_state=42)  # 定义折叠的参数:3次,随机种子是42,确保可复现
# 原书程序运行时出错,提示说要加上shuffle=True
for train_index, test_index in skfolds.split(X_train, y_train_5):
    # 每个折叠由StratifiedKFold执行分层抽样产生,其所包含的各个类的比例符合整体比例。
    clone_clf = clone(sgd_clf)  # 复制SGDClassifier分类器。每次迭代都创建一个副本,然后用测试集进行预测,每次互相不影响
    X_train_folds = X_train[train_index]  # 用迭代的train_index/test_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))  # 分别输出:0.9502, 0.96565, 0.96495
(2)cross_val_score函数实现交叉验证

现在,用cross_val_score()函数来评估SGDClassifier模型,采用K-折交叉验证法(3个折叠)。
记住,K-折交叉验证的意思是将训练集分解成K个折叠(在本例中,为3折),然后每次留其中1个折叠进行预测,剩余的折叠用来训练(参见第2章)

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

所有折叠交叉验证的准确率(正确预测的比率)超过93%?看起来挺神奇的,是吗?

(3)准确率对比

在你开始激动之前,我们来定义一个蠢笨的分类器Never5Classifier,它将每张图都分类成“非5”:

from sklearn.base import BaseEstimator


class Never5Classifier(BaseEstimator):
    def fit(self, X, y=None):
        return self
    
    def predict(self, X):
        return np.zeros((len(X), 1), dtype=bool)

能猜到这个模型的准确率吗?我们看看:

never_5_clf = Never5Classifier()
cross_val_score_result = cross_val_score(never_5_clf, X_train, y_train_5, cv=3, scoring="accuracy")
print(cross_val_score_result)  # 输出 [0.91125, 0.90855, 0.90915]

没错,准确率同样超过90%!
这是因为只有大约10%的图片是数字5,所以如果你猜一张图不是5,90%的概率你都是正确的,简直超越了大预言家!
这说明准确率通常无法成为分类器的首要性能指标,特别是当你处理有偏数据集时(即某些类比其他类更为频繁)。

3.3.2 混淆矩阵

评估分类器性能的更好方法是混淆矩阵,其总体思路就是统计A类别实例被分成为B类别的次数。
混淆矩阵中的行表示实际类别,列表示预测类别。

例如,要想知道分类器将数字3和数字5混淆多少次,只需要通过混淆矩阵的第5行第3列来查看。

(1)输出混淆矩阵

要计算混淆矩阵,需要先有一组预测才能将其与实际目标进行比较。当然,可以通过测试集来进行预测,但是现在先不要动它
(测试集最好留到项目的最后,准备启动分类器时再使用)。作为替代,可以使用cross_val_predict()函数:

与cross_val_score()函数一样,cross_val_predict()函数同样执行K-折交叉验证,但返回的不是评估分数,而是每个折叠的预测。这意味着对于每个实例都可以得到一个干净的预测(“干净”的意思是模型预测时使用的数据在其训练期间从未见过)。

from sklearn.model_selection import cross_val_predict
y_train_pred = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3)  # 得到交叉验证的预测结果

现在可以使用confusion_matrix()函数来获取混淆矩阵了。只需要给出目标(实际)类别(y_train_5)和预测类别(y_train_pred)即可:

from sklearn.metrics 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

新四石路打卤面

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

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

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

打赏作者

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

抵扣说明:

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

余额充值