読者です 読者をやめる 読者になる 読者になる

新人SEの学習記録

14年度入社SEの学習記録用に始めたブログです。気づけば社会人3年目に突入。

学習記録:ゼロから作るDeepLearning

3章:ニューラルネットワーク(続き)

出力層の設計

ソフトマックス関数

機械学習の問題は,分類問題(データがどのクラスに属するか)と回帰問題(ある入力データから数値の予測を行う)に大別できる。
一般的に,回帰問題では恒等関数を,分類問題ではソフトマックス関数を用いる。

前述したとおり,恒等関数は入力をそのまま出力する。一方,ソフトマックス関数は次の式で表される。
yk = exp(ak) / ∑exp(ai)

ソフトマックス関数の実装は以下のとおり。

import numpy as np

def softmax(a):
    exp_a = np.exp(a)
    sum_exp_a = np.sum(exp_a)
    return exp_a / sum_exp_a

a = np.array([0.3,2.9,4.0])
print(softmax(a))

この実装には欠陥があり,それは指数関数の計算結果が非常に大きな値になってしまう可能性がある点である。
これによる数値のオーバーフローを防ぐため,分子・分母の指数関数expの中で,定数C(一般には入力信号の最大値)をマイナスするのが一般的である。

def softmax(a):
    c = np.max(a)
    exp_a = np.exp(a-c)   # ここでCを引いておく
    sum_exp_a = np.sum(exp_a)
    return exp_a / sum_exp_a     # 結果は変わらない

さて,このソフトマックス関数には,出力が0から1.0の間の実数になり,総和が1になるという特徴がある。

a = np.array([0.3,2.9,4.0])
print(softmax(a))
print(np.sum(softmax(a)))
% python softmax.py
[ 0.01821127  0.24519181  0.73659691]
1.0

この性質から,ソフトマックス関数の出力を確率として解釈することができる。
例えば上記の例なら,y[2]の確率が73%で一番高いので,y[2]のクラスである,といった判断ができることになる。
ただし,ソフトマックス関数の適用前後における値の大小関係は変わらない(上の例だと適用前はa[2]が一番大きく,適用後も2番目の要素であるy[2]が一番大きい)ので,一般的には出力層のソフトマックス関数は省略することが多い(指数関数の計算にはそれなりの計算資源を食うため)。

手書き数字認識

機械学習の手順は学習と推論の2フェーズに分けられる。
学習フェーズでモデルを学習し,推論フェーズでモデルを使って未知のデータに対して推論を行う。
ここでは,学習済のパラメータを使って,ニューラルネットワークの推論処理を実装してみる。

MNISTデータ・セット

MNISTは機会学習の分野で有名なデータセットで,0から9の数字画像(28x28ピクセル)で構成され,訓練画像とテスト画像が用意されている。
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]
img = img.reshape(28,28)
img_show(img)

本書のサンプルプログラム中のdataset/mnist.pyにあるload_mnist関数により,MNISTデータのダウンロードから画像データのNumPy配列への変換を一気に行うことができる。
画像の表示にはPILモジュールを使っており,訓練画像の1枚目(「5」の画像)を表示している。
なお,load_mnist関数でflatten=Trueを指定していると,画像がNumPy配列に1次元で格納されるため,もとの28x28のサイズにreshapeしてから表示をする必要がある。

推論処理

このMNISTデータセットに対して推論処理を行うニューラルネットワークを実装する。
入力層は784個(画像サイズが28x28のため),出力層は10個(0〜9)のニューロンで構成する。
隠れ層は二つとし,それぞれ50個と100個のニューロンを持つものとする(ここの個数は任意に設定できる)。

# MNISTデータを取得する                                                              
def get_data():
    # flatten: 入力画像を1次元に配列にする(Falseなら1x28x28の3次元配列)              
    # normalize: 入力画像を0〜1の値に正規化する(Falseなら0〜255)                     
    # one_hot_label: 正解のラベルだけ1の配列にする(Falseなら2,7など正解の値が入る)   
    (x_train, t_train), (x_test, t_test) = \
         load_mnist(flatten=True, normalize=True, one_hot_label=False)
    return x_test, t_test

# 学習済の重みパラメータ(sample_weight.pkl)を使ってネットワークを初期化する          
# このファイルには重みとバイアスがディクショナリ型の変数として保存されている         
def init_network():
    with open("sample_weight.pkl", "rb") as f:
        # pickle(実行中のオブジェクトをファイルとして保存する機能)を使う             
        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])
    # 配列yの中で最大の(最も確率の高い)インデックスを取得                            
    p = np.argmax(y)
    # pが正解(t[i])と等しければ正解数を増やす                                        
    if p == t[i]:
        accuracy_cnt += 1

# 正解率を精度として出力                                                             
print("Accuracy: " + str(float(accuracy_cnt) / len(x)))
% python neuralnet_mnist.py
Accuracy: 0.9352

この結果は,93.52%正しく分類できた,ということを表す。
今後ニューラルネットワークの構造や学習方法を工夫することで,99%以上の精度を目指す。