ギークなエンジニアを目指す男

機械学習系の知識を蓄えようとするブログ

ニューラルネットワークの構築 〜活性化関数と実際の構築〜

前回の続きです。

本日はconnpassで募集していた新宿のもくもく会で勉強しています。
集中できて素晴らしい。(主催者の方、ありがとうございます)
また参加しようと思います。(コーヒーとお菓子食べれるし。笑)

前回記事はこちら

taxa-program.hatenablog.com

ソフトマックス関数

ソフトマックス関数の分子は入力信号の指数関数、分母はすべての入力信号の指数関数の和から構成されています。

mathtrain.jp

早速pythonでソフトマックス関数を実装してみます。

# ソフトマックス関数の実装
def softmax(a):
    c = np.max(a)
    exp_a = np.exp(a - c) # オーバーフロー対策でcを引いている
    sum_exp_a = np.sum(exp_a)
    y = exp_a / sum_exp_a
    return y

# 試しに計算してみる
a = np.array([0.3, 2.9, 4.0])
y = softmax(a)
print(y) # [0.01821127 0.24519181 0.73659691]

出力を見て分かるように、ソフトマックス関数の出力は0~1.0の間の実数になります。 また、総和は1になっていますね。
この総和が1という性質がとても重要で、この性質によってソフトマックス関数の出力を確率として解釈することができます。

上の例ではy[0]の確率が0.018(1.8%)、y[1]の確率が0.245(24.5%)、y[2]の確率が0.737(73.7%)のように解釈できます。この確率の結果から、2番目の要素が最も大きいため、2番目のクラスだろう、と分類できます。

しかし、ソフトマックス関数を利用しても、各要素の大小関係は変化しないため、ニューラルネットワークで分類を行う際には、ソフトマックス関数を省略することが多いみたい。
(学習フェーズでソフトマックス関数を用いて、推論のフェーズでは省略されることが多いようです)

出力層ニューロンの数

出力数のニューロン数は解く問題によって適宜決定する必要があります。
当たり前だが、クラス分類したい場合は分類したいクラス数に出力ニューロン数を決定する必要があります。

MNSITからデータを拝借してNNを構築してみる

MNSITとは、簡単なコンピュータ・ビジョンのデータセットで、手書き数字の画像からなります。
詳細

http://tensorflow.classcat.com/2016/03/09/tensorflow-cc-mnist-for-ml-beginners/

まずは、画像を表示させてみます。
(下記PGはあらかじめデータセットを取得した状態です。最後に全てのソースコードはアップする予定です)

mine_mnist_show.py

# coding: utf-8
# minstの画像を表示するプログラム
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()

# minstのデータを読み込む
# flatten=Trueで読み込むと、読み込んだデータは1次元配列になるため、
# のちほど元の画像サイズに変更する必要がある(Image.fromarray)
# (画像, ラベル)で取得できる
(x_train, t_train), (x_test, t_test) = load_mnist(flatten=True, normalize=False)

img = x_train[0]
label = t_train[0]

# 0番目のデータを表示してみる
print(label) # 5
print(img.shape) # (784,)
img = img.reshape(28, 28) # 形状を元の画像サイズに変形
print(img.shape) # (28, 28)

img_show(img)

ここでは、最終的に画像ファイルが表示されます。

f:id:taxa_program:20180602140105p:plain

では、続いてニューラルネットワークの構築を行なってみます。

まず、この画像セットは0〜9までの画像となっているので、出力層のニューロン数は10にします。(10クラスの分類を行う)

入力層ですが、画像ファイルが28 * 28なので、784個のニューロンとなります。

mine_neuralnet_mnist.py

# coding: utf-8
import sys, os
sys.path.append(os.pardir)  # 親ディレクトリのファイルをインポートするための設定
import numpy as np
import pickle
from dataset.mnist import load_mnist # datasetフォルダのnmistファイルからload_mnistモジュールを読み込み
from common.functions import sigmoid, softmax # commonフォルダのfunctionファイルからsigmoid,softmaxモジュールを読み込み


# mnistからデータを取得
def get_data():
    (x_train, t_test), (x_test, t_test) = load_mnist(normalize=True, flatten=True, one_hot_label=False)
    return x_test, t_test

# 学習済みパラメータの読み込み
# sample_weight.pklには重みとバイアスのパラメータがディクショナリ型の変数として保存されている
def init_network():
    with open("sample_weight.pkl", 'rb') as f:
        network = pickle.load(f)
    return network

# 予測を行う
# この関数は各ラベルの確率がNumPy配列として出力される
# ex)[0.1, 0.3, 0.2, ... , 0.04]
# 0の確率が0.1, 1の確率が0.3 ... という解釈できる
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 # 正解の精度

# xに格納された画像データを1枚ずつ取り出して処理
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))) # Accuracy: 0.9352

正答率が約93%と表示されました。(結構良い正答率な気がします)

さて、上記のコードでは画像を1枚ずつ読み込み処理しています。
(10000枚の画像ファイルがあった場合、forループを10000回行います)

これでは効率が悪いような気がしますね。。。
せっかく配列を利用して計算しているのに、その恩恵を受けきれていない気がします。
そこで、データを一定の量でまとめて処理するように修正してみます。
(これをバッチ処理と呼ぶらしいです)

NNでバッチ処理を行なってみる

さきほどのコードから、少しだけ修正してバッチ処理を行わせてみます。
バッチ数の追加と、for文を修正しました。

batch_size = 100 # バッチの数
accuracy_cnt = 0 # 正解の精度

# xに格納された画像データをbatch_sizeずつ取り出して処理
for i in range(0, len(x), batch_size):
    # [0:100], [100:200], [200:300] ....
    x_batch = x[i:i+batch_size]
    y_batch = predict(network, x_batch)
    p = np.argmax(y_batch, axis=1) # axis=1 は1次元目の要素毎に最大値のインデックスを見つける
    accuracy_cnt += np.sum(p == t[i:i+batch_size])

これでループの回数が減り、処理速度の向上が見込めると思います。

3章まで学んでみましたが、ニューラルネットワークの面白さがなんとなーーーーーく
分かり始めたような気がします。

次は4章頑張ります。

あ、ソースコードはこちらです。

github.com