Python
scikit-learn
svm
MNIST
機械学習入門

手書き数字データ(MNIST)をSVMで分類してみた

基本のやつ、えむにすとやってみた。

手順

①MNISTデータのダウンロード
②MNISTのバイナリファイルをCSVに書き出し
③CSVデータを画像データに書き出して、ちゃんとCSVが書き出せているかを確認
④SVMで学習と評価

実行

①MNISTデータのダウンロード

MNISTデータはすでにtrain/test用のデータが分かれてgzファイルでダウンロードできます。

download.py
import os
from urllib.request import urlopen

def download(fname):
    #MNISTファイルをサーバーから取得
    print("%s downloading..." % fname)
    with urlopen("http://yann.lecun.com/exdb/mnist/" + fname) as res:
        d = res.read()
    #mnistフォルダの下にデータを名前をつけて保存
        with open("mnist/" + fname, "wb") as f:
            f.write(d)

if __name__ == "__main__":

    if not os.path.exists("mnist"):
        os.mkdir("mnist")

    download("train-images-idx3-ubyte.gz") #train画像ファイル
    download("train-labels-idx1-ubyte.gz") #train正解ラベルファイル
    download("t10k-images-idx3-ubyte.gz") #test画像ファイル
    download("t10k-labels-idx1-ubyte.gz") #test正解ラベルファイル

②MNISTのバイナリファイルをCSVに書き出し

 ダウンロードしたMNISTデータはgzipのバイナリファイルのため、学習させられるようにCSVデータにします。

prepare.py
import os
import gzip
import struct

def csv_image(fname, type_):
    """
    画像データのCSV出力
     fname : MNISTのファイル名
    type_ : training / test
    """

    # 画像データをGzipファイルから読み取る

    #バイナリモードで読み込み
    with gzip.open(os.path.join("mnist", fname), "rb") as f:
        #">IIII":ビッグエンディアン(見た目通りの順番)で4バイトずつのパックで読み取り
        #_ :はじめの4バイトはマジックナンバーが書かれている
        #cnt:次の4バイトは画像の数
        #row:次の4バイトは行の数
        #cols:次の4バイトは列の数
        _, cnt, rows, cols = struct.unpack(">IIII", f.read(16))
        # 画像読み込み
        images = []
        for i in range(cnt):
            binarys = f.read(rows * cols)
            images.append(",".join([str(b) for b in binarys]))

    # CSV結果として出力
    with open(os.path.join("csv", type_ + "_image.csv"), "w") as f:
        f.write("\n".join(images))


def csv_label(fname, type_):
    """
    ラベルデータのCSV出力
     fname : MNISTのファイル名
    type_ : training / test
    """

    # ラベルデータをGzipファイルから読み取り
    with gzip.open(os.path.join("mnist", fname), "rb") as f:
        _, cnt = struct.unpack(">II", f.read(8))
        labels = []
        for i in range(cnt):
            label = str(struct.unpack("B", f.read(1))[0])
            labels.append(label)

    # CSV結果として出力します.
    with open(os.path.join("csv", type_ + "_label.csv"), "w") as f:
        f.write("\n".join(labels))


if __name__ == "__main__":

    if not os.path.exists("csv"):
        os.mkdir("csv")

    # トレーニングデータ.
    csv_image("train-images-idx3-ubyte.gz", "training")
    csv_label("train-labels-idx1-ubyte.gz", "training")

    # テストデータ.
    csv_image("t10k-images-idx3-ubyte.gz", "test")
    csv_label("t10k-labels-idx1-ubyte.gz", "test")
③CSVデータを画像データに書き出して、ちゃんとCSVが書き出せているかを確認

チェックチェック。

check.py
import os

CNT = 100 #100文字分だけ画像データ出力

if __name__ == "__main__":

    if not os.path.exists("image"):
        os.mkdir("image")

    with open(os.path.join("csv", "training_image.csv")) as f:
        images = f.read().split("\n")

    for i, image in enumerate(images[:CNT]):
        with open(os.path.join("image", "%d.pgm" % i), "w") as fw:
            s = "P2 28 28 255\n" #P2(PGM形式記号), 28*28サイズ, 最終画像数をヘッダーとして書く
            s += " ".join(image.split(","))
            fw.write(s)
結果

こんな感じに出力できました。
スクリーンショット 2018-04-07 0.58.22.png

④SVMで学習と評価

これまでは下準備。さて学習です。

svm.py
import os
from sklearn import svm, metrics
from sklearn.externals import joblib

# 学習用データの数
SIZE_TRAINING = 5000

# 検証用データの数
SIZE_TEST = 500

def load_data(type_, size):
    """
        type_ : training / test
        size : 返却する要素数
    """
    #csvデータを改行してデータ数分区切っていく
    with open(os.path.join("csv", "%s_image.csv" % type_)) as f:
        images = f.read().split("\n")[:size]
    with open(os.path.join("csv", "%s_label.csv" % type_)) as f:
        labels = f.read().split("\n")[:size]

             #各ピクセルに相当する数値(白黒:白0~黒255)を256で割って0-1の値に変換
    images = [[int(i)/256 for i in image.split(",")] for image in images]
    labels = [int(l) for l in labels]

    return images, labels


if __name__ == "__main__":

    # トレーニングデータを取得します.
    images, labels = load_data("training", SIZE_TRAINING)

    # 学習
    print("学習開始")
    clf = svm.SVC()
    clf.fit(images, labels)

    # テストデータを取得
    images, labels = load_data("test", SIZE_TEST)

    # 予測
    print("予測開始")
    predict = clf.predict(images)

    # 結果表示
    print("結果")
    ac_score = metrics.accuracy_score(labels, predict)
    cl_report = metrics.classification_report(labels, predict)
    print("正解率 = ", ac_score)
    print(cl_report)

    # 結果を保存する
    if not os.path.exists("result"):
        os.mkdir("result")
    joblib.dump(clf, os.path.join("result", "svm.pkl"))
結果
tonosakiyuni-no-MacBook:answer yuni$ python3 svm.py
学習開始
予測開始
結果
正解率 =  0.902
             precision    recall  f1-score   support

          0       0.91      0.93      0.92        42
          1       0.94      1.00      0.97        67
          2       0.94      0.87      0.91        55
          3       0.90      0.82      0.86        45
          4       0.88      0.96      0.92        55
          5       0.82      0.92      0.87        50
          6       0.93      0.86      0.89        43
          7       0.86      0.86      0.86        49
          8       0.90      0.88      0.89        40
          9       0.94      0.87      0.90        54

avg / total       0.90      0.90      0.90       500

90%でした。
precision と recall の平均が f1-score なので、ここを見るといいらしい。
「1」だけダントツいいね。「1」だけ...

学習する画像数を増やせば精度は上がるはず!
と思ったのでやってみた。
学習データ: 5000 → 30000

tonosakiyuni-no-MacBook:answer yuni$ python3 svm.py
学習開始
予測開始
結果
正解率 =  0.94
             precision    recall  f1-score   support

          0       0.91      0.98      0.94        42
          1       1.00      1.00      1.00        67
          2       0.94      0.91      0.93        55
          3       0.91      0.87      0.89        45
          4       0.98      0.98      0.98        55
          5       0.88      0.92      0.90        50
          6       0.97      0.91      0.94        43
          7       0.90      0.94      0.92        49
          8       0.91      0.97      0.94        40
          9       0.96      0.91      0.93        54

avg / total       0.94      0.94      0.94       500

94%でした。
もっと上がるとおもたのに。
やっぱり「1」だけいいね、「1」だけ。

今日はここまで、お疲れ様でした。