Torchでdeep learning

#初めに
  この記事はTorchの基本的な使い方の解説?をします。ちょっとした応用や便利ツールなどは[こちら](http://qiita.com/perrying/items/707db5ea50bdb4364ce2)
　caffe,chainer,theanoとdeeplearningライブラリを使ってきて最近torchに乗り換えたのでtorchについてのチュートリアルをまとめます。
コードは[こちら](https://github.com/torch/tutorials)の公式チュートリアルの[2_supervised](https://github.com/torch/tutorials/tree/master/2_supervised)を参考にしました。

#torchについて
　日本ではdeeplearningのライブラリといえばcaffeに始まり、最近勢いのあるTensorflowや日本で開発がされているchainerなどが主流で、あまりtorchは使われていないように思えます。しかし、世界的にはtorchは主流のdeeplearningライブラリで、Facebookが使用していることでも有名です。画像処理における最新の手法の実装も多く、Fast R-CNNで使われているROIPoolingやsegmentationで最近用いられているDilatedConvolutionなどもtorchでは実装済みです。またvolumeデータに対する関数(3DConvolutionやDilatedConvolutionの3dバージョンなど)も充実しています。

　torchの特徴としては対応言語がluaであり、torchのとっつきにくさの一つでもあると思います(自分もそうでした)。自分はプログラミングの知識がないので詳しいことは言えませんが、luaはスクリプト言語であるためpythonなどに近い書き方で、jitコンパイラを用いるためpythonなどよりも動作が速いとい利点があります。また、lua自体にはpythonで言うnumpyやmatplotlibのような便利libraryはあまり多くありませんが(自分が詳しくないだけかもしれません)torchの拡張として様々なlibraryが用意されているためそこまで困ることはありません。

#インストール
　インストールは[公式のinstallation](http://torch.ch/docs/getting-started.html)にあるよう以下の3行の実行でインストール可能でpathの書き込みも自動で行ってくれます(cuda周りの設定は自分で)。自分はubuntu14.04とubuntu16.04それぞれでインストールを行いました。またtitan xとgtx 1080のそれぞれに対して、cuda7.5(cuda8.0), cudnn v5をインストールしてgpu計算の動作確認できています。

``` sh
git clone https://github.com/torch/distro.git ~/torch --recursive
cd ~/torch; bash install-deps;
./install.sh
```
注意として、torchのclone先がホームディレクトリ指定になっていることと、torchをどこにcloneしてもpathがホームディレクトリにcloneされた前提で書き込まれることが挙げられます。別なディレクトリにcloneしたい場合は1行めの~/torchを外し、./install.shを実行後のpathの書き込みの有無をnoにして自分でpathを指定してください。
　また、add-apt-repositoryがないと怒られた場合は以下を実行して解決できます。

```sh
sudo apt-get install software-properties-common python-software-properties
```
同様にreadline.*周りのエラーが起きた場合は

```
sudo apt-get install libreadline-dev
```
で解決できます。

#torchでの行列(画像)の扱いについて
　ここからはtorchを使ったmnistのチュートリアルを解説していきます。公式のチュートリアルの[2_supervised](https://github.com/torch/tutorials/tree/master/2_supervised)では[Street View House Numbers Dataset](http://ufldl.stanford.edu/housenumbers/)というdatasetを使って数字の認識を行っていますが、このデータセットはmnistと比較するとタスクとして難しく単純なモデルではlossが落ちず精度がなかなか上がらないため、学習できているかわかりにくいです。そのためこの記事ではdatasetをmnistに差し替えます。mnistのデータセットは公式チュートリアルの[A_datasets](https://github.com/torch/tutorials/tree/master/A_datasets)のmnist.luaを実行することでdownloadできます。

　torchではpythonで言うnumpyのような行列演算をtorch.Tensorを用いて行います。詳しく書くと記事が長くなるため、numpyとtorch.Tensorの[対応表](https://github.com/torch/torch7/wiki/Torch-for-Numpy-users)を参照してください。またluaで画像の読み込みはimage.load()を持ちいて行います。その他luaとpythonの細かい違いは書くと長くなるので割愛します。

#いよいよdeeplearning
##modelの定義
torchではニューラルネットのmodelを組むのにnnライブラリを用います。まずnn.Sequential()でモデルを定義し、定義されたモデルに対しadd()を用いて畳み込みやプーリング層などを追加していきます。今回はchainerのmnistの例にならって3層の多層パーセプトロンを定義します。

```lua:model.lua
require 'nn'
model = nn.Sequential()
model:add(nn.View(32*32))
model:add(nn.Linear(32*32, 1000)):add(nn.ReLU())
model:add(nn.Linear(1000, 1000)):add(nn.ReLU())
model:add(nn.Linear(1000, 10))
model:add(nn.LogSoftMax())
model = model:float()  --modelをfloatに変換
```
このモデルはchainerのmnistのsampleで使われているmodelとほぼ同様のものになっています(mnist.luaでダウンロードしてくるdataが32×32の画像であるため入力が32×32になっています)。nn.View()は入力された(batchsize, channel数, width, height)次元のtorch.Tensorを(batchsize, 総入力数)次元に変換します。今回は(batchsize, 1, 32, 32)の入力が(batchsize, 32*32)に変換されます。nn.Linear()は全結合層を表し(入力ユニット数, 出力ユニット数)により定義します。nn.ReLU()は活性化関数です。また、今回出力はSoftMaxではなくLogSoftMaxにしています。理由としては今回loss関数で用いるnn.ClassNLLCriterionがnn.LogSoftMax()と対になっているためです。

　今回畳み込みやプーリングの処理は行わないためmodelにadd()していませんがnn.SpatialConvolutionやnn.SpatialMaxPoolingをadd()することでCNNが簡単に組めます。torchは他のライブラリと違い入力のtorch.Tensorを3次元にしてもforwardすることができます(一部できないものもある)。入力の次元数によりバッチ処理の判断を自動で行ってくれるため、画像１枚を入力する際にわざわざ4次元にする手間がありません。また、torchではcudnnを使うConvolutionやfacebookが実装した高速なConvolutionなど様々な種類のConvolutionがあります。詳しくは[こちら](http://ultraist.hatenablog.com/entry/2015/01/30/221102)の記事が解説してくれています。
##パラメータ更新方法
　学習するのにはloss関数と最適化手法を決める必要があります。torchではloss関数はnnの中に用意されています(詳しくは[こちら](https://github.com/torch/nn/blob/master/doc/criterion.md))。今回はSoftMaxを使った多クラス分類のためnn.ClassNLLCriterionを用います。また、最適化手法はoptimの中に用意されており(詳しくは[こちら](https://github.com/torch/optim))今回はoptim.adagradを用います。

　loss関数と最適化手法が決まったのであとはlossの計算とbackwardの記述をするだけで学習が行えます。公式チュートリアルの[4_train.lua](https://github.com/torch/tutorials/blob/master/2_supervised/4_train.lua)では学習の部分がmini-batchと言いつつ画像を一枚一枚forwardしてbackwardするよう記述されています。更新自体はまとめて行われるようになっているためmini-batch処理として問題はないと思いますがその分計算が遅くなるので今回はforwardとbackwardもmini-batchごとに行います。

```lua:trainer.lua
require optim
--loss関数の定義
criterion = nn.ClassNLLCriterion()
--confusion matrixの定義(学習するだけなら必要なし)
confusion = optim.ConfusionMatrix(#classes,classes)
--最適化手法の定義
optimizer = optim.adagrad
--最適化手法の初期値の指定(他の最適化手法の場合momentumやweightDecayなども必要となる場合がある)
config = {learningRate = 0.001}

--学習対象のパラメータを取得
param, gparam = model:getParameters()

function train(traindata, bsize)
  print('training')
  model:training() --modelを学習モードに変更(これによりdropoutの有無が決まる)
  shuffle = torch.randperm(traindata.data:size(1))


  for t = 1,traindata:size(), bsize do
    xlua.progress(t, traindata:size()) --progressbarを表示

--入力サンプルとラベルの初期化
    local inputs = torch.FloatTensor(math.min(bsize, traindata:size()-t+1), traindata.data:size(2), traindata.data:size(3), traindata.data:size(4))
    local targets = torch.DoubleTensor(math.min(bsize, traindata:size()-t+1))

    for i = t, math.min(t+bsize-1,traindata:size()) do
      local input = traindata.data[shuffle[i]]
      local target = traindata.labels[shuffle[i]]
      inputs[i-t+1] = input
      targets[i-t+1] = target
    end

    local feval = function(x)
      -- get new parameters
      if x ~= param then
        param:copy(x)
      end

      gparam:zero()
--forward
      local output = model:forward(inputs)
      local err = criterion:forward(output, targets) -- lossの計算
--backward
      local df_do = criterion:backward(output, targets) -- loss関数の微分計算
      
      model:backward(inputs, df_do) -- 勾配のbackward
--confusion matrixに結果を記述
      confusion:batchAdd(output:exp(), targets)

      return err, gparam
    end
    optimizer(feval, param, config)
  end
  print(confusion)
--confusion matrixを初期化
  confusion:zero()
end

```
同様のノリでテストのコードも書きます。

```lua:tester.lua
function test(testdata, bsize)
  model:evaluate() --評価モードに変更(dropoutの停止)
  print('testing')

  for t = 1, testdata:size(), bsize do
    xlua.progress(t, testdata:size())

    inputs = testdata.data[{{t, math.min(t+bsize-1, testdata:size())},{},{},{}}]
    targets = testdata.labels[{{t, math.min(t+bsize-1, testdata:size())}}]

    local output = model:forward(inputs)
    local err = criterion:forward(output, targets)

    confusion:batchAdd(output:exp(), targets)

  end

  print(confusion)
  confusion:zero()
end
```
注意として、チュートリアルでは1サンプルずつforwardしているためconfusion:add()を持ちていますが、mini-batch処理の場合はconfusion:batchAdd()を用います。
##学習
学習のコードが書けたのであとはサンプルデータを読み込ませて実際に学習を行うだけです。[A_datasets](https://github.com/torch/tutorials/tree/master/A_datasets)のmnist.luaでtrain_32x32.t7とtest_32x32.t7に保存がしてある前提で行います。train_32x32.t7とtest_32x32.t7はtorch.ByteTensorのrgb画像と正解ラベルを持ったtableになっています。

```lua:main.lua
--confusion matrix用にクラスの定義
classes = {'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'}
--torch.Tensorのdefaultをtorch.FloatTensorに変更
torch.setdefaulttensortype('torch.FloatTensor')

--dataの読み込み
traindata = torch.load('train_32x32.t7', 'ascii')
traindata.size = function() return traindata.data:size(1) end  --sizeを定義
traindata.data = traindata.data:float() --floatに変換

testdata = torch.load('test_32x32.t7', 'ascii')
testdata.size = function() return testdata.data:size(1) end
testdata.data = testdata.data:float()

dofile('model.lua')
dofile('trainer.lua')
dofile('tester.lua')

for epoch = 1, 50 do
 print('epoch ===> '..epoch)
 train(traindata, 256)
 test(testdata, 256)
end
```
これで学習が行えます。

#まとめ
　以上で今回のtorchに関する記事は終わりです。まだtorchをメインで使い始めて日が浅いので自分自身わかっていない部分もあります。また、間違いやよりよい方法もあるかもしれないので気をつけて下さい。