ConvNetJsの仕様と使い方

ConvNetJsの日本語の資料が少なかったので、[このページ](https://cs.stanford.edu/people/karpathy/convnetjs/docs.html)をひと通り訳してみました。
まだ執筆中です。間違いは指摘してくださると助かります。もともとはConvNetJsの使い方を個人用に非公開でまとめたかったのですが、間違って記事を公開設定にしちゃった(非公開に戻せなかった)ので、そのまま公開用に転用してます。

#Vol
ConvNetJsの核となる概念と言えます。簡単に言うと3次元の配列です。Volはこれに加えて、トレーニングの際に計算される勾配や、Layerのサイズなどの情報も保持しています。

```javascript:
//32x32x3のVolを生成
const v = new convnetjs.Vol(32, 32, 3);
//生成する際、要素を全て0で初期化
const v = new convnetjs.Vol(32, 32, 3, 0.0); 
//1x1x3サイズのVolを生成
const v = new convnetjs.Vol(1, 1, 3);
 
//1x1x3のVolを生成して、1.2, 3.5, 3.6で初期化
const v = new convnetjs.Vol([1.2, 3.5, 3.6]);
 
// Volの係数(w)や傾き(dw)を取り出す
v.w[0] // =1.2
v.dw[0] // =0 (傾きは0で初期化されている)
 
// 次のようにVolの値を直接セットしたり、取り出したりできる
const vol3d = new convnetjs.Vol(10, 10, 5);
vol3d.set(2,0,1,5.0); // (2,0,1) の要素の値を5.0にセット
vol3d.get(2,0,1) // =5
```

#Net
複数のLayerを保持します。こいつにVolを放り込むとニューラルネットの計算が実行されて、最終層の値が帰ってきます。ちなみに学習を行うときはNetの各層が```backward()```を実行して、それぞれのニューロンで勾配を計算します。

#Layer
NetはLayerが連なったものです。先頭は入力層('Input')、最後はloss layer(後述 Layerの種類を参照)でなければなりません。各層はVolが受け取ると、新たなVolを生成して次の層に受け渡す仕組みです, which is why I prefer to refer to them as transformers.
次の例を見てみましょう

```javascript:
var layer_defs = [];
// minimal network: a simple binary SVM classifer in 2-dimensional space
layer_defs.push({type:'input', out_sx:1, out_sy:1, out_depth:2});
layer_defs.push({type:'svm', num_classes:2});
 
// Netを生成
var net = new convnetjs.Net();
net.makeLayers(layer_defs);
 
// 1x1x2のVolを生成し、要素を［0.5,-1.3］にセット
var x = new convnetjs.Vol(1,1,2);
x.w[0] = 0.5;
x.w[1] = -1.3;

//Netに先程のVolを渡す。scoreには最終層から渡されたVolがはいる。
var scores = net.forward(x);
console.log('score for class 0 is assigned:'  + scores.w[0]);
```

#Layerの種類
Netの構成要素となるLayerの種類を紹介します。

##Input Layer (入力層)
先頭はこのLayerでなければなりません。ここでレイヤーで入力層のサイズを宣言することになります。

```javascript:
// 1x1x20の入力層を設定
{type:'input', out_sx:1, out_sy:1, out_depth:20}
// 24x24の画像をRGBで入力したいときは、次のように設定します
{type:'input', out_sx:24, out_sy:24, out_depth:3}
```

##Fully Connected Layer (中間層)
Layerの中の各ニューロンは前層のニューロンの値の重み付き平均を計算し、結果を活性化関数に渡します。ReLUはよく使われる活性化関数ですが、使うときは注意が必要で、learning rateを小さめに設定しないと勾配発散の原因となります。

```javascript:
// ニューロン数10の層を作る。活性化関数なし。
{type:'fc', num_neurons:10}
// 活性化関数を指定して、ニューロン数10の層を作る。
{type:'fc', num_neurons:10, activation:'sigmoid'}
{type:'fc', num_neurons:10, activation:'tanh'}
{type:'fc', num_neurons:10, activation:'relu'}

// maxoutの層。num_neuronsは偶数でなければなりません。
// 原理的に次元が減少します。この場合出力のサイズは5になります。
{type:'fc', num_neurons:10, activation:'maxout'} 

// maxoutで最大値を計算する層の数を指定します。
//当然num_neuronsはgroup_sizeで割り切れる必要があります
// この場合出力のサイズは12/4 = 3になります。
{type:'fc', num_neurons:12, group_size: 4, activation:'maxout'}
// 学習でドロップアウトを使用するとき、無視するニューロンの割合を指定します。
{type:'fc', num_neurons:10, activation:'relu', drop_prob: 0.5}
```

##Loss Layer Classifier Layers
Deep Learningで入力データを特定の種類に分類したいときは、こちらを使いましょう。softmaxはデータの合計が1になるように出力します。SVMは確率ではなく、スコアを学習させたいときに用いてください。

```javascript:
layer_defs.push({type:'softmax', num_classes:2});
layer_defs.push({type:'svm', num_classes:2});
```
When you are training a classifier layer, your classes must be numbers that begin at 0. For a binary problem, these would be class 0 and class 1. For K classes, the classes are 0..K-1.

##Loss layers L2 Regression Layer
えいご難しくて読めなかったので後で書く

#畳み込みニューラルネットワーク
今は省略

#Trainer (学習)
各種Layerの扱いは以上です。続いて学習の役割を担うTrainerについて見ていきましょう。TrainerクラスにNetと学習データを与えると、ニューラルネットの係数を補正して学習が行われます。用意した教師データ全体にわたって学習を行わせることで、徐々に正しい結果を得られるようになります。

```javascript:
// Momentum SGD で学習させる例. 学習データ10個毎に、係数を補正します。
var trainer = new convnetjs.Trainer(net, {
    method: 'sgd', learning_rate: 0.01,
    l2_decay: 0.001, momentum: 0.9,
    batch_size: 10, l1_decay: 0.001});

// adadelta で学習させる例。初心者向き。
var trainer = new convnetjs.Trainer(net, {
    method: 'adadelta', l2_decay: 0.001,
    batch_size: 10});

// adagrad で学習させる例。
var trainer = new convnetjs.Trainer(net, {
    method: 'adagrad', l2_decay: 0.001,
    l1_decay: 0.001, batch_size: 10});
```
学習方法は'method'を'sgd' 'adagrad' 'adadelta'のいずれかに指定することで変えられます。これら3つの学習方法の関係は[この論文](http://www.matthewzeiler.com/wp-content/uploads/2017/07/googleTR2012.pdf)を参照してください。関連して、ConvNetJsでは[MNISTのデモでこれらを比較したもの](https://cs.stanford.edu/people/karpathy/convnetjs/demo/trainers.html)を公開しています。また、以下のTipsも参考にしてください。

- 初心者の方にはAdadeltaかAdagradがおすすめです。学習係数を自動で補正してくれる上、比較的容易に意図した結果が得られます。
- SGDはMomentumを使用するときに実装します。慣例的には0.9に設定して使うことが多いです。また学習係数を少しずつ慎重に調整する必要があります。ReLUを用いる場合、学習係数が大きすぎると最適な係数に収束しないうえ、勾配発散が起きる可能性もあります。かと言って学習係数が小さすぎると、学習がなかなか進みません。学習の際のcostの遷移を見ながら、学習係数を調整してください。
- l2_decayは高く設定するとネットワーク will be regularized very strongly. 教師データが少ない場合はl2_decayを高めにするのが有効です。また学習エラーが少ない場合も、少し高めにしておくとより良い結果が得られることがあります。学習エラーが多い場合は、この値を小さくしてみましょう。
- Use l1_decay instead of l2_decay if you'd like your network to have sparse weights at the end, as l1 norm on weights is sparsity encouraging. なんのことかわからないようであれば、0のままにしてください。(デフォルトでは0になってます)
- 通常batch_sizeは1にします。batch_sizeはネットワークのgradient stepsをがどれくらい正確になるかを制御するときに使います。1つのbatchで100個の教師データを与えると、学習の際に係数補正の正確さが向上します。が、実際は1にしておくのが適切でしょう。
- どの学習方法を使えば良いのかわからなければ、まずはadadeltaで試してみましょう。

実際に例を見ていきます。

```javascript:
var x = new convnetjs.Vol(1,1,d);
x.w[0] = 1; // 1つ目の要素の値を1にセット
 
// if your loss on top is
// layer_defs.push({type:'svm', num_classes: 5});
// use something like... (lets say x is class 3)
var stats = trainer.train(x, 3);
 
// if your loss on top is
// layer_defs.push({type:'regression', num_neurons: 1});
// use something like... (note the LIST!)
var stats = trainer.train(x, [0.7]);
// if your loss on top is
// layer_defs.push({type:'regression', num_neurons: 3});
// use something like
var stats = trainer.train(x, [0.7, 0.1, 0.3]);
```
Trainerから返されるオブジェクトにはcostや、計算にかかった時間なども含まれています。
最後の例を見てみましょう。ループを用いたより実践的な例です。

```javascript:
var trainer = new convnetjs.SGDTrainer(net, {
    learning_rate:0.01, momentum:0.9,
    batch_size:16, l2_decay:0.001});
for(var i=0;i<my_data.length;i++) {
  var x = new convnetjs.Vol(1,1,2,0.0); //要素を0に初期化して1x1x2のVolを生成
  x.w[0] = my_data[i][0]; // Vol.wはデータの配列です
  x.w[1] = my_data[i][1];
  trainer.train(x, my_labels[i]);
}
```

#JSONを使った学習データの読み込み・書き出し
toJSON()やfromJSON()関数でデータの読み書きが可能です。

```javascript:
// ネットワークのデータをJSONに変換
var json = net.toJSON(); //オブジェクトとして出力される
var str = JSON.stringify(json); //文字列として出力される
 
// JSONデータからネットワークを再構成
var json = JSON.parse(str); // 文字列をオブジェクトに直す
var net2 = new convnetjs.Net(); // 空のNetを作る
net2.fromJSON(json); // JSONから全てのデータが引き継がれます
```