React.jsでBGMつきスライドショーを作る

React.jsの練習としてスライドショーアプリを作ってみました。

React Slideshow Sample f:id:sanshonoki:20170801050429p:plain

単なるスライドショーでは面白くないのでBGMをYouTubeから検索して流せるようにしました。

検索ボックスに曲名などのキーワードを入れてボタンをクリックすればYouTubeから検索してそれを自動再生するようにしてあります。お気に入りである AIさんの Story をテキストボックスの初期値に入れています。:-)

他にも

あたり今回のサンプル写真のテーマ(子どもの成長)にハマるかなと思っています。 他にもハマる曲があったらぜひ教えてください

コードはこちらにあり、public/images/slick/ 以下に好きな写真を置けばその写真を使ってスライドショーができるので興味ある方はご覧ください

github.com

BGM検索&再生

やっていることはシンプルです。

  1. YouTubeでキーワード検索する (GET https://www.youtube.com/results?search_query=...
  2. 検索結果のhtmlから曲のId(data-context-item-id)とタイトルを抜き出す
  3. iframeで表示する(https://www.youtube.com/embed/{曲Id}?autoplay=1

React.js側からYouTubeページを取得しようとすると No 'Access-Control-Allow-Origin' header is present on the requested resourceエラーが出てアクセスできないのでYouTube検索はRails側でやっています。

スライドショー

スライドショー機能は React対応した react-slick があったのでそれを使いましたが表示で少し苦戦したのでメモ

写真が真ん中に表示されず左寄せになってしまう

以下の設定をCSSに加えることにより中央に表示されるようになりました。 これで正しいやり方なのかはよく分かりません..

  div.slick-initialized div.slick-slide {
    display: flex;
    justify-content: center;
  }
左右のArrowが表示されない

slick-theme.cssでなぜか color: transparent になってました。 なのでCSSを上書きすると表示されるようになりました。

  .slick-next:before, .slick-prev:before {
    color: gray;
  }

会議と打ち合わせ

最近、「博報堂のすごい打ち合わせ」という本を読みました。

f:id:sanshonoki:20170714041701j:plain https://www.amazon.co.jp/dp/4797391340

「5割雑談でも最高の結論を導き出す博報堂の打ち合わせ術」というそそる内容で始まります。。

面白い内容で通勤の帰りの電車の中で一気に読破してしまったのですがその中でもああ、そうかと思って頭に一番残ったのが「会議」と「打ち合わせ」は違うというものです。

  • 会議: 情報の共有(報告、連絡、相談)のために行われる
  • 打ち合わせ: 考えやアイデアを出し合い、積み上げていくために行われる

自分は「会議」よりも「打ち合わせ」していきたいです。

そして、それを頭に焼き付けるために今回、それぞれに別名(ラベル)をつけてみました。

  • 会議とは・・・「予定調和」である
  • 打ち合わせとは・・・「真剣勝負」である

これを見ると「打ち合わせ」したくなりませんか?w

さぁ、真剣勝負しましょう♪

これから必要なABCDE!

ABCDE! って何よ?!

って思われたと思いますが

イノベーションを生むのに必要になってくると思われる資質や要素を昔考えてみたことがあって今日はそれというかその覚え方を紹介してみます。

A ・・・ Analysis、Abduction、AI

B ・・・ Business domain knowledge、Business Intelligence

C ・・・ Coding、Communication、Collaboration

D ・・・ Design、Design thinking

E ・・・ Engineering、Experience、English

!・・・ Inspiration


いっぱいありますね。。(;^_^A

この中で Abduction は 耳慣れない単語だと思いますが 仮説推論 という意味です。つまり、仮説を生み出す力です。

アブダクション - Wikipedia

結果や結論を説明するための仮説を形成することを言うこともある。哲学やコンピュータの分野でも定義づけされた言葉として使われている。アブダクションの意味や思考法は、演繹法帰納法ともまた異なるものであり、失敗の原因を探ったり、計画を立案したり、暗黙的な仮説を形成したりすることにも応用できる。例えば、プログラムの論理的な誤りを探し出し直すという過程では、アブダクティヴな解釈と推論が行われており、一般的な立証論理の手法と通じるものがある

AI は当時は入れてなかったのですが今回のブログで書くにあたって足しました。 あと、Communication・Collaboration や English もやはり大事… ということでリストに追加しました。 身につけるべきことがどんどん増えていきますね。。大変な世の中です。

Experience は資質ではないですが新しいアイディア、概念を生むにはいろいろな経験をすることが必要だという意味で入っています。

自分もあらためてこれらのスキル、要素を意識していこうと思います。┗(  ̄◇ ̄)

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

果たして転移学習で改善するのか?! やってみました! ((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はいつもお世話になっているので(無料プランだけど…)この触れ込みを聞くと試さずにはいられません。。 ( ̄ー ̄)

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

(注)2017/10/1からトライアルのGPU利用時間が100->2時間に変わったようです

Is my free plan changed to 2 hours free GPU according to new free plan?

CLIの使い方もそこそこ変わっているようです。(これはいい意味で)

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]