JavaScriptで手書きひらがなの識別

###はじめに

前回の[JavaScriptで手書き数字の識別](http://qiita.com/tantai/items/ea4a742417c25baea029)は他サイトの学習データを利用しました
今回は「データセットを作る」「学習する」もやります

デモ
http://pie.karou.jp/zy.html

###ソース

csvデータ作成ページ http://pie.karou.jp/zv.html

```html
<!DOCTYPE html>
<meta charset="utf-8">
<title>csvデータ作成ページ</title>
小さな黒四角にひらがな(あ～ん)を書いてcsvデータを作成<br>
<canvas id="pad" width="32" height="32"></canvas>
<b id="guide">あ</b>を書いてください。その後キーボードで"s"を押す。<br>
<textarea id="csv" cols="80" rows="5" wrap="off"></textarea>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/signature_pad/1.5.3/signature_pad.min.js"></script>
<script>
   var hiragana = "あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん".split("");
   var cnt = 0;

   var signaturePad = new SignaturePad($("#pad")[0], {
      penColor: "white",
      backgroundColor: "black",
   });

   document.onkeydown = function(e) {
      if (e.keyCode == 83) { //s
         var ctx = $("#pad")[0].getContext('2d');
         var rgba = ctx.getImageData(0, 0, 32, 32);
         var arr = [];
         var label = cnt % hiragana.length;
         arr.push(label);
         for (var i = 0; i < rgba.data.length; i += 4) arr.push(rgba.data[i]);
         var str = $("#csv").val();
         $("#csv").val(str + arr.join(",") + "\n");
         signaturePad.clear();
         cnt++;
         $("#guide").text(hiragana[cnt % hiragana.length]);
      }
   };
</script>
```
学習ページ http://pie.karou.jp/zw.html

```html
<!DOCTYPE html>
<meta charset="utf-8">
<title>学習ページ</title>
csvデータ貼り付け用テキストエリア<br>
<textarea id="csv" cols="80" rows="5" wrap="off"></textarea>
<p><input type="button" value="学習" onclick="learnFn()"></p>
<textarea id="json"></textarea>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/convnetjs/0.3.0/convnet.js"></script>
<script>
   var hiragana = "あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん".split("");

   function learnFn() {
      var net = new convnetjs.Net();
      var layer = [];
      layer.push({type: 'input', out_sx: 32, out_sy: 32, out_depth: 1});
      layer.push({type: 'conv', sx: 5, filters: 8, stride: 1, pad: 2, activation: 'relu'});
      layer.push({type: 'pool', sx: 2,stride: 2});
      layer.push({type: 'conv',sx: 5, filters: 16, stride: 1, pad: 2, activation: 'relu'});
      layer.push({ type: 'pool', sx: 3, stride: 3});
      layer.push({ type: 'softmax', num_classes: 5});
      net.makeLayers(layer);
      var trainer = new convnetjs.SGDTrainer(net, {
         method: 'adadelta',
         batch_size: 20,
         l2_decay: 0.001
      });
      var str = $("#csv").val();
      var csvArr = str.trim().split("\n");
      csvArr = csvArr.map(v => v.split(',').map(Number));
      for (var i = 0; i < csvArr.length; i++) {
         var label = csvArr[i][0];
         var input = new convnetjs.Vol(32, 32, 1);
         for (var j = 1; j < csvArr[i].length; j++) input.w[j - 1] = csvArr[i][j] / 255 - 0.5;
         trainer.train(input, label);
      }
      var json = net.toJSON();
      $("#json").val(JSON.stringify(json));
   }
</script>
```
識別ページ http://pie.karou.jp/zx.html

```html
<!DOCTYPE html>
<meta charset="utf-8">
<title>識別ページ</title>
JSON 貼り付け用テキストエリア<br>
<textarea id="json"></textarea><br>
JSONを貼り付けた後に黒色の小さな正方形にマウスでひらがなを書いて識別してください<br>
<canvas id="pad" width="32" height="32"></canvas>
<button onclick="checkFn()">識別</button>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/signature_pad/1.5.3/signature_pad.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/convnetjs/0.3.0/convnet.js"></script>
<script>
   var hiragana = "あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん".split("");

   var signaturePad = new SignaturePad($("#pad")[0], {
      penColor: "white",
      backgroundColor: "black",
   });

   function checkFn() {
      var ctx = $("#pad")[0].getContext('2d');
      var rgba = ctx.getImageData(0, 0, 32, 32);
      var net = new convnetjs.Net();
      var netObj = JSON.parse($("#json").val());
      net.fromJSON(netObj);
      var input = new convnetjs.Vol(32, 32, 1);
      for (var i = 0; i < rgba.data.length; i += 4) input.w[i / 4] = rgba.data[i] / 255 - 0.5;
      var output = net.forward(input);
      var max = Math.max(...output.w);
      var index = output.w.indexOf(max);
      alert(hiragana[index]);
      signaturePad.clear();
   }
</script>
```