パドック画像から距離適性を推測する(転移学習編)

果たして転移学習で改善するのか?! やってみました! ((p・ω・q))

chainerで転移学習をするにあたって以下の記事を参考にしました。

転移学習でハマってかつ学習結果も惨敗… でしたがchainerで転移学習する方法の勉強になりました。。

ハマった点

  1. 学習済みAlexnetのパラメータがcopyされない

    • 対策: L.Classifierのモデル(model)でなく model.predictor を copy_model の引数として渡す
    • 対策: モデルを定義するときに 入力次元として None を使わない
    • chainerのAlexnetを用いてFine Tuningをする | TOMMY NOTES 」の記事の中の copy_model 関数はインデントずれがあってそのままコピペするとうまく動作しません…

    chainerでは入力次元を None としたときは前方向への伝搬(Forward propagation)を計算する中で動的に次元を計算するので計算をしてない状態では次元が不定となり、copy_modelの中での次元比較の際ミスマッチとなりコピーされません。

    あと、参考にした記事ではcopy_model(original_model, model)でコピーできてそうなのですが私のコードではcopy_model(original_model, model.predictor)としないとコピーできませんでした。。

  2. FC層だけ学習させるときにEvaluatorでエラーがでる

    • 対策: hook関数を使って重みを更新しないレイヤの勾配を削除する

    最初に https://github.com/chainer/chainer/issues/724https://groups.google.com/forum/#!searchin/chainer/Finetuning/chainer/H4IWqcMBA2w/8cxt58YrBwAJ でやられている volatileフラグを制御する方法でやってたのですが、これだとEvaluatorの実行時に ValueError: ON and OFF flags cannot be mixed. というエラーが出てしまいました…。(Evaluatorをオフにするとエラーは出ずに学習できました)

    最終的に、Evaluatorでもエラーを出さずにうまくいった方法は「Chainerでfine-tuningを行う - Qiita 」にあった hook関数を使って勾配をリセットするというものでした。ちなみに、この方法はすべてのレイヤで勾配を一度計算することになるので計算時間は短縮されません。。(volatileを使うと勾配は計算しなくなるので計算時間が短くなります)

    あと、chainer v2から導入された chainer.no_backprop_mode()のスコープを使うとうまくいきそうです。v1.x.xでもこのスコープは(なぜか)使えてしまうのですがただ重みは全レイヤで更新されてしまいここで時間を食ってしまいました..。以下のMNISTでは chainer v2では特定のレイヤだけ重み更新できることを確認しています。


MNISTで特定のレイヤだけ重みを更新するサンプル github.com

Alexnetを転移学習した結果

誤差は以下のようになりました。

f:id:sanshonoki:20170626044354p:plain

学習データの誤差が減らずうまく学習できないことが伺えます…。[´゚Д゚`]

最後は発散してどんな入力に対しても同じ出力値(4.2 = おおよその中央値)が出るようになってしまいました…。 通常の学習の検証用データでの誤差が同じ値を出し続ける場合の誤差よりも大きいということで何とも切ないです.. ( ;∀;)

ちなみにFC層だけ学習させるのではなく、全階層で学習させたら通常と同等の誤差まで減っていきました。

なので、Kerasで学ぶ転移学習 で書かれている

データが少ない・似ていない これは転移学習が困難なパターンです。データが少ないので過学習を防ぐために上層だけを学習させたいところですが、似ていないデータを使って学習しているため、上層の特徴を使ってもうまく学習できないと考えられます。

というパターンなのでしょう。。

一般的な物体認識問題とはかなり異なる問題設定だったということでしょう、そりゃあそうですよねという気はしますが残念です。。でも、勉強になりました。

最後に、誰かの役に立つかもしれないので転移学習に関係する箇所のコードをのせておきます。

– train_ft.py –

class DelGradient(object):
    name = 'DelGradient'

    def __init__(self, delTgt):
        self.delTgt = delTgt

    def __call__(self, opt):
        for name, param in opt.target.namedparams():
            for d in self.delTgt:
                if d in name:
                    grad = param.grad
                    with chainer.cuda.get_device(grad):
                        grad *= 0

def copy_model(src, dst):
    assert isinstance(src, chainer.Chain)
    assert isinstance(dst, chainer.Chain)
    for child in src.children():
        if child.name not in dst.__dict__: continue
        dst_child = dst[child.name]
        if type(child) != type(dst_child): continue
        if isinstance(child, chainer.Chain):
            copy_model(child, dst_child)
        if isinstance(child, chainer.Link):
            match = True
            for a, b in zip(child.namedparams(), dst_child.namedparams()):
                if a[0] != b[0]:
                    match = False
                    break
                if a[1].data.shape != b[1].data.shape:
                    match = False
                    break
            if not match:
                print('Ignore %s because of parameter mismatch' % child.name)
                continue
            for a, b in zip(child.namedparams(), dst_child.namedparams()):
                b[1].data = a[1].data
            print('Copy %s' % child.name)

def main():
    ...

    model = L.Classifier(alexnet.FromCaffeAlexnet(1), lossfun=F.mean_squared_err
or)

    original_model = pickle.load(open('alexnet.pkl', 'rb'))
    copy_model(original_model, model.predictor)
    model.compute_accuracy = False

    ...

    optimizer.add_hook(DelGradient(["conv1", "conv2", "conv3", "conv4", "conv5"]))

    ...

– alexnet.py –

class FromCaffeAlexnet(chainer.Chain):
    insize = 128
    def __init__(self, n_out):
        super(FromCaffeAlexnet, self).__init__(
            # conv1=L.Convolution2D(None, 96, 11, stride=2),
            # conv2=L.Convolution2D(None, 256, 5, pad=2),
            # conv3=L.Convolution2D(None, 384, 3, pad=1),
            # conv4=L.Convolution2D(None, 384, 3, pad=1),
            # conv5=L.Convolution2D(None, 256, 3, pad=1),
            # my_fc6=L.Linear(None, 4096),
            # my_fc7=L.Linear(None, 1024),
            # my_fc8=L.Linear(None, n_out),

            # Don't use None when you copy parameters
            conv1=L.Convolution2D(3, 96, 11, stride=2),
            conv2=L.Convolution2D(96, 256, 5, pad=2),
            conv3=L.Convolution2D(256, 384, 3, pad=1),
            conv4=L.Convolution2D(384, 384, 3, pad=1),
            conv5=L.Convolution2D(384, 256, 3, pad=1),
            # my_fc6=L.Linear(None, 4096),
            # my_fc7=L.Linear(None, 1024),
            # my_fc8=L.Linear(None, n_out),
            my_fc6=L.Linear(256 * 7 * 7, 4096),
            my_fc7=L.Linear(4096, 1024),
            my_fc8=L.Linear(1024, n_out),
        )
        self.train = True
 
    def __call__(self, x):
        # for chainer v1.x.x 
        h = F.max_pooling_2d(F.local_response_normalization(
            F.relu(self.conv1(x))), 3, stride=2)
        h = F.max_pooling_2d(F.local_response_normalization(
            F.relu(self.conv2(h))), 3, stride=2)
        h = F.relu(self.conv3(h))
        h = F.relu(self.conv4(h))
        h = F.max_pooling_2d(F.relu(self.conv5(h)), 3, stride=2)
        h = F.dropout(F.relu(self.my_fc6(h)), train=self.train)
        h = F.dropout(F.relu(self.my_fc7(h)), train=self.train)
        h = self.my_fc8(h)

        # for chainer v2.x.x
        # You don't need to use DelGradient hook.

        # with chainer.no_backprop_mode():
        #     h = F.max_pooling_2d(F.local_response_normalization(
        #         F.relu(self.conv1(x))), 3, stride=2)
        #     h = F.max_pooling_2d(F.local_response_normalization(
        #         F.relu(self.conv2(h))), 3, stride=2)
        #     h = F.relu(self.conv3(h))
        #     h = F.relu(self.conv4(h))
        #     h = F.max_pooling_2d(F.relu(self.conv5(h)), 3, stride=2)
        #     with chainer.force_backprop_mode():
        #         h = F.dropout(F.relu(self.my_fc6(h)), train=self.train)
        #         h = F.dropout(F.relu(self.my_fc7(h)), train=self.train)
        #         h = self.my_fc8(h)

        return h

コード全体はこちらにあります。 github.com

FloydHubを使う

FloydHubディープラーニング向けのHerokuという位置付けのPasSサービスです。 Herokuはいつもお世話になっているので(無料プランだけど…)この触れ込みを聞くと試さずにはいられません。。 ( ̄ー ̄)

ということで、使ってみました。

FloydHub とは

  • 簡単にクラウド上で学習が実行できる (実際に簡単だった! ^^)
  • クレジットカードなしでも登録できトライアルとして100時間分GPUが利用できる
  • コストはAWSの約50%で課金は秒単位でリーズナブル

Every one who signups to Floydhub will receive 100 hours of free CPU / GPU time for running your projects

とあるのでもしかしたら CPUとGPUを合わせて100時間かもしれません。 利用時間はダッシュボードで確認できます。

私が好きなChainer含めて主要なフレームワークが使用できます。 http://docs.floydhub.com/home/environments/

Chainerは↓の表の通り世界的に見るとまだまだ普及してないですがそのChainerもサポートされているのは嬉しいです。 TensorFlow以外は基本的に最新版のみのようです。

使えるFramework Github star
TensorFlow 60651
Caffe 18509
Keras 16571
MXNet 10051
Torch 6995
Theano 6453
PyTorch 5459
chainer 2594
Kur 537

使い方

公式ページのGet started 通りやればできます。

ジョブの実行

$ floyd init YourProjectNameでプロジェクトを作成したら $ floyd run "python train.py"のように floyd runに続けて実行するスクリプトを渡します。 必ずしもpythonを使う必要はなく $ floyd run "ls -la ." といったこともできます。

ジョブをrunするたびにディレクトリ全体がアップロードされるので不要なファイルが プロジェクトディレクトリの中にないか気をつける必要があります。 アップロードしたくないファイルは .floydignoreに記述できます。

なので、学習に使う巨大なデータはdataコマンドを使って使い回し可能なデータセットとしてアップロードしておく必要があります。

シェルスクリプトには実行属性がつかないので $ floyd run "./yourtest.sh" はNGです。 $ floyd run "sh ./yourtest.sh"とします。

GPUインスタンス

floyd run に --gpuオプションをつけて実行するとGPUインスタンス、何もつけないとCPUインスタンスです。

TensorFlowは基本的に自動的にGPUを使ってくれますが chainerだと通常、プログラム側の引数にも --gpu で渡してやる必要があります。

$ floyd run --gpu --env chainer "python train.py --gpu 0"

な感じです。TensorFlowがデフォルトとなっていてそれ以外は--envオプションでフレームワークを指定しないとエラーになります。

ログ

プログラムの出力ログは $ floyd logs [-t] <RUN_ID> で参照できます。 ダッシュボードからだと見つかりにくいのでコマンドラインで確認するのが良さそうです。

ダッシュボード上でログを参照する方法

f:id:sanshonoki:20170616214717j:plain f:id:sanshonoki:20170616214738j:plain

Experimentsの各ジョブを開いたときのVIEW LOGボタンではプログラムの出力ログは見れません。。

出力ファイル

プログラム上で /output ディレクトリにファイル出力すると $ floyd output <RUN_ID> で出力ファイルを参照できます。 $ floyd output <DATA_ID> は NG です。

DATA_ID は $ floyd info <RUN_ID> で調べます。

データセット

巨大なデータはデータセットとして一度アップロードすると学習プロジェクトの実行時に再アップロードなしに何度でも参照できます。

手順は

  1. データを置いてあるディレクトリに移動
  2. データプロジェクト作成 $ floyd data init YourDataName
  3. アップロードする $ floyd data upload

あとは $ floyd run --data <DATA_ID> "python train.py"のように --dataオプションで DATA_ID を渡すとプログラムの中で /inputディレクトリとして参照できます。

データセットをブラウザで確認するのは $ floyd data output <DATA_ID>、データセットを削除するのは $ floyd data delete [-y] <DATA_ID> です。

すべてのデータセットの一覧は $ floyd data status で可能です。

また、floyd run した各ジョブの出力結果も DATA_ID を持っており --data <DATA_ID> で 出力ディレクトリを /input ディレクトリとして使えます。

Jupyter Notebook

$ floyd run --mode jupyter とすると Jupyter Notebookも使えます。Jupyter Notebookを立ち上げている間は何もしなくても課金対象となるのでスクリプトを実行するのに比べるとちょっと勿体無い感はありますね。。

注意点としては明示的に stop しないとずっと課金され続けてしまう点です。

使い終わったら $ floyd stop <RUN_ID> を忘れずに !! (あるいは、ダッシュボード上で停止ボタンをクリックする)

公式ページでも

Once you have experimented with your code, you need to manually stop your “job”. Run the stop command for this. Remember Jupyter notebooks are charged for the entire duration they are up, not just when you execute code. So make sure the stop the notebooks when you are no longer working on them.

と注意書きがありますが目立つように書いてないです。 何か勘ぐってしまうのは私だけでしょうか。。

保存したNotebookは /output に出力されますので $ floyd output <RUN_ID>で参照できます。

古いジョブやデータセットの削除

  • ジョブの削除: $ floyd delete [-y] <RUN_ID> <RUN_ID>...
  • データの削除: $ floyd data delete [-y] <DATA_ID> <DATA_ID>...

で削除できますがジョブIDやデータIDを明示的に指定してやる必要があります。 最近になって複数個同時に削除できるようになったみたいですがIDを調べるのがかなり面倒です。

プロジェクト単位で一括で削除するコマンドは今のところないようです。

Priceページ

Floyd stores any output files generated by the project and stored under /output directory at run time. You will be charged for the size of data genarated by your project. Pricing details below.

と書いてあるようにジョブ実行時の出力ファイルも課金対象となってしまうのでプロジェクト単位での一括削除は近いうちに対応されるのではないかと思います。

ジョブやデータを一括で削除する方法

statusコマンドとシェルスクリプトを組み合わせれば一括で削除することが可能です。

  • ジョブの一括削除

    $ floyd status 2>&1 | awk '{print $1}' | tail +3 | xargs floyd delete -y

  • データの一括削除:

    $ floyd data status 2>&1 | awk '{print $1}' | tail +3 | xargs floyd data delete -y

なお、ジョブの一括削除の場合はプロジェクトディレクトリ上で実行する必要があります。

ジョブを削除してもジョブが出力したデータは残り続けるのでこれまた盲点です。

価格比較

AWSとの比較

$1 = 110円 として

インスタンス コスト(1時間) スペック GPU
AWS EC2 g2.2xlarge 98.78円 8コア、メモリ15GB GPUメモリ4GB ストレージ60GB GRID K520
FloydHub G1 47.52円 4コア、メモリ61GB GPUメモリ12GB ストレージ100GB Tesla K80

AWSのEC2 GPUインスタンスを使うのに比べて約50%の料金でスペック差も歴然です。 EC2は時間単位で課金されるので 1秒でも使うと1時間分課金されますが FloydHub は秒単位で課金されるので変なストレスもかかりません。 (´◡`)

自作PCとの比較

自分のPCに積んでいる GeForce GTX 950 と 個人ユースでは高性能にあたる GeForce GTX 1080 の2パターンで試算してみました。

参考記事

電気代 = 28円 / 1kWh として

GPUボード
(推奨システム電力)
コスト(1時間) スペック ボード価格
GeForce GTX 950
(350W)
約9.8円 GPUメモリ2 GB 約1.5 - 2万円
GeForce GTX 1080
(500W)
約14円 GPUメモリ8GB 約7万円

「趣味用に安く深層学習PCを作った」の初期コストは約12万円なので 3580時間以上で 自作PCの総コスト(イニシャル+電気代)< FloydHubのコスト となります。約10-15円/時間と比べるとFloydHubの 47.52円/時間 はそれでもやっぱりまだ高いよなぁ..という印象を持ちますが初期コスト、構築の手間とメンテナンスのことも考えると、十分アリなんじゃないかなと思います。

まとめ

FloydHub、本当に簡単にディープラーニングGPU学習を始められます。 仕事で使うとあっという間に100時間分の無料枠は使いきると思いますがこれからディープラーニング始める人は挫折するかしないかの分かれ目までは無料枠内でいけると思うのでぜひ使ったほうがいいと思います。 (*´д`)o

競走馬のパドック画像の検索

競走馬のパドック画像から距離適性をディープラーニングで予測する」の記事で競走馬のパドック画像をせっかく収集したので パドック画像を検索できるアプリケーションを作ってみました。

こちらです。 (`・ω´・)✧

https://padock-photo-search.herokuapp.com/ f:id:sanshonoki:20170611061145p:plain

シンプルな検索フォームに名前を入れてSearchボタンをクリック!

f:id:sanshonoki:20170611060746p:plain

このように検索ワードにマッチしたパドック画像が表示されます。

並べて眺めることで成長が感じられるかもしれません。いや、感じてください。。

今回のアプリケーションはRails5.1で作りました。話題の新機能、Webpacker + React は使わずお預け状態。。勉強を兼ねて書き直してみたいと思います。(◍•ᴗ•◍)

github.com

UdacityのSirajのCoding challengesのコードの在り処をまとめた

UdacityのDeep learning基礎講座のSirajのCoding challenges 、更なるスキルアップを図るにはうってつけと思いつつも受講中はスルーしてました。。 ( ̄ー ̄;

修了できたのを機にこれからやろうと思い、とりあえずソースコードの在り処をまとめてみました。

github.com

$ git clone --recursive git@github.com:tanakatsu/udacity-dlnd-siraj-coding-challenges-collection.git

でコードをまとめてダウンロードできます。(約1.3Gあります)

あとは、やるだけです (๑•̀д•́๑)

と、言い訳ができないように追い込んでいきます。。

目次はこちら(↓)です。"Runner up" = 準優勝ってことを初めて知りました。

Week1 How to Make a Prediction [video]

Week2 How to Make a Neural Network [video]

Week3 How to Do Sentiment Analysis [video]

Week4 How to Do Mathematics Easily [video]

Week5 How to Make Data Amazing [video]

Week6 How to Make an Image Classifier [video]

Week7 How to Predict Stock Prices Easily [video]

Week8 How to Generate Art [video]

Week9 How to Generate Music [video]

Week10 How to Make a Text Summarizer [video]

Week11 How to Make a Language Translator [video]

Week12 How to Make a Chatbot [video]

Week13 How to Win Slot Machines [video]

Week14 How to Generate Images [video]

Week15 How to Generate Video [video]

Week16 How to Convert Text to Images [video]

Week17 How to Learn from Little Data [video]

Udacity Deep Learning Nanodegree Foundation を修了してみての感想

1月末から始まり約4ヶ月にわたったUdacity Deep Learning Nanodegree Foundation、無事に修了できました。 :-)

f:id:sanshonoki:20170524213626p:plain

ブログ用に講座のトップページをキャプチャしてみたんですが599ドル ?!

Oh My God、200ドルも値段上がってるよー ((( ;゚Д゚)))

費用対効果についてはあとで触れますが599ドルだったら多分受講してなかった(正確に書くと、受講させてもらえなかった)と思います。。

他の方の記事

国内の他の受講者の方が早くも記事を書いていますね。参考になると思います。 講座の内容はこれらのほうが断然詳しく書かれているのでこれらをご参照ください。 今回も他人のふんどしで相撲を取るスタイルです。。(; ̄ー ̄川

私の感想

少し辛口なところもありますが率直な感想。 (`・ω・´)

SirajのYouTube動画

  • イントロ
    • 凝った動画で「AIでこんなことできるのね」と、イントロとして面白い
    • 5-10分で短いので集中して見れる
    • 本編はTensorFlowオンリーだけどここではKerasがよく使われる。。
  • Live session
    • 約1時間のLive coding。時間の都合でスキップしたりエラーを解消できずに放置して結局コピペだったりグダグダ。集中力を保って最後まで見るのは難しい
  • Coding Challenge も取り組めればスキル向上しそう。でもこの講座を受けるレベルの人にはそんな余裕はないかも。。

このYouTube講座は公開されていてUdacityの受講者じゃなくても見れます。 www.youtube.com

修了できたのでCoding Challengeもトライしてみようかなぁ

本編の学習コンテンツ

  • 技術解説
    • アニメーションも使った動画解説は英語の解説記事を読むより敷居が低く効率的なのは確か
    • でも、結局は咀嚼できるまで何度も読み返すプロセスがないと “理解できた気がする” で終わってしまいそう
  • 練習課題
    • プロジェクト課題と違ってミニテスト形式じゃないので最後までコーディングしてみたもののうまく動かずどこで詰まっているか分からないというパターンに苦しむ人は多いかも
    • MatのSolution解説の適当さ感が気になる。「とりあえず説明したから」的な。そう思うのは私だけだろうか…

これもGithubでJupyter Notebookのコンテンツは公開されています。

github.com

通常の練習課題にもテストコード入れて欲しいなと思います。Solutionに手が伸びてしまう人は多いと思う。。

提出課題(プロジェクト)

  • 各所でテストコードがあり、step by stepで確認しつつ進められたのは良かった
  • 参考となる直前の練習課題と同じようにやっていたらハマる箇所が何箇所かあってちょっといじわる
  • レビューは「ok」「Hyperparameterのチューニングの余地あり」「もっとpythonらしいコードの書き方がある」ぐらいしか返ってこなかった。。

練習課題と同じようにやっていたらハマる所はもう少しヒント(「ここは練習課題と違うからしっかり考えてみてね」的な注意書き)があってもいいかなと思います。レビューは気に入らなかったら何回でも出せるみたいです。

運営

  • いつの間にか過去のweekのコンテンツが増えていたりする
  • GithubのコンテンツもWeeklyのコンテンツ公開に合わせたアップデート以外にもバグ修正含めてちょこちょこ更新されている
  • でも、運営側から通知が何もないので基本的に気づかない

これ、絶対に改善してほしいです。><

ユーザーフォーラム

  • 同じ質問があふれている。。
  • たまには自分にとって役に立つ情報もあるが全部目を通すのは辛い…

同じ質問がたくさん出るというのはみんな同じ箇所でスタックしているということで学習コンテンツの改善の余地があると思います。既出の質問かどうかちゃんとチェックするのは大変なので同じ質問が出るのは致し方ないところですね。。

その他(TensorFlow)

  • 前半は tf.Variableを使ってWeightやbiasを明示的に実装するが後半は基本的に高レベルAPI(tf.contrib.layersやtf.layers)が使われる
  • FullConnectedの実装が tf.contrib.layers.fully_connected になって、それがいつの間にか tf.layers.dense になったりで混乱する
  • TFLearnを1回だけ使う。謎。TFLearn を使うぐらいなら Keras を使ってほしい

特に説明がなかった気がするけど基本的には高レベルAPIを使っていけばいいのでしょうか。それにしてもTensorFlowはAPIの変化が激しすぎます。。 ┌┤´д`├┐

まとめ

  • Deep Learningのメインストリームの各種技術のつかみを一通りおさえられる。でも、身に付くというのはまた別の話。。
  • 勉強にはなる。お金をかけてかつ〆切りもあるので怠けず続けられる
  • 費用対効果は疑問。。

高いクオリティの無料の講座もある中、学習コンテンツの現状クオリティを考えると399ドル(当時)はやっぱり高いんじゃないかなー。599ドルになるとなおさら感あります。

受講生のみアクセスできる教材、フォーラムやレビュー(提出課題のフィードバック)に値段相応の価値を感じなかったので高い意志力を持って怠けずに続けられる人は無料公開されているSirajのYouTubeビデオGithubJupyter Notebookでも十分なのかなと思いました。。

無料の講座

人工知能に関する断創録」のブログで紹介されていて知ったfast.aiの講座(Practical Deep Learning For Coders)をやってみようと思ってます

競走馬のパドック画像から距離適性をディープラーニングで予測する

まだまだ改善したいですが競馬の祭典ダービー当日ということでいったん現状をまとめてみます。

やりたいこと

短距離馬と長距離馬は馬体の特徴が違うと言われています。 それをAIで判別できるようにしたい! というのが今回の試みです。

パッと見た瞬間に、胴が短いな、詰まっているなと思ったら、短距離馬の胴体である。反対に、胴が長く、窮屈なところがないなと感じたら、長距離馬の胴体である。 (ガラスの競馬場: 集中連載:「馬を見る天才になる!」第4回 より)

果たして、ウマくいくのか〜?! o(´Д`*)o

短距離馬 長距離馬
http://keibado.cplaza.ne.jp/keibabook/031006/images/pp08.jpg http://keibado.cplaza.ne.jp/keibabook/110516/images/pp09.jpg

(写真は競馬道のPHOTOパドックのサイトから)

学習データの生成

1. パドック画像の収集

入力となるパドック画像は競馬道のサイトからクローラで収集してきます。

2017年1/23時点で のべ頭数 8707頭、ユニーク頭数としては 2528頭分の画像を収集できました。

sanshonoki.hatenablog.com

2. ラベル(距離適性)の収集

次に画像に対してラベル(距離適性)をつけていきます。

Netkeibaの各馬のページの適性レビューのデータを使って距離適性のグラフから数値(0.0-1.0) として取得します。

sanshonoki.hatenablog.com

学習

いよいよ学習です。

CNN で 回帰(Regression)学習します。

  • input: パドック画像 (32 x 32)
  • target: 距離適性の値 (1.0 - 10.0): 0.0-1.0 を10倍にスケール
  • モデル f:id:sanshonoki:20170525051758p:plain
  • 学習最適化アルゴリズム: Adam
  • データセット
    • test: 2016年のGIレース8レース分
    • validation: テストを除く2016年のデータ
    • train: 2016年以外のデータ

いろいろやって現状の最善の学習結果は以下です。 validationエラーが全然下がってきません…。 (´Д`。)

f:id:sanshonoki:20170525054508p:plain:w400

どちらとも言えないのであれば、平均的な馬体の馬なのであろう。必ずしも、体型のみで短距離・長距離を見分けられるわけではなく、どっちつかずの体型の馬の方が圧倒的に多いのが実際である。 (ガラスの競馬場: 集中連載:「馬を見る天才になる!」第4回 より)

とのことなので仕方ないのでしょうか..

やったこと
  • 学習データを増やす
    • flip画像の生成
    • コントラストを少しずつ変えた画像の生成
  • 初期化アルゴリズム で He のアルゴリズム を使う
  • 平均画像(mean.npy)を作成して入力を正規化する

あと、最初はターゲットを0.0-1.0としていましたが性能がよくなかったです。誤差関数が MSE(mean squared error) ということで二乗することにより誤差がより小さく出るので誤差逆伝搬が効果的に働いてなかったのだと思います。

いろいろとお膳立てされていて試行錯誤を必要としないチュートリアルと違ってホント苦しんでいます・・その分勉強になってますが。 validationエラーはあまり下がりませんでしたが素人が予測するよりかはマシな印象はあります。

予測結果の例

以下は2016年のGIレースから6頭分の予測結果です。 みなさんの目による予測はいかがでしょうか。。? (´・с_・`)

画像 DL予測 正解
f:id:sanshonoki:20170525050551j:plain 0.254587 こちら
f:id:sanshonoki:20170525051022j:plain 0.336478 こちら
f:id:sanshonoki:20170525051135j:plain 0.472271 こちら
f:id:sanshonoki:20170525050830j:plain 0.513455 こちら
f:id:sanshonoki:20170525050658j:plain 0.603956 こちら
f:id:sanshonoki:20170525051236j:plain 0.705816 こちら

0.0に近いほど短距離、1.0に近いほど長距離です

改善のために…

果たしてこれでうまくいくのかは分かりませんが改善として今思いつくこと

  • Preprocess処理(特にハンドエンジニアリング)をがんばる
    • 馬体部分だけを抽出した画像で学習するとか..
  • 入力画像サイズを大きくして(64x64 etc..)転移学習をやってみる

引き続きやっていこうと思います。

デモサイト

Heroku上で試せるようにしました。 興味ある方は遊んでみてください。。

https://padock-photo-classify.herokuapp.com/

また今回使ったコードは以下にあります。

(学習) github.com

(Webアプリ) github.com

参考記事

性能改善のために参考になった記事です。 ブログ記事にも書いたAndrew Ng先生のNIPS2016での講義ももちろん参考になりました。(●´Д`●)

Nuts and Bolts of Applying Deep Learning

CS231の講義の関連動画を辿ってAndrew Ng先生がNIPS2016で行った講演「Nuts and Bolts of Applying Deep Learning」を見つけました。これがすごく面白い内容だったので超主観的にポイントをまとめてみます。

講演のビデオやスライド、Web上で見つけたブログは以下にあります。

スライドと1番目のBlog記事がよくまとまっているのでそれを読めば十分な気はします。が、自分の勉強としてまとめていきます。。

Major Deep Learning Trends [2:50]

  • データが少ないときは ハンドエンジニアリングで性能の差がつく
  • ビッグデータ時代は データ量で性能が決まる

To hit the top margin, you need a huge amount of data and a large NN model.

定式化: データ量 x 大きなモデル = 性能

End-to-end Deep learning [13:57]

  • End-to-endアプローチは 莫大なデータがある場合においてうまくいく
  • 例えば、自動運転では精度を満たすだけのデータが足りずまだ難しい
  • そういうケースではハンドエンジニアリングは依然として重要である

End-to-end works only when you have enough (x,y) data to learn function of needed level of complexity.

データ量万歳\(^o^)/です。

Bias-Variance tradeoff [27:04]

  • どんな状況であれ やるべきことは Bigger modelMore data
  • Bias と Variance を同時に改善できる(トレードオフの関係にならなくなる)
  • New model architectureの検討はハードル高いし大抵の問題はそこまで必要ない

基本的なワークフロー f:id:sanshonoki:20170520204228p:plain

Data synthesis (Data augmentation)[31:46]

  • データ合成はデータ量を増やせて性能改善にとても有効(More data)
    • ex. ノイズのない音声にノイズをのせるetc..
  • とはいえ、あくまでも人工的なデータなので学習にあまり効かない場合もある

Mismatched train/test (new bias/variance) [38:53]

学習用データの環境とアプリケーションの環境が異なるケースが増えてきている

学習環境とアプリケーション環境のデータセットの違いも考慮したワークフロー f:id:sanshonoki:20170520204239p:plain

Human-level performance [51:26]

  • 人間の認識精度をリファレンスとすべき
  • リファレンスと学習結果の差を見て 何にフォーカスするか判断できる

    human-levelエラーとtrainエラーの差が大 trainエラーとdevエラーの差が大
    Biasにフォーカス Varianceにフォーカス

What can AI/DL do ? [1:06:33]

AIアプリケーションとして有望なもの

  • 人間が1秒以内でできるタスク
  • 繰り返しシーケンスの次の結果の予測
    • ex. ユーザーが次の広告をクリックするかどうか

Personal advice (building ML career) [1:10:32]

  • 論文をたくさん読んで結果を再現する
  • Dirty work(データ収集、前処理、パラメータチューニング、デバッグ、データベースの最適化 etc…)をしよう

Dirty workは進捗がなかなか実感できないので勇気づけられます。「No magic」ということなので愚直にやりましょうということですね。。