Pylearn2でMaxout + CNNを使う

Pylearn2 で Maxout + CNN(Convolutional Neural Network) を使ったときにいくつかはまったのでメモ。
GPU必須です。

## 環境

* OS: Windows7 64bit
* Python2.7.8 |Anaconda 2.1.0 (64-bit)  
Python DistributionのAnacondaを使っています。
* Theano  
[公式ページのManual installation](http://deeplearning.net/software/theano/install.html#alternative-manual-installation) に従ってインストールしました。
* Pylearn2
* Cuda6.5
* Visual Studio 2013

## 準備(Windows用)

以下はWindows環境で必要な設定・修正です。
LinuxやMacでは必要ないかもしれませんが、確認していません。

### pthreadライブラリ をインストールする

pylearn2内部でpthreadを使っているのでインストールします。
pthreadなしだと学習時に以下のエラーメッセージを出力します。

> pylearn2\sandbox\cuda_convnet\nvmatrix.cuh(39) fatal error C1083: include ファイルを開けません。'pthread.h':No such file or directory

今回は以下のサイトからライブラリをダウンロードしました。
https://sourceware.org/pthreads-win32/

### pthreadライブラリへのパスを設定

pylearn2/sandbox/cuda_convnet/pthreads.py でpthreadライブラリへのパスを設定します。
以下に例を記載します。

```pthreads.py
from theano.configparser import AddConfigVar, StrParam

AddConfigVar('pthreads.inc_dir',
        "location of pthread.h",
        StrParam("C:\\lib\\pthreads-w32-2-9-1-release\\Pre-built.2\\include"))

AddConfigVar('pthreads.lib_dir',
        "location of library implementing pthreads",
        StrParam("C:\\lib\\pthreads-w32-2-9-1-release\\Pre-built.2\\lib\\x64"))

AddConfigVar('pthreads.lib',
        'name of the library that implements pthreads (e.g. "pthreadVC2" if using pthreadVC2.dll/.lib from pthreads-win32)',
        StrParam("pthreadVC2"))
```

### pthreadVC2.dll を実行ディレクトリに置く

POSIX threadに含まれるpthreadVC2.dllを以下のディレクトリに置きます。
pylearn2/scripts/papers/maxout

pthreadVC2.dllの配置を行わないと以下のエラーが発生します。
これはpydファイルから参照しているdllが見つからないために発生しているエラーです。

> DLL load failed:

同様のエラーが発生する場合には、以下のサイトにあるDependency Walkerを使ってpydの依存関係を調べるとよいと思います。
http://www.dependencywalker.com/

### cuda_ndarrayライブラリへのパスを通す

Pylearn2による学習時にcuファイルのコンパイルを行うのですが、リンクエラーが発生します。
以下のファイルを修正して、cuda_ndarrayライブラリを読み込めるようにします。

```pylearn2/sandbox/cuda_convnet/convnet_compile.py
                    compiler.compile_str('cuda_convnet',
                            code,
                            location = cuda_convnet_loc,
                            include_dirs = [this_dir, config.pthreads.inc_dir] if config.pthreads.inc_dir else [this_dir],
                            lib_dirs = nvcc_compiler.rpath_defaults + [cuda_convnet_loc] + ([config.pthreads.lib_dir] if config.pthreads.lib_dir else []),
-                            libs = ['cublas', config.pthreads.lib] if config.pthreads.lib else ['cublas'],
+                            libs = ['cublas', 'cuda_ndarray', config.pthreads.lib] if config.pthreads.lib else ['cublas', 'cuda_ndarray'],
                            preargs = ['-O3'] + args,
                            py_module=False)
```

```pylearn2/sandbox/cuda_convnet/base_acts.py
     def c_libraries(self):
         if config.pthreads.lib:
-            return ['cuda_convnet', config.pthreads.lib]
+            return ['cuda_convnet', 'cuda_ndarray', config.pthreads.lib]
         else:
-            return ['cuda_convnet']
+            return ['cuda_convnet', 'cuda_ndarray']
```

## 学習

Pylearn2のpylearn2/scripts/papers/maxout にある mnist.yaml を使いました。
このファイルで使われている pylearn2.models.maxout.MaxoutConvC01B が Maxout + CNN のモデルクラスです。
MaxoutConvC01B を使うためにはGPUが必要です。
pylearn2/scripts/papers/maxout に移動した後、以下のコマンドで学習を行います。

```
python ..\..\train.py mnist.yaml
```

学習後のモデルは mnist_best.pkl に保存されます。

## 結果の確認

以下のファイルを作成し、
python mnist_result.py mnist_best.pkl
を実行するとテストデータに対する認識結果を確認することができます。
私の環境では 9940/10000 でした。

```mnist_result.py
import numpy as np
import pickle
import sys
import theano
import pylearn2.datasets.mnist as mnist
from pylearn2.space import VectorSpace


def simulate(inputs, model):
    space = VectorSpace(inputs.shape[1])
    X = space.get_theano_batch()
    Y = model.fprop(space.format_as(X, model.get_input_space()))
    f = theano.function([X], Y)
    result = []
    for x in xrange(0, len(inputs), 100):
      result.extend(f(inputs[x:x + 100]))
    return result

def countCorrectResults(outputs, labels):
    correct = 0;
    for output, label in zip(outputs, labels):
        if np.argmax(output) == label:
            correct += 1
    return correct
 
def score(dataset, model):
    outputs = simulate(dataset.X, model)
    correct = countCorrectResults(outputs, dataset.y)

    return {
        'correct': correct,
        'total': len(dataset.X)
    }

model = pickle.load(open(sys.argv[1]))
test_data = mnist.MNIST(which_set='test')
print '%(correct)d / %(total)d' % score(test_data, model)
```

## さらに学習

mnist.yamlで学習した後、mnist_continued.yamlを使って学習を行うと、mnist_best.pklをさらに学習するようです。
学習後のモデルはmnist_continued.pklに保存されます。