TensorFlowでCAPTCHAを解く(データ収集編)

# 前置き
TensorFlowが流行っているので流れに乗るべく触ってみている。
公式HPのチュートリアルをざっとやってみてそろそろ次のステップでなにか作ってみようかと思った。
ディープラーニング自体については、<a  href="http://www.amazon.co.jp/gp/product/4061529021/ref=as_li_ss_tl?ie=UTF8&camp=247&creative=7399&creativeASIN=4061529021&linkCode=as2&tag=shohyo006-22">深層学習 (機械学習プロフェッショナルシリーズ)</a><img src="http://ir-jp.amazon-adsystem.com/e/ir?t=shohyo006-22&l=as2&o=9&a=4061529021" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" />で並行して勉強中。

# 目標
特に目標があってTensorFlowを触り始めたわけではないのだが、勉強をする際になにかしら目標を置くのは大事。

みんなどういうことをしているのかなとググったら[ゆるゆりの制作会社を識別](http://kivantium.hateblo.jp/entry/2015/11/18/233834)したり、[おそ松さんの六つ子を見分け](http://bohemia.hatenablog.com/entry/2015/11/22/174603)たり、[巨乳かどうかを判別](http://qiita.com/summer4an/items/db0124eee8103c1d3b85)したり、[顔が似ているAV女優を教えて](http://qiita.com/tmnck/items/af82deb04d432f1f4f6e)くれたりする例が見つかった。
やはり皆見た目にわかりやすい画像認識から入る人が多い様子。(チュートリアルがCNN画像認識というのが大きいかな。)

というわけで画像認識をやろうと思い、たどり着いたのがCAPTCHA。サインアップとかダウンロードとかするときに出てくる面倒なアレ。
"コンピュータで解くのが難しい問題"の代表格なので、事例としては最適？
また、もともと機械的に生成しているものなので、きっと訓練データは自動生成できる、という目論見。

"deep learning captcha"でググると論文含めた事例がちょいちょい出てくるのでN番煎じだが気にしない。

# データ生成
## Securimage
オープンソースのCAPTCHA生成システムをさくっと調べて選んだのが[Securimage PHP Captcha](https://www.phpcaptcha.org/)。
これを使って訓練/テストデータを自動生成する。(コードサマリは下部。)

## 生成するデータ
Securimageをいじりながら、訓練/テストデータとしてどのようなデータを使うかを適当に決めた。
この辺りは実際に解きたい問題に合わせて決めるところだけど、今回はえいやーで決めた。

* CAPTCHA文字列長は1文字から5文字
* 各文字列長につき、訓練データ10000枚、テストデータ2000枚を生成
    * つまり、全部で60000枚の画像データを生成
    * 実際使う時に調整する可能性あり(GPU無い環境なのでスペックの問題も・・・)
* グレースケール
* CAPTCHAに使う文字は今回はアルファベット大文字のみ([A-Z])
* 画像の歪み具合は弱いものから強いものまでを混在

こんな感じ。左はJOBMV、右はHGVが正解。(←これをTensorFlowで求めるのが最終目標)
![JOBMV_w.png](https://qiita-image-store.s3.amazonaws.com/0/44717/ed6227b4-e39f-2561-9743-6368daf0ed0a.png) &nbsp;&nbsp;&nbsp; ![HGV_s.png](https://qiita-image-store.s3.amazonaws.com/0/44717/2d07a94f-956b-7e71-8397-cfc6c923af71.png)

## 出力ファイルのフォルダ構成

```
out/
 ├ train/
 │   ├ 1/   # それぞれ10000枚の画像データ
 │   ├ 2/
 │   ├ 3/
 │   ├ 4/
 │   └ 5/
 ├ test/
 │   ├ 1/   # それぞれ2000枚の画像データ
 │   ├ 2/
 │   ├ 3/
 │   ├ 4/
 │   └ 5/
 └ info.csv # 画像情報のCSVファイル
```

info.csvはこんな感じ

```
train,out/train/1/1.png,M,1,1.56,8
train,out/train/1/2.png,H,1,1.6,4
・・・
test,out/test/5/1999.png,GMZIV,5,1.68,3
test,out/test/5/2000.png,YUMDL,5,1.17,5
```

順に

* 訓練データか、テストデータか(train / test)
* 画像の絶対パス
* 正解ラベル([A-Z]{1,5})
* 文字列(正解ラベル)の長さ(1～5)
* 歪みの大きさ(1～1.7)
* 邪魔な線の数

後ろ2つは、最終的に正解率が出た際に相関を見たら面白いかもと思ってとりあえず出している。

## コードサマリ
実際に書いたコードのサマリ。

まずはSecurimageを入手。

```bash
git clone https://github.com/dapphp/securimage.git
cd securimage
git checkout 3.6.4 #2016/5/6時点での最新リリース
```

どうやらWebにCAPTCHAを出力する以外の使い方は想定していないようで(そりゃそうか)、ローカルでCAPTCHA画像を取得するような機能はなさそう。というわけで、ちょっとsecurimage.phpに手を入れる。
といっても、下記の変更を実質4行加えただけ。

* 以下のprotected変数を外部から取得できるようにpublicにした
    * \$im : 画像データ
    * \$code, \$code_display : 正解データ
* 画像生成後にHTTPヘッダとともに画像を標準出力に出力していたのをやめた

```diff
@@ -804,7 +804,7 @@ class Securimage
      *
      * @var resource
      */
-    protected $im;
+    public $im;

     /**
      * A temporary GD image resource of the captcha image for distortion
@@ -843,7 +843,7 @@ class Securimage
      *
      * @var string|bool Captcha challenge value
      */
-    protected $code;
+    public $code;

     /**
      * The display value of the captcha to draw on the image
@@ -852,7 +852,7 @@ class Securimage
      *
      * @var string Captcha display value to draw on the image
      */
-    protected $code_display;
+    public $code_display;

     /**
      * Alternate text to draw as the captcha image text
@@ -1753,7 +1753,7 @@ class Securimage
             $this->addSignature();
         }

-        $this->output();
+        #$this->output();
     }

     /**

```

続いて、生成スクリプトを記述。
phpとshが1ファイルずつ。
gitでcloneした(securimage.phpと同じ)ディレクトリに置いておく。

```php:create_data.php
<?php

require_once dirname(__FILE__) . '/securimage.php';

// 画像を生成する
function make_image($len)
{
    $img = new Securimage();

    // 使う文字は英語大文字のみ
    $img->charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    // 画像の高さ
    $img->image_height = 100;
    // 画像の幅
    $img->image_widht = 215;
    // 文字列の長さ
    $img->code_length = $len;
    // 邪魔な線の数
    $img->num_lines = rand(3, 8);
    // 色の設定
    $img->image_bg_color = new Securimage_Color("#ffffff");
    $img->text_color     = new Securimage_Color("#000000");
    $img->line_color     = new Securimage_Color("#000000");
    $img->noise_color    = new Securimage_Color("#000000");
    // 画像の歪み具合
    $img->perturbation = rand(100, 170) / 100.0;

    $img->show();
    return $img;
}

$task = $argv[1];
$len = $argv[2];
$filename = $argv[3];

// PNG画像をファイル出力
$img = make_image($len);
imagepng($img->im, $filename);
imagedestroy($img->im);

// CSVファイルに追記
$line_arr = [$task, $filename, $img->code_display, $img->code_length, $img->perturbation, $img->num_lines];
echo implode(",", $line_arr) . "\n";
```

```bash:create_data.sh
#/bin/bash

# 出力先ディレクトリ
outdir="out"
# CSVファイル出力先
info_file="$outdir/info.csv"

# キャプチャ文字列の長さ(最小)
len_min=1
# キャプチャ文字列の長さ(最大)
len_max=5

# 訓練用データ(各文字列長につきこの枚数)
train_num=10000
# テスト用データの枚数(各文字列長につきこの枚数)
test_num=2000

# フォルダの初期化
rm -rf "$outdir"
mkdir "$outdir"
mkdir "$outdir/train"
mkdir "$outdir/test"

for len in `seq $len_min $len_max`;do
    mkdir "$outdir/test/$len"
    mkdir "$outdir/train/$len"

    for id in `seq 1 $train_num`;do
        task="train"
        filename="$outdir/$task/$len/${id}.png"
        php ./create_data.php $task $len $filename >>$info_file
    done

    for id in `seq 1 $test_num`;do
        task="test"
        filename="$outdir/$task/$len/${id}.png"
        php ./create_data.php $task $len $filename >>$info_file
    done
done
```


上記を実行する

```bash
chmod +x create_data.sh
./create_data.sh
```
60000枚の画像を生成するのでそれなりに時間はかかるが、待ってたらそのうち終わる。(うちの環境で2時間弱かかった。)

$outdir以下に画像ファイルとCSVファイルが生成される。

# まとめ
* TensorFlowを触り始めた。
* 遊ぶための題材としてCAPTCHAを選んだ。
    * CAPTCHAデータを自動生成した。
* 「実はもうやっててあとは書くだけ！」ということはなく、これから詰まりながらちょこちこやっていく予定。
    * 早速TensorFlowにPNG画像を読み込むことができずに詰まっている。
    * tf.imageまわりの情報少ない・・・
* 記事内ではTensorFlowに全く触れないことに書いてから気づいた。次回からは多分触れるはず。