stMind

about Tech, Computer vision and Machine learning

Tensorflowでロゴ画像を分類する

UdacityのDeep LearningコースでTensorflowを試してみたものの、いまいちしっくりこない感じがありました。こういうのは、自分で何かしら作って試してみるのが一番ということで、ブランドロゴ画像の分類をしてみました。

なお、ここで書いたコードは

GitHub - satojkovic/DeepLogo: A brand logo recognition system using deep convolutional neural networks.

にあります。python3系ならば動くと思います。scipy、scikit-learn、Pillow(PIL)、numpyあたりを使ってますので、必要に応じてインストールしてください。

タスクとデータセット

データセットflikcr_logos_27_datasetを使います。AdidasAppleBMWなどなどの27種類のロゴ分類タスクです。

f:id:satojkovic:20160821151857p:plain

ロゴ識別CNN

今回作るCNNは、ナンバープレート認識をTensorflowでやってるブログを大いに参考にしました。発想としては単純で、ロゴ画像って文字や数字、模様から出来ていて、一方でナンバープレートは数字と文字で構成されていて、特徴として非常に似ているので、CNNのネットワーク構成がそのまま使えるかなと思った、とそれだけです。 違うのは、ナンバープレートはグレースケールなのに対して、ロゴ画像は色も特徴の一つになりそうなので、カラー画像で3チャンネルの入力になるところと、画像サイズが64 x 32になっているところです。 オリジナルのネットワーク図を書き換えると、次のような構成。

f:id:satojkovic:20160821161807p:plain

コードとしては、train_deep_logo_cnn.pyとtest_deep_logo_cnn.pyにあるmodelが該当するところです。重みとバイアス項は、mainの所で記述してmodelメソッドに渡しています。

def model(data, w_conv1, b_conv1, w_conv2, b_conv2, w_conv3, b_conv3, w_fc1,
          b_fc1, w_fc2, b_fc2):
    # First layer
    h_conv1 = tf.nn.relu(
        tf.nn.conv2d(
            data, w_conv1, [1, 1, 1, 1], padding='SAME') + b_conv1)
    h_pool1 = tf.nn.max_pool(
        h_conv1, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')

    # Second layer
    h_conv2 = tf.nn.relu(
        tf.nn.conv2d(
            h_pool1, w_conv2, [1, 1, 1, 1], padding='SAME') + b_conv2)
    h_pool2 = tf.nn.max_pool(
        h_conv2, ksize=[1, 1, 2, 1], strides=[1, 1, 2, 1], padding='SAME')

    # Third layer
    h_conv3 = tf.nn.relu(
        tf.nn.conv2d(
            h_pool2, w_conv3, [1, 1, 1, 1], padding='SAME') + b_conv3)
    h_pool3 = tf.nn.max_pool(
        h_conv3, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')

    # Fully connected layer
    conv_layer_flat = tf.reshape(h_pool3, [-1, 16 * 4 * 128])
    h_fc1 = tf.nn.relu(tf.matmul(conv_layer_flat, w_fc1) + b_fc1)

    # Output layer
    out = tf.matmul(h_fc1, w_fc2) + b_fc2

    return out

学習画像の生成

学習画像は、データセットアノテーションファイルを使ってロゴ部分だけを切り出し(crop)、切り出した画像についてデータオーグメンテーション(シフト、回転、拡大縮小)を適用して、全体で21万枚程度に増やしました。27クラスあるので、クラス毎に8000枚くらいになる計算ですね。実際には、結構偏りがありますが。

その後、train75%、test25%の割合にして、フォルダに画像を分けるようにしています。ここまでを、crop_and_aug.pyで実行します。

def main():
    annot_train = np.loadtxt(os.path.join(TRAIN_DIR, ANNOT_FILE), dtype='a')
    print('train_annotation: %d, %d ' % (annot_train.shape))

    # cropping and data augmentation
    crop_and_aug(annot_train)

    # train_test_split
    do_train_test_split()

さらに加えて、Tensorflowを使ってCNNを学習するためのデータ形式にするのに、gen_train_valid_test.pyを使ってpickleファイルをクラス毎に生成しています。ここまでやって、CNNを学習する準備が完了になります。学習画像の例(回転、シフトしているのが分かりにくいな...)。

f:id:satojkovic:20160821172356j:plain

学習とロゴ分類テスト

学習はtrain_deep_logo_cnn.pyで実行します。大体、3000ステップくらいでロスが小さくなって、学習が収束しているようです。学習データに対するAccuracyは90%、テストデータに対するAccuracyもあまり変わらず89%程度になっていました。また、学習が終わったモデルでテストしてみると、だいたい正しく分類してくれているようです。

$ python test_deep_logo_cnn.py 
Test image: flickr_logos_27_dataset/flickr_logos_27_dataset_cropped_augmented_images/Google/test/272645705_Google_6_2_r-13.jpg
Model restored
Class name: Google

Test image: flickr_logos_27_dataset/flickr_logos_27_dataset_cropped_augmented_images/Nbc/test/3570522135_Nbc_6_2_r0.jpg
Model restored
Class name: Nbc

Test image: flickr_logos_27_dataset/flickr_logos_27_dataset_cropped_augmented_images/Mini/test/2792546855_Mini_2_2_p-1-1.jpg
Model restored
Class name: Mini

まとめ

独自データセットで実際にTensorflowを使ってみることで、かなり理解が進みました。Tensorflow自体は使い方を知ってみれば難しくはなく、それよりもデータをどのように準備すればいいのかを決めて、データを準備するところがあまり面白くはないのですが、頑張る必要がありますね。

また、今回は参考になりそうなブログを見つけて、ほぼそのままのネットワーク構成を使いましたが、最初はあまり複雑なネットワーク構成を独自に作ったりせずに、シンプルな既存モデルから始めるのがアプローチとしては良いのかなと思いました。

matlabでは行列を行ベクトルにするとcolumn major order

3x3の行列Aを9x1の行ベクトルに直すと、1, 2, 3, 4, 5, 6, 7, 8, 9となると思っていたら、1, 4, 7, 2, 5, 8, 3, 6, 9だった... (column major order)

>> A

A =

     1     2     3
     4     5     6
     7     8     9

>> A(:)

ans =

     1
     4
     7
     2
     5
     8
     3
     6
     9

>> 

ちなみに、numpyだと指定しない限り、row major orderになる。

In [15]: A
Out[15]: 
matrix([[1, 2, 3],
        [4, 5, 6],
        [7, 8, 9]])

In [16]: np.ravel(A)
Out[16]: array([1, 2, 3, 4, 5, 6, 7, 8, 9])

In [17]: np.ravel(A, order='F')
Out[17]: array([1, 4, 7, 2, 5, 8, 3, 6, 9])

アーセナルの攻撃スタイルにみるUnix哲学

バーディー、結局残留ですってね。

www.soccer-king.jp

アーセナルに来たら、相手も引いて守るし、スペースがない中でバーディーは活躍できないんじゃないかなんて言われてましたが、 僕は十分フィットするなぁと思ってました。というよりも、バーディーのようなタイプのストライカーがちょうどはまるスタイルが 強いアーセナルの時の攻め方だと思います。

どういうことか? 昨年のユナイテッド戦の動画を見てもらうとすごくわかりやすい。1分22秒くらいから始まる二点目のシーンです。


HIGHLIGHTS ● BPL ► Arsenal 3 vs 0 Manchester United - 4 Oct 2015 | English Commentary

ベンゲルが作り上げたアーセナルらしい攻撃というのは、最も少ないパス交換で縦に速く動いてゴールを奪うシンプルさにこそあるのだと思うのです。 相手からのロングボールを跳ね返して、サンティ・カソルラがボールを拾った位置がハーフラインよりも自陣寄り。そこから、アレクシスへの縦パス、アレクシスからエジルへの落とし、そしてエジルからウォルコットへのスルーパスの合計3本でゴール前まで侵入しています。最後は、エジルがゴールへ流し込んで仕上げとなったのですが、その間わずか10秒ほど。 こういう攻撃ができている時はチーム状態がものすごくいいなぁと思えるし、実際この時はもう一点追加して勝ちを収めました。 複雑なパスではなく、ゴール直結の縦に速いパス交換を選手同士が連動しながら行ってゴールを奪う、まさに一つ一つの目的直結のコマンドをパイプで組み合わせて大きな目的を達成するUNIXそのものではないですか。(凄いこじつけ感)

確かに、引いて守る相手に対して、これでもかとパスを回してゴールを決めるスタイルも素晴らしくはありますが、アーセナルには合ってないんじゃないかなぁ。バーディー獲得は幻と消えてしまいましたが、そういうストライカーを是非獲得してほしいなぁと思った次第です。

How AlphaGo WorksとAlphaGoの裏側

www.slideshare.net

DeepMindがNatureに投稿した論文を、CMUのPhDの方が解説しているプレゼン資料がslideshareにありました。
AlphaGoの仕組みがとても分かりやすくまとめてあり、英語ですが一読の価値ありです。
スライドのアウトラインを部分的に日本語で書き出してみました。

まずはコンピュータ囲碁AIの定義

  • 碁盤を行列で表現
  • 1(黒)/ -1(白)/ 0(碁石なし)
  • d手目の碁盤の状態sが与えられた時に、最適なアクションaを選ぶ

実現方法

  • あらゆる碁盤の状態をどうやってシミュレートする?
  • d手目の全aについて、ゲームが終了となるまでシミュレーションして勝ちor負けを観測
  • 全シミュレーションで最も勝ち数が多いaを選択
  • これは不可能
  • 可能な碁盤の状態は、宇宙の原子数よりも多いと言われている

探索空間を絞り込むことがポイント

  • アクションaの候補を絞り込む = 幅削減
    • Policy Network
  • アクションaの良さを評価(位置の評価) = 深さ削減
    • Value Network
    • 最後までシミュレートしない
    • v(s): 碁盤の状態sの時の評価

アクションaの候補の絞り込み

  • P(a|s)を学習
  • プロ棋士の打ち方を学習(教師あり学習)
    • 5段から9段のプロ棋士
    • 対局数160K、30Mの棋譜データ
  • 13層のCNNで実現
    • 碁盤の状態sが与えられた時のaの確率P(a|s)
  • 自己対戦で強化学習

碁盤の状態の評価

  • CNNにレグレッション層を追加
  • 1に近いほど良い、0に近いほど悪い

Monte Carlo Search Tree

  • Selection
    • アクションaの候補絞り込み(Policy Network)
    • Shallow Networkを使ってP(a|s)の高速化(Rollout)
  • Expansion
  • Evaluation
    • 碁盤の状態評価(Value Network)
  • Backup

Results

  • Rollout + Policy Network + Value Networkでプロ5段くらいと予想される

スライドには図面があって、それを見て英語の解説を読むとまた理解は深まると思います。

誰も教えてくれない秘密の扉の喩え話

真面目で努力家な人ほど、正攻法なんだけど一番成功するのが難しい方法をとってしまいがちなんだよなぁ。成功が難しいというか、競争率の激しい方法ですね。

大変に成功した人というのは、人生やビジネス、そして成功はナイトクラブのように捉える。  
つまり、入り口は常に三つ存在する。  
最初の入り口は、99%の人が、中に入ることを期待しながら、列に並んでいる。  
二つ目の入り口は、ビリオネアやセレブだけが使える。  
しかし、ここに三つ目の入り口がある。その入り口は、入場待ちの列から離れ、狭い路地を通り、ゴミ収集コンテナをよじ登り、扉を100回叩き、
窓を破り、キッチンを通ってこっそり入るような所。ただ、常にこの入り口はある。  
ビルゲイツが初めてのソフトウェアを売り、またスティーブンスピルバーグがハリウッドのメジャースタジオで最年少のディレクターになったのは、どのような方法であったにせよ、三つ目の入り口を使ったのである。  
Alex Banayan

Alex Banayanさんの喩えでいうところの、三つ目の扉のような誰も教えてくれない仕組みに、言われる前に気づけるかどうか。
これを書いた人は、Quoraのproduct designに応募した時、モバイルアプリのデザイン案を作ってproduct designのトップにメールで送ったら返信があって、job interviewに進めたらしいです。
なるほど、三つ目の扉かぁ。

参考にならないかもしれない東京マラソン参加レポート

f:id:satojkovic:20160228145155j:plain

走ってきました、東京マラソン2016。
フィニッシュ時間は5時間20分。
ラスト10キロは両足がほぼつった状態で、なんとかゴールまでたどり着いた感じでしたが、
最後まで歩かずに走りきったので満足です。

来年も走りたいなぁと思いつつ、抽選に当選しないといけないので、
必ず走れる訳ではないけど、今年走ってわかったことを記録として残しておきます。

  1. 当然だけど、スタート前は大混雑

    今回は過去最高の3万7千人参加だったのもあり、スタート前の混雑は半端なかったです。 特にトイレ待ちはすごくて、道路が分断されるくらいの列になってました。スタート前に一度行っておこうと思っていたのですが、 30分待っても列が全然進まないので、途中で諦めてスタート場所に移動することにしました。 トイレはスタートラインの手前にあって、スタートラインの手前だったので時間ロスもないし、 比較的空いてたし、実は穴場かもしれないと思いました。

  2. スタートラインを通過するまで、これまた30分

    自分のスタートゾーンはKでした。Aから始まって、B、C…ときてKなので、後ろの方でした。9時10分にスタートなんですが、 Kゾーンはピクリとも動かず。そのまま10分立ち尽くし、いつ動くのか?と思ってたら、15分程度でチョロチョロと動き出し、 20分くらいでようやく道路に出て走り始めることができました。スタートラインを通過したのは、22分くらいでしたね。 あと、Kともなるとスタートラインから随分と遠くにいて、200mくらいは余計に走る必要があるのは盲点でした。

  3. 完走してからも、まだまだ歩く

    完走した後は、精も根も尽き果てたという感じなんですが、タオルやメダルを受け取って、 預けた荷物を受け取る列に並び、着替えゾーンまで歩いて、これが実にしんどかった(笑。 東京マラソンくらいの大規模になると、ゴールから着替えゾーンまでが非常に遠いです。

とにかく、いろいろなところが大規模で、さすが東京マラソンだな〜と感じるところがいっぱいでした。
もちろん、東京の名所を巡りながら走ることができるのは最高に気持ちのよい体験でした。

TensorFlowのRun a TensorFlow demo modelでつまずかない

UdacityでTensorFlowを使ったディープラーニングの講義が始まりましたね。

www.udacity.com

遅ればせながらTensorFlowをインストールすることにしました。pipでインストールは問題なかったですが、手書き文字認識のデモを実行してみると、タイムアウトになってしまいました。GithubのIssuesによると、Yann Lecun教授の個人ページなので、アクセスしにくい状況になっているのではないかということでした。

github.com

同じIssuesにアーカイブサイトのURLが貼ってあって、convolutional.pyのSOURCE_URLをそちらに変えて再度実行してみると、デモが動きました!

SOURCE_URL = 'https://web.archive.org/web/20160117040036/http://yann.lecun.com/exdb/mnist/'

もし、同じ状況になって困っている人がいたら参考にしていただければ。