Lua版 ゼロから作るDeep Learning その９［損失関数（バッチ対応版）］


過去記事
--
[Lua版 ゼロから作るDeep Learning その１[パーセプトロンの実装]](http://qiita.com/Kazuki-Nakamae/items/16c82b5ac8a85d4adee3)
[Lua版 ゼロから作るDeep Learning その２［活性化関数］](http://qiita.com/Kazuki-Nakamae/items/5199447a54642620139a)
[Lua版 ゼロから作るDeep Learning その３［３層ニューラルネットワークの実装］](http://qiita.com/Kazuki-Nakamae/items/57c2977d0a9a38bdf6b8)
[Lua版 ゼロから作るDeep Learning その４［ソフトマックス関数の実装］]
(http://qiita.com/Kazuki-Nakamae/items/20e53a02a8b759583d31)
[Lua版 ゼロから作るDeep Learning その５［MNIST画像の表示］](http://qiita.com/Kazuki-Nakamae/items/6373443510f94a67e910)
[Lua版 ゼロから作るDeep Learning その5.5［pklファイルをLua Torchで使えるようにする］](http://qiita.com/Kazuki-Nakamae/items/b56c3d23fc21fdc260b0)
[Lua版 ゼロから作るDeep Learning その６［ニューラルネットワークの推論処理］](http://qiita.com/Kazuki-Nakamae/items/4a8198ec56c8a9658bd3)
[Lua版 ゼロから作るDeep Learning その７［バッチ処理］](http://qiita.com/Kazuki-Nakamae/items/3900c1f4e23a2ffdd48d)
[Lua版 ゼロから作るDeep Learning その８［損失関数］](http://qiita.com/Kazuki-Nakamae/items/6c95cf75a0fb73909933)




はじめに
--
　今回は原書4章の損失関数に手を加えてミニバッチ学習が行えるようにします。
　

[バッチ対応版]交差エントロピー誤差の実装
--
　前回実装した交差エントロピー誤差算出関数をバッチ対応版に変更します。
　スクリプトは以下の通りです。

```lua:cross_entropy_error_batch(lossfunc_batch.lua内に定義)
---バッチ対応版交差エントロピー誤差算出関数
-- テンソル同士の交差エントロピー誤差(-∑tilogyi)を求める
-- @param y 入力１、今回はNNが出力する確率リスト {Type:Tensor}
-- @param t 入力２、今回は正解ラベルリスト {Type:ByteTensor}
-- @return 交差エントロピー誤差 {Type:number}
function cross_entropy_error_batch(y, t)
    if y:dim() == 1 then
        y = y:resize(1,y:nElement())
    end
    local batch_size = y:size()[1]
    return -( getElement( y, torch.range( 1, batch_size ), t + 1 ):log() ):sum() / batch_size
end
```
　
　python の実装例となるべく同じ形で実装しました。
　ただnumpy の配列のようにnparray\[[x(i1),x(i2),x(i3)][t(i1),t(i2),t(i3)]] (in = インデックス順)のような対応付けた配列を出力する機能（下記参照）はありません。

```python3:例
>>> x = np.array([[1, 2], [3, 4], [5, 6]])
>>> x[[0, 1, 2], [0, 1, 0]]
array([1, 4, 5])
```
　そのためTorchで似たような動作をするgetElement()関数を定義します。

```lua:getElement関数(exTorch.lua内に定義)
---任意要素アクセス関数
-- 入力されたテンソルに対して、次元分の指定テンソルの順序でアクセスした要素のリストを返す。
-- @param readTensor データサイズ (Type：tensor)
-- @param ... 指定テンソルを内包するテーブル (Type：table)
-- @return 取得した要素のリスト (Type：Tensor)
function getElement(readTensor,...)
    local args = {...}
    local elelist = {}
    for order = 1, args[1]:size()[1] do
        local indexlist = {}
        for dim = 1, readTensor:dim() do
            table.insert(indexlist,(args[dim])[order])
        end
        table.insert(elelist,readTensor[indexlist])
    end
    return torch.Tensor(elelist)
end
```

```lua:例
>>>x = torch.Tensor({{1, 2}, {3, 4}, {5, 6}})
>>>print( getElement( x, torch.Tensor({0, 1, 2}) + 1, torch.Tensor({0, 1, 0}) + 1 ) )
 1
 4
 5
[torch.DoubleTensor of size 3]
```

　よさそうなのでこの関数を用いて計算してみたいと思います。
　まずは前回の１次元のテンソルでの出力を見てみましょう。
 
```lua:mnist_minibatch.lua（一部）
--バッチサイズ1の場合
require './lossfunc_batch.lua'
require './exTorch.lua'

print("correct judgement : ")
local t = torch.Tensor({2})
local y = torch.Tensor({0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0})

print( cross_entropy_error_batch(y, t) .."\n")

print("incorrect judgement : ")
t = torch.Tensor({2})
y = torch.Tensor({0.1, 0.05, 0.1, 0.0, 0.05, 0.1, 0.0, 0.6, 0.5, 0.0})

print( cross_entropy_error_batch(y, t) .."\n")
```

```出力結果
correct judgement : 	
0.51082562376599
	
incorrect judgement : 	
2.302585092994
	
```
　
　以前と同じような計算結果となっているのが確認できました。
　それでは次にバッチを１よりも多く設定した場合を試します。せっかくなので今回はMNISTの訓練データを利用して行いたいと思います。


MNISTデータの適用
--

　まず訓練データを準備します。原書の付録データの中にはいっているdatasetディレクトリをカレントディレクトリに置いて```saveMMISTtrain.py```を実行します。

![スクリーンショット 2017-06-30 17.27.36.png](https://qiita-image-store.s3.amazonaws.com/0/147329/98033d13-34b2-07ff-49a6-f64b9188f1ac.png "スクリーンショット 2017-06-30 17.27.36.png")



```python3:saveMMISTtrain.py
# coding: utf-8
import sys, os
import numpy as np
import pickle
from dataset.mnist import load_mnist

(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, flatten=True, one_hot_label=False)

np.save("x_train",x_train)
np.save("t_train",t_train)
```
```bash:saveMMISTtrain.pyの実行
$python3 saveMMISTtrain.py
```

　これでカレントディレクトリ上に訓練データをnumpy 形式でまとめたx_train.npy とt_train.npy ができると思います。
　
　それではこの訓練データをLua上で読み込み、以前に使った重みとバイアスで交差エントロピー誤差を計算してみたいと思います。


```lua:mnist_minibatch.lua
require './activationFunc.lua'
require './softmax.lua'
require './lossfunc_batch.lua'
require './exTorch.lua'
npy4th = require 'npy4th' --https://github.com/htwaijry/npy4th (Author:Hani Altwaijry)



--バッチサイズ1の場合

（先ほど示したので省略）


--バッチサイズ10の場合（MNISTデータを適用）

---データ取得関数
-- MNISTの訓練データ取得する。
-- @return 訓練データのイメージ ,訓練データのラベル {Type:ByteTensor}
function get_traindata()
    --画像データ
    local x_train = npy4th.loadnpy('x_train.npy')
    --ラベルデータ
    local t_train = npy4th.loadnpy('t_train.npy')

    return x_train, t_train
end

---ネットワーク生成関数.
-- 重みとバイアスが決定した3層NNを返す。今回はpklで記述された重みを用いる
-- @return 3層NN (Type：table)
function init_network()
    local network = npy4th.loadnpz('sample_weight.npz')

    return network
end

---分類関数.
-- ネットワークにしたがって分類計算を行う。
-- @param network 入力 (Type：torch.table)
-- @param x 入力
-- @return 出力 (Type：torch.DoubleTensor)
function predict(network, x)
    local W1, W2, W3 = network['W1'], network['W2'], network['W3']
    local b1, b2, b3 = network['b1'], network['b2'], network['b3']

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

    return y

end

local x_train, t_train = get_traindata()

--ネットワークの生成（仮）
local network = init_network()

--ミニバッチ学習用訓練データ取得
local train_size = x_train:size()[1] --訓練データ数
local batch_size = 10 --バッチ数
local seed = 1
local batch_mask = getRandIndex( train_size, batch_size, seed )
print("Selected index : ")
print(batch_mask)
local x_train_batch = x_train:index( 1, batch_mask )
local t_train_batch = t_train:index( 1, batch_mask )

local y_pred_batch = predict( network, x_train_batch )

print("cross entropy error : "..cross_entropy_error_batch(y_pred_batch, t_train_batch))
```
  
　Torch にはnp.random.choice( )にあたるメソッドがないため自前で実装します。
　torch.randperm( n )は１からnまでの整数のランダムな順列を出力するため、これをバッチサイズ分切り取るgetRandIndex( )を定義しました。

```lua:getRandIndex関数(exTorch.lua内に定義)
---ランダムインデックス取得関数
-- 入力されたサイズ内で指定した数の整数を取得する
-- @param datasize データサイズ (Type：number)
-- @param getsize 取得サイズ (Type：number)
-- @param seed 乱数のシード (Type：number)
-- @return インデックスリスト (Type：long Tensor)
function getRandIndex(datasize,getsize,seed)
    torch.manualSeed(seed)
    --データサイズ分の整数のランダムな順列からgetsize分切り取る
    return torch.randperm(datasize):sub(1,getsize):long()
end
```
　
　それではmnist_minibatch.lua の出力結果を見てみましょう。
　


```bash:出力結果
$ th mnist_minibatch.lua
correct judgement : 	
0.51082562376599
	
incorrect judgement : 	
2.302585092994
	
Selected index : 	
 35846
 27523
 33255
 23646
 11300
 16179
 58356
 27003
 35816
 26560
[torch.LongTensor of size 10]

cross entropy error : 3.8591951772777	
```
　
 
　先ほどのバッチサイズ１の場合の検証から、一つ間違えるごとに２程度増大すると考えますとバッチサイズ１０で３．８５という数字はかなり低く抑えられているように思います。
　ただこの部分に関しては原書付録のサンプルコードや計算例が見当たらないため、numpyの場合での確認をしていません。どなたか検証された方がいらっしゃっればご教授お願いしたく思います。
　

おわりに
--

　今回は以上です。

　次回は勾配計算をみていきたいと思います。
　
　ありがとうございました。