darknetでMNISTを学習する

Cで書かれたニューラルネット環境[darknet](http://pjreddie.com/darknet/)ですが、訓練済のネットを使う例はあちこちで見かけますが、訓練をしている例が見当たりません。darknet本家にもこの手のドキュメントはないので、ソースを読みながら見よう見まねでやってみることにしました。ソースはすっきりしていて読みやすいですね。

githubにもろもろ素材を上げておきました（[こちら](https://github.com/ashitani/darknet_mnist)）ので参考にしてください。

## 準備

### 元データのダウンロード

data/mnist/以下に [THE MNIST DATABASE](http://yann.lecun.com/exdb/mnist/)から下記をダウンロードします。

- t10k-images-idx3-ubyte.gz
- t10k-labels-idx1-ubyte.gz
- train-images-idx3-ubyte.gz
- train-labels-idx1-ubyte.gz

gunzipで展開しておきます。

### データフォーマット調整

データ・フォーマットは[こちら](http://yann.lecun.com/exdb/mnist/)の下のほうに書かれています。ヘッダがあってあとはバイトの羅列のようなのでnumpyで簡単に抜き出せます。

ラベルファイルという、クラス番号とクラスIDを紐付けるものが必要なようです。今回は下記のようにします。行がクラス番号0-9に相当します。mnist.label.listというファイルに書いておきます。

```
c0
c1
..
c9
```

画像についてはファイル名のリストをtxtで羅列しますが、classifierの場合はパスの文字列からクラスIDの文字列を検索することでクラスと対応付けているようなので、trainとvalidをそれぞれ下記のように、"[trainかvalidか]\_[画像ID]\_[クラスID].png"のように命名します。

- t_00000_c0.png
- v_00000_c0.png

ファイル名一覧をmnist.train.list, mnist.valid.listに書き出します。


ダウンロードから変換までを行うスクリプトが[こちら](https://github.com/ashitani/darknet_mnist/blob/master/data/mnist/download_and_convert_mnist.py)です。

### datasetファイル（データのありかなど）

cfg/mnist.dataset　ファイルに記述します。

```
classes=10
train  = data/mnist/mnist.train.list
valid  = data/mnist/mnist.valid.list
backup = /tmp/backup/
labels = data/mnist.labels.list
names  = data/mnist.names.list
top = 5
```

train, valid, labels はさきほど作りましたね。namesはどのclass_idに名前をつける場合に使うテキストです。MNISTなので0から9と書いておけばよいですね。

```
0
1
...
9
```

### cfgファイル（ネットワーク構成）

いよいよニューラルネットワークの構成を書きます。cfg/mnist_lenet.cfg ファイルに記述します。

冒頭には[net]という項目に、学習条件などが含まれています。注意ですが、darknetのclassifierはカラー画像を前提としていますので、channel=3と指定する必要があります。元画像が1チャネルでもopencvが3チャネル化しながら読み込みます。

angleからaspectはaugmentation用の条件ですね。これはclassifierでは使われていないように見えますが、ひとまず変形はしないということで全部1にしておきます。学習のループ回数は max_batches回です。

```
[net]
batch=100
subdivisions=1
height=28
width=28
channels=3
momentum=0.9
decay=0.00005
max_crop=28

learning_rate=0.01
policy=poly
power=4
max_batches=500

angle=1
hue=1
saturation=1
exposure=1
aspect=1
```

これ以降にはネットワーク構造を書きます。羅列しておくとSequencialな構造になるようですね。逆にSequencialな構造しか書けないのかな。

今回は [TensorFlowのチュートリアル](https://www.tensorflow.org/versions/r0.11/tutorials/mnist/pros/index.html)にある4層のLe-Netを記述してみます。

```
[convolutional]
filters=32
size=5
stride=1
pad=1
activation=relu

[maxpool]
size=2
stride=2

[convolutional]
filters=64
size=5
stride=1
pad=1
activation=relu

[maxpool]
size=2
stride=2

[connected]
output= 1024
activation=relu

[dropout]
probability=.5

[connected]
output= 10
activation=linear

[softmax]
groups=1

[cost]
type=sse
```

# 訓練

```
./darknet classifier train cfg/mnist.dataset cfg/mnist_lenet.cfg
```

costとか学習率とかが表示されていきます。最終的に、/tmp/backupに重みが出力されます。

# 推論

/tmp/backout/mnist_lenet.weightsをコピーしてきます。

~~predict.c/predict_classifier()のresize_network()でエラーになるので
network.c/resize_network()の~~
~~if(w == net->w && h == net->h) return 0;~~
~~のコメントを外してdarknetをビルドしなおします。~~

(2018/10/20追記：twitterで指摘いただいてチェックしましたが、現時点での最新のdarknetではエラーにはなりませんでした。)

```
./darknet classifier predict cfg/mnist.dataset cfg/mnist_lenet.cfg ./mnist_lenet.weights data/mnist/images/v_00000_c7.png
```

とすると、下記が出力されます。


```
layer     filters    size              input                output
    0 conv     32  5 x 5 / 1    28 x  28 x   3   ->    28 x  28 x  32
    1 max          2 x 2 / 2    28 x  28 x  32   ->    14 x  14 x  32
    2 conv     64  5 x 5 / 1    14 x  14 x  32   ->    14 x  14 x  64
    3 max          2 x 2 / 2    14 x  14 x  64   ->     7 x   7 x  64
    4 connected                            3136  ->  1024
    5 dropout       p = 0.50               1024  ->  1024
    6 connected                            1024  ->    10
    7 softmax                                          10
    8 cost                                             10
Loading weights from ./mnist_lenet.weights...Done!
data/mnist/images/v_00000_c7.png: Predicted in 0.004880 seconds.
7: 0.995198
5: 0.003397
9: 0.000618
0: 0.000574
3: 0.000106
```

7の画像が「99.5%で7」と認識されました。いいですね。