スマートビームレーザーがすごい

超小型レーザープロジェクター Smart BEAM Laser を買いました。

f:id:sanshonoki:20171105074603j:plain

これがめっちゃよい

焦点調整不要! 電源ケーブル不要! 無線接続で投影可能! ƪ(•◡•ƪ)"

画質はそこそこですが他の方のレビューでもかなり評判高いです。

無線接続の仕方

公式のFAQページでも書いてありますが iOSAndroid で 使用モードが異なる ので注意! (モード切り替えは電源ボタンを2回連続押し)

少しハマってしまいました。。

分かりにくいですね..

iOS (iPad)
  • Media Sharing モード にする
  • WiFi は SMARTBEAMLASER-○○ につないでおく
  • 起動したアプリから AirPlay する
Android
  • Media Mirroring モード にする
  • WiFiはONにする (無線機器につながっていなくてもok)
  • Miracast する (使い方は機種によって大分違う。マニュアルを見るべし)
  • 4GやWiFiつないだ状態でも使える

Android だと 4GやWiFiつないだ状態でもミラーリングができるので YouTubeなどオンラインコンテンツも投影できます。Google Play等で購入したビデオは再生するのにインターネット接続が必要なのでこれはありがたいです ( •ᴗ•)

台形補正

アプリストアから「SmartBeamLaser」アプリをインストールすると台形補正ができます。 これで上下方向±15°くらいは補正できます。補正できるレンジは大きくなく、斜めの投影は補正できないのでおまけの域は出ませんがないよりはいいかなと思います。

f:id:sanshonoki:20171102215322p:plain:w250

アプリは Media Sharingモードじゃないと本体に繋がらないので Android のときは切り替えが必要でやや面倒です。

あと、メニュー画面を見ても分かるようにアプリがあると音量もコントロールできます。

Amazonの星の評価...

今回、Amazonで買ったのですがショップから評価依頼が来てビックリ。

一般的なAmazonの評価基準につきましては、 良いと思われる場合は、星5つ 普通と思われる場合は、星4つ 悪いと感じられた場合は、星3つ 非常に悪い場合には、星2もしくは1が、一般的なAmazonでの評価と判断されます。

え、普通が 4 なんですか?!

マジ?

星3つが普通だと思っているたくさんの人がいてここに星4つを普通だとする人が混ざってくると、みんなが「普通」だと思っているものが 3.0ではなく 3.0 + α と評価がかさ上げされてしまう... ╰(゚x゚​)╯

Amazon、恐ろしい。。

fast.aiでTensorFlowをバックエンドとして使う

fast.ai はKerasのバックエンドとしてTheanoを前提としていますがTensorFlowも使うことができます。

~/.keras/keras.json

{
    "image_dim_ordering": "tf",
    "epsilon": 1e-07,
    "floatx": "float32",
    "backend": "tensorflow"
}

とすればTensorFlowになり、特にエラーもなくJupyter Notebookを実行できます。

が、

これだけではうまくいきません。。

なぜか、学習したときの精度が低くなっちゃう.. ><

Lesson1のThe punchline: state of the art custom model in 7 lines of codeを実行すると、

Theano TensorFlow
val_acc: 0.9825 val_acc: 0.9190

となります。。

原因

VGG16モデルの転移学習用の重みデータ vgg16.h5vgg16_bn.h5 が Theano用になっているからです。

KerasのWikiページ Converting convolution kernels from Theano to TensorFlow and vice versa · fchollet/keras Wiki · GitHub に重みファイルの変換の方法が記載されていますが

どうやらKeras v2用のコードのようでfast.aiで使用するKeras v1だとエラーになります。

Keras v1での重み変換

ここのコードを参考にしてうまく変換できました。:-)

実装したものはこちら gist.github.com

このコードで作った vgg16.tf.h5vgg16.h5 の代わりに使うようにして TensorFlow バックエンドで学習を実行すると、

Theano TensorFlow(重み変換前) TensorFlow(重み変換後)
val_acc: 0.9825 val_acc: 0.9190 val_acc: 0.9840

期待通りの精度が出るようになりました。( •ᴗ•)

utils.py の修正とバックエンド切り替えスクリプト

このままでは Theano と TensorFlow のバックエンドを頻繁に変えたいときに面倒なので utils.py を少し修正します。

== 46行目あたり ==

if K.backend() == 'theano':
    import theano
    from theano import shared, tensor as T
    from theano.tensor.nnet import conv2d, nnet
    from theano.tensor.signal import pool

のように修正し、バックエンドが Theano のときだけ Theanoのライブラリを読み込むようにします。

さらに、次のようなシェルスクリプトを作ると

switch_keras_backend.sh

#!/bin/bash

if [ $# -ne 1 ]; then
  echo "ERROR: Wrong arguments"
  echo "Usage: switch_keras_backend.sh th|tf"
  exit 1
fi

if [ $1 != 'th' ] && [ $1 != 'tf' ]; then
  echo "ERROR: Wrong parameter. Parameter must be 'th' or 'tf'."
  echo "Usage: switch_keras_backend.sh th|tf"
  exit 1
fi

backend=$1
if [ $backend = 'th' ]; then
  echo "Use theano as backend"
elif [ $backend = 'tf' ]; then
  echo "Use tensorflow as backend"
fi

cd ~/.keras
ln -nsf keras.$backend.json keras.json

cd ~/.keras/models
ln -nsf vgg16.$backend.h5 vgg16.h5
ln -nsf vgg16_bn.$backend.h5 vgg16_bn.h5

echo 'Done!'

重みファイルも簡単に切り替えができます。

vgg16.h5keras.json はそれぞれのバックエンド毎に用意したファイルへのシンボリックリンクにしておく必要あり)

Theanoのインストール

fast.ai の Practical Deep Learning For Coders, Part 1 は Keras v1(バックエンドはTheano)を使用します。

なので、早速 Ubuntu14.04に Theanoをインストールしていきます。

インストール

公式ページ に書いてある通り、以下の1行で楽々インストール

$ conda install theano pygpu

.theanorcに手こずった..

しかし、やはりというか設定関係で少し苦労..。

ubuntu14.04 theanoとかgpgpu環境構築メモ - 備忘録とか日常とか を参考にして

cnmen = True としたら ValueError: could not convert string to float: True というエラーが出た。。

どうやら cnmem = True とするのはTheano 0.7までのときの設定のようで Theano 0.8からは cnmem = 1 のように float型の値を入れるらしい。

https://github.com/Theano/Theano/issues/4170

ところが、cnmem = 1 としたら先のエラーは解消したもののconvolutionを使った学習実行時に

RuntimeError: ('The following error happened while compiling the node', GpuDnnSoftmax{tensor_format='bc01', mode='channel', algo='log'}(GpuContiguous.0), '\n', 'could not create cuDNN handle: CUDNN_STATUS_INTERNAL_ERROR')

という新たなエラーが...! (。ŏ﹏ŏ)

いろいろ試してみると cnmem = 0〜0.9 までだったら問題ないことが分かりました。

公式ページの http://deeplearning.net/software/theano_versions/0.9.X/library/config.html#config.config.lib.cnmem を読む限りは

cnmem = 1cnmem = 0.95 でも設定としては大丈夫そうなのだが..

CNMeM

cnmem = 0 (CNMeM is disabled)がよいのか? cnmem = 0.9 (CNMeM is enabled with initial size: 90.0% of memory)がよいのか?

公式ページによると

Controls the use of CNMeM (a faster CUDA memory allocator). Applies to the old GPU backend CUDA backend up to Theano release 0.8.

This could cause memory fragmentation. So if you have a memory error while using CNMeM, try to allocate more memory at the start or disable it. If you try this, report your result on :reftheano-dev.

とあり、

  • CNMeMを使うとCUDAのメモリ割り当てが早くなる
  • ただし、メモリの断片化を引き起こす可能性がある

とのことです。

自分の環境(GeForce GTX 950)でLesson1の「The punchline: state of the art custom model in 7 lines of code」 のコードを使って比較してみたところ

cnmem = 0 (CNMeM disabled) cnmem = 0.9 (CNMeM enabled)
約806s / epoch 約785s / epoch

と 実際に CNMeMを有効にしたほうが速くなりました。:-)

Making Theano Faster with CuDNN and CNMeM on Windows 10 – Ankivil の記事でも CNMeM + CuDNN が速いという報告になっています。(赤い実線に注目)

https://ankivil.com/wp-content/uploads/2016/08/cuddn_cnmem_combined_graph_vgg16.png (図は https://ankivil.com/wp-content/uploads/2016/08/cuddn_cnmem_combined_graph_vgg16.png から引用)

AWSのGPUインスタンスを立てる

ちょっと前からfast.ai が提供しているディープラーニング学習のオンラインコースPractical Deep Learning For Coders, Part 1を始めています。

このコースはためになり面白い! ƪ(•◡•ƪ)"

(Udacityのコースより全然よい。しかも無料)

しかし、

自宅マシンのスペックが非力(メインメモリ8GB、GPUメモリ2GB)なため課題の途中でMEMORY ERRORの嵐。。(◞‸◟)

FloydHubはプロモーション期間が10/1で終了してしまいGPU無料枠を失ってしまいましたが UdacityのDeep Learning Nanodegreeを受講したときの特典、AWS100ドルクーポンがまだ残ってる!

ということで、AWSGPUインスタンスを立ち上げます。

fast.aiのgithubのレポジトリにはAWSのセットアップスクリプトが含まれています。若干の改造が必要でしたが超簡単にGPUインスタンスが使えるようになりました。:-)

なお、fast.aiのコースの内容についてはこのブログが大変参考になります。

futurismo.biz

スクリプトの改修点

setup_p2.sh

EC2インスタンスを作成するスクリプトです。そのまま実行すると、

Only us-west-2 (Oregon), eu-west-1 (Ireland), and us-east-1 (Virginia) are currently supported

というエラーが出ちゃいました。。

なので、強制的にOregonリージョンを使うようにしました。

# get the correct ami
#export region=$(aws configure get region)
export region='us-west-2'
export AWS_DEFAULT_REGION='us-west-2'

これでものの数分でP2インスタンスが起動できちゃいます。

P2インスタンスのコスト(2017/10/6時点)

P2インスタンスは東京リージョンでも使えるようになったのでスクリプトを修正すれば東京リージョンでも起動できると思いますが以下のようにコストが1.7倍するので学習用である限りは東京リージョンで使うのをやめたほうがいいと思います

リージョン P2インスタンスのコスト
米国東部 バージニア北部(us-east-1) $0.9
米国東部 オハイオ(us-east-2) $0.9
米国西部 オレゴン(us-west-2) $0.9
欧州アイルランド(eu-west-1) $0.972
欧州フランクフルト(eu-central-1) $1.362
アジアパシフィック ソウル(ap-northeast-2) $1.465
アジアパシフィック 東京(ap-northeast-1) $1.542
アジアパシフィック シドニー(ap-southeast-2) $1.542
アジアパシフィック シンガポール(ap-southeast-1) $1.718
アジアパシフィック ムンバイ(ap-south-1) $1.542
aws-alias.sh

setup_p2.shを実行すると fast-ai-commands.txtというファイルが作成され、その中で設定すべき環境変数が記述されているのですが.bash_profileなどに書き写すのに少し抵抗があり、これを自動的に設定するようにしました。

以下のコードを先頭に足します。

export AWS_DEFAULT_REGION='us-west-2' 
eval $(grep -e "^export" fast-ai-commands.txt) 

これでaws-alias.shを実行したとき、必要な環境変数が自動的に設定されてます。

ただ、このままでは aws-alias.shで定義されている aws-ssh 等がなぜかうまく動きません...

$instanceIpx.x.x.xのようになっているべきところ、"x.x.x.x"とダブルクォーテーションがついてしまっているのが原因でした。

なので、これを取り除くように修正します。( | sed -e "s/\"//g"を追加します)

# Original
#alias aws-start='aws ec2 start-instances --instance-ids $instanceId && aws ec2
wait instance-running --instance-ids $instanceId && export instanceIp=`aws ec2 d
escribe-instances --filters "Name=instance-id,Values=$instanceId" --query "Reser
vations[0].Instances[0].PublicIpAddress"` && echo $instanceIp'

# 修正版
alias aws-start='aws ec2 start-instances --instance-ids $instanceId && aws ec2 w
ait instance-running --instance-ids $instanceId && export instanceIp=`aws ec2 de
scribe-instances --filters "Name=instance-id,Values=$instanceId" --query "Reserv
ations[0].Instances[0].PublicIpAddress" | sed -e "s/\"//g"` && echo $instanceIp'

# Original
#alias aws-ip='export instanceIp=`aws ec2 describe-instances --filters "Name=ins
tance-id,Values=$instanceId" --query "Reservations[0].Instances[0].PublicIpAddre
ss"` && echo $instanceIp'

# 修正版
alias aws-ip='export instanceIp=`aws ec2 describe-instances --filters "Name=inst
ance-id,Values=$instanceId" --query "Reservations[0].Instances[0].PublicIpAddres
s" | sed -e "s/\"//g"` && echo $instanceIp'

これで、一度 source aws-alias.sh したあと、aws-startaws-sshaws-stopのコマンドで楽にEC2インスタンスの制御とsshログインができます。

簡単にP2インスタンスが起動できちゃうので使いすぎに注意ですね。。

Jupyter Notebook起動時に求められるパスワードは Lesson1のNote に記載されており、 dl_course です。

Slackメッセージの感情分析

前の記事に引き続き、Slackメッセージの感情分析を行います。

メッセージの収集

まずはSlackメッセージの収集からです。

Slackチャネル上に飛び交うメッセージをログングするボットを作成し、対象チャネルにinviteしてメッセージを収集します。

ボットはhubotで作りました。 ロギングするスクリプトは以下で、これを scripts/ディレクトリに配置しておきます。

=== logger.coffee ===

LOG_DIR = 'log'

fs = require('fs')
util = require('util')
dateFormat = require('dateformat')

unless fs.existsSync(LOG_DIR)
  fs.mkdirSync(LOG_DIR)

module.exports = (robot) ->
  robot.hear /.*/, (res) ->
    msg = res.match
    today = dateFormat(new Date(), "yyyymmdd")
    logfile = util.format('%s/%s.txt', LOG_DIR, today)

    fs.appendFile logfile, msg + "\n", 'utf8', (err) ->
      if err
        console.log(err)

このボットはメッセージをひっそりせっせとログファイルに保存し続けます。:-)

データセットの作成

続いて、ここが最も肝心なデータセットの構築です。

入力メッセージと出力ラベルの組をどうするか?

例えば、次のようにメッセージとリアクションのやり取りがあったとします。

f:id:sanshonoki:20171007074317j:plain

ログファイルには次のように記録されます。

ありがとうございます:angel: :smile:
:+1:

コメントに対するリアクション③(:tada:)は残念ながら記録されません.. ><

また、このボットが複数チャネルでメッセージ収集している場合、

ありがとうございます:angel: :smile:
何か他の話題のメッセージ
:+1:

と①と④の間に他の話題のメッセージが混入してくる場合があります。

なので、①と④を入力メッセージと出力ラベルの組とすることは難しく、①と②を入力とラベルの組としました。

データ数を増やすという意味では (①, ③)や(①, ④)の組み合わせも含めたかったです...

基本的なアルゴリズム
  1. メッセージ部分とラベル部分に分ける

    リアクション文字は :pray::sob:のように :何とか: というようになっています。 正規表現 ':[a-z\+]{1}[\w_-]+:' でこのパターンを取り出します。

    'ありがとうございます'[:angel:, :smile:] に分割します。

    正規表現のテストは https://regex101.com/ が役に立ちました。 f:id:sanshonoki:20171008070639p:plain

  2. MeCabでメッセージ部分を単語に分割する

    'ありがとうございます'['ありがとう', 'ござい', 'ます'] と3つの単語に分割されます

  3. メッセージとラベルの組を作る

    • メッセージ部分: ['ありがとう', 'ござい', 'ます']
    • ラベル部分: [:angel:, :smile:]

    から

    • ['ありがとう', 'ござい', 'ます'] -> :angel:
    • ['ありがとう', 'ござい', 'ます'] -> :smile:

    という2つの組を作ります

ラベルのマージ

最初は メッセージ -> リアクション という予測ができると面白いなと考えていましたが ラベルの数のばらつきが大きいため難しそうでした。

【理由】ラベル数の分布に大きな偏りがあると最も数が多いラベルのみを出力するようにネットワークが学習されてしまいます

なので、ラベルをマージして positive か negative かの2種類になるようにしました。

変換テーブルの例
元ラベル 変換後
f:id:sanshonoki:20171007083023p:plain:w20:smile: positive
f:id:sanshonoki:20171007083804p:plain:w20:tada: positive
f:id:sanshonoki:20171007083835p:plain:w20:sob: negative
f:id:sanshonoki:20171007084420p:plain:w20:scream: negative
f:id:sanshonoki:20171007084454p:plain:w20:bow: (なし)

f:id:sanshonoki:20171007084454p:plain:w40 は "ごめんなさい"(negative)の意味で使われること以外にも "よろしくお願いします" の意味でも使われるので マッピングなし としました。(このようなリアクションは:bow:以外にもいくつかあります)

マージすることで入力とラベルが同じになった組はさらに1つにまとめます。

学習

サンプル数

46449メッセージから学習用と検証用のデータセットを構築しました。

学習データセット(90%) 検証用データセット(10%)
1048 117

それぞれ positive と negative が 半々ずつ含まれています。

positive、negativeに分類できるリアクション絵文字を含む文 となるとかなり少なくなってしまいました。。⊂(¯×¯٥)⊃

パラメータ

前回やったときとパラメータを少し変えましたがそれ以外は同じです。

  • seq_len: 30
  • hidden_unit: 300

結果

5〜10 epoch学習して 65〜70%の正解率となりました。(データ数が少ないせいかばらつき大きいです) 同じラベルを常に出力する あるいは完全にランダムに出力する と 約50% になるはずなので一応は感情を捉えていると思います。

あらためて入力データを眺めてみると、人間が見ても positive なのか negative なのか 分からない入力もたくさんありました..。

判断が難しいメッセージの例
入力メッセージ 正解ラベル
冬 よ 来い positive
経由 で negative
待機 positive
つくら ね ば negative

今回はデータ数が少なくて精度がイマイチというのもありますがSlackコミュニケーションの特性上、文脈が複数に分かれたメッセージとしてやり取りされるのでこのまま単純にデータ数を増やすだけでは精度は改善しないかもしれません。。

しかし、、データ集めるの難しいですね。。

判定ボット

せっかくなのでボットで判定できるようにしました ƪ(•◡•ƪ)"

f:id:sanshonoki:20171008063956j:plain

FloydHubのトライアルで使えるGPU利用枠が激減..

なんと、

FloydHubのトライアルで使えるGPU時間が10/1から大幅に減ってしまったようです... ><

100時間 → 2時間 (◞‸◟)

Our promotional period, during which we offered 100 hours free GPU has already ended. If you signed up during that period, you should still have the credits. The old plans are valid till Oct 1st 2017

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

まだ残り時間あったよな? と、

新しいGPUジョブを動かしてみると、

Error: You do not have enough credits to run this job. Please upgrade your plan or buy a powerup to continue running jobs

無情なエラー(T-T)

100時間のうち80時間ぐらいは残っていたはずなのに0になっていました..。

公式ページによると今のFreeプランは

  • first 2 GPU hours (トライアル特典)
  • 400 GB storage
  • 20 CPU hours / mo.

のようです。

CPUしか動かせないんだったら 自分のMac でいいのではないでしょうか。。

ちなみに、有料プラン は

  • 最初の10時間は 約154円/時間(基本)
  • それ以降は 約77円/時間 (追加分)

のようです。

RNNで感情分析(Sentiment Analysis)

RNN(Recurrent Neural Network)を使って感情分析(Sentiment Analysis)をしてみます。 今回はchainerを使って実装していく中で理解に苦労した点を図にまとめます。

RNNとは何なのか? RNNの基本、については以下の記事が分かりやすいです。

ちなみに、3つ目の記事は おなじみのMNIST文字認識が CNN ではなくRNN で実装されていて興味深かったです。

Sentiment Analysis

感情分析を理解および実装していく上でポイントになるのは以下の2点です。

  1. CNN(画像分類)との違い
  2. ロスの計算

CNN(画像分類)との違い

CNNで画像分類をするときは次のようにバッチサイズ分のデータを1回で入力します。

f:id:sanshonoki:20170929051417p:plain:w450

一方、感情認識では 時間方向の依存関係、つまり文脈を学習するので時間方向に入力していく必要があります。

f:id:sanshonoki:20171002202054p:plain

つまり、バッチサイズ分の文章を取り出し、それを位置ごとに切って文章の長さの回数分ネットワークに入力します。

ちなみに、このように時間方向に展開していくことを unroll や unfold と言うようです。

ロスの計算

感情分析の場合、文章(単語列)に対して1つのラベルがつくので最後の単語の出力(y_n)に対してのみ教師ラベルとのロスを計算します。それ以外の出力は無視します。

f:id:sanshonoki:20171002215028p:plain:w400

chainer のコードだと

if j <  seqlen - 1:
    model.predictor(x)
else:
    accum_loss = model(x, t)
    accum_loss.backward()

あるいは

loss = model(x, t)
if j == seqlen - 1:
    accum_loss += loss 
    accum_loss.backward()

といった形になります。

また、TensorFlowの場合は時間方向にまとめて展開できる dynamic_rnnモジュールを使って以下のように表現できます。 (Embed、LSTM層とFC層が別々になっています。)

f:id:sanshonoki:20171002215824p:plain:w400

感情分析のチートシート的なまとめ図

記憶の定着のためにまとめてみました。( •ᴗ•)

f:id:sanshonoki:20171002215211p:plain

その他のRNN応用でのロスの計算

感情分析以外もRNNの応用はいくつかあり

  • 文章生成
  • 機械翻訳(seq2seq)
  • 画像のキャプショニング

などがあります。

f:id:sanshonoki:20171003210723p:plain:w400

(画像は http://karpathy.github.io/2015/05/21/rnn-effectiveness/ から)

入力と教師ラベルの与え方でよく混乱するのでこれも図にしました。

ポイントは

  • 推論(予測)フェーズと違って出力はロスの計算以外に使わない(推論フェーズでは出力が次の入力になる)
  • 入力(x)を1つずらして教師ラベル(t)を作る

かなと思います。

文章生成(character-level language model)

f:id:sanshonoki:20171002215425p:plain:w350

機械翻訳(seq2seq)

f:id:sanshonoki:20171003211707p:plain

<GO><EOS> は文章の始まりと終わりを表す記号です。

機械翻訳では入力単語列を反転して入れると結果が良いらしいです。 順番に入れると1番目の単語を予測する上で大事な最初の入力単語の情報がデコーダの時点でより失われるのだと勝手に理解しています。

chainerでの実装

データセット

UdacityのDeepLearning nanodegree講座のSentiment-RNNの課題で使ったデータセットを使います。

映画レビューの英語テキストを positive, negative の2クラスに分類するという課題で実装が正しければ数epochの学習の後、おおよそ80%程度の精度が出るはずです。

コード

chainerの昔のptbのサンプルを参考にしつつ実装してみました。

(trainerを使っても実装しようとしましたがなんかうまく動いてくれません。。><)

github.com

動かしたところ80%の精度が出たのでアルゴリズムは大丈夫のようです。ƪ(•◡•ƪ)"

次はこれを使って Slackのメッセージを分類してみようと思います。