備忘録_手書き数字認識

備忘録_手書き数字認識

MNIST

  • 手書き数字の画像セット
  • 機械学習の分野で最も有名なデータセット
  • 0から9までの数字画像から構成
  • 訓練画像 60,000枚
  • テスト画像 10,000枚
  • 訓練画像を使って学習を行い、学習したモデルでテスト画像に対してどれだけ正しく分類できるかを計測する
  • 28×28のグレースケール画像(1チャンネル)
  • 各ピクセルは0から255までの値を取る
  • それぞれの画像データに対して対応するラベルが与えられている

MNISTデータを読み込む下準備

ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装ではMNISTデータセットのダウンロードから画像データのNumPy配列への変換までをサポートする便利なPythonスクリプトであるmnist.pyを提供して頂きました。ありがとうございます。個人的にはいきなり提供頂いたようにみえ(まえがきに記載ありました)、一瞬戸惑いましたので手順を簡単にメモします。

  1. GitHubリポジトリからゼロから作るDeep Learningで使用するプログラムをダウンロードする。
    方法は様々かと思いますが、私のPCにはTortoiseGitがありましたので任意の場所にクローンをしました。
    リポジトリURL https://github.com/oreilly-japan/deep-learning-from-scratch
  2. PyCharmからクローンしたdeep-learning-from-scratchを開いてPython Consoleから実行する。
    私の場合はfrom dataset.mnist import load_mnistを実行したところ、ModuleNotFoundErrorが発生しました。対応方法としてはメニューバーの『Files』、『Settings』で設定を開きサイドメニューから Project: deep-learning-from-sc…を押し、C:\ProgramData\Anaconda3\python.exeに変更したところ解決できました。
    <出力メッセージ>
    Traceback (most recent call last):
    File “<input>”, line 1, in <module>
    File “C:\Users\hogehoge\AppData\Roaming\JetBrains\PyCharm Community Edition 2018.1\helpers\pydev\_pydev_bundle\pydev_import_hook.py”, line 20, in do_import
    module = self._system_import(name, *args, **kwargs)
    File “C:\Users\hogehoge\Documents\git\DeepLearning\deep-learning-from-scratch\dataset\mnist.py”, line 10, in <module>
    import numpy as np
    File “C:\Users\hogehoge\AppData\Roaming\JetBrains\PyCharm Community Edition 2018.1\helpers\pydev\_pydev_bundle\pydev_import_hook.py”, line 20, in do_import
    module = self._system_import(name, *args, **kwargs)
    ModuleNotFoundError: No module named ‘numpy’

MNISTデータを読み込む

import sys, os
sys.path.append(os.pardir) #親ディレクトリのファイルをインポートするための設定
from dataset.mnist import load_mnist
#最初の呼び出しは数分待つ。。。。
(x_train,t_train), (x_test,t_test) = load_mnist(flatten=True, normalize=False)
Downloading train-images-idx3-ubyte.gz ... 
Done
Downloading train-labels-idx1-ubyte.gz ... 
Done
Downloading t10k-images-idx3-ubyte.gz ... 
Done
Downloading t10k-labels-idx1-ubyte.gz ... 
Done
Converting train-images-idx3-ubyte.gz to NumPy Array ...
Done
Converting train-labels-idx1-ubyte.gz to NumPy Array ...
Done
Converting t10k-images-idx3-ubyte.gz to NumPy Array ...
Done
Converting t10k-labels-idx1-ubyte.gz to NumPy Array ...
Done
Creating pickle file ...
Done!
#それぞれのデータの形状を出力
print(x_train.shape)
(60000, 784)
print(t_train.shape) 
(60000,)
print(x_test.shape)
(10000, 784)
print(t_test.shape) 
(10000,)

mnist.pyの関数load_mnist()

  • MNISTデータセットを読み込む
  • ダウンロードを行う為、ネット接続が必要で数分かかる
  • 「(訓練画像、訓練ラベル),(テスト画像,テストラベル)」という形式で読み込んだMNISTデータが返却される
  • load_mnist(normalize=True, flatten=True, one_hot_label=False)のように3つの引数を設定することができる
  • normalize⇒入力画像を0.0~1.0の間に正規化するかどうか
  • flatten⇒入力画像を平らにする(1次元配列にする)かどうか
    False:1×28×28の3次元配列
    True:784個の要素からなる1次元配列
  • one_hot_label⇒one-hot表現として格納するかどうか
    False:単純に正解となるラベルが格納される
    True:ラベルはone-hot表現として格納される
    ※one-hot表現とは、[0,0,1,0,0,0,0,0,0]のように、正解となるラベルだけが1でそれ以外は0の配列で表現すること

MNIST画像表示

import sys, os
sys.path.append(os.pardir)
import numpy as np
from dataset.mnist import load_mnist
from PIL import Image
def img_show(img):
    pil_img = Image.fromarray(np.uint8(img))
    pil_img.show()
(x_train, t_train), (x_test, t_test) = load_mnist(flatten=True, normalize=False)
img = x_train[0]
label = t_train[0]
print(label)
5
print(img.shape)
(784,)
img = img.reshape(28, 28)
print(img.shape)
(28, 28)
img_show(img)

  • 画像の表示はPIL(Python Image Library)モジュールを使用
  • 訓練画像の1枚目が表示される
  • flatten = Trueとして読み込んだ画像はNumPy配列として1列(1次元)で格納されている
  • 画像の表示に際しては、元の形状である28×28のサイズに再変形(reshape)する必要がある
  • NumPy配列の形状の変形はreshape()メソッドによって行う
  • Numpyとして格納された画像データをPIL用のデータオブジェクトに変換する必要があり、Image.fromarray()によって行う

ニューラルネットワークでの推論処理

  • 入力層 784
    画像サイズの28×28=784より
  • 出力層 10
    10クラス分類(数字の0から9の10クラス)
  • 隠れ層が2つ 1つ目 50 2つ目 100
import sys, os

sys.path.append(os.pardir)  # 親ディレクトリのファイルをインポートするための設定
import numpy as np
import pickle
from dataset.mnist import load_mnist


def sigmoid(x):
    return 1 / (1 + np.exp(-x))


def softmax(a):
    c = np.max(a)
    exp_a = np.exp(a - c)  # オーバフロー対策
    sum_exp_a = np.sum(exp_a)
    y = exp_a / sum_exp_a
    return y


def get_data():
    (x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, flatten=True, one_hot_label=False)
    return x_test, t_test


def init_network():
    with open("sample_weight.pkl", 'rb') as f:
        network = pickle.load(f)
    return network


def predict(network, x):
    W1, W2, W3 = network['W1'], network['W2'], network['W3']
    b1, b2, b3 = network['b1'], network['b2'], network['b3']

    a1 = np.dot(x, W1) + b1
    z1 = sigmoid(a1)
    a2 = np.dot(z1, W2) + b2
    z2 = sigmoid(a2)
    a3 = np.dot(z2, W3) + b3
    y = softmax(a3)
    return y


x, t = get_data()
network = init_network()

accuracy_cnt = 0
for i in range(len(x)):
    y = predict(network, x[i])
    p = np.argmax(y)  # 最も確率の高い要素のインデックスを取得
    if p == t[i]:
       accuracy_cnt += 1

print("Accuracy:" + str(float(accuracy_cnt)/len(x)))

  1. MNISTデータセット取得(get_data())
  2. ネットワーク生成(init_network())
  3. xに格納された画像データを1枚ずつfor文で取り出し、precict()関数によって分類
    precict()関数の結果は各ラベルの確率がNumpy配列として出力される
  4. 確率リストから最も大きな値のインデックス(何番目の要素が1番確率が高いか)を取り出し、予測結果とする(argmax())
  5. ニューラルネットワークが予測したこと答え(p)と正解ラベル(t)を比較する
  6. 正解した割合を認識精度(Accuracy)として出力する
  • init_network()
    sample_weight.pklに保存された学習済みの重みパラメータを読み込む
  • sample_weight.pkl
    重みとバイアスのパラメータがディクショナリ型の変数で保存されている
    今回使用したファイルはゼロから作るDeep Learningのリポジトリ内のch3にあったものを使用
    W1,W2,W3,b1,b2,b3が格納されている
  • W1,W2,W3,b1,b2,b3の配列の形状
    W1⇒ (784, 50)
    b1⇒(50,)
    W2⇒ (50, 100)
    b2⇒(100,)
    W3⇒ (100, 10)
    b3⇒(10,)
  • np.argmax(x)
    引数xに与えられた配列で最大の値を持つ要素のインデックスを取得する
  • init_network()以外の関数の説明リンク
    sigmoid()
    softmax()
    predict()

処理時間を減らす為実装の変更

784の要素からなる1次元配列が入力され、1次元の配列(要素数10)が出力されるという流れになっている

100枚の画像をまとめて1回のpredict()関数で処理したいとする。その為には、xの形状を100×784として、100枚分のデータをまとめて入力データとすることができる

ハイライト部分が変更した箇所

x, t = get_data()
network = init_network()

batch_size = 100  # バッチの数
accuracy_cnt = 0

for i in range(0, len(x), batch_size):
    x_batch = x[i:i + batch_size]
    y_batch = predict(network, x_batch)
    p = np.argmax(y_batch, axis=1)
    accuracy_cnt += np.sum(p == t[i:i + batch_size])

print("Accuracy:" + str(float(accuracy_cnt) / len(x)))
  • range()関数
    range(start,end)⇒startからend-1までの整数からなるリストを作成
    range(start,end,step)⇒リストの要素の次の値がstepで指定された値だけ増加するリストを作成
    今回の場合は[0, 100, 200, 300…]のリストを作成する為、最初はi=0、次はi=100になる
  • x[i:i+batch_size]
    スライシングでインデックスのi番目からi+batch_size番目までを取得
    i=0の場合 ⇒ x[0:100]⇒先頭から100番目を取得
    i=100の場合 ⇒ x[100:200]⇒101番目から200番目を取得
  • argmax()
    最大値のインデックスを取得
  • axis=1
    100×10の配列の中で1次元目の要素ごとに(1次元目を軸として)、最大値のインデックスを見つけることを指定している
  • np.sum(p == t[i:i + batch_size])
    結果(p)と答え(t[i:i + batch_size])を比較する
    NumPy配列どうしで比較演算子(==)によって、True/Falseからなるブーリアン配列を作成し、Trueの個数を算出する

変更前後での処理時間の比較

start = time.time()
elapsed_time = time.time() - start
print("elapsed_time:{0}".format(elapsed_time) + "[sec]")

私のPCの場合、
変更前は約5秒、変更後は約1秒の処理時間となり、約4秒処理時間を短縮できる

短縮できる理由は数値計算を扱うライブラリの多くは大きな配列の計算を効率よく処理できるような最適化が行われている為である。大きな配列を1度に計算するほうが、分割した小さい配列を少しづつ計算するよりも速く計算が完了する。