Lua版 ゼロから作るDeep Learning その１４［二層ニューラルネットワークの実装］


過去記事まとめ
--
[Lua版 ゼロから作るDeep Learning まとめ](http://qiita.com/Kazuki-Nakamae/items/e1d79bf10f433c4aca54)


はじめに
--
　今回は原書4章の二層ニューラルネットワークを実装します。
　スクリプトは以下の通りです。


```lua:two_layer_net.lua
--Copyright (C) 2017  Kazuki Nakamae
--Released under MIT License
--license available in LICENSE file

common = require './common'

--[[***クラスの定義*******************************************************************]]

--- simpleNetクラス（オブジェクト）
-- 単純なニューラルネットワークを生成する
-- @param input_size 入力層のニューロン数{Type:Tensor}
-- @param hidden_size 隠れ層内のニューロン数{Type:Tensor}
-- @param output_size 出力層のニューロン数{Type:Tensor}
TwoLayerNet={}
TwoLayerNet.new = function(input_size, hidden_size, output_size, weight_init_std)

        --デフォルト引数
        if not weight_init_std then
            weight_init_std = 0.01
        end

        local obj = {}

        --メンバ変数
        --重みとバイアスを設定
        obj.param = {}
        obj.param['W1'] = weight_init_std * torch.randn(input_size, hidden_size)
        obj.param['b1'] = torch.Tensor(hidden_size):zero()
        obj.param['W2'] = weight_init_std * torch.randn(hidden_size, output_size)
        obj.param['b2'] = torch.Tensor(output_size):zero()

        -- @function self.predict()
        -- 推論処理を行う。
        -- @param x 入力データ{Type:Tensor}
        -- @return 出力：推定される確率{Type:Tensor}
        obj.predict = function(self, x)
            local W1, W2 = self.param['W1']:clone(), self.param['W2']:clone()
            local b1, b2 = self.param['b1']:clone(), self.param['b2']:clone()

            local a1 = common.mulTensor(x, W1) + common.makeIterTensor(b1,common.mulTensor(x, W1):size()[1])
            local z1 = common.sigmoid(a1)
            local a2 = common.mulTensor(z1,W2) + common.makeIterTensor(b2,common.mulTensor(z1,W2):size()[1])
            local y = common.softmax(a2)

            return y
        end

        -- @function self.loss()
        -- 入力データと教師データとの誤差を算出する
        -- @param x 入力データ{Type:Tensor}
        -- @param t 教師データ{Type:Tensor}
        -- @return 損失関数の出力値{Type:Tensor}
        obj.loss = function(self, x, t)
            local y = self:predict(x)
            local loss = common.cross_entropy_error(y, t)

            return loss
        end

        -- @function self.accuracy()
        -- 入力データと教師データとの間の精度を算出する
        -- @param x 入力データ{Type:Tensor}
        -- @param t 教師データ{Type:Tensor}
        -- @return 入力データと教師データとの間の精度{Type:number}
        obj.accuracy = function(self, x, t)
            local y = self:predict(x)
            local y_value, y_indices = torch.max(y, 2)
            local t_value, t_indices = torch.max(t, 2)
            local accuracy_cnt = 0
            --要素それぞれを比較して合計を計算
            accuracy_cnt = accuracy_cnt + torch.sum(torch.eq(y_indices:byte(), t_indices:byte()))
            return accuracy_cnt/x:size()[1]
        end

        -- @function self.numerical_gradient()
        -- 入力データ・教師データでの損失関数でのパラメータに対する勾配を計算する。
        -- @param x 入力データ{Type:Tensor}
        -- @param t 教師データ{Type:Tensor}
        -- @return 各パラメータの勾配{Type:table}
        obj.numerical_gradient = function(self, x, t)
            local loss_W = (function(w) return self:loss(x, t) end)

            local grads = {}
            print("W1の勾配計算...")
            grads['W1'] = common.numerical_gradient(loss_W, self.param['W1'])
            print("b1の勾配計算...")
            grads['b1'] = common.numerical_gradient(loss_W, self.param['b1'])
            print("W2の勾配計算...")
            grads['W2'] = common.numerical_gradient(loss_W, self.param['W2'])
            print("b2の勾配計算...")
            grads['b2'] = common.numerical_gradient(loss_W, self.param['b2'])
            print("計算完了")

            return grads
        end

        return obj
end



--[[***処理部*******************************************************************]]

torch.manualSeed(2017) --シード

--二層NNを作成
local net = TwoLayerNet.new(10, 100, 10)

--パラメータのサイズの確認
print(net.param['W1']:size())
print(net.param['b1']:size())
print(net.param['W2']:size())
print(net.param['b2']:size())

--テスト用のダミーデータ
local t = thresMaxTensor(torch.rand(100, 10)) --ダミーの正解ラベルデータ(100枚分)
local x = torch.rand(100, 10) --ダミーの入力データ(100枚分)

--推論処理の実行
print("predict")
print(net:predict(x))

--精度計算の実行
print("accuracy")
print(net:accuracy(x, t))

--勾配計算の実行
print("gradient")
local grads = net:numerical_gradient(x, t)
print("W1の勾配")
print(grads['W1'])
print("b1の勾配")
print(grads['b1'])
print("W2の勾配")
print(grads['W2'])
print("b2の勾配")
print(grads['b2'])
```
　commonモジュールにはいくつかの関数を追加しました。全てを掲載するのは大変ですので、参照したい方は
　```git clone https://github.com/Kazuki-Nakamae/Lua_DLfromScratch```
　でダウンロードをお願いします。
　
　実行結果は以下の通りです。


```bash:実行結果
$ th two_layer_net.lua
  10
 100
[torch.LongStorage of size 2]

 100
[torch.LongStorage of size 1]

 100
  10
[torch.LongStorage of size 2]

 10
[torch.LongStorage of size 1]

predict	
0.001 *
  0.9481  0.9998  0.9788  1.1157  0.9948  1.0450  1.0016  0.9372  1.0582  0.9215
  0.9487  0.9994  0.9786  1.1153  0.9940  1.0450  1.0012  0.9371  1.0579  0.9213
  0.9486  0.9998  0.9787  1.1154  0.9947  1.0449  1.0013  0.9370  1.0578  0.9215
  0.9485  0.9998  0.9785  1.1159  0.9945  1.0454  1.0013  0.9372  1.0582  0.9211
  0.9480  0.9997  0.9790  1.1160  0.9947  1.0450  1.0013  0.9373  1.0584  0.9214
（省略）
  0.9482  0.9995  0.9790  1.1156  0.9944  1.0453  1.0012  0.9370  1.0582  0.9215
  0.9482  0.9998  0.9783  1.1158  0.9946  1.0452  1.0017  0.9373  1.0583  0.9211
  0.9484  0.9997  0.9788  1.1157  0.9947  1.0449  1.0014  0.9369  1.0579  0.9214
  0.9483  0.9995  0.9785  1.1158  0.9943  1.0448  1.0015  0.9374  1.0584  0.9210
[torch.DoubleTensor of size 100x10]

accuracy	
0.11	
gradient	
W1の勾配計算...	
b1の勾配計算...	
W2の勾配計算...	
b2の勾配計算...	
計算完了	
W1の勾配	
Columns 1 to 10
0.0001 *
 -0.3235  0.1465  0.5569  0.0444 -0.0357  0.4571  0.7823 -0.6325 -0.1288  0.4536
 -0.6902 -0.8642  0.2166 -0.4575 -0.2497  0.8204  0.1569  0.8706  0.6036  0.6208
（省略）
  0.0043 -0.4105 -0.8103  0.3283  0.6450  0.0948  1.0122 -0.4403  1.0292 -1.2415
  0.7551  0.3141 -0.7287 -1.2468  0.1211  0.7715  0.8980 -0.6475 -0.1752 -1.3011
  0.7917  0.6158 -1.0305  0.1253 -0.3767  1.8064  0.1073  0.6189  0.6285 -1.1415
[torch.DoubleTensor of size 10x100]

b1の勾配	
0.0001 *
-0.2294
-0.9850
 0.6545
-0.2656
-0.4577
（省略） 2.5307
 1.6259
 0.1867
 0.9712
-2.7923
[torch.DoubleTensor of size 100]

W2の勾配	
0.01 *
  1.2484  0.5044 -0.6002  0.0720 -1.5266 -0.2778  1.5299 -1.8472  0.2941  0.6031
  1.2466  0.4875 -0.6205  0.0833 -1.5222 -0.2827  1.5101 -1.8013  0.2938  0.6052
  1.2450  0.5008 -0.6053  0.0748 -1.5406 -0.2678  1.5166 -1.8152  0.2873  0.6043
  1.2347  0.4984 -0.5946  0.0805 -1.5285 -0.2737  1.5024 -1.8206  0.2912  0.6101
  1.2246  0.4983 -0.6008  0.0800 -1.5160 -0.2804  1.4864 -1.7826  0.2896  0.6009
（省略）
  1.2395  0.5087 -0.5958  0.0758 -1.5269 -0.2769  1.5117 -1.8295  0.2921  0.6014
  1.2338  0.5068 -0.5911  0.0773 -1.5408 -0.2708  1.5030 -1.8121  0.2892  0.6047
  1.2433  0.5034 -0.5993  0.0758 -1.5334 -0.2711  1.5053 -1.8196  0.2891  0.6065
  1.2384  0.4902 -0.6125  0.0751 -1.5260 -0.2614  1.4974 -1.8060  0.2881  0.6167
  1.2498  0.5068 -0.5909  0.0758 -1.5358 -0.2713  1.5147 -1.8425  0.2894  0.6040
[torch.DoubleTensor of size 100x10]

b2の勾配	
0.01 *
 2.4835
 0.9965
-1.2124
 0.1568
-3.0549
-0.5500
 3.0138
-3.6284
 0.5817
 1.2133
[torch.DoubleTensor of size 10]

```

　原書のデータサイズは784ですが、今回のスクリプトで実行すると遅すぎて終わらないため10に縮めています。（それでもかなり遅いですが・・・）。
　
　スクリプトの量が多くなってきましたが、内容は今までのものと同じです。ただ正解ラベルデータに関して、MNISTのときは正解インデックスのリストにしていましたが、今回は正解のインデックスを1、不正解のインデックスを0とする二値化形式のテンソルで正解ラベルを記述しています。そのため誤差エントロピー関数の記述が以前と異なる点に注意してください。

```Lua:commmon.cross_entropy_error()
---バッチ対応版交差エントロピー誤差算出関数
-- テンソル同士の交差エントロピー誤差(-∑tilogyi)を求める
-- @param y 入力１、今回はNNが出力する確率リスト {Type:Tensor}
-- @param t 入力２、今回は正解ラベルリスト {Type:ByteTensor}
-- @return 交差エントロピー誤差 {Type:number}
function cross_entropy_error(y, t)
    if y:dim() == 1 then
        y = y:resize(1,y:nElement())
    end
    local batch_size = y:size()[1]
    return -( y:log():cmul(t) ):sum() / batch_size --ここが変更
end
```


おわりに
--

　今回は以上です。

　次回からはこの２層のニューラルネットワークにミニバッチ処理を実装していきましょう。
　
　ありがとうございました。