20191219のTensorFlowに関する記事は3件です。

自動運転から、色々なAI RC Carまでのつながりをざっと見渡してみる

本記事は、AI RC Car Advent Calendar 2019の19日目です。
昨日は、 @arigadget氏による「AIカーをいろんなところで走らせてみた。」でした。

本内容は、技術書典7『30日で動かして学ぶ! 自動運転』第0.8版よりAdvent Calendar向けに抜粋、加筆したものです。

自動運転のこれまでと、これから

AIカーの話に入る前に、自動運転全般の話をしましょう。人工知能、深層学習の適用先のひとつとして、自動車の自律走行が盛んに取り上げられます。

自動運転の歴史

現在の自動運転開発競争は、どのように始まったのでしょうか。何がその適用を加速させたのでしょう。YouTubeなどに残された映像を頼りに、遡ってみましょう。

車の自動運転をもう少し抽象化するなら「自動での乗り物の操縦」です。その意味では、すでに飛行機や電車が先行しています。飛行機では、以前からオートパイロットシステムが使われています。電車も、新交通システムは完全自動で運行されています。

では、自動車で困難な点は何でしょうか。

  • 経路が固定されていない (電車は、移動がレールに制限される)
  • 周囲の物との相互影響の多さ (飛行機は、比較的他機との距離が保たれる)

こうした点が思い浮かびます。前後左右の車、歩行者などの複雑な障害に対応し、かつ自身で数ある選択肢から経路を選び、その通りに操縦する、という難しさがありそうです。

古くは1991年より、自動運転化のはしりとして、先進安全自動車(ASV)の検討の取り組みがはじまりました。その成果は一足先にヒット、普及しています。例えばEyeSightなどの衝突被害軽減ブレーキ、Adaptive Cruise Control(ACC)、その他は、そうしたコンセプトから生まれたものです。

2000年代、完全自動運転では、DARPA(アメリカ国防高等研究計画局)によって、3回の競技会が開催されました。最初の2回は郊外で開催されたDARPA Desert Challengeという砂漠のコースにおけるコンペです。最後の1回は2007年に行われたUrban Challengeです。こちらは、市街地を模した場所を走行するものでした。それぞれの映像が残されています。

この競技会にも参加し、チーム「Stanley」で2005年に優勝、2007年に2位の成績をあげたスタンフォード大のSebastian Thrun氏は、その後Googleに移籍し、自動運転研究をスタートしたとされています。一定の成果をもとに、2011年にはGoogleでの自動運転研究者として、TEDに登壇し、その映像が今も残っています。

余談ですが、同氏は確率ロボティクスの大家であり、Google X、UdacityというMOOCsそれぞれの設立に関わっています

2015年にはChris Urmson氏の講演を聞くことができます。後にGoogleを去りますが、この当時はWaymoの技術チーフです。

2010年代前半のGoogleによる前進がWake up callとなり、自動車業界、IT業界入り乱れての自動運転開発競争が始まりました。

米国に目を向けると、自動運転開発ではWaymoが頭一つ抜けている、というのがコンセンサスでしょう。Waymo Oneと呼ばれる自律走行タクシーのサービスを米国複数都市で立ち上げています。このWaymoの源流を辿ると、上に挙げたSebastian Thrun氏のGoogle加入に行き着きます。

Google以外にも、Uber、Lyftなどのライドシェア企業は自動タクシーを見据えた開発、内外の自動車企業は現在の車の機能拡張として、Tesla社は電気自動車とともにオートクルーズ機能をバンドル、Wayve、Comma.aiなど大学発、ハッカー発の自動運転企業と、たくさんのプレイヤーがひしめき合っています。

プレーヤーは米国だけではありません。欧州にも面白い会社があります。複数の自動運転バスを作っている会社があります。日本でも、SBドライブ社は、現在は仏NAVYA社の自動走行バス NAVYA ARMAを用いた実証実験を行なっています。また、中国では、Baidu社がApollo計画という、自動運転プラットフォームのオープンソース化とパートナ開拓を進めています。

ただ、研究や実用化の進展への期待とともに、2016年5月には米国でTesla社のレベル2自動運転における死亡事故、2018年3月にはUberのレベル4自動運転試験中の死亡事故が取り上げられ、技術面、倫理面での論議を呼びました。各国の法規制や各社の立ち位置に、「安全性」と「発展のためどこまでリスクを取るか」のトレードオフが現れます。

産業と学術、日本と海外各国、それぞれが自動運転開発において、それぞれ歩みを進めてきた様子が、構造を持って掴めてきたでしょうか。

自動運転のレベル分け

ここまで話してきた自動運転ですが、言葉を聞いて思い浮かべる自動の度合いは様々でしょう。米SAE(Society of Automotive Engineers)は、2014年1月に自動運転の成熟度の定義を発表しました。米国、欧州を皮切りに、日本を含めた各国の自動運転政策の検討にこの分類が採用され、デファクトスタンダードとなりました。

SAEの定義では、自動運転を6段階に分類します。

  • レベル0 ドライバーが全て操作する
  • レベル1 システムが加減速またはハンドル操作を支援する
  • レベル2 システムが加減速とハンドル操作を支援する
  • レベル3 指定の場所でシステムが全てを操作、緊急時はドライバーが操作する
  • レベル4 指定の場所でシステムが全てを操作する
  • レベル5 場所の限定なくシステムが全てを操作する

レベルは、下へ行くほど難易度が高くなります。

2010年代一般に販売される車でも、レベル2まで備えているものがあります。例えば、車の前面に取り付けられたセンサーで回避行動をとる機能は、レベル2と言えます。この定義では、レベル3から5が、一般に思い浮かべられる「自動運転」です。

2019年、日本においての自動運転車は、主に自家用車、商用車の二つの文脈に分かれます。また商用車は、あらかじめ指定した区域内を走るバスやタクシー、または高速道路を隊列走行するトラック、という二種類の実証実験が盛んに計画、または実施されています。

法規制とその適切な緩和については様々な意見があるでしょうが、日本は一定健闘していると言えるでしょう。日本では、直近2020年に高速道路におけるレベル3自動運転がマイルストーンとして掲げられ、法整備、各社の研究開発が進められています。

3つの要素 - 認知・判断・操作

さて、自動運転を実現するには、人間の代わりに、コンピュータが何を行えれば良いのでしょうか。まず、人間が車を運転する様子を、できるだけ細かく想像してみましょう。

例えば、あなたが高速道路の走行車線を走っているとします。

  • 運転席に座り、前方を注視、時にミラーを確認します。周囲に何があり、どんなスピードで動いていて、という状態を把握します。前の車がハザードランプをたいて、減速したとします。もう少し前方に目をやると、どうも渋滞というわけではありません。一台だけで故障か、何らかの事情で減速、停止をしようとしているようです。ミラーで右後ろを確認し、追越車線には、後ろに迫る車はありません。

  • その状態と、自分の向かう計画を照らし合わせて、次の行動を決めます。走行車線を守るなら、減速しなければ、目の前の車にすぐに追突してしまうでしょう。車線を守り、減速する。これがとりうる選択肢Aです。追越車線へ、速度を守りながら車線を変更する。これもとりうる選択肢Bです。選択肢Aをとっても、前の車がそのまま停止してしまうとしたら、そこに付き合う必要はないでしょう。ここでは選択肢Bをとるとしましょう。

  • 選択肢Bを取るなら、まず方向指示器で右折を知らせ、速度は保ちながら、ステアリングをゆるやかに右に傾けます。左右の手と足を協調させ、こうした操作を車に対して行います。

これらそれぞれのパラグラフが、認知、判断、操作という、自動運転の3つの要素に対応します。

まとめると以下のようになります。

認知

  • 前方の車の減速
  • 追越車線の確認

判断

  • 選択肢A、Bの着想
  • 選択肢Bを取る判断

操作

  • 方向指示器の操作
  • アクセルの維持
  • ステアリングの操作

自動運転のソフトウェアは、基本この3つを連携させ、実現しています。次に、それらの実現方法をざっと見てみましょう。

認知

現在、ひとつではなく、複数のセンサーを相互補完させる「センサーフュージョン」が主なアプローチとしてとられています。ステレオカメラ、LIDAR、レーダーなどのセンサ、GNSS、SLAMなどを組み合わせ、自身をとりまく環境、自身の場所の把握、自身だけでなく周囲の物体の動きを認識します。

判断

上の認知で得た情報を元に、次の行動の選択肢を洗い出します。そして、それらの選択肢をスコア付けし、どの選択肢を取るのがベストかを判断します。

操作

選択した行動を、できるだけ精緻に現実の環境で再現するための操作を行います。現実の世界には、命令と実際の動きの時差、制御で指定する振る舞いと実際の動きの誤差など、考慮すべきことがたくさんあります。ここは制御工学の世界です。

ラジコンカー自律走行における3要素

上記の自動運転車で行なっていることを聞くと、個人の手ではとても扱いきれない代物に感じられませんか。しかし、DIYによる自動運転車、そのレースが流行の兆しを見せています。

こちらの映像を見てみましょう。

image.png

驚くべきことに、この3台は全て自律走行しています。このラジコンカー自律走行における、認知、判断、操作を考えてみましょう。

Donkey Car

認知

標準のDonkey Carにおける入力は、正面に設置された単眼の広角カメラのみ。それ以外のセンサーはありません。オプションで、IMU、ステレオカメラその他を追加することはできます。広角カメラからは、一定の間隔で、画像が取得されます。

判断

この入力画像を元に、ステアリングは右か左か、スロットリング(アクセル)は踏み込むか否か、が学習済みの深層学習モデルから出力されます。

操作

モデルの出力を元に、サーボコントローラへ所定の制御信号が出力され、ステアリング、スロットリングが調整されます。

DonkeyCarは、先ほど挙げた3つのポイントを単純化し、うまく動かしているモデルです。

MIT Racecar

また、同じラジコンカーが対象ながら、今度はこちらの映像を見てみましょう。

image.png

後ほど紹介するMIT RaceCarやF1/10(F1tenth)では、実車の自律走行に近い、認知から操作までのパイプラインを持っています。

認知

単眼または二眼のステレオカメラ、IMUといわれる加速度・ジャイロセンサ、LIDAR、その他のセンサー情報を統合(センサーフュージョン)し、自車の状態を認識します。

判断

深層学習ではなく、一定のルールに基づき、行動の選択肢を挙げ、スコア付けなどの基準に従い、ベストな行動を選択します。

操作

判断を元に所定の制御が行われます。PIDなどの制御工学の知見が使われます。

ニュースの画面の向こうにしかなかった自動運転が、少し身近に感じられて来たでしょうか。

ラジコンカーの自律走行化に必要なこと

まず、一般的なラジコンカーの構成、およびそれらを自動運転化する時の流れや、要素を説明します。

ラジコンカーの主要部品

まず、自動運転化のベースとなる、改造前の一般的なラジコンカーの構成を説明します。

ラジコンカーは、一般に以下から構成されています。

  • ボディ
  • シャーシ
  • 送信機
  • 受信機
  • ESC
  • メインモータ
  • ステアリングサーボ
  • バッテリー(駆動用)

例えば、タミヤのTT-02というキットだと、このように対応しています。

image.png

https://www.amazon.co.jp//dp/B07L4BNNXW および https://www.tamiya.com/japan/products/58600/index.html より引用、改変

それぞれどういったものかを詳しく見てみましょう。

ボディ

軽量な樹脂製のカバーが、数カ所シャーシにピン留めされています。自動運転化にあたっては、既存のカッコ良い車の外形は取り外し、無骨だが必要なボードを保持できる、実用的なボディに取り替えます。

キットを用いるにせよ、自作をするにせよ、レーザーカッターを使って切り出されたアクリル板や樹脂と、3Dプリントされたパーツの組み合わせがよく使われます。

ただ、レーザーカッターや3Dプリンターが手元にない、しかしキットではなく自分で作ってみたい、という場合も心配いりません。筆者は、初回のプロトタイプとして、ダブルのダンボールを切り出して、ボディを作りました。後続の章で、そうした方法にも触れます。

シャーシ

以下に挙げる、送信機、受信機、ESC、メインモーター、ステアリングサーボ、バッテリーなどの部品や、車輪が取り付けられる、車の基礎となる部分です。

送信機

上の画像には含めていませんが、人が操作するためのプロポを指します。複数の周波数帯が使われますが、最近はWiFiと同様の産業バンド 2.4GHz帯を使うものが主流のようです。

受信機

同周波数帯を使い、信号を受け取ります。受け取った信号を、ここではまだ詳しく解説しませんが、ラジコン用のPWMという制御信号に変換します。その信号はESC、ステアリングサーボに渡されます。

ESC

「アクセルとブレーキ」を制御します。PWM信号を受け、メインモータへ供給する電力を制御します。

メインモーター

ESCからの供給電力でモーターを回し、ギアを通してタイヤへ動力を伝え、車を動かします。

ステアリングサーボ

「ハンドル操作」を制御します。PWM信号を受けて、モータの回転角を変え、左・中央・右と、指定の位置へタイヤの向きを合わせます。

バッテリー(駆動用)

ステアリングサーボ、ESCを通したメインモータ、受信機等への給電をします。最も電力を消費するのはメインモータです。

自律走行ラジコンカーの主要部品

これを自動運転化する時には、 受信機部分をサーボコントローラ基盤で置き換え、それをコントロールするRaspberry Pi、Jetson Nanoなど、基本Linuxベースの小型コンピュータを搭載します。そのコンピュータはカメラモジュールを備えています。まず、そのカメラからの画像と、必要に応じて外部センサの値を入力用に取得します。それを学習済みの機械学習モデルに渡します。そのモデルからの出力を得て、それを元にステアリングとアクセルを制御します。

それにより、先ほどのベースに、以下の改造が施されます。

  • ボディ 取り外す
  • シャーシ
  • 送信機 カメラ付き小型コンピュータを車載
  • 受信機 サーボコントローラ
  • ESC
  • メインモータ
  • ステアリングサーボ
  • バッテリー(駆動用)
  • バッテリー(車載コンピュータ用) 追加

まず、部品のつながりは、どのように変わるのでしょうか。

image.png

Jetson Nano/ Raspberry Piなどの搭載コンピュータ (ざっくり言うと送信機の代替)

学習済みモデルを動作させ、カメラ入力を元に、アクセル・ブレーキ・ハンドルを制御します。モデルの学習は、同コンピュータ上で行うこともありますし、外部クラウドで行なった上で、学習済みモデルを再度コンピュータ上に展開することもあります。

カメラや、その他のセンサ

搭載する小型コンピュータに対応するカメラモジュールを使います。Raspberry Pi用の広角カメラモジュールが、Raspberry PiだけでなくJetson Nanoの場合にも使われます。また、より発展したプロジェクトでは、加速度、深度カメラ、LiDARなど、他のセンサを取り付けることもあります。

サーボコントローラ (ざっくり言うと受信機の代替)

PCA9685というチップを用いた、I2Cインタフェースを通して制御されるサーボコントローラを使うことが一般的なようです。I2Cについても、PWM同様ここでは詳しい解説はスキップし、後に譲ります。

バッテリー(車載コンピュータ用)

駆動用のバッテリーに加えて、車載コンピュータ用の追加バッテリーが必要です。

それ以外にも、以下が必要です。

ラップトップPC

学習やテスト走行時に、WiFi経由でJetson Nano/ Raspberry Piなどの搭載コンピュータへ接続し、様々な操作を行います。

ジョイスティック

学習時の操作に、ジョイスティックを用いて、より直感的・便利に行うこともあります。

さて、これらを一から揃えるのは大変だと思われるかもしれません。そんなみなさんには、キットの形で販売されています。DonkeyCarのフルセットは4万円程度で揃えることができます。せっかくなので、自分で揃えてみたい、既存のRCカーを改造してみたい、という方はいかがでしょうか。BOM(Bill of Materials)という形で、どういった部品を買う必要があるか、目録が整理されています。それに従って購入し、組み上げると、1-2万円であなただけの自動運転車を組み上げることができます。

機械学習と自律走行

まず、機械学習は万能ではありません。従来の制御の仕組みと、機械学習を組み合わせて使います。RCカーでは、少しエクストリームな方法を試しているのだと理解してください。認知、判断、操作に対して、それぞれのAI RCカーでの各モデルの使い方をマッピングしてみましょう。

image.png

世界のAI RC Carプロジェクト

ここで、様々な自動運転ラジコンカープロジェクトを紹介します。先ほど挙げたように、自律走行を行えるカーは、エントリーレベルのものから、本物の自動運転に近い構成まで、様々なものがあります。

今回のAI RC Car Advent Calendar 2019では、主にDonkeyCarとJetRacer関連の記事が紹介されています。

DonkeyCar

DIY文化の旗手であり、米WIRED誌の元編集長Chris Anderson氏が、2016年12月にDIY Robocarsというコミュニティを立ち上げました。自作・自動操縦ラジコンカーレースを開催し、コミュニティでは盛んに知見が共有されています。Donkey Carはそこから生まれた、自動運転ラジコンのオープンソースプラットフォームです。ハードウェア、ソフトウェア両方が公開されています。

TensorFlowをRaspberry Pi上で(Jetson Nanoにも対応)動かし、市販のラジコンカーをオンボードカメラ映像を頼りに走らせられます。

image.png

https://www.donkeycar.com/ より引用

NVIDIA JetRacer

2019年3月に発表されたJetRacerは、NVIDIA Jetson Nanoの教育・コミュニティ活動促進の一環でオープンソース化されました。他にもJetBotなどが公開されています。2種類のシャーシが選べますが、日本で取り組む場合は、手に入りやすいTT-02というタミヤの初心者向けフレームを使います。

image.png

https://github.com/NVIDIA-AI-IOT/jetracer/ より引用

AWS DeepRacer

AWS Re: Inventで発表された。AWS SageMakerなどのMLaaSに親しみ、オンライン・オフライン両方のリーグに参加できます。完成機体がAmazonから販売される予定がありましたが、日本では技適関連でまだ売り出されていません。

2019年12月のAWS re:Inventでは、2つのニュースがありました。

ひとつは、アマゾンの自動運転「DeepRacerリーグ」で日本人が“圧倒的”世界トップになった理由の記事にあるように、DeepRacerのファイナル開催と、日本人ワンツーフィニッシュです。

また、DeepRacer Evoが発表されました。新たにステレオカメラ、LIDARを搭載し、2020年に発売予定です。新しい車体とセンサーで、他車との抜きつ抜かれつ、といったレースが今後展開されるかもしれません。

image.png

https://www.amazon.com/dp/B07JMHRKQG より引用

MIT Racecar

MIT Racecarは、もともとマサチューセッツ工科大学(MIT)におけるロボティクス授業で開発されました。先に紹介したDonkeyCarやDeepRacerなどと比べ、以下の点で、より実車の制御に近い構成を取っています。

  • アーキテクチャとして、ROSを採用
  • センサーは、単眼カメラだけでなく、LiDARやステレオカメラを搭載できる
  • 未知の場所の地図を生成しながら、一定のルールに沿って走れる(DonkeyCarやDeepRacerは、決まったコースを教えこむ)

DonkeyCar、DeepRacerと比較して、より「シリアスな」プロジェクトといえるでしょう。外見こそ違いますが、同じ技術は実車の自動運転だけでなく、ルンバなどのお掃除ロボット、倉庫などでの荷物の自律搬送車で使われます。

MIT Racecar関連の資料は、上のWebサイトや、ICRA2019のワークショップサイトに公開されています。派生のプロジェクトとして、F1/10(F1tenth)が挙げられます。

image.png

https://www.jetsonhacks.com/racecar-j/ より引用

F1tenth(F1/10)

F1/10は、ペンシルバニア大学(UPenn)で、MIT Racecarの派生として生まれ、今はプロジェクトを率いたMadhur Behl氏が移ったことから、バージニア大学でも教育の一環で使われています。1/10スケールのラジコンカーを用いて、F1のようにレースを行う、というのが名称の由来のようです。インターナショナルな、F1/10カーによるレースコンペも主催しています。また、MIT Racecar同様、授業の資料や、レースコンペの模様などが公開されており、良いリソースです。

M5Stackを使ったAI Car

通常サイズのRC Carだけではありません。M5自動運転カー - Qiita
にあるように、卓上、極小AI Carを作るプロジェクトもあります。

(参考)Autoware

自動運転は、様々な車メーカーに閉じられて開発されているものだけではありません。名古屋大学発のスタートアップであるティアフォーは、Autowareという自動運転OSSを開発しています。実際にこれらの車に積載し、実証実験を行なっています。

image.png

https://tech.tier4.jp/ より引用

Autowareというソフトウェアは、オープンソースであるため、個人で実際に触って動かしてみることができます。

  • アーキテクチャとして、ROSを採用
  • センサーは、単眼カメラだけでなく、LiDARやステレオカメラを搭載できる
  • 未知の場所の地図を生成したり、既存の地図を参照しながら、一定のルールに沿って走れる

といった形で、MIT Racecarと似ています。実際にラジコンカーに載せてみた例が、インターネット上の記事にありました。

それぞれの違いと共通点

それぞれ扱う車体の大きさやアプローチも異なり、別のものに見えるかもしれませんが、実現を支える技術、アーキテクチャは似ています。

車やステアリングなどの「動かす」要素、カメラやLiDARなどの「センサー」要素が、それぞれ情報をやりとりし、理想的な経路を辿れるように制御します。そのためには、それぞれのノードが、リアルタイムでメッセージをやりとりします。ただし、メッセージングの提供方法は、ROSに頼ったり、それぞれのソフトウェアで独自に実装したり、異なったアプローチをとっています。

このように、AI RC Carから、実車の自動運転までのつながりを見ることができました。これならラジコンカーを動かしてみることで、自動運転の要素技術を体感しながら学べそうですね。

興味を持たれたら、各自充実した内容のAI RC Car Advent Calendar 2019から、次の一歩を進めてみてください。

初出について

本内容は、技術書典7『30日で動かして学ぶ! 自動運転』第0.8版より抜粋、加筆しました。2/28開催予定の技術書典8、また同時に電子版にて、さらにハンズオンなどを加えた第1版を頒布予定です。ご興味を持たれたら、ぜひご覧ください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

手認識が実用レベルに到達した件

MediaPipe(Google)のHandTrackingで、Issueで話題になっているジェスチャー実装について書きます。

こんな感じで動きました

百聞は一見にしかずということで先に検証結果です。
output.gif

MediaPipe

今年6月のCVPR2019でMediaPipeの発表があり、
そのアプリケーションのひとつとしてHandTrackingが公開されました。
MediaPipeはMLパイプラインと呼ばれるコードを書かなくても
ビジュアライズツールだけでMLアプリケーションを構築するフレームワークです。

image.png

ROSをやったことがある人はツールをイメージしやすいと思います。
HandTracking以外にも顔認識、ヘアセグメンテーション、物体認識に物体追跡のアプリケーションが公開しています。

HandTrackingのマルチプラットフォーム対応状況

下記の環境で動作します。

  • デスクトップ(C++)
  • iOS(ObjC)
  • Android(Java)
  • WebAssembly('19年度末に公開予定)

実際にデスクトップ(Linux)と、iOSで動作を確認しました。
HandTrackingを直ぐに試したい人はWebAssembly版のDemoが試せます。

このHandTrackingの何がすごいのか?

実際にHandTrackingが使えるレベルになるには認識精度と処理速度の両方の実現が必要です。
一般に認識精度と処理速度はトレードオフの関係で両立するのは大変です。

アプローチとして、手のひらの認識に絞ることで、指を含めるアプローチと比べアンカー(領域)の数を3~5分の1に減らし、かつ高精度を達成したそうです。
結果として、計算量を減らせ、高速でかつ高精度を達成できたそうです。

詳しくはブログを参照ください。
On-Device, Real-Time Hand Tracking with MediaPipe

手認識は何処で使えるか?

r-vte.gif

PCの画面操作、VR空間で使えます。

  • 仮想オブジェクトとのインタラクション
  • ジェスチャーベース制御インターフェース
  • 手話の理解

これらタスクに対応するには、ジェスチャーが必須です。
しかし、MediaPipeのHandTrackingではジェスチャーの実装予定はないそうです。

ジェスチャーを実装してみる

今回、Issue#40で紹介しているブログの距離索引の実装を試しました。

ブログでは全身の姿勢推定(PoseNet)で試していますが、ハンドジェスチャーでも使えるそうです。
実装は、ベクトルをスケールして、L2 normalizationし、コサイン類似度で求めるという内容です。
最後にVPTreeで探索しましたが。。。どうもうまくいきません。

もう一度HandTrackingの実装を思い出します。

今回の手認識はふたつのモデルが使われています。

  • 手のひら検出器モデル(BlazePalm) palm_detection.tflite
  • 手の目印モデル hand_landmark.tflite

処理手順としては、
1. 手のひらの方向と領域を識別
2. 方向に合わせ回転させた後、領域から手の目印を推論

「あー回転ね、回転したらいいのか」ということで実装したところ記事冒頭の検証結果となりました。

導入編

環境はMacで構築しました。
なお、Python版のHandTrackingですが、本家の実装ではないので精度はいまひとつです。
了承のうえお試しください。

導入に際してですが、MediaPipeではTensorflowを拡張した実装があります。
今後Tensorflowでも実装予定のようですが、ビルドが必要で導入が手間です。
なのでそこは飛ばしてカスタムしたモデルを導入しました。
palm_detection_without_custom_op.tflite

コマンドラインで構築

$ git clone https://github.com/metalwhale/hand_tracking
$ cd hand_tracking
$ wget https://raw.githubusercontent.com/wolterlw/hand_tracking/optical_flow/hand_tracker2.py

// 回転前の値を取得する為の変更点
$ diff -u hand_tracker2.py.1 hand_tracker2.py
--- hand_tracker2.py.1  2019-12-19 18:14:51.767858700 +0900
+++ hand_tracker2.py    2019-12-19 18:14:32.905475000 +0900
@@ -269,6 +269,7 @@
         Minv = np.linalg.inv(Mtr)
         kp_orig = (self._pad1(reg) @ Minv.T)[:,:2]
         hand['joints'] = kp_orig
+        hand['base_joints'] = reg
         return hand

     def __call__(self, img, hands=None):

$ wget https://gist.githubusercontent.com/otmb/d8837508d2694b11fbbda8229b9bb4ec/raw/5f8088239110549d0cab527699f8770b50990c87/hand_gesture.py
$ pip3 install opencv-python tensorflow
$ pip3 install scikit-learn
$ pip3 install vptree
$ mkdir gestures
// gesturesフォルダに適当なジェスチャ画像を追加後実行
$ python3 hand_gesture.py

iOSでジェスチャー導入したかったのですが今のところAPIから回転が取得できないようです。
Access hand landmarks position in iOS #237

おわりに

今回は、ジェスチャー実装を試しました。
ついにか、ようやくかHandTrackingが実用レベルに到達しました。
来年(2020年)は様々な活用が出てきそうですね!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

EchoStateNetworkをtf-kerasで書いてみた

深層学習以外の機械学習と応用技術 by QuantumCore Advent Calendar 2019では「Qore」APIを使った記事がほとんどですが、

②リザーバコンピューティングについてや深層学習以外の機械学習アルゴリズムについての紹介記事

でもOKとのことで、本記事ではリザーバコンピューティングの根幹技術であるEchoStateNetwork(ESN)を解説しながらtensorflow(-keras)で実装します。

「Qore」APIは自体は高性能なようで非常に興味はありますが、やはり得体の知れないもの(愚生視点)を使うのは不安なので、自分でスクラッチ実装してみることで基本の理解を試みます。ちなみに、すでにgithubにはESNの実装がごまんとupされており、tensorflowによる実装も何番煎じが分かりませんが、ただtf.kerasでの実装はまだないかと思います。多分。

ESNCellの解説と実装

ESNはRNNの1種でもあるため、 tf.keras.layers.RNNにESN用のCellを投げることで容易に実装出来ます。
そこで、Cellの部分の実装の紹介と簡単に解説したいと思います。
最初に、ESNCellのコードはこちら。

class ESNCell(tf.keras.layers.Layer):
    """
    ESN single cell for "tf.keras.layers.RNN".
    """

    def __init__(self, units, sr_scale=1.0, density=0.2, leaking_rate=0.9, **kwargs):

        def _W_initializer(shape, dtype=None, partition_info=None):
            w_init = tf.random.normal(shape, dtype=dtype)
            mask = tf.cast(tf.math.less_equal(tf.random.uniform(shape), self._density), dtype) #sparse 0-1 matrix
            w_init_sparse = w_init * mask
            Eigenvalues_w_init, Eigenvectors_w_init = tf.linalg.eigh(w_init_sparse)
            Spectral_radius = tf.math.reduce_max(tf.abs(Eigenvalues_w_init))
            w_init_sparse_r = w_init_sparse * self._sr_scale / Spectral_radius #normalization based on Spectral_radius
            return w_init_sparse_r

        self.state_size = units
        self._sr_scale = sr_scale
        self._density = density
        self._leaking_rate = leaking_rate
        self._W_initializer = _W_initializer
        super(ESNCell, self).__init__(**kwargs)

    def build(self, input_shape):
        self.W_in = self.add_weight(shape= (input_shape[-1], self.state_size),
                                    initializer=tf.random_normal_initializer,
                                    trainable = False,
                                    name='W_in')
        self.W = self.add_weight(shape=(self.state_size, self.state_size),
                                 initializer=self._W_initializer,
                                 trainable = False,
                                 name='W')
        self.b = self.add_weight(shape=(self.state_size,),
                                 initializer=tf.random_normal_initializer,
                                 trainable = False,
                                 name='Bias')
        self.built = True

    def call(self, inputs, states):
        x_n_1 = states[0]
        x_tilda_n = tf.math.tanh(tf.tensordot(inputs, self.W_in, axes=1) + tf.tensordot(x_n_1, self.W, axes=1) + self.b)
        x_n = (1 - self._leaking_rate) * x_n_1 + self._leaking_rate * x_tilda_n
        return x_n, [x_n]

RNNが分かっている前提でお話します。
ESNのセルが持つ重みはRNNと同様、入力用の重み$W_{in}$と、状態の重み$W$およびバイアス$b$です。
tf.kerasではまずdef build(self, input_shape):の部分でこれらを定義します。
ここで重要なのが

  1. 重みは初期化の後はずっと固定で、学習はされない
  2. 状態重み$W$はSpectralRadiusが1以下のスパースな行列(ただし経験的に1以上でも機能する)

の2点です。
前者の方はtrainable = FalseすればOKです。
後者を満たすため、_W_initializerを自作しています。

_W_initializerでは、まずランダムな行列w_initを作り、密度がdensityな0-1スパース行列maskを作ります。それらをドット積でかけることで、スパースな行列w_init_sparseが完成します。さらに、これのSpectralRadiusを求めます。SpectralRadiusは「固有値の絶対値の最大値」で求まります。最後にw_init_sparseをSpectralRadiusで割れば、SpectralRadiusが1のランダムスパース行列の完成です。もちろんここから任意の値sr_scaleをかけてやれば、SpectralRadiusがsr_scaleとなるランダムスパース行列が出来ます。

最後に、RNNの計算の部分def call(self, inputs, states):です。ここでは、次の式に基づいて計算しています。

時刻$n$のときの状態ベクトルを$x(n)$とし、入力を$u(n)$とすると、状態ベクトルの更新式は

$\tilde{x}(n) = tanh(W_{in}u(n) + Wx(n-1) + b)\tag{1}$
$x(n) = (1-\alpha)x(n-1)+\alpha\tilde{x}(n)\tag{2}$

$\alpha$は漏れ率です。特に気にせず1でもなんでもいいです。
計算後、RNN同様状態ベクトルが出力および次の時刻のセルへ渡されます。

以上が実装の解説です。前述した通り、上記のコードでクラスを定義したらtf.keras.layers.RNNにESN用のCellを渡すだけです。あとはRNNと同じです。

例えば、単純な回帰問題の場合は

$y(n) = W_{out}x(n) + b_{out}\tag{3}$

esn_cell = ESNCell()
x_n = tf.keras.layers.RNN(esn_cell)(u_n)
y = tf.keras.layers.Dense(1)(x_n)

こんな感じです。

学習について

式(3)の回帰問題を考えます。
ESNCell内の重みは学習されないので、$W_{out}$のみを更新すればよいことになります。
すると、学習は単なる線形回帰問題です。
バッチ学習であれば線形回帰、リッジ回帰。
オンラインで学習する場合はオンライン用の線形回帰(LMSなど)です。
また、線形回帰問題なので学習は爆速です。

動作確認

上記の回帰問題をESNで解くものをscikit-learn風のクラスで実装しました。
学習はTikhonov正則化(リッジ回帰)で実装してます。
コードはこちらです。
以下、そこのexample.ipynbの内容となります。

では早速動かしてみましょう。
下図のようなカオス単変量時系列データにおいて、16点の部分時系列から次の1点を予測する問題を考えます。
data.png
ちなみにこれは「Pythonでカオス・フラクタルを見よう!」で紹介されていたDuffing振動子の時系列データです。データ生成もこちらのコードからお借りしています。

import tensorflow as tf
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from model import EchoStateNetwork

#get Duffing time series data
data = np.loadtxt("DuffingOscillatorData.txt")

plt.figure(figsize=(16,8))
plt.plot(data)
plt.xlim(0, len(data))

それでは、窓幅16でデータを作成します。

window_size_d = 16
pred_length_l = 1

def create_sliding_window_data(X, window_size, pred_length):
    data = [X[i:i+window_size] for i in range(len(X)-window_size-pred_length)]
    target = [X[i+window_size:i+window_size+pred_length] for i in range(len(X)-window_size-pred_length)]
    return np.array(data,dtype=np.float32), np.array(target,dtype=np.float32)

X_windows, y_windows = create_sliding_window_data(data,window_size_d,pred_length_l)
X_train, X_test, y_train, y_test = train_test_split(X_windows, y_windows, test_size=0.8,shuffle=False)
X_train = np.expand_dims(X_train,axis=2)
X_test = np.expand_dims(X_test,axis=2)

後はデータをモデルに入れるだけです。
scikit-learnに慣れてる人なら一目瞭然です。

#Run ESN
ESN = EchoStateNetwork(units=32)
ESN.fit(X_train, y_train)
print("Train MSE:",ESN.MSE_Score(X_train, y_train))
print("Test MSE:",ESN.MSE_Score(X_test, y_test))
y_test_hat = ESN.predict(X_test)

結果を可視化するとこうなりました。
result.png

緑の部分が学習範囲です。難解なカオスな時系列を、たったの300点しか学習していないのに、凄まじい精度で予測出来ていますね。

まとめ

本記事では、リザーバコンピューティングの基本技術であるESNについて、解説と実装を紹介しました。
難解なカオス時系列での実験では驚くほどうまくフィットし、リザーバコンピューティングのポテンシャルの大きさを認識しました。「QuantumCore社」の今後にも期待が高まります。
なお、本記事のコードは全てこちらにアップしております。

文献

M. Lukos̆evicius, A Practical Guide to Applying Echo State Networks, Neural Networks: Tricks of the Trade, Lecture Notes in Computer Science, vol. 7700.

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む