sklearn机器学习之分类问题(使用mnist作为数据集)

0.前言

本次还是利用《hands_on_ML_with_Sklearn_and_TF》这本书的内容进行学习,以前学习了数据的处理(测试集的划分以及数据预处理),这次我们将重心放到机器学习的一个重点——分类问题,本次使用的数据集是比较经典的mnist数据集。它有着 70000 张规格较小的手写数字图片,由美国的高中生和美国人口调查局的职员手写而成。这相当于机器学习当中的“Hello World”,人们无论什么时候提出一个新的分类算法,都想知道该算法在这个数据集上的表现如何。机器学习的初学者迟早也会处理 MNIST 这个数据集。接下来就是进行数据集的读取工作。

1.数据集的提取

数据集的提取是机器学习的基础工作,对于mnist有很多获取方法,这次我们介绍一种利用scipy.io进行提取。我们首先要下载mnist_all.mat文件放在路径下。如图所示:


最后调用下面的代码你将得到上图中mnist-original.mat的结果。

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

注意一定要下载mnist_all.mat这个文件不然读取不了。

对于我们已经获取的mnist数据,其数据格式:

{'__header__': b'MATLAB 5.0 MAT-file Platform: posix, Created on: Sun May  6 12:33:08 2018', '__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

另外我们进行训练集与测试集的划分,其实可以使用sklearn中的内置函数进行划分,但现在我们使用原始的方法进行数据划分:

x_train, x_test, y_train, y_test = x[:60000], x[60000:], y[:60000], y[60000:]
shuffle_index = np.random.permutation(60000) # permutation函数是用来打乱数据顺序的函数
x_train, y_train = x_train[shuffle_index], y_train[shuffle_index]

这样我们得到了数据与标签,接下来我们要进行分类处理,我们将分类分为二分类与多分类。

2.二分类

2.1构建二分类数据

我们首先将标签进行处理得到一个二分类的标签结果(因为原本的数据是一个多分类的标签):

# 训练一个二分器
y_train_5 = (y_train == 5)
y_test_5 = (y_test == 5)

2.2进行二分类

接着我们使用sklearn中自带的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.ravel()) # x_train.shape = (60000, 784), y_train_5.ravel().shape = (60000, )
# 这里的reshape是一个重点,因为fit中的数据的形状为60000*784
# 而原本的some_digit的形状为一个list,所以我们要进行reshape操作使其变成一个(1, -1)的矩阵
result = sgd_clf.predict(some_digit.reshape(1, -1))
print(result)

这里要注意x_train.shape() = (n_samples, n_features),训练集是一个{array-like, sparsematrix}这是内部代码所规定的东西y_train.shape() = (n_samples,),标签结果是一个numpy array。所以这里我们使用了y_train_5.ravel()

以上的东西是代码的细节问题,这一点十分的重要,在之后的操作中会反复出现相似的痛惜,我们要相互比较。

另外还有一点需要我们去注意,在进行预测的过程中我们使用了some_digit.reshape(1, -1)的方法进行预测,表示预测集的格式与训练集的输入的格式应该一样。

3.多分类

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

3.1多分类的策略

“一对所有”(OvA)策略:训练10个二分类器,每一个对应一个数字(探测器 0,探测器 1,探测器 2,以此类推)。然后当你想对某张图片进行分类的时候,让每一个分类器对这个图片进行分类,选出决策分数最高的那个分类器。

“一对一”(OvO)策略:一个分类器用来处理数字 0 和数字 1,一个用来处理数字 0 和数字 2,一个用来处理数字 1 和 2,以此类推。如果有 N 个类。你需要训练N*(N-1)/2个分类器。

3.2不同策略的适用范围

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

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

3.3多分类的实际操作

一般而言我们可以是用SGDClassifier(这种方法对二分类以及多分类都行),RandomForestClassifier,代码如下:

# 多类分类
from sklearn.linear_model import SGDClassifier
from sklearn.ensemble import RandomForestClassifier
sgd_clf.fit(x_train, y_train.ravel())
print(sgd_clf.predict([some_digit])) # =>array[[5.]]
some_digit_scores = sgd_clf.decision_function([some_digit])
print(some_digit_scores)

forest_clf.fit(x_train, y_train.ravel())
print(forest_clf.predict([some_digit]))
需要注意的有decision_function()函数,这个函数是用来打印测试集在每个类别的分类概率,同时随机森林算法中也有这种思想predict_proba()这个函数也可以用于其他的预测方法中:
forest_clf.predict_proba([some_digit])
array([[-311402.62954431, -363517.28355739, -446449.5306454 ,
        -183226.61023518, -414337.15339485, 161855.74572176,
        -452576.39616343, -471957.14962573, -518542.33997148,
        -536774.63961222]])

有个问题是在之前的代码中其实都没有使用ravel()这个函数,但实际进行运行的过程中,编译器会提示你进行ravel()的书写。

当然你也可以强制使用OneVsOneClassifier的方式进行操作

from sklearn.multiclass import OneVsOneClassifier
ovo_clf = OneVsOneClassifier(SGDClassifier(max_iter=5, random_state=42))
ovo_clf.fit(X_train, y_train)
ovo_clf.predict([some_digit])

4.多标签分类

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

也就是说对于多个特征进行二分类或是多分类,比如:我们将y_train>7当做一个分类标准,同时将y_train % 2 == 1当做第二个分类标准,那么一个输入值就有两个输出分类结果,代码如下:

# 多标签分类,有点意思
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)

result = knn_clf.predict([some_digit])
print(result)

这段代码使用的KNeighborsClassifier这个方法进行分类,输出的值为array([[False, True]], dtype=bool)

5.总结

总的来说使用sklearn来处理分类问题是比较简单的,需要进行的超参的处理也并不多,接下来要进行线性回归的总结了。

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页