Enjoy Engineer Life

C#,Unity,C++,UE4などの技術ブログや雑記を徒然なるままに書いていきます。

【読書メモ】Deep Learningを学ぶ〜学習済みパラメータを使用して手書き文字を推論〜Part 14

最初に

本記事は以下の本から得た知見を自分用のメモも兼ねてまとめた記事となります。ただし本の内容をそのまま書き写ししているわけではなく、ある程度自身で調べたものや感想が混じっています。ご了承ください。

f:id:rossamu:20190103000547p:plain ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装

また、本記事は株式会社よむネコのプロジェクトの業務内のお時間を頂いて蓄積した知見となります。
以下会社URLです。

http://www.yomune.co/ f:id:rossamu:20191023153505p:plain



今回は、実際に手書き数字を認識するような、分類問題を扱ってみたいと思います。尚、学習済みのパラメータを使用します。



推論処理の実装

ニューラルネットワークの推論処理は「順方向伝播(forward propagation)」とも言われます。


機械学習でも問題を解く手順は「学習」と「推論」がありますが、ニューラルネットワークで問題を解く場合にも、まずは学習(訓練)データを使って重みパラメータの学習をし、推論時には学習済みパラメータを使って分類問題を扱います


今回はMNIST(Mixed National Institute of Standards and Technology database:エムニスト)というデータセットを利用します。MNISTは機械学習分野の中でも最も有名なデータセットの一つで、論文として発表するような研究での実験用データとしてもよく利用されているようです。


MNISTには手書き数字画像のデータ60,000枚と、テスト画像10,000枚+その画像に書かれた数字の正解ラベルデータがセットになっています。

この画像データは28×28のグレー画像で、各ピクセルは0〜255までの値を取ります。それぞれの画像にそれぞれの数字に対応するラベルが与えられています。

"MNISTデータセットDL→画像データをNumPy配列へ変換"までをまず行います。



MNISTデータセットのDLを行う

まずはGithubでこの本のソースコードをCloneかzipで落としてきます。

https://github.com/oreilly-japan/deep-learning-from-scratch



まず、sysosのimportを行います。

   import sys, os


次にos.getcwd()でカレントディレクトリのパスを確認してください。そこから先ほど落としてきたソースコードディレクトリ内にある「ch03」へ移動します。Pythonでの移動はas.chdir('移動先のフォルダパス')を使用します。


それでは、MNISTのデータセットDLを行ってみます。

   # sys.path は importするファイルを検索するためのパスを示す文字列のリスト.
    # appendはリストの末尾に要素を足す.
    # os.pardirは親ディレクトリを表す.
    sys.path.append(os.pardir)  # 親ディレクトリのファイルをimport.

    # from モジュール名 import クラス(又は関数)名
    # dataset > mnist.py の load_mnistという関数を直接importして使用できるようにする.
    from dataset.mnist import load_mnist
    
    # 初回のみMNISTデータのDLを行うためネット接続が必須.少し待つ.
    # 2回目以降はローカルに保存したファイル(pickle)を読み込む
    # (訓練画像, 訓練ラベル), (テスト画像, テストラベル) = load_mnist(normalize=True, flatten=True, one_hot_label=False)
        # normalize:入力画像を0.0~1.0に正規化するかどうか.Falseならピクセルは元の0~255.
        # flatten:入力画像を平ら(1次元配列)にするかどうか.
        # Falseなら入力画像は1 * 28 * 28 の3次元配列.
                # Trueなら784個の要素を持つ1次元配列.
        # one_hot_label:ラベルをone-hot表現(後述)として格納するかどうか.
    (x_train, t_train), (x_test, t_test) = load_mnist(flatten=True,
    normalize=False)



One-hot表現とは?

正解となるラベルだけが1、それ以外は0となる配列のことです。

個人的にわかりやすいと思った解説が乗っているページは以下の通りです。



MNIST画像の表示

   import numpy as np
    
    # 画像の表示にPIL(Python Image Library)モジュールを使用
    from PIL import Image

    def img_show(img):
        # Numpyとして格納された画像データをPIL用のデータオブジェクトに変換
        pil_img = Image.fromarray(np.uint8(img))
        pil_img.show()

    img = x_train[0]
    label = t_train[0]
    
    # 表示する画像に対応したラベル内容
    print(label)
    # 結果:5
    
    # Numpy配列として1次元で格納されているため、
    # 標準サイズの28 × 28 = 784という数値になっている.
    print(img.shape)
    # 結果:(784,)
    
    # 28×28の標準サイズに再変形
    img = img.reshape(28, 28)
    
    img_show(img)

上記のような処理を行うことで、「5」を表す画像が表示されると思います。



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

ネットワークは入力層が(28*28=)784個、出力層を10個(0~9までのクラス数)のニューロンで構成します。

又、隠れ層は2つ存在するとして、1つ目の隠れ層が50個、2つ目の層が100個といった任意のニューロンで構成されるものとします。

   import pickl
    # 中身は以前実装していたsigmoid, softmaxと同様
    from common.functions import sigmoid, softmax

    def get_data():
        # 前処理として正規化(normalize)を行う
        (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():
        # sample_weight.pklに保存された学習済みの重みとバイアスのパラメータを読み込む.
        # 重みとバイアスのパラメータはディクショナリ型の変数として保存されている.
        with open("sample_weight.pkl", 'rb') as f:
            network = pickle.load(f)
        return network

    # 各ラベルの確率をNumPy配列で出力.
    # [(0の確率が)0.1, (1の確率が)0.3, (2の確率が)0.2...]
    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 # 一致していれば正解数をインクリメント

    # 正解数 / xの要素数 = 正解率
    print("Accuracy:" + str(float(accuracy_cnt) / len(x))
    # 結果:0.9352 つまり93.52%



for文の中の、y = predict()で各ラベルの確率をNumPy配列として出力し、p = np.argmax(y)で最も(確率の)値が大きなインデックスを取得し、それを予測結果とします。今回、前処理として正規化を行っています。前処理には識別性能の向上や学習の高速化などに効果があります。前処理には例えば以下のようなものがあります。


  • 「正規化」:学習データの値が0~1の範囲に収まるよう加工を施す。
    • 方法:学習データの中の最大値を取り出し、全てのデータをその値で割る。
    • 注意:多次元データでは正規化が有効であるかはわからない。例えば、各次元が(身長、体重、体脂肪率)だった場合は各成分毎に正規化が必要かもしれない。
  • 「標準化」:入力データの平均を0に、分散を1にするような加工を施す。データは概ね-1~1の範囲に留まる。
    • メリット:観測して集めたデータは必ずしも範囲が明確ではないため、統計的なパラメータを使用して標準化を行うのが一般的。
  • 「白色化」:データ全体の分布の形状を均一にする。(一旦簡略化)



最後に

今回は学習済みのパラメーターを用いて分類問題を簡易的に扱って見ました。少し手元で実行するために手間取ったりもしましたが、無事ここまで読了することができました。次回はこれをバッチ処理にしていきたいと思います。またバッチ処理のメリットも合わせて記事にしていきます。次回で本の3章が終わりとなります。まだ先は長いですが、頑張って行きましょう。



参考