DeepLearningに自作画像データを入れて遊んでみる


昨今いろいろなサービスで機械学習が使われています。中でもDeepLearningと呼ばれる手法は高性能な方法として注目されています。この記事では、pylearn2というLISA-Lab発の機械学習ライブラリを使って、実際にDeepLearningを動かしてみる方法について書きます。対象は画像ファイルです。

pyleran2のチュートリアルを動かしてみるまでの記事はたくさんあるようですが、自分で作ったデータを学習させてみるところまで書いてある情報がほとんどないようなので、「DeepLearning」とか「pylearn2」がすごいのはわかったけどどうやってつかえばええねんって人向けです。
pylearn2のインストール、使い方、チュートリアルの部分は割愛します。

また、方法はいろいろあると思いますが、ここで書くのはその一例です。

## 手順
全体の流れは次のようになります。各々詳しく説明します。

1.画像ファイルをCSVファイルに変換
2.CVSをpklに変換
3.学習させる
4.識別テスト


## 下準備
今回は、作業ディレクトリはpylearn2のディレクトリ下の次の場所に作っています。
今回扱うファイルは基本的に全てこのディレクトリ直下に置いているものとして説明します。

```
pylearn2/pylearn2/script/tutorials/sample
```

また、CSVデータセットを読み込んだりテスト用のスクリプトが[pylearn2 in practice](http://fastml.com/pylearn2-in-practice/)で公開されているので拝借します。ページ下部にリンクがあるGitHubページからダウンロードできます。

ダウンロードが終わったら、中にある"adult_dataset.py"を次のディレクトリに入れておきます。

```
pylearn2/pylearn2/datasets
```


## 画像ファイルをCSVファイルに変換
はじめに、画像ファイルをCSVファイルに変換します。学習させる画像のデータ全てをひとつのCSVデータとして出力します。
CSVのルールは、下の例のように各画像を1行として、先頭を所属クラス、それ以降を画素のデータとしてコンマ","で区切ります。もちろんコンマ以外でも後に変更可能ですが、ここではコンマで説明します。

```csv:train.csv
クラス,画素データ1,画素データ2,画素データ3,...
クラス,画素データ1,画素データ2,画素データ3,...
クラス,画素データ1,画素データ2,画素データ3,...
...
```

方法はなんでも構いませんが、とりあえずOpenCVを使って行いました。例としてはこんな感じです。
簡単のため、連番で名前付けた200枚の画像を入れて、最初の100枚はクラス0、後半の100枚をクラス1としています。今回は2クラス分類ですが、もっと増やすこともできるので必要に応じて整数で増やしてください。この辺りも必要に応じて変更します。

```C++:main.cpp
int main() {
	FILE *fp = fopen("train.csv", "w");
    Iplimage *input = cvLoadImage("trainingImage.png", CV_LOAD_IMAGE_GRAYSCALE);
    
    int numFiles = 200;
    int numFirstClass = 100;
    
    for(int i=0; i<numFiles; i++) {
    
        if(i<numFirstClass) fprintf(fp, "0");
        else                fprintf(fp, "1");
    
        for (int y = 0; y < input->height; y++) {
            for (int x = 0; x < input->width; x++) {
                uchar pixelValue = 0;
                pixelValue = (uchar)input->imageData[y*input->width+x];
                fprintf(fp, ",%d", (int)pixelValue);
            }
        }
        fprintf(fp, "\n");
    }
}
```

こんな感じのファイルが出来上がります（数値は例です）。全部で200行になっているはずです。作ったファイルは作業ディレクトリに入れておきます。

```
0,13,15,18,41,11,...
0,19,40,50,31,23,...
...
...
1,135,244,210,15,150,...
1,45,167,84,210,100,...
```


## CSVをpklに変換


作成したCSVをpythonで扱いやすいようにpklファイルに変換します。
ソースはこんな感じです。

```python:
from pylearn2.datasets.adult_dataset import AdultDataset
import pickle

print 'convert: train.csv -> train.pkl'
pyln_data = AdultDataset('train.csv',one_hot=True)
pickle.dump(pyln_data, open('train.pkl', 'w'))
```

## 学習させる
データが準備できたので実際に学習させます。
今回作るネットワークは、AutoEncoderを2層と判別器としてSoftmaxRegression層を置いた3層ネットワークを作ります。

各層のyamlはこんな感じです。任意で設定できる数字は大雑把なので各々パラメータは必要に応じて変更してください。
重要になるのは、

- nvis : 入力ユニット数。1層目は画像の画素数と同数、2層目は1層目のHiddenLayerのユニット数(nhid)と同数でなければならない。
- n_Classes : 出力クラス数。分類したいクラス数を指定する。今回の例だと2。

で、ここがちゃんと設定できていないとそもそも学習してくれないので上手くいかないときはここが間違えていないか、もしくはCSVファイル作成の段階でどこか間違えていないかチェックしてみるといいかもしれません。

一層目、二層目のプレトレー二ング結果はDAE_l1.pkl、DAE_l2.pklに出力されます。

```yaml:dae_l1.yaml
!obj:pylearn2.train.Train {
    dataset: &train !pkl: "train.pkl",

    model: !obj:pylearn2.models.autoencoder.DenoisingAutoencoder {
        nvis : 200,
        nhid : 100,
        irange : 0.05,
        corruptor: !obj:pylearn2.corruption.BinomialCorruptor {
            corruption_level: .1,
        },
        act_enc: "tanh",
        act_dec: null,    # Linear activation on the decoder side.
    },
    algorithm: !obj:pylearn2.training_algorithms.sgd.SGD {
        learning_rate : 1e-3,
        batch_size : 5,
        monitoring_batches : 1,
        monitoring_dataset : *train,
        cost : !obj:pylearn2.costs.autoencoder.MeanSquaredReconstructionError {},
        termination_criterion : !obj:pylearn2.termination_criteria.EpochCounter {
            max_epochs: 10,
        },
    },
    save_path: "DAE_l1.pkl",
    save_freq: 1
}
```

```yaml:dae_l2.yaml
!obj:pylearn2.train.Train {
    dataset: &train !obj:pylearn2.datasets.transformer_dataset.TransformerDataset {
        raw: !pkl: "train.pkl",
        transformer: !pkl: "DAE_l1.pkl"
    },
    model: !obj:pylearn2.models.autoencoder.DenoisingAutoencoder {
        nvis : 100,
        nhid : 20,
        irange : 0.05,
        corruptor: !obj:pylearn2.corruption.BinomialCorruptor {
            corruption_level: .2,
        },
        act_enc: "tanh",
        act_dec: null,    # Linear activation on the decoder side.
    },
    algorithm: !obj:pylearn2.training_algorithms.sgd.SGD {
        learning_rate : 1e-3,
        batch_size : 5,
        monitoring_batches : 1,
        monitoring_dataset : *train,
        cost : !obj:pylearn2.costs.autoencoder.MeanSquaredReconstructionError {},
        termination_criterion : !obj:pylearn2.termination_criteria.EpochCounter {
            max_epochs: 10,
        },
    },
    save_path: "DAE_l2.pkl",
    save_freq: 1
}
```

```yaml:dae_mlp.yaml
!obj:pylearn2.train.Train {
    dataset: &train !pkl: "train.pkl",

    model: !obj:pylearn2.models.mlp.MLP {
        batch_size: 5,
        layers: [
                 !obj:pylearn2.models.mlp.PretrainedLayer {
                     layer_name: 'h1',
                     layer_content: !pkl: "DAE_l1.pkl"
                 },
                 !obj:pylearn2.models.mlp.PretrainedLayer {
                     layer_name: 'h2',
                     layer_content: !pkl: "DAE_l2.pkl"
                 },
                 !obj:pylearn2.models.mlp.Softmax {
                     layer_name: 'y',
                     n_classes: 2,
                     irange: 0.05
                 }
                ],
        nvis: 200
    },
    algorithm: !obj:pylearn2.training_algorithms.sgd.SGD {
        learning_rate: .05,
        learning_rule: !obj:pylearn2.training_algorithms.learning_rule.Momentum {
            init_momentum: .5,
        },
        monitoring_dataset:
            {
                'valid' : *train,
            },
        cost: !obj:pylearn2.costs.mlp.Default {},
        termination_criterion: !obj:pylearn2.termination_criteria.And {
            criteria: [
                !obj:pylearn2.termination_criteria.MonitorBased {
                    channel_name: "valid_y_misclass",
                    prop_decrease: 0.,
                    N: 100
                },
                !obj:pylearn2.termination_criteria.EpochCounter {
                    max_epochs: 50
                }
            ]
        },
        update_callbacks: !obj:pylearn2.training_algorithms.sgd.ExponentialDecay {
            decay_factor: 1.00004,
            min_lr: .000001
        }
    },
    extensions: [
        !obj:pylearn2.training_algorithms.learning_rule.MomentumAdjustor {
            start: 1,
            saturate: 250,
            final_momentum: .7
        }
    ],
    save_path: "mlp.pkl",
    save_freq: 1
}

```

yamlが準備できたら、次のようなスクリプトを実行して学習させます。

```python:train.py
from pylearn2.config import yaml_parse
import os
import pickle

def train_step(config_file):
    assert(os.path.exists(config_file))
    _yaml = open(config_file).read()
    _train = yaml_parse.load(_yaml)
    _train.main_loop()
    return _train

l1_train = train_step('dae_l1.yaml')
l2_train = train_step('dae_l2.yaml')
_train = train_step('dae_mlp.yaml')
```

実行すると、"mlp.pkl"というファイルが生成されます。これが学習結果になので、次はこれを使って実際に識別テストをしてみます。

## 識別テスト
テストデータの作成方法は学習データと同様で、画像をCSVファイルに変換->pklに変換します。ここでは"test.pkl"としています。

```python:
from pylearn2.datasets.adult_dataset import AdultDataset
import pickle

print 'convert: test.csv -> test.pkl'
pyln_data = AdultDataset('test.csv', one_hot=True)
pickle.dump(pyln_data, open('test.pkl', 'w'))
```


次のようなスクリプトを実行すると、テストデータ中何個のデータが正しいクラスとして識別されたかを出力できます。

```python:test.py
import numpy as np
import pickle
import theano

# function for classifying a input vector
def classify(inp,model,input_size):
    inp = np.asarray(inp)
    inp.shape = (1, input_size)
    return np.argmax(model.fprop(theano.shared(inp, name='inputs')).eval())
 
# function for calculating and printing the models accuracy on a given dataset
def score(dataset, model, input_size):
    nr_correct = 0
    for features, label in zip(dataset.X,dataset.y):
        if classify(features,model, input_size) == np.argmax(label):
            nr_correct += 1
    print '{}/{} correct'.format(nr_correct, len(dataset.X))
    return nr_correct, len(dataset.X)

model = pickle.load(open('mlp.pkl'))
test_data = pickle.load(open('test.pkl'))
score(test_data, model, 200)

```

## おわり
以上一通り、自作データを使って学習からテストまでできると思います。途中で気がついたのですが、どうやらpylearn2にもCSVデータセットに対応した読み込みスクリプト(pylearn2/pylearn2/datasets/csv_dataset.py)が含まれているようなのでそちらを使ってもいいかもしれません。


## 参考した資料 & 参考になる資料
[実装DeepLearning](http://www.slideshare.net/yurieoka37/ss-28152060)
[pylearn2 dev Documentation](http://deeplearning.net/software/pylearn2/)
[pylearn2 in practice](http://fastml.com/pylearn2-in-practice/)