社畜エンジニア発掘戦線

駆けだしAIエンジニア

アルゴリズムの関数化2

おつかれさまです。

前回に続いて、もう少しだけ関数化の練習をしてみたいと思います。今回は、隠れ層を追加したニューラルネットワークの構築に使ったアルゴリズムを関数化してみます。まぁ、ほとんど前回やっちゃったんですけどね。

流れはほとんど同じなので、変更点だけピックアップしてコードを書いていこうと思います。

シグモイド関数の追加

まず、ここでは隠れ層を追加したので、隠れ層に使用した活性化関数:シグモイド関数を追加します。

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

ここは問題ないですね。

全結合層の編集

次の変更点は、このシグモイド関数を用いた全結合層(隠れ層)の追加です。全結合層は前回で関数化が完了していますが、softmax関数しか対応していません。処理のアルゴリズムは同じですが、活性化関数だけが異なります。

def affine(img,W,B):
    return softmax(np.dot(img, W)+B)

この全結合処理を様々な活性化関数でも対応できるようにしてみます。

まず最初に、この層行われる計算のinput(上ではimgとしています)とWの行列の型が合っていなければ、そもそも計算ができません。ターミナル上にはエラーが表示されます。まぁこのエラーを参照してもいいんですが、英語だし、自分で分かるようにエラー文を表示させてみることにします。

def affine(input,W,B,f):
    if input.shape[1] != W.shape[0]:
        print('Affine Error: 入力とWの行列の型が合っていません')
        sys.exit()

    else:
        〜活性化関数の処理〜

入力の変数はimgからinputとします。fは使いたい活性化関数を指定する変数です。

エラーの分岐はifを使って「入力の1行目(0行目はバッチ数)」と「Wの0行目」が一致するときとしないときで処理を分けます。一致しないときはエラー文「'Affine Error: 入力とWの行列の型が合っていません'」を出力してsys.exit()でプログラムを終了させます。この処理を入れることで、プログラムのどこでエラーが発生したのか分かりやすくなりました。

sys.exit()は実行中のプログラムを終了させるコマンドで、これを使うにはsysというモジュールが必要です。プログラムの最初に下記のインポートも追記しておきます。

import sys


では、活性化関数を使い分けるコードを書いていきたいと思います。とりあえず、「シグモイド関数」、「ソフトマックス関数」、「関数なし(XW+B)」、「入力の打ち間違い(エラー)」の4通り作ってみます。使い分けがあるので、ここも分岐のifを使います。4通りなのでelifが必要です。

    else:
        if f == 'softmax':
            return softmax(np.dot(input, W)+B)

        elif f == 'sigmoid':
            return sigmoid(np.dot(input, W)+B)

        elif f == 'none':
            return np.dot(input, W)+B

        else:
            print('Affine Error: 関数が不明です')
            sys.exit()

fは活性化関数を指定する入力変数で、文字列で入力するようにします。なので「’’」がいります。文字の打ち間違えとかしちゃった場合、エラー文「'Affine Error: 関数が不明です'」を出力してプログラムを終了するようにします。

誤差逆伝搬の考慮

あと、考慮すべきアルゴリズムは逆伝搬計算に用いる微分式です。

    dWo = np.dot(H.T,(Y-T_batch))
    dBo = np.reshape(np.sum(Y-T_batch, axis=0),(1,10))

    dWh = np.dot(X_batch.T,H*(1-H)*np.dot(Y-T_batch,Wo.T))
    dBh = np.sum(H*(1-H)*np.dot(Y-T_batch,Wo.T), axis=0, keepdims=True)

このコードは損失関数EをパラメータW(B)で微分することに由来しています。

出力層:{\displaystyle 
\frac{\partial E}{\partial W} = \frac{\partial Y}{\partial W} \frac{\partial E}{\partial Y}
}

隠れ層:{\displaystyle 
\frac{\partial E}{\partial W} = \frac{\partial H}{\partial W} \frac{\partial Y}{\partial H} \frac{\partial E}{\partial Y} 
}

ここのコードもいい具合に一般化して関数にしてしまおうと考えているんですが、いいアイデアが思いつきません…。この逆伝搬が中間層(隠れ層)なのか出力層なのかで必要な変数が異なるので、うまくまとまらないんですよね〜。思いついたらまた記事にするので、今回はこのまま書いて動作確認することにします。

動作確認

これで関数の追加は完了です。関数を記述しているファイルは「function1.py」として保存しておきましょう。では、「MNIST_hidden.py」というファイルに隠れ層追加のニューラルネットワークのを記述してみます。

MNIST_hidden.py
github.com

function1.py
github.com

実際にプログラムを走らせてみると、前回と同じような結果が得られます。

f:id:sutokun:20190518224904p:plain:w400
f:id:sutokun:20190518224919p:plain:w400

ちなみに計算時間は、

6.0202858448028564

問題なく関数は動作してそうです。

まとめ

こんな感じで、まとめられそうなアルゴリズムがあればどんどん外部ファイルにエクスポートして、プログラムをスッキリ分かりやすく書いていければと思います。逆伝搬の計算は、ちょっとじっくり考えることにします。


元の記事
週末のディープラーニング - 社畜エンジニア発掘戦線

Twitter
世界の社畜 (@sekai_syachiku) | Twitter