note.nkmk.me

scikit-learnのSVMでMNISTの手書き数字データを分類

Date: 2017-08-15 / tags: Python, scikit-learn, 機械学習
スポンサーリンク

MNIST手書き数字データセット

MNISTは手書き数字のデータセット。

0から9まで10種類の手書き数字が28×28ピクセルの8ビット画像として格納されている。

irisデータセットに引き続き、scikit-learnのSVM(サポートベクターマシン)でMNISTを分類する。

irisデータセットの例は以下。

データ読み込み

流れはirisデータセットの場合と同じで、まずはデータを準備する。

scikit-learnの関数datasets.fetch_mldata()でMNISTのデータをダウンロードして使用する。

data_homeで指定したディレクトリにmldataというディレクトリが作られ、そこにMATLAB形式(.mat)のファイルがダウンロードされる。次回からはダウンロードされたファイルを読んでくれる。

受け取れるのはirisのときと同様に辞書ライクなデータ。

from sklearn import datasets, model_selection, svm, metrics

mnist = datasets.fetch_mldata('MNIST original', data_home='data/src/download/')

print(type(mnist))
print(mnist.keys())
# <class 'sklearn.datasets.base.Bunch'>
# dict_keys(['DESCR', 'COL_NAMES', 'target', 'data'])

データとラベルをそれぞれ取り出す。

mnist_data = mnist.data / 255
mnist_label = mnist.target

print(mnist_data.shape)
print(mnist_label.shape)
# (70000, 784)
# (70000,)

データは28×28で784次元、ラベルは0〜9の数値で1次元。サンプル数は70000。データは8bit(0〜255)なので255で割って0〜1に規格化している。

学習用とテスト用に分割

データを学習用とテスト用に分割する。

model_selection.train_test_split()を使う。

train_sizetest_sizeにそれぞれのサンプル数を指定する。多いと学習に時間がかかるのでとりあえず少なめに設定。

train_size = 500
test_size = 100
data_train, data_test, label_train, label_test = model_selection.train_test_split(mnist_data, mnist_label, test_size=test_size, train_size=train_size)

SVMで学習し結果を確認

準備したデータを使ってSVMで学習し、metrics.accuracy_score()で正解率を確認する。

clf = svm.SVC()
clf.fit(data_train, label_train)
pre = clf.predict(data_test)

ac_score = metrics.accuracy_score(label_test, pre)
print(ac_score)
# 0.77

正解率は低め。

timeitで処理時間を測定

timeitで処理時間を測定する。timeitに関しては以下の記事も参照。

import timeit

num = 10

train_size = 500
test_size = 100
data_train, data_test, label_train, label_test = model_selection.train_test_split(mnist_data, mnist_label, test_size=test_size, train_size=train_size)

clf = svm.SVC()
print(timeit.timeit(lambda: clf.fit(data_train, label_train), number=num) / num)
pre = clf.predict(data_test)
ac_score = metrics.accuracy_score(label_test, pre)
print(ac_score)
# 0.3631532890023664
# 0.73

学習部分であるfit()の処理に360ミリ秒ほどかかっている。正解率の結果が変わっているのはmodel_selection.train_test_split()でデータがランダムにシャッフルされ、使用するデータが異なるため。

ここで、svm.SVC()の代わりにsvm.LinearSVC()を使う。svm.LinearSVC()は線形カーネルに特化したSVMでそのぶん高速。

clf = svm.LinearSVC()
print(timeit.timeit(lambda: clf.fit(data_train, label_train), number=num) / num)
pre = clf.predict(data_test)
ac_score = metrics.accuracy_score(label_test, pre)
print(ac_score)
# 0.10481857029953971
# 0.76

処理時間は100ミリ秒程度でsvm.SVC()より3倍以上高速化された。正解率はほぼ変わらず。

データ数を増やして実行

高速で実行可能なsvm.LinearSVC()を使って、データ数を増やして再度学習させる。

train_size = 10000
test_size = 2000
data_train, data_test, label_train, label_test = model_selection.train_test_split(mnist_data, mnist_label, test_size=test_size, train_size=train_size)

clf = svm.LinearSVC()
print(timeit.timeit(lambda: clf.fit(data_train, label_train), number=num) / num)
pre = clf.predict(data_test)

ac_score = metrics.accuracy_score(label_test, pre)
print(ac_score)
# 5.980748841702007
# 0.8945

学習時間は長いが正解率が向上した。特にパラメータのチューニングをしなくてもデータ数を増やすだけで正解率が上がることが分かる。

混同行列を確認

正解率だけでなく、どのように誤分類されたかを混同行列(confusion matrix)で確認する。metrics.confusion_matrix()を使う。

co_mat = metrics.confusion_matrix(label_test, pre)
print(co_mat)
# [[186   0   0   2   1   1   0   1   2   0]
#  [  0 210   1   0   1   2   2   1   3   0]
#  [  1   3 192   8   2   6   2   3   5   3]
#  [  1   1   7 163   0   6   1   1   7   2]
#  [  1   0   1   0 166   1   2   2   1   6]
#  [  5   3   1   6   5 168   7   3   6   3]
#  [  1   0   4   0   1   4 170   0   3   0]
#  [  1   1   3   2   2   0   0 196   0   6]
#  [  0   4   3   3   3  13   1   1 162   6]
#  [  2   1   0   2   5   2   0   6   2 176]]

この場合、行が正解のlabel_test、列が予測したpreを表している。

85と誤認識したのが多い。

スポンサーリンク
シェア
このエントリーをはてなブックマークに追加

関連カテゴリー

関連記事