Goalist Developers Blog

kerasのモデルをデプロイする手順

こんちは、ゴーリストのチナパです!

機械学習を触ったことがあるみなさんはkerasをご存知かと思います。初級から上級の方にもとても使いやすい、フレキシブルなライブラリです。学習の時に

model.save(file_name) #.h5ファイルで保存
model = keras.models.load_model(file_name) #ファイルを読み込み、モデルをロードする

のようにに楽に保存したり、保存されたファイルをロードしたりできます。

ただし、このまま本番環境に持って行こうとしたら非常に勿体無いです。なぜなら、この保存のやり方は学習中のモデルのためですので、本番環境にはいらない情報がたくさんあります。

これから、本番環境用のTenserflowのSavedModelをつくる方法を説明いたします。今回はtensorflow 1.13でやっています、tensorflow 2.0でまたちょっと変わりますので、気をつけてください。

f:id:c-pattamada:20190514144554j:plain
要注意:本番環境に写す前に、あなたのAIは人類の敗北を望んでいないことを確認しましょう: Photo by Matan Segev from Pexels

下準備

まずは、今回は学習ではないなく、本番のつもりですをkerasくんに教えましょう。(色々心の準備あるからでしょう)

import keras.backend as K
import tensorflow as tf
from keras.models import load_model

K.set_learning_phase(0) # 「もう学習していませんよ!」をkerasで書くとこう

これで、Dropoutレイヤーとレギュラライザーなどの、学習専用の物が無視されます。

すでにmodel.save()で保存されたモデルがありましたらそのファイルのパスを用意しまて、モデルをロードしましょう(ない方はしたの部分を見てください)

file_name = ... #あなたのモデルのパス
model = load_model(file_name)

そのようなファイルはすでに持っていない方は以下のようにモデルを作成してましたら、チュートリアルをやってみれます。
from keras.applications.resnet50 import ResNet50
model = ResNet50(weights='imagenet')

では、準備できてますね。

Signature作成

input_sig = 'my_input'
output_sig = 'predictions'
signature = predict_signature_def(inputs={input_sig: new_model.input}, outputs={output_sig: new_model.output})

簡単そうに見えますが、一体何をしているでしょう?

本番環境のSavedModelにはグラフのどのノードがinputとして使ったほうが良いのか、結果を見る時にはどこにみれば良いのかを定義するためのオブジェクトです。 今回は⓵predict_signature_def()を使っていますが、他に ⓶classification_signature_def()と⓷regression_signature_def()もあります。 違いは何なのかというと、inputのアウトプットの形に応じて使い分けられる物です。ここで定義したinput_sigとoutput_sigは後でまた使います。

(ちなみに、⓶はクラッシフィケーション専用のメソッドです、例えば、画像に対してのラベルを出したい場合などには、このメソッドで文字列のラベルまで出してくれたり便利な部分があります。⓷は同じようにregression系の機械学習に便利です。⓵の方は割と応用時なので、こちらに使います)。

モデル保存

まずは、export_path、つまり保存先のディレクトリ名を定義します。これはすでに存在しましたらエラーが発生するので、最後のフォルダーがまだ存在しないようにします(ここでは”1”となってます)。

export_path = /path/to/model/1/  #例のパスです、最後のフォルダがまだ存在しないフォルダー名(例えば、モデルのバージョン番号)を使ってください
signature_name = 'predict_output' # こちらもまた後で使います。
with K.get_session() as sess:
    builder = tf.saved_model.builder.SavedModelBuilder(export_path)
    builder.add_meta_graph_and_variables(
        sess, [tf.saved_model.tag_constants.SERVING],
        signature_def_map={signature_name: signature},
        main_op=tf.tables_initializer())
    builder.save()

そして、tensorflowのSavedModelBuilderを使って保存します。 本番環境のつもりなのでtf.saved_model.tag_constants.SERVINGのフラグを設定します。

先ほど作成したsignatureがsignature_def_map={'predict_output': signature}このように設定します。こちらの'predict_output’の文字列は自由です。

実行しましたら

f:id:c-pattamada:20190514143120p:plain
SavedModelの保存形式
のように保存されるはずです。

イエイ!これで保存できました!

グーグルのAI platformを使いたい場合にもこのような保存形式であれば大丈夫です。

モデルをロードする

さて、モデルを保存しましたが、この形式をどうやって使えるでしょうか? 自作のFlaskサーバーで利用したい場合には便利かも知れません。

いかのようにロードできますが、今までのpredictなどの昨日がうまくいきません。

with tf.Session(graph=tf.Graph()) as sess:
    model_x = tf.saved_model.loader.load(sess, [tag_constants.SERVING], export_path)

そこで、上記に定義したsignature_defを利用します。 まとめて、

my_testing_data = ... #ここでモデルのインプットデータを用意します

with tf.Session(graph=tf.Graph()) as sess:
    model_x = tf.saved_model.loader.load(sess, [tag_constants.SERVING], export_path)
    sig_def = model_x.signature_def[signature_name]
    in_name = sig_def.inputs[input_sig].name
    out_name = sig_def.outputs[output_sig].name
    # print(in_name, out_name) #きになる方はここでプリントしたら、レイヤー名が出力されます。
    result = sess.run(out_name,
                 feed_dict={in_name: my_testing_data})
    

こうして、Signature defを利用して、モデルの中のレイヤー名に依存せずにモデルの利用ができます。これは本番環境にモデル置き換えなどがある場合にはとても便利です。

まとめ

今回はkerasのモデルを本番環境のために準備するステップを説明してみました。モデルのsignatureを作成して、保存し、またロードして実行するまでやりました。ただし、tensorflow 2.0にはまたちょっと変わっていきますのでご注意ください。

本番環境を用意しましたら、cliで実行するか、flaskのサーバーに置くか、他の色々な選択肢もありますが今回はここまでにします!

AutoML:機械学習の次の波

f:id:vivek081166:20190424142833j:plain
Source: www.pexels.com

AIと機械学習は、依然として初心者が参入しにくい専門性の分野です。 豊富な専門知識と事業リソースをもった企業は少ししかありません。~Fei-Fei Li

Mercari は日本で人気のあるショッピングアプリで、画像の分類にAutoML (Automated Machine Learing) Vision(GoogleのAutoMLソリューション)を使用しています。Mercari によると、「写真をアップロードすると、12の主要ブランドのブランド名を提案する独自のMLモデルを開発しています」

TensorFlowでトレーニングされた独自のモデルは75%の精度を達成しましたが、50,000のトレーニング画像を含むアドバンストモードのAutoML Visionは91.3%の精度を達成しました。これは15%もの大幅な向上です。そのような驚くべき結果により、MercariはAutoMLをシステムに統合しました。

これは、複雑なシナリオに対処するための機械学習モデルを進化させることによってもたらされた恩恵のほんの一例です。AutoML(Automated Machine Learning)が今日のML (Machine Learning)ベースのソリューションを根本的に変えたのです。

自動機械学習:AutoML

近年、機械学習はさまざまな分野で大きな進歩をもたらしてきました。金融サービス、医療、小売、交通などの分野では、何らかの形で機械学習システムが使用されており、その結果は有望視されています。

今日の機械学習は、研究に限定されているのではなく、エンタープライズドメインに進出しています。ただし、多くの企業は豊富なデータサイエンティストチームに投資できるだけの事業資源を持っていないために、人に依存した従来のMLプロセスでは解決できない問題が多くありました。Auto MLは、そのような状況への答えになるかもしれません。

自動機械学習(AutoML)は、実世界の問題に機械学習を適用するというend-to-end のプロセスを自動化する過程です。AutoMLは、機械学習の分野で高度な専門知識を持たない人でも、それらを利用できるようにしたのです。

機械学習モデルは、次の4つのプロセスで構成されています。

f:id:vivek081166:20190424143407p:plain

データの取り込みから前処理、最適化、そして結果の予測まで、すべてのステップを人間が管理し、実行します。 AutoMLは基本的に2つの主要な側面に焦点を合わせています 。それはデータ収集/収集と予測すす。これらの間で行われる前処理と最適化のステップは、簡単に自動化することができます。

AutoMLの必要性

機械学習システムの需要は過去数年間で急上昇しました。これは、今日の幅広いアプリケーションでのML の成功によるものです。ただ、機械学習によって特定のビジネスが促進される可能性があることが明確だとしても、多くの企業がMLモデルの展開に苦労しています。

まずは、データサイエンティストのチームを立ち上げる必要があります。第二に、チームを抱えていても、 どの問題にどのモデルが最適であるかを決めることはしばしば知識より多くの経験を必要とします。

AutoMLは、モデルのパフォーマンスを犠牲にすることなく、最小限の労力でMLパイプラインの最大ステップ数を自動化するのに役立ちます。

利点

AutoMLの利点を要約すると、次の3つになります。

  • 作業を自動化することで生産性を高めます。そうするとモデルではなく問題にもっと集中することができます。

  • MLパイプラインを自動化することで、手動時に発生する人為的なエラーを回避するのにも役立ちます。

  • 結局のところ、AutoMLは、MLの力を誰もが利用できるようにすることによって、機械学習の民主化に向けた一歩です。

AutoMLフレームワーク

AutoMLは、機械学習パイプラインの一部または全部を自動化できます。そこで、まずはフレームワークのいくつかを見てみましょう。これは完全なリストではありませんが、幅広い分野で使用されているフレームワークについて紹介しようと思います。

1. MLBox

f:id:vivek081166:20190424143752p:plain
https://mlbox.readthedocs.io/en/latest/

MLBoxは強力な自動機械学習Pythonライブラリです。 公式文書によると、このライブラリは以下の機能を提供します。

  • 高速読み取りおよび分散データの前処理/クリーニング/フォーマット

  • 非常に正確な特徴量選択、リーク検出、そして正確なハイパーパラメータ最適化

  • 分類と回帰のための最先端の予測モデル(ディープラーニング、スタッキング、LightGBMなど)

  • モデル解釈による予測

パイプライン

MLBoxのメインパッケージには、次の作業を自動化するための3つのサブパッケージが含まれています。 * 処理:データの読み取りと前処理 * 最適化:モデルのテストと交差検定 * 予測:予測をする

インストール

現在、MLBoxはLinuxとのみ互換性がありますが、WindowsとMacOSのサポートは間もなく追加される予定です。

#gcc
sudo apt-get install build-essential

#cmake
pip install cmake

pip install mlbox

デモ

ハウスプライス回帰問題で "MLBox"自動MLパッケージを実行する。

gist.github.com

2. Auto-Sklearn

Auto-Sklearnは、Scikit-learnの上に構築された自動機械学習パッケージです。 Auto-Sklearnを使用すれば、アルゴリズムの選択やハイパーパラメータの調整は不要です。 Auto-Sklearnには、ワンホットエンコーディング、PCAなどのフィーチャーエンジニアリング手法が含まれています。

Auto-Sklearnは、中小規模のデータセットではうまく機能しますが、大規模なデータセットに適用するとパフォーマンスが低下します。

インストール

Auto-Sklearnは現在Linuxマシンでのみ動作します。

#dependencies
curl https://raw.githubusercontent.com/automl/auto-sklearn/master/requirements.txt | xargs -n 1 -L 1 pip install

#auto-sklearn
pip install auto-sklearn

デモ

次の例は、Auto-Sklearnを使用して近似のデータを元に予測し、単純な回帰モデルを作る方法です。

gist.github.com

3. Tree-Based Pipeline Optimization Tool (TPOT)

f:id:vivek081166:20190424144939p:plain
https://epistasislab.github.io/tpot/

TPOTは、ジェネティックプログラミングを使用して機械学習パイプラインを最適化するPython自動機械学習ツールです。

TPOTはScikit-learnフレームワークを拡張しますが、独自のリグレッサおよび分類子メソッドを使用します。

f:id:vivek081166:20190424144610p:plain
TPOTによって自動化された機械学習プロセスの一部

インストール

pip install tpot

詳しい手順については、公式文書のTPOTインストール手順を参照してください。

デモ

TPOTでMNIST数字を分類する

gist.github.com

4. H2O

f:id:vivek081166:20190424145540p:plain
http://docs.h2o.ai/
H2Oは、RとPythonの両方をサポートする、H20.aiのオープンソース分散型インメモリ機械学習プラットフォームです。

H2Oは、フィーチャエンジニアリング、モデル検証、モデル調整、モデル選択、モデル展開など、最も困難なデータ科学および機械学習ワークフローの一部を自動化します。

インストール

H2Oをダウンロードしてインストールするには、以下のリンクを利用してください。

h2o-release.s3.amazonaws.com

デモ

H2OAutoML クラスをPythonで基本的な使い方は下記の通りです。

gist.github.com

5. AutoKeras

f:id:vivek081166:20190424145528p:plain
https://autokeras.com/

Auto-Kerasは、機械学習自動化するためにDATA Labが作ったオープンソースライブラリです。KerasディープラーニングフレームワークをベースにしたAuto-Kerasは、ディープラーニングモデル用のアーキテクチャとハイパーパラメータを自動的に検索する機能を、私たちに提供してくれます。

Auto-KearsのAPIはScikit-Learn APIと似ているのでとても使いやすいです。現在のバージョンでは、ディープラーニングプロセス中にハイパーパラメータを自動的に検索する機能が提供されています。

Auto-Kerasは、自動化されたNeural Architecture Search(NAS)アルゴリズムを使用してMLプロセスを簡素化します。

インストール

インストールもシンプルです。

デモ

下記はMNISTデータセットを利用したAuto-Kerasライブラリのデモです。

gist.github.com

6. Cloud AutoML

Cloud AutoML は機械学習プロダクトのスイートです。AutoML を利用すると、機械学習の専門知識があまりない開発者でも Google の最先端の転移学習とニューラル アーキテクチャ検索技術を利用して、ビジネスニーズに合った高品質のモデルをトレーニングできます。

Cloud AutoMLは、自身のデータに基づいてモデルをトレーニング、評価、改善、および展開するためのシンプルなグラフィカルユーザーインターフェイス(GUI)を提供します。現在、このスイートは以下のAutoMLソリューションを提供しています。

f:id:vivek081166:20190424150050p:plain

GoogleのAutoMLの欠点はオープンソースではないということです。 AutoML Visionの場合、モデルをトレーニングするのにかかる時間と、予測のためにAutoML Visionに送信する画像の数によってコストが異なります。詳しくはこちらをご覧ください。

cloud.google.com

7. TransmogrifAI

TransmogrifAIは、Salesforceのオープンソースの自動機械学習ライブラリです。

Einsteinと呼ばれる同社の主力MLプラットフォームもTransmogrifAIによって供給されています。 これは、Apache Spark上で動作するScalaで書かれた構造化データ用のend to endのAutoMLライブラリです。

インストール

# JAVA 1.8 インストール

# Spark 2.3.x 取得
git clone https://github.com/salesforce/TransmogrifAI.git

cd TransmogrifAI && ./gradlew compileTestScala installDist

インストール手順については、資料をお読みください。

デモ

TransmogrifAIによるタイタニック生存者の予測してみましょう。詳しくはこちらをご覧ください。

gist.github.com

AutoMLの未来

AutoMLの基本的な目的は、パイプラインの作成やハイパーパラメータの調整などの反復的な作業を自動化して、自分が解決すべきビジネス上の課題により多くの時間を費やすことができるようにすることです。

AutoMLが成功するかどうかは、多くの人に使われることと機械学習分が進歩することにかかっています。ただ、AutoMLが将来の機械学習の大部分を占めることは間違いないでしょう。

参照

  1. Efficient and Robust Automated Machine Learning
  2. Benchmarking Automatic Machine Learning Frameworks

決定境界(Decision Boundary)とニューラルネットの紹介

ゴーリストのチナパです!この度は技術的な記事ではなく、「AIに任せましょう」などを聞くときに、実際に何が起きているのかの直感を作るための記事です。(プログラミングなしです)

データでモデルを学習しそして分類の判断を行う時に、何かの規則を作ってから判断しているのが事実です。複雑な問題であるほど人間の頭で想像しにくくなるかも知れませんが、直感を作ることはできます。

f:id:c-pattamada:20190413162628j:plain
りんごが... 左!

Public Domain Pictures · Photography

簡単な問題でやってみよう

この度はhttps://playground.tensorflow.org, という実験できる場所から始めましょう。ここでは、ノープログラミングでニューラルネットと遊べます。

f:id:c-pattamada:20190413162957p:plain

初めての方だと、色々がありすぎてびっくりするかも知れませんが、一歩ずつ進めて行きます!

スタート地点はこちらです。左側の「Data」のところで、写真の通り、一番簡単なデータを選びましょう。すると、右側の「Output」にそれが反映されます。(一旦、左のメニューにNoiseも0%だと確認しましょう)。

f:id:c-pattamada:20190413163035p:plain

これは二次元のグラフですのでちゃんと二つの軸があります、x軸とy軸と言われてる時もありますが、こちらで横軸がX1と縦軸がX2です。この二つを私たちが測ってます(この場合はランダムで作られてるけど、現実の状況であれば測っていたりしている)。点の種類は二つあります、オレンジと青、そしてこの例ではご覧の通り綺麗な二つの固まりになってます。

計算機を使うまでもなく、オレンジの点がX1=-2, X2=-2あたりが平均みたいだし、青の点がX1=2, X2=2の周りに集中されてます。 さて、こんな簡単の問題には機械学習がどう解決するのかも見てみましょう。上のあたりのPlayボタンを押してみてください。

押してから、すぐグラフが綺麗なオレンジと青に分けれれていきます。一瞬にでもこのパターンが見抜かれてます。ま、簡単だしな。

f:id:c-pattamada:20190413163126p:plain

グラフに左がわがオレンジになり、右側が青になりました。そして、斜め白い線がオレンジ組と青くみの間を通ってます。

「この二つの組みを分けてください」と子供に聞いたら、似たような線も書いたかも知れません。この場合、この白い一直線が「決定境界」と言います。 白い線の左はオレンジ、右が青。つまり、この線によって判断されてます。

ちょっとずるいことをしてみましょう。

こんな簡単な問題にも、情報が足りなかったら、良い判断が一気にとりにくくなります。

f:id:c-pattamada:20190413163215p:plain

「Features」の下にあるX1の変数あたりをクリックしたら、色が薄めます。これで、今回はX1の情報は機械学習のモデルに渡さないことになってます。つまり、縦軸であるX2の情報だけで、点がオレンジなのか、青なのかを判断しないといけない状況です。またPlayをおし、結果をみましょう。

f:id:c-pattamada:20190413163254p:plain

今度はグラフが横に分けられてます。過ちもあるかも知れません(私の場合、一つの青点がオレンジ背景の部分にあります。なぜなら、縦軸のX2の情報しか得てないため、その情報だけで判断されてるからです。だら、横線になってます。

つまり、決定境界が使ってる変数(Feature、情報)にとても影響されます。

ま、今のところはわかりやすい、ほとんど一直線の例しかみてないので、ハードルを高くしましょう。

渦巻(ナルト?)の出番

X1をもう一回クリックし、モデルにこの情報を与えましょう。ただし、Dataの部分で右下にある渦巻みたいな物を選びましょう。そして、Noiseも入れましょう(私は30%にしましたが、お好きに選んでください)。

f:id:c-pattamada:20190413163420p:plain

Playを押したら….

f:id:c-pattamada:20190413163448p:plain

全然分からない、と言わんばかりのうすい背景と白い横線の決定境界… このうすい色は「自信度が低い」という意味です。学生に聞いて 「なんか…上の辺にオレンジがもう少しあるから…こっちがオレンジになるかな…?」みたいな答えです。

ま…一直線を書いてこのデータを分類するのが不可能だからです。渦巻だから。 では、どうしたら一直線ではない決定境界を作ることができます?

方法があります。 「Feature」の項目で一直線ではないデータで一直線的ではないデータを含めること。

Featureの項目で一番したにあるsin(X1)とsin(X2)も選択して、同じネットワークをplayしましょう。

f:id:c-pattamada:20190413163837p:plain

一気に、決定境界がとんでもないことになってます。

f:id:c-pattamada:20190413163858p:plain

しかも、割とあってるし。一体どうやってここまで分かったでしょう?「渦巻」と分かったのか?

ニューラルネットの中身の分析

ここで、真ん中にある「hidden layer」の部分を見てみましょう。

f:id:c-pattamada:20190413164931p:plain

このネットワークには2つにhidden layerがあります。一つ目には4つの「ニューロン」(神経)があって、2つめは2つの「ニューロン」があります。それぞれのものが何を「気づいている」 のかを見るために、マウスを上にホバーしましたら、右のグラフでそのニューロンのアウトプットが観れます。

みなさんとは少し違うかも知れませんが、私の場合最初のhidden layerでは、四つの「気づき」があるみたいです。

f:id:c-pattamada:20190413164827p:plain

1番と2番がとても似ています。これは、sinグラフみたいな波があるようです。この上にはオレンジ、下には青。ですが、これだけでは渦巻にはなりません。

3番目の「気づき」は上から下の階層一番上にはオレンジ、次は青、次はオレンジ、一番したは青。この中には、青が左肩よりだとオレンジは右肩よりだとも気づいているみたです、面白い。 どうやってこれができたのかが気になりましたら、このニューロンに入っていく線をホバーしてみると”weight is 〇〇”みたいのものがあります。

f:id:c-pattamada:20190413164950p:plain
ホバーしてからの写真をまとめた

ここでは、(-0.33 x X1) + (0.3 x X2) + (sin(X1) x 1.6) + (sin(X2) x 2.7)のようにこのグラフが作られてますが、数学はそこまでとしましょう。

4番目もX2の逆に似ていて、とりあえず上がオレンジ、下が青と気づいています。

この4つの気づいきを更に組み合わせて、2番のhidden layer にアウトプットを得て、またそのアウトプットを組み合わせて、結果の「渦巻」のようなデータを割と精度高く分類することができる決定境界を描くことができました。

まとめ

ここでX1とX2しか使わなかったため、綺麗な二次元グラフで写すこともできましたが、ディープラーニングのより複雑な分類問題も、結果的に似ているようなことをやっています。フィーチャ数が大きければ大きいほど描きにくい決定境界になりますし、それでこそ便利な道具になっていくこともあります。

例えば...

f:id:c-pattamada:20190413165056p:plain
結構渦巻っぽくない?

では、他の種類のデータも、遊んで観てください!hidden layer数を増やして、減らして影響を見ることもできますので、分類で使うdeep learningがどのような決定境界を作ることができるのかの直感がこれでできましたら幸いです。

Deep Learning Using Raw Audio Files

// Feed raw audio files directly into the deep neural network without any feature extraction. //

If you have observed, conventional audio and speech analysis systems are typically built using a pipeline structure, where the first step is to extract various low dimensional hand-crafted acoustic features (e.g., MFCC, pitch, RMSE, Chroma, and whatnot).

Although hand-crafted acoustic features are typically well designed, is still not possible to retain all useful information due to the human knowledge bias and the high compression ratio. And of course, the feature engineering you will have to perform will depend on the type of audio problem that you are working on.

But, how about learning directly from raw waveforms (i.e., raw audio files are directly fed into the deep neural network)?

In this post, let's take learnings from this paper and try to apply it to the following Kaggle dataset.

www.kaggle.com

Go ahead and download the Heatbeat Sounds dataset. Here is how one of the sample audio files from the dataset sounds like

clyp.it

f:id:vivek081166:20190410152348p:plain

The downloaded dataset will have a label either "normal", "unlabelled", or one of the various categories of abnormal heartbeats. 

Our objective here is to solve the heartbeat classification problem by directly feeding raw audio files to a deep neural network without doing any hand-crafted feature extraction.

Prepare Data

Let's prepare the data to make it easily accessible to the model. 

extract_class_id(): Audio file names have its label in it, so let's separate all the files based on its name and give it a class id. For this experiment let's consider "unlabelled" as a separate class. So as shown above, in total, we'll have 5 classes. 

convert_data(): We'll normalize the raw audio data and also make all audio files of equal length by cutting them into 10s if the file is shorter than 10s, pad it with zeros. For each audio file, finally put the class id, sampling rate, and audio data together and dump it into a .pkl file and while doing this make sure to have a proper division of train and test dataset.

gist.github.com

Create and compile the model

f:id:vivek081166:20190410152941p:plain

As written in the research paper, this architecture takes input time-series waveforms, represented as a long 1D vector, instead of hand-tuned features or specially designed spectrograms. 

There are many models with different complexities explained in the paper. For our experiment, we will use the m5 model. 

m5 has 4 convolutional layers followed by Batch Normalization and Pooling. a callback keras.callback is also assigned to the model to reduce the learning rate if the accuracy does not increase over 10 epochs.

gist.github.com

Start training and see the results

Let's start training our model and see how it performs on the heartbeat sound dataset.

As per the above code, the model will be trained over 400 epochs, however, the loss gradient flattened out at 42 epochs for me, and these were the results. How did yours do?

Epoch 42/400
128/832 [===>..........................] - ETA: 14s - loss: 0.0995 - acc: 0.9766
256/832 [========>.....................] - ETA: 11s - loss: 0.0915 - acc: 0.9844
384/832 [============>.................] - ETA: 9s - loss: 0.0896 - acc: 0.9844 
512/832 [=================>............] - ETA: 6s - loss: 0.0911 - acc: 0.9824
640/832 [======================>.......] - ETA: 4s - loss: 0.0899 - acc: 0.9844
768/832 [==========================>...] - ETA: 1s - loss: 0.0910 - acc: 0.9844
832/832 [==============================] - 18s 22ms/step - loss: 0.0908 - acc: 0.9844 - val_loss: 0.3131 - val_acc: 0.9200

Congratulations! You’ve saved a lot of time and effort extracting features from audio files. Moreover, by directly feeding the raw audio files the model is doing pretty well.

With this, we learned how to feed raw audio files to a deep neural network. Now you can take this knowledge and apply to the audio problem that you want to solve. You just need to collect audio data normalize it and feed it to your model.

The above code is available at following GitHub repository

github.com

That's it for this post, my name is Vivek Amilkanthwar. See you soon with one of such next time; until then, Happy Learning :)

References:

1) https://arxiv.org/pdf/1610.00087.pdf
2) https://github.com/philipperemy/very-deep-convnets-raw-waveforms
3) https://openreview.net/pdf?id=S1Ow_e-Rb

Teachable Desktop Automation

// Teach your computer to recognize gestures and trigger a set of actions to perform after a certain gesture is recognized.//

Hello World! I'm very excited to share with you my recent experiment wherein I tried to teach my computer certain gestures and whenever those gestures are recognized certain actions will be performed.

In this blog, I'll explain all you need to do to achieve the following

IF: I wave to my webcam
THEN: move the mouse pointer a little to its right

I have used power for Node js to achieve this. The idea is to create a native desktop app which will have access to the operating system to perform certain actions like a mouse click or a keyboard button press and also on the same native desktop app we'll try to train our model and draw inferences locally.

To make it work, I thought of using tensorflow.js and robotjs in an Electron App created using Angular.

f:id:vivek081166:20190329175638p:plain

So, are you ready? let's get started…

Generate the Angular App

Let's start by creating a new Angular project from scratch using the angular-cli

npm install -g @angular/cli

ng new teachable-desktop-automation

cd teachable-desktop-automation

Install Electron

Add Electron and also add its type definitions to the project as dev-dependency

npm install electron --save-dev

npm install @types/electron --save-dev

Configuring the Electron App

Create a new directory inside of the projects root directory and name it as "electron". We will use this folder to place all electron related files.

Afterward, make a new file and call it "main.ts" inside of the "electron" folder. This file will be the main starting point of our electron application.

Finally, create a new "tsconfig.json" file inside of the directory. We need this file to compile the TypeScript file into JavaScript one.

Use the following as the content of "tsconfig.json" file.

gist.github.com

Now it's time to fill the "main.ts" with some code to fire up our electron app.

gist.github.com

Visit electronjs for details

Make a custom build command

Create a custom build command for compiling main.ts & starting electron. To do this, update "package.json" in your project as shown below

{
  "name": "teachable-desktop-automation",
  "version": "0.0.0",
  "main": "electron/dist/main.js", // <-- this was added
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e",
    "electron": "ng build --base-href ./ && tsc --p electron && electron ."  // <-- this was added
  },
  // ...omitted
}

We can now start our app using npm:

npm run electron

f:id:vivek081166:20190329180447p:plain

There we go… our native desktop app is up and running! However, it is not doing anything yet.

Let's make it work and also add some intelligence to it…

Add Robotjs to the project

In order to simulate a mouse click or a keyboard button press, we will need robotjs in our project.
I installed robotjs with the following command

npm install robotjs

and then tried to use in the project by referring to some examples on their official documentation. However, I struggled a lot to make robotjs work on the electron app. Finally here is a workaround that I came up with

Add ngx-electron to the project

npm install ngx-electron

And then inject its service to the component where you want to use the robot and use remote.require() to capture the robot package.

import { Component } from '@angular/core';
import { ElectronService } from 'ngx-electron';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
constructor(private electronService: ElectronService) {
   
   this.robot = this.electronService.remote.require('robotjs');
// move mouse pointer to right
   const mousePosition = this.robot.getMousePos();
   this.robot.moveMouse(mousePosition.x + 5, mousePosition.y);
}
}
Add Tensorflow.js to the project

We'll be creating a KNN classifier that can be trained live in our electron app (native desktop app) with images from the webcam.

npm install @tensorflow/tfjs

npm install @tensorflow-models/knn-classifier

npm install @tensorflow-models/mobilenet

A quick reference for KNN Classifier and MobileNet package

Here is a quick reference to the methods that we'll be using in our app. You can always refer tfjs-models for all the details on implementation.

KNN Classifier

  • knnClassifier.create() : Returns a KNNImageClassifier.
  • .addExample(example, classIndex) : Adds an example to the specific class training set.
  • .predictClass(image) : Runs the prediction on the image, and returns an object with a top class index and confidence score.

MobileNet

  • .load() : Loads and returns a model object.
  • .infer(image, endpoint) : Get an intermediate activation or logit as Tensorflow.js tensors. Takes an image and the optional endpoint to predict through.

Finally make it work

For this blog post, I'll keep aside the cosmetics part (CSS I mean) apart and concentrate only on the core functionality

Using some boilerplate code from Teachable Machine and injecting robotjs into app component here is how it looks

gist.github.com

and now when running the command npm run electron you see me (kidding)

f:id:vivek081166:20190329181118p:plain

Let's Test it

I'll train the image classifier on me waving to the webcam (Class 2) and also with me doing nothing (Class 1).

Following are the events are associated with these two classes

Class 1: Do nothing
Class 2: Move mouse pointer slightly to the right

youtu.be

With this, your computer can learn your gestures and can perform a whole lot of different things because you have direct access to your operating system.

The source code of this project can be found at below URL…

github.com

I have shared my simple experiment with you. Now it's your turn try to build something with it and consider sharing that with me as well :D

I have just scratched the surface, any enhancements, improvements to the project are welcome though GitHub pull requests.

Well, that's it for this post… Thank you for reading until the end. My name is Vivek Amilkanthawar, see you soon with another one.

Googleのbertを利用してみました〜!

こんにちは、チナパです!

先日、Word2vecを利用して、単語から数字のための辞書を作成してみました。その続きで、Googleが最近リリースした「bert」(Bidirectional Encoder Representations from Transformers)を利用してみましょう。

f:id:c-pattamada:20190329191856p:plain
人間よりできる!

bertとは?

まずはそもそもこれは何なのか、なんですごいのかを説明します。

現在までの自然言語処理の技術が文章を読みながら、今までの言葉からコンテキストを理解して、順番での次の言葉の意味を今までの言葉によってのコンテキストで影響されるような技術が代表的でした。

それはRNNの構築を利用して行ってました。

去年、ELMoとbertでは「Attention」を利用し、より精度の高い結果を出せるようになってる。

例えば、チャットボットを作成する時に、Attentionの構築では前のメッセージのキーワードを覚えながらお返事を作成するようなイメージです。

Bertはそれをより強化的に使っています。

“Multi-headed attention”と説明されていますが、複数な箇所をバラバラに注目しながら解析しているような巨大モデルです。

英語の自然言語処理のベンチマークの一つのSQuADでは人間より良い結果が簡単に出せるようです。

bertが事前に単語を学習されたままで利用もできますので、試してみましょう!

素晴らしい!では、どうやって使える??

Bertは割と重いので、今回はColabでやってみました。

!pip install bert-tensorflow

from google.colab import drive
drive.mount('/content/gdrive')

driveの許可を与えて、bertの語彙データをtensorflow_hub_からアクセスしましょう。

from bert.tokenization import FullTokenizer
import pandas as pd
import tensorflow_hub as hub

bert_path = "https://tfhub.dev/google/bert_multi_cased_L-12_H-768_A-12/1" # 日本語用のモデルがこちら

sess = tf.Session()

def create_tokenizer_from_hub_module():
    """Get the vocab file and casing info from the Hub module."""
    bert_module =  hub.Module(bert_path)
    tokenization_info = bert_module(signature="tokenization_info", as_dict=True)
    vocab_file, do_lower_case = sess.run(
        [
            tokenization_info["vocab_file"],
            tokenization_info["do_lower_case"],
        ]
    )

    return FullTokenizer(vocab_file=vocab_file, do_lower_case=do_lower_case)
  
tokenizer = create_tokenizer_from_hub_module()

これで、bertのtokenizerのインスタンスを作りました。MeCabみたいに、文字列を言葉に分けるためのものです。bertでは、漢字が全部一文字ずつのトークンに変換されます。

tokenizer.tokenize('こんにちは、今日の天気はいかがでしょうか?') すると、

['こ',
 '##ん',
 '##に',
 '##ち',
 '##は',
 '、',
 '今',
 '日',
 'の',
 '天',
 '気',
 'は',
 '##い',
 '##か',
 '##が',
 '##で',
 '##し',
 '##ょう',
 '##か',
 '?']

みたいなとんでもない結果が出ます。

"##" が続いているような風に見える。トークンから文字列に戻そうとするとこのあたりは気をつけないと… 後は、変な分からない文字がありましたら、このように出てきます。

tokenizer.tokenize('゛')

# アウトプット=> ['[UNK]']

さて、ベクトル化しましょう。

bertでトークンをベクトル変えるために、3つのインプットが必要です。

tokenizer.convert_tokens_to_ids(...)

で作成できるid以外にsegment idとinput maskが必要です。

input_idがbertで素早くベクトルに変換するためのものです。segment_idは文の番号です、これでメッセージとその返事が分けられたりできます。padding系のインプットを無視できるように、input_mask_を利用します。

f:id:c-pattamada:20190329200016p:plain
そう、これがいい!

一旦は、一つの文章だけの処理をしましょう。上記の3つのベクトルがこのようなメソッドで作られます。

def convert_string_to_bert_input(tokenizer, input_string, max_length=128):

    tokens = []
    tokens.append("[CLS]")
    tokens.extend(tokenizer.tokenize(input_string))
    if len(tokens) > max_seq_length - 2:
        tokens = tokens[0 : (max_seq_length - 2)]
    tokens.append("[SEP]")
    
    segment_ids = [0] len(tokens)
    input_ids = tokenizer.convert_tokens_to_ids(tokens)
    # これから加えるpaddingが無視できるように
    input_mask = [1] * len(tokens)
    
    while len(input_ids) < max_seq_length:
        input_ids.append(0)
        input_mask.append(0)
        segment_ids.append(0)

    return np.array(input_ids),
            np.array(input_mask),
            np.array(segment_ids)

上記のメソッドで"[CLS]"と"[SEP]"を追加しているのが見えます。これはbertで利用する「開始文字」と「文を分ける文字」になってます。複数の文章がある場合に、間に"[SEP]"を入れます。

Pandasでベクトル化したいデータを読み込み、上記のメソッドで変換しましょう。

data_path = ...
input_column = ...
df = pd.read_csv(data_path)

features = df[input_column].map(
                       lambda my_string:
                           convert_string_to_bert_input(tokenizer, my_string)
                       )

bertをkerasで利用

よっし、これからbertをkerasのモデルに利用する魔法を使いましょう。

https://github.com/strongio/keras-bert/blob/master/keras-bert.ipynb

こちらはかなり参考になりました。

class BertLayer(tf.layers.Layer):
    def __init__(self, n_fine_tune_layers=10, **kwargs):
        self.n_fine_tune_layers = n_fine_tune_layers
        self.trainable = False
        self.output_size = 768
        super(BertLayer, self).__init__(**kwargs)

    def build(self, input_shape):
        self.bert = hub.Module(
            bert_path,
            trainable=self.trainable,
            name="{}_module".format(self.name)
        )

        trainable_vars = self.bert.variables

        # Remove unused layers
        trainable_vars = [var for var in trainable_vars if not "/cls/" in var.name]

        # Select how many layers to fine tune
        trainable_vars = trainable_vars[-self.n_fine_tune_layers :]

        # Add to trainable weights
        for var in trainable_vars:
            self._trainable_weights.append(var)
            
        for var in self.bert.variables:
            if var not in self._trainable_weights:
                self._non_trainable_weights.append(var)

        super(BertLayer, self).build(input_shape)

    def call(self, inputs):
        inputs = [K.cast(x, dtype="int32") for x in inputs]
        input_ids, input_mask, segment_ids = inputs
        bert_inputs = dict(
            input_ids=input_ids, input_mask=input_mask, segment_ids=segment_ids
        )
        result = self.bert(inputs=bert_inputs, signature="tokens", as_dict=True)[
            "pooled_output"
        ]
        return result

    def compute_output_shape(self, input_shape):
        return (input_shape[0], self.output_size)

これはkerasのカスタムLayerを作成し、その中にbertを利用するようなコードです。 自分のカスタムLayerを書くときにこちらもとても便利です https://keras.io/layers/writing-your-own-keras-layers/

重要な部分は3箇所あります。

1. build()

def build(self, input_shape):
    self.bert = hub.Module(
            bert_path,
            trainable=self.trainable,
            name="{}_module".format(self.name)
        )

ここでtensor flow hubを使って、bert_path_で定義されたモデルを利用します。

2. call()

input_ids, input_mask, segment_ids = inputs
        bert_inputs = dict(
            input_ids=input_ids, input_mask=input_mask, segment_ids=segment_ids
        )
        result = self.bert(inputs=bert_inputs, signature="tokens", as_dict=True)[
            "pooled_output"
        ]

ここでは、先ほど用意したinputをdictに変え、bertのモデルの結果を求める。 現在はsignature="tokens"以外のサポートがありません(29/03/2019)、"pooled_output"と別に"sequence_output"もあります。pooledの方が単語ベクトルを作成する時に使いますので、そちらで行きます。

3. output size

self.output_size = 768

こちらは使ってるモデルに応じて設定する数字です。普通のモデルのそれぞれのレイヤーが768次元で帰ってきます。Largeのモデルでは1024次元です。

これで先ほど作成した3つの変数をdictに入れ、self.bertで結果を得ます。 上記のlayerを普通のkerasのモデルで使えます。

例:

def get_model(max_length=128, num_classes=5):
    input_ids = tf.keras.layers.Input(shape=(max_seq_length,), name="input_ids")
    in_mask = tf.keras.layers.Input(shape=(max_seq_length,), name="input_masks")
    in_segment = tf.keras.layers.Input(shape=(max_seq_length,), name="segment_ids")
    bert_inputs = [input_ids, in_mask, in_segment]
    
    bert_output = BertLayer(n_fine_tune_layers=1)(bert_inputs)
    dense = tf.keras.layers.Dense(128, activation='relu')(bert_output)
    pred = tf.keras.layers.Dense(num_classes=5, activation='sigmoid')(dense)
    
    model = tf.keras.models.Model(inputs=bert_inputs, outputs=pred)
    model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
    model.summary()

ぜひ試してみましょう!

まとめ

今回、 今までの自然言語処理のやり方、とbertの簡単な説明をしました。それから、文字列をbertに適切な形に変換して、モデルで利用できるような形にしました。

今回は、自分も理解し切れてない部分もありますが、ぜひフィードバックを聞きたいです〜

Word2Vecで辞書を作成して見ました

こんにちは!チナパです!日本語の単語が難しくて、辞書が必要だなと思いました。私はhttps://jisho.org をよく使いますが、なかなかパソコンに説明しにくくて...

はい、冗談でした。

自然言語処理では日本語(あるいは他の言語)の「辞書」を利用する必要があります。「辞書」とはなんなのかがご存知かと思いますが、単語を決まった次元の数字ベクトルに変換するための辞書のことです。

f:id:c-pattamada:20190325201950j:plain
ベクトル辞書?!

このタスクは「文字レベル」、「単語レベル」、そして最近「文レベル」にもできるようになってます。

文字レベルと単語レベルの方はGloVeとWord2Vecなどの方法があります、今日はWord2Vecでシンプルな辞書をPythonで作って見ましょう。

注意: 自然言語処理では辞書の最適化によってタスクにかなりの改善も見られますが、今回は一旦デモができるレベルの辞書にはなれます。

必要な材料

  • パソコンで2-3GBぐらいの容量(cloudとColabでもできますが、今回はローカルにします)
  • 以下のライブラリ
    • MeCab, wget, gensim, jaconv
import MeCab
import wget
from gensim.corpora import WikiCorpus
from gensim.models import Word2Vec
from gensim.models.word2vec import LineSentence
import jaconv
from multiprocessing import cpu_count
import os
VECTORS_SIZE = 50

wiki_file_name = 'jawiki-latest-pages-articles1.xml-p1p106175.bz2'
ja_wiki_latest_url = 'https://dumps.wikimedia.org/jawiki/latest/jawiki-latest-pages-articles1.xml-p1p106175.bz2'

# 上記のwikiファイルは部分的なものです。
# 他の種類のデータがいかのurlで見つけられます
# https://dumps.wikimedia.org/jawiki/latest/ 

次元数は文字レベルの場合に20辺りでもいいかもしれませんが、単語レベルで行う際には50次元最低はオススメです。その以下であれば(自分の経験で)言葉の意味が捕まえきれてないケースが多いです。

データを手に入れよう

さて、まずはデータを大量に必要ですので、wikipediaをダウンロードしましょう!

if not os.path.isfile(wiki_file_name):
    wget.download(ja_wiki_latest_url, bar=wget.bar_adaptive)

今回は一部をダウンロードしています、役15分かかりました。全ての記事をダウンロードする場合は、3GBと3時間ぐらいが必要となります。

日本語の場合、同じ言葉や文字が全角か半角だったりするケースがあります。ローマジの場合、大文字と小文字のほとんどの場合に同じ意味を持つ(waterとwater) ここで、jaconvのライブラリを使い、すべてを全角に統一します。

def normalize_text(text):
    return jaconv.h2z(text, digit=True, ascii=True, kana=True).lower()

半角に統一したい場合、jaconv.z2hとメソッドを利用できます。 辞書を利用する際、同じように統一する必要があります。

上記のメソッドを利用して、以前ダウンロードしたwikipediaデータを全角に変えながら、普通のtxtファイルに保存しましょう。

wiki_text_file_name = 'wiki.txt'

def read_wiki(wiki_data, save_file):
    if os.path.isfile(save_file):
        print('Skipping reading wiki file...')
        return
    with open(save_file, 'w') as out:
            wiki = WikiCorpus(wiki_data, lemmatize=False, dictionary={}, processes=cpu_count())
            wiki.metadata = True
            texts = wiki.get_texts()
            for i, article in enumerate(texts):
                text = article[0]  # article[1] は記事名です
                sentences = [normalize_text(line) for line in text]
                text = ' '.join(sentences) + u'\n'
                out.write(text)
                if i % 1000 == 0 and i != 0:
                    print('Logged', i, 'articles')
    print('wiki保存完了')


read_wiki(wiki_file_name, wiki_text_file_name)

ここでは、gensimのWikiCorpusを利用して簡単に解読できます。これで学習できるデータが揃いましたので次のステップに行きましょう。

単語トーケン作成

こちらはメカブの出番です。

def get_words(text: str, mt: MeCab.Tagger) -> List[str]:
    mt.parse('')
    parsed = mt.parseToNode(text)
    components = []
    while parsed:
        if len(parsed.surface) >= 1:  # EOSを覗くためにあります
            components.append(parsed.surface)
        parsed = parsed.next
    return components

get_wordsのメソッドを利用して文書を単語(トーケン)に分けることができます。MeCabのparseToNode(text)メソッドの結果にはsurface とfeatureの二つのものがありますが、ここではsurfaceしか利用しません。

では、先ほど作成したwikipediaのデータをトーケン化しましょう。

token_file = 'tokens.txt'

def tokenize_text(input_filename: str, output_filename: str, mt: MeCab.Tagger):
    lines = 0
    if os.path.isfile(output_filename):
        lines = count_lines(output_filename)  # 続く場合には何行を飛ばすかを調べる
    batch = []
    with open(input_filename, 'r') as data:
        for i, text in enumerate(data.readlines()):
            if i < lines:
                continue
            tokenized_text = ' '.join(get_words(text, mt))
            batch.append(tokenized_text)
            if i % 10000 == 0 and i != 0:
                write_tokens(batch, output_filename)
                batch = []
                print('Tokenized ,', i, 'lines')
    write_tokens(batch, output_filename)
    print('トーケン作成完了')


def write_tokens(batch: List[str], file_name: str):
    with open(file_name, 'a+') as out:
        for out_line in batch:
            out.write(out_line)
            out.write('\n')

            
def count_lines(file: str) -> int:
    count = 0
    with open(file) as d:
        for line in d:
            count += 1
    return count
    
tagger = MeCab.Tagger('-d /usr/local/lib/mecab/dic/mecab-ipadic-neologd') #neologd がなければ、別なものを使ってもOK
tokenize_text(wiki_text_file_name, token_file, tagger)

Wikipediaの記事ごとをトーケンに変え、半角スペースを入れ、またファイルに出力しています。メモリーにもつのもありかもしれませんが、RAMを結構使うケースが多いために、ファイルに出力させていただいてます。

ベクトル化

いおいおベクトル作成の時がきました。コード的には簡単にかけます。

vector_file = 'ja-MeCab-50.data.model'
def generate_vectors(input_filename, output_filename):
    if os.path.isfile(output_filename):
        return
    model = Word2Vec(LineSentence(input_filename),
                     size=VECTORS_SIZE, window=5, min_count=5,
                     workers=cpu_count(), iter=5)
    model.save(output_filename)
    print('ベクトル作成完了。')

generate_vectors(token_file, vector_file)

GensimのWord2Vecで、上記のように定義された50次元のベクトルが作成されます。min_count_は辞書に含まれるための最低回数です。使ってるデータに応じて編集してみてください。

ベクトル作成もかなりの時間かけますので、ローカルで行う場合、一晩中学習させてもらう感じではオススメです。また、google datalabやawsで実装してみた方が良いかもしれません。

お試しタイム!

これで作成できましたので、どうやって使えばいいのか、、

from gensim.models import load_model
import pprint

model = load_model(vector_file)
model = model.wv

pprint.pprint(model['東京'])
pprint.pprint(mm.most_similar(positive='東京', topn=5))

で試してみてください!

f:id:c-pattamada:20190326105354p:plain
私のお試しの結果

まとめ

今回はベクトル辞書の作成について説明いたしました。wikipediaのデータで学習し、MeCabを利用してトーケン化して、最後にWord2Vecのベクトル辞書の作成を行いました。

この辞書の結果がEmbeddingとしてニューラルネットにも使えます。でもそちらはまた別に機会に。

トップの写真はこちらからいただきました: https://www.pexels.com/photo/black-and-white-book-business-close-up-267669/

今回のコードは以下です gist.github.com

また、こちらも参考になりました。 GitHub - philipperemy/japanese-words-to-vectors: Word2vec (word to vectors) approach for Japanese language using Gensim and Mecab.