scikit-learnのSVMでMNISTの手書き数字データを分類
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_size
とtest_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
を表している。
8
を5
と誤認識したのが多い。