masato-ka's diary

日々思ったこととか、やったことの備忘録。

データドリブンなLチカという新時代へのパラダイムシフトをRaspberry Pi picoで体験する。

この記事について

 データドリブンなLチカという言葉をご存知だろうか?実はこの記事のために作った造語だ。今回はデータドリブンなLチカの意味ついて説明する。さらにRaspberry Pi picoを使ってTensorflow Lite for MicrocontrollerのLチカデモを実行する方法について説明する。データドリブンなLチカって言ってみたかっただけなのでネタ半分で読んで欲しい。そして、Raspberry Pi picoが積み基板化している方にぜひチャレンジしていただき、新時代へのパラダイムシフトを体験することを願っている。

f:id:masato-ka:20210214113158p:plain
Raspberry Pi pico

Lチカとは(おさらい)

 Lチカとはマイコンに接続したLEDを点滅させるHelloWorld的なプログラムのことだ。sleep命令やタイマー割り込み、PWM出力といったマイコンの機能を使い倒してLEDの点滅間隔をプログラムする。例えば蛍のようなゆっくりとした点滅パターンを作りたい場合、sin関数を使ってPWMの出力を変化させる。

Lチカの明滅パターンをデータで定義する

 データドリブンなLチカはLEDの点滅パターンをデータによって定義する。先ほどの例と同様の点滅を作る場合はsin関数の出力をデータとして記録する。その記録したデータを学習させたニューラルネットワークを使ってPWMの出力を変化させる。

f:id:masato-ka:20210214113705p:plain
普通のLチカとデータドリブンなLチカの違い

後述するデータドリブンなL チカのデモを実行した様子がこちら。見た目は普通のLチカと違いはない。

上記ツイートの例だと実行速度がはやく普通の点滅に見えるため、sin波の変化がわかりやすいように調整してみた。


Raspberry Pi pico led blink with neural network that approximates a sine wave.

データドリブンなLチカが示すパラダイムシフト

 データドリブンなLチカの利点は点滅パターンを作り出す関数を定義せずともその点滅を実装できることだ。ニューラルネットワークはデータさえあれば任意の関数を近似(まね)できる特性がある。この特性を利用することで、データから任意の点滅を実装することができる。元の関数が不明でも問題ない。

 つまり、データドリブンなLチカは実装という作業を「アルゴリズムを考えてコードに落としこむ」から「アルゴリズムを定義するデータを集める」に変化させたのだ。処理の実装ではなくデータの収集、これがデータドリブンなLチカが示すパラダイムシフトだ。

f:id:masato-ka:20210214115812p:plain
データドリブンなLチカが示すSoftware2.0の到来

 このように処理を記述するプログラミングに代わって、データによって機能を定義するソフトウェア開発をSoftware2.0と呼ぶ。マイコンの世界もついにSoftware2.0が到来したというわけだ。

 sin波のような単純な関数では旨味はないが、一見、不規則に見えるような点滅など複雑な点滅を実装する場合はどちらが良いだろうか。

Raspberry Pi picoでデータドリブンなLチカを実行する。

 実際にデモを体験してみよう。Raspberry Pi picoでデータドリブンなLチカを実現するためにはニューラルネットワークを実装する必要がある。Raspberry Pi picoではTensorFlow Lite for microcontroller(以下TF Lite micro)という、TensorFlow の組み込み向け実装がポーティングされている。  

 前出のLチカ動画はTF Lite microのhello_worldデモを実行したものだ。このサンプルではsin関数を学習したニューラルネットワークを使い、LEDの点滅を制御している。今回はこのデモの実行方法を説明する。

github.com

事前準備

公式のGetting Startedドキュメントを参考にコンパイラ、cmakeといったツールチェインとpico-sdkをダウンロードしておく。pico-sdk環境変数PICO_SDK_PATHにパスを通しておく。わからない場合はぐぐると丁寧に解説した記事が出てくるのでそちらを参考にして欲しい。

続いて、TF Lite microのリポジトリをローカルにクローンしておく。

>git clone https://github.com/raspberrypi/pico-tflmicro.git

hello_worldサンプルの修正

2021年2月14日現在、pico-tflmicroのhello_worldのコードはそのままでは動かない。ビルドするとタイポに起因するエラーが発生する。pico-tflmicro/example/hello_world/rp2/output_handler.cppを適当なエディタで開き修正する。

  • 34行目
pwm_clear_irq(pwm_gpio_to_slice(PICO_DEFAULT_LED_PIN));
pwm_clear_irq(pwm_gpio_to_slice_num(PICO_DEFAULT_LED_PIN));
  • 41,42行目
reset_block(RESETS_RESET_PWM_RST_N_BITS);
unreset_block_wait(RESETS_RESET_PWM_RST_N_BITS);
reset_block(RESETS_RESET_PWM_BITS);
unreset_block_wait(RESETS_RESET_PWM_BITS);
  • 45,47行目
// Tell the LED pin that the PWM is in charge of its value.
gpio_funcsel(PICO_DEFAULT_LED_PIN, GPIO_FUNC_PWM);
// Figure out which slice we just connected to the LED pin
pwm_inst_t slice = pwm_gpio_to_slice(PICO_DEFAULT_LED_PIN);
// Tell the LED pin that the PWM is in charge of its value.
gpio_set_function(PICO_DEFAULT_LED_PIN, GPIO_FUNC_PWM);
// Figure out which slice we just connected to the LED pin
uint slice = pwm_gpio_to_slice_num(PICO_DEFAULT_LED_PIN);
  • 52,54行目
// Mask our slice's IRQ output into the PWM block's single interrupt line,
// and register our interrupt handler
pwm_clear_irq(slice);
pwm_enable_irq(slice, true);
irq_set_exclusive_handler(PWM_IRQ_WRAP, on_pwm_wrap);
irq_enable(PWM_IRQ_WRAP, true);
// Mask our slice's IRQ output into the PWM block's single interrupt line,
// and register our interrupt handler
pwm_clear_irq(slice);
pwm_set_irq_enabled(slice, true);
irq_set_exclusive_handler(PWM_IRQ_WRAP, on_pwm_wrap);
irq_set_enabled(PWM_IRQ_WRAP, true);
  • 60行目
pwm_config_divider(&config, 4.f);
pwm_config_set_clkdiv(&config, 4.f);
  • 85行目

エラーは出ないが、LEDの点滅速度調整のため、修正する。

sleep_ms(10);
sleep_ms(50);

hello_worldサンプルをビルド

以下のコマンドでサンプルをビルドする。

>cd pico-tflmicro
>mkdir build
>cd build
>cmake ..
>cd example/hello_world
>make -j4

ビルドが終わったらRaspberry Pi picoをPCにつなぎ、hello_world.uf2をコピーする。しばらくするとRaspberry Pi picoの基板に乗っているLEDが点滅し始める。

ようこそ、データドリブンなSoftware 2.0の世界へ。

まとめ

 データドリブンなLチカでは、プログラムではなくデータによってLチカの点滅パターンを定義することができる。さらに、Raspberry Pi picoはTF Lite microのデモを実行することで簡単にこのデータドリブンなLチカを体験することができる。

 Lチカだけでなく、センサの値を入力としたり、出力をモータに変えてみたりと、データドリブンな処理定義はその応用範囲が広く、マイコン工作の可能性を広げてくれる。TF Lite microの使い方やそのほかの応用事例は以下の過去記事を参照して欲しい。

masato-ka.hatenablog.com

masato-ka.hatenablog.com

DockerだけでROS2を開発できるコマンドラインツールを作った

1.この記事について

DockerだけでROS2の開発をするコマンドラインツールを作ったので、その使い方などを簡単にまとめた記事になる。

こんなコマンドが公式であったら嬉しいけどもっと賢いベストプラクティスがきっとあると思うので教えてほしい。

demo

2. モチベーション

次に計画しているプロジェクトに向けて、ナウでヤングなROS2に手を出してみた。インストールは簡単になっているが、多くの依存関係をローカルにインストールしたくない。AWS RoboMakerも検証してみたが、簡単にROS環境が手に入るとはいえ、インスタンスの起動時間が気になってしまう。そこでローカルに環境は構築せずに、DockerのみでROS2開発ができる環境を考えてみた。

3. DockerによるROS2開発で目指したこと。

Dockerを使ってROS2を動かす方法は種々紹介されているが、自分が目指した条件は以下の3点だ。大きな思いとしては可能な限りローカル環境で開発する場合と違いを出したくないということ。

  1. ROSのパッケージはローカル環境に置きたい - ローカルIDEなどリッチな環境を使いたい。
  2. 起動したコンテナは使い捨てる - コンテナを保持して管理する手間を増やしたくない。
  3. ネイティブと同じようなGUI環境(Gazebo, Rviz2)を起動できる - いちいちデスクトップ表示させてそこから起動とかしたくない。

4. 作ったツールの紹介

前述の条件をみたすROS2コンテナの使い方を検証して、コマンドラインツール化した。以下のリポジトリをクローンして、開発用Dockerイメージのビルド、そのイメージ名をros2-docker.shに指定してパスを通すだけで利用できる。

github.com

ros2-docker.shを使うことで、指定したローカルのワークスペースに対してros2コマンドやcolconを複雑な設定なしにDockerコンテナ内で閉じて実行することができる。任意のCI環境で使うとすごい便利じゃないかなと思ってる。

パッケージを作ってビルドしてみる。

新しくパッケージを作ってビルドする方法はこんな感じ。ローカルにROSのワークスペースを掘って、ワークスペース名を指定してcreateを実行するだけ。ros2-docker.shはrobo_wsをボリュームマウントしたdockerコンテナを作り、ros2 createに引数を渡して実行する。

>mkdir -p robo_ws/src
>ros2-docker.sh robo_ws create --build-type ament_python --node-name hello_node robo_pkg

これをビルド実行する場合はこのまま

>ros2-docker.sh robo_ws build
>ros20docker.sh robo_ws run robo_pkg hello_node

とすると、install/setup.shのsourceなど含めてよろしくやってくれる。

Moveit2のデモを動かしてみる。

もうちょっと複雑かつ、GUIの起動を伴うアプリケーションの場合をみてみる。

  • MoveIt2のサンプルを実行する場合

MoveIt2のリポジトリ群をvcsコマンドでチェクアウトしてくる。ちなみにvcsコマンドの実装をサボったため、 チェックアウトしたフォルダを手動でsrcフォルダ配下へ移動させている。

>mkdir -p moveit_ws/src
>curl wget https://raw.githubusercontent.com/ros-planning/moveit2/main/moveit2.repos -O moveit_ws/.rosintall
>ros2-docker moveit_ws vcs
>mv moveit_ws/* moveit_ws/src

ros2-docker.shは必ずコンテナを捨ててしまうため、rosdepしてもリセットされてしまう。そこで-oオプションで新しいDockerイメージ名を指定して、依存関係を解決したDockerイメージを作成する。結局イメージは管理しないといけないけどそこは妥協。

>ros2-docker -t moveit_depends_image moveit_ws rosdep install -r --from-paths . --ignore-src --rosdistro foxy -y

依存関係を解決したDockerイメージ名を-iオプションで指定してビルドを実行する。ちなみに私のMacBook pro 2020(Core i5)だと3時間くらいビルドにかかった。

>ros2-docker -i moveit_depends_image moveit_ws build --event-handlers desktop_notification- status- --cmake-args -DCMAKE_BUILD_TYPE=Release

run_moveit_cpp.launch.pyを実行する。実行時にros2-docker.shへ-gオプションを渡すとX Windowの仮想ディスプレイを立ち上げて、VNCサーバーを起動する。適当なVNCツールを使ってlocalhost:5900版へアクセスする。

>ros2-docker -i moveit_depends_image -g moveit_ws launch run_moveit_cpp run_moveit_cpp.launch.py

VNCで黒い画面しか表されない場合は画面の表示位置がおかしいので、moveit_ws/install/run_moveit_cpp/share/run_moveit_cpp/launch/run_moveit_cpp.rvizをローカルのエディタで開いてWindow GeometryのXとYの値を0に設定する。

5. 現段階の制限事項

制限事項として以下3点、今後順次対応する予定でいる。

  1. 現段階だとDockerコンテナからハードウェアへアクセスする種々の設定はできない。
  2. vcsコマンドはワークスペースディレクトリ直下の.rosinstallファイル決め打ちで処理する
  3. デバッガなどは使えない。

6. まとめ

ROS2はroscoreの廃止やノード間の通信を自己解決してくれるので、ROS1よりもDockerで扱いやすくなっている。とはいえ、本格的な開発をDockerでやるのは無理がある感もある。お試しで遊んでみたり、CIやテスト環境、本番環境へのデプロイなど重宝する場面はあるだろう。 もしこんな風にしたら?や、これやる必要ないよ、みたいなコメントがあると嬉しい。

WioLTEとEdgeAIのハンズオンしたいけどできないのでハンズオン資料だけ公開する。

この記事について

この記事はSORACOM アドベントカレンダー4日目の記事です。8月ごろにWioLTEとEdgeAIを使ったハンズオンを企画しようと思いたち、資料を作成していました。残念なことに私的な事情により身動き取れず頓挫した次第です。ハンズオン自体は暖かくなってきた頃にもう一度企画してみたいと思いますが、ハンズオン資料だけ先に公開しておきます。

ハンスオンの内容について

 内容はこのブログの1つ前の記事で書いた内容です。「ブザーのON/OFFをWIoLTE上で処理されるニューラルネットワークで判定します。」詳しくは過去記事を参照してください。

masato-ka.hatenablog.com

 ハンズオンの狙いはセンサの信号処理と、WioLTEで動かすニューラルネットワーク=Edge AIの使い方をマスターしてもらうことです。その裏で、「プログラミング」から「データを学習させる」のパラダイムシフトを体験してもらえればと思います。多分そんなこと考えて計画したはずです。かっこいいこと書きましたが特に考えずに面白いネタと思ってもらえれば大丈夫です。

ハンズオン資料について

ハンズオン資料はGItHubに公開しています。以下のリポジトリのREADMEがハンズオンの資料です。WioLTEやソラコムサービス設定の細かい部分はソラコムさんのハンズオン資料にリンクさせていただいてます。必要なソースコードと一緒にリポジトリをフォーク、クローンして使ってください。

github.com

機材は「Grove IoTスターターキット for SORACOMエディション」が必要です。SORACOMアカウントを持ってない場合は作成してください。

ハンズオン内容の応用

ハンズオンではブザーの発音を識別しますが、学習データをオリジナルに変えるだけで応用できます。例えば、扉の開閉や扇風機のON/OFFとその風量、掃除機の動作状況といった振動が発生するイベントを検知できます。ぜひ身の回りのイベント=振動を探してトライしてください。

まとめ

 この年末年始はどこにも行かないという方も多いのではないでしょうか。旅行や帰省の代わりにIoT+AIでお家開発合宿を楽しんでみるのはいかがでしょう。

 ハンズオン資料の通りに試してもうまく行かなかったよ、間違ってるよ、という箇所があればこのブログのコメント蘭やGItHubのISSUEまたはプルリクエストをいただければと思います。そしてハンズオンの内容が気に入った人はこのブログはもちろんのことGithubリポジトリにスターつけていただけるとモチベーションが湧いてきますのでお願いします。

WioLTEとTensorFlowで加速度センサの値を分析、SORACOM で可視化する。

1. この記事について

 加速度センサを使い、機器の状態を遠隔で監視することはIoTの一般的なユースケースの一つだ。ソラコムのサービスとWioLTEを利用することでそのようなユースケースを爆速で実装することができる。このユースケースを実現する際の課題の一つは加速度データの処理方法にある。この記事ではWioLTE上にディープラーニングによるAIを実装し、加速度からメタ情報を抽出する。抽出した加速度のメタ情報をSORACOM Harvestで可視化させてみる。

2. 時系列の加速度データ活用の課題

 加速度のような時系列のデータをモバイル回線を使ってクラウドへ転送すると通信量が高くなる。そこで、エッジ側で加速度の振動の変化や特定の動きといったユースケースに応じた「イベント」を検知して、必要なデータ、またはイベントをクラウドで通知する方法が取られる。  ユースケースに応じたイベントを検知するロジックはイベントが複雑になるにつれ、ルール作成の難易度が高くなる。さらにWioLTEのようなプロトタイピング用のマイコンへの実行も難しくなる。

3. WioLTEへのディープラーニング実装による課題の解決

 今回はWioLTEへディープラーニングによる加速度のイベント検知機能の実装を行った。

3.1 なぜディープラーニングなのか?

ディープラーニングを利用した理由は以下の3点になる。

1.ルール作成を自動化できる

 収集したセンサーデータを直接ディープラーニングへ学習させることで、期待するルールを高い精度で取得することが可能になる。もちろん、ディープラーニング特有の試行錯誤は必要になるが、複雑なイベントを検知する場合は人が人がルールを考える工程を省くことができる。

2.ディープラーニングコモディティ化

 TensorFlowといったディープラーニングの開発環境が成熟することによりライブラリの部品を組み合わせるビルディングブロックにより実装が容易になった。基礎知識の習得は必要だが、必要な情報もWEB上で簡単に入手できる。

3.Edge AIのコモディティ化

 マイコンディープラーニングを利用するためには別途マイコンへその計算処理を実装する必要があった。TensorFlow lite for Microcontrollers(以下TF Lite Microと略す)の登場により、クラウドで学習した結果を機械的マイコンへインポートするためのツールセットが整った。これによりWioLTEでのディープラーニング利用が可能になった。

3.2 EdgeAIを実現するTensorFlow Lite for microcontrollers

 TF Lite Microはマイコンディープラーニングモデルを読み込み実行するライブラリだ。現在はARM系のチップを対象としている。ESP32やSTM系のチップが載ったWioLTEなどのボードでArduinoのライブラリとして利用できる。

 TF Lite Microの仕組みは下図のようになっている。TensorFlowを使いクラウドで学習させたモデルをTensorFlow Lite形式に変換した後、xxdコマンドによりC言語のヘッダファイル形式としてモデルのバイナリデータを取得する。マイコン側のプログラムではTL Lite Microで出力したヘッダファイルを読み込み推論を実行するコードを記述する。

f:id:masato-ka:20200830113913p:plain
TF Lite Microを使ったEdge AI

 一般的にモデルのパラメータ数(=モデルのサイズ)が大きければ複雑なルールを学習できるが、TF Lite Microではモデルのサイズをマイコンのメモリ容量に収める必要がある。TensorFlow Lite形式への変換でモデルは圧縮されるが、元のパラメータ数が大きすぎるとTF Lite Microで動かすことができない。学習ではモデルパラメータ数を抑える形で目標とする推論結果の精度が出るように調整を加えていく必要がある。

実際の動作

 実際にWioLTEとTF Lite Microを使って加速度によるイベント検知機能を実装した。ブザーと加速度センサを貼り合わせてブザーON時とブザーOFF時の振動の違いをイベントとして検知する。振動を検知した場合はWioLTEのLEDが点灯する。

      

 動画の後半では手による不規則な振動を加えた場合でもLEDはブザーON/OFFに合わせて点灯している様子がわかる。事前にディープラーニングに学習させるデータにはブザーの振動以外の振動を混ぜている。そのためブザー以外の振動が入力されてもブザーのON/OFFを検知することができる。    検出したブザーのON/OFFをSORACOM Harvestに送ることでクラウド側で可視化させることができるようになる。

まとめ

 TF Lite Microを使い、WioLTE上にディープラーニングインポートして、加速度センサの情報から観測する対象の状態を認識させることができた。LTE回線でHarvest Dataを送ることで、クラウドでの可視化を行っている。機械の動作状態、歩行や姿勢などの人の状態など単純なセンサによるセンシングでは可視化が難しい。TF Lite Microのような低リソースなEdge AIを容易に実現できる環境が整い始めたことで可視化の難しい事象をSORACOMサービスで簡単に可視化できるようになる。

TensorFlow Lite for MicrocontrollersとESP32を用いた安価なAI自動運転の検証

1. この記事について

 DonkeyCarなど行動模倣(Behavior Cloning)を行うAI RC CARではカメラ画像を入力とした推論と走行が一般的だ。1。この場合、エッジ側にもそれなりの計算力が必要になり、実機ハードウェアの構成が高額になる。この記事ではカメラを使用せずに単純なセンサ情報のみで同様の仕組みを実現できることをシミュレーション環境と実機での検証を通して確認した。検証では3つの距離センサの値を入力とする3層のニューラルネットワークを用いて自動走行を実現した。さらに実機の検証ではTensorFlow Lite for Microcontrollers2とESP32を使い、安価な構成でニューラルネットワークによるラジコンカーの自動運転が実現できることを示した。

検証で作成したコードは以下のGithubリポジトリで公開している。

github.com

2. シミュレーションによる予備検証

まず初めに、実現の目論見を確認するため、シミュレーションによる予備検証を実施した。

2.1 シミュレーション環境

 シミュレーション環境は詳解確率ロボティクス3に記載の差動2輪ロボットのシミュレーション環境を参考に実装した。別途、コースをあらわす壁と距離センサを実装している。Pytorch版のシミュレーション環境はGistで公開している。TensorFlow版は前述のリポジトリから参照できる。実装フレームワークが違うのみでそれぞれに違いはない。

gist.github.com

 ロボットには3つの距離センサを設置した。それぞれのセンサは前方方向に1つとそれを原点として左右に60度開いて設置している。

 シミュレーションはデータ収集・学習・推論走行の3つのパートに別れている。「データ収集」では比例制御によりコースを自動走行する。比例制御は左右のセンサ値の差分を入力として操舵の制御値を決定する。また学習データとして観測されるセンサの値と対になる制御値の記録を行う。「学習」では学習データを元にニューラルネットワークを訓練を実行する。「推論走行」では学習したニューラルネットワークを使い自動走行を実行し、その評価を行う。

2.2 ニューラルネットワークモデル

シミュレーションで取得した学習データは以下の形でCSV形式で保存した。速度の値は学習データとして記録されるが固定値であるため意味を持たない。各センサの値入力として操舵角と速度を推定する回帰問題として学習させる。

左センサ値,中央センサ値,右センサ値,操舵角,速度

 定義したニューラルネットワークモデルは3層の全結合層から構成される。アクティベーション関数は1層目と2層目にReluを使用し、最終段には設定しない。また、Loss関数はMSEを設定している。以下、TensorFlowでの実装例を示す。

import tensorflow as tf

from tensorflow import keras
from tensorflow.keras import layers

model = keras.Sequential([
    layers.Dense(8, activation='relu', input_shape=(3,)),
    layers.Dense(8, activation='relu'),
    layers.Dense(2),
  ])

optimizer = keras.optimizers.SGD(lr=0.01, decay=1e-6, momentum=0.9, nesterov=True)
model.compile(loss='mse',
              optimizer = optimizer,
              metrics = ['mae'])
model.summary()

2.3 シミュレーション検証の結果

シミュレーションでは実行の様子を動画で確認できる。ここでは結果の画像を掲載する。画像中の円が自動運転車を表している。自動運転車から3本出ている赤い線がセンサの計測方向となる。観測されるセンサの値は各線と壁の接触点と自動運転車との直線距離となる。3000ステップの試行を行い、3000レコードのデータを集めた。

f:id:masato-ka:20200708130329p:plain
比例制御による自動走行(学習走行)

収集したデータを使い訓練したニューラルネットワークを比例制御と置き換えて自動走行を実行した。初期位置をX軸方向にずらして実行している。以下の画像を確認すると比例制御の動きと同じ軌跡で自動運転が行われていることが確認できる。線形制御の出力データからニューラルネットワークがその関数を「近似’している。

f:id:masato-ka:20200708130302p:plain
ニューラルネットワークによる自動走行

いずれも人を介在しない自動走行だが、前者は人が走行できるようにルールをコードベースに落とし込んでいる。しかし、後者はデータから自動的にルールを発見させているという点で大きな違いがある。

3. 実機による評価

 シミュレーションにおいて距離センサ3つの入力のみで行動模倣を実現できることがわかった。実機へも適用できるか検証を実施した。

3.1 検証車両の紹介

 今回実機検証の車両としてRumiCarというオープンハードウェアの車両を用いて検証を行なった。RumiCarは安価なラジコンカーを改造し、ESP32と距離センサVL53L0Xを3つ搭載している。ベースとなるラジコンカーの制約としてスロットルは全身、後進、停止、ステアリングは直進、右、左という離散的な制御しかできない。

www.rumicar.com

3.3 実機に合わせたニューラルネットワークの変更とESP32へのデプロイ

ニューラルネットワークの構成をシミュレーションから実機に合わせて変更した。センサの値から「直進」「左」「右」の3つのクラスを推論するよう分類問題として学習させる。

import tensorflow as tf

from tensorflow import keras
from tensorflow.keras import layers

model = keras.Sequential([
    layers.Dense(8, activation='relu', input_shape=(3,)),
    layers.Dense(8, activation='relu'),
    layers.Dense(3, activation="softmax"),
  ])

optimizer = keras.optimizers.SGD(lr=0.01, decay=1e-6, momentum=0.9, nesterov=True)

model.compile(optimizer='adam', 
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])
model.summary()

訓練後のモデルをTensorFlow Lite for microcontrollers向けに変換する。TensorFlow Lite形式で保存したモデルをxxdコマンドを使い、バイト配列として出力する。出力されたバイト配列をファームウェアの一部としてESP32といった低リソースのマイコンへデプロイする。TensorFlow Lite for microcontrollersの詳細な利用方法については公式を参照[^2]。

converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
tflite_model = converter.convert() #量子化はせずFP32のまま処理していことに注意!
open("converted_model.tflite", "wb").write(tflite_model) #TFLite形式で保存


!apt-get install xxd
!xxd -i converted_model.tflite > airc_model.cpp  #xxdコマンドでモデルをC言語で参照可能なバイト配列として出力する。

TensorFlow Lite microcontrollersはCPUやフレームワークといった利用する組み込み環境に合わせて自分でビルドする必要がある。Arduino フレームワークとESP32の組み合わせはビルド済みのライブラリがリリースされており、Arduino IDE, PlatoformIOのライブラリ管理から「TensorFlowLite_ESP32」でインストールできる。今回はこちらのライブラリを利用して実装を行なった。

github.com

詳細な実装コードついては冒頭のリポジトリを参照してほしい。

3.4 実機検証結果

 実機の検証においても比例制御による自動走行で学習データを収集した。前述の制御の制約に合わせるため出力結果をルールベースで各命令に対応させている。ニューラルネットワークの訓練と形式の変換はGoogle Colaboraotryで行い、ESP32へプログラムとともに書き込む。

 以下が実機検証の動画である。コースは紙を山折りにした物を円形に配置し作成した。実機でもニューラルネットワークがセンサのデータから制御値を推論して走行できていることがわかる。

youtu.be

4. まとめ

この記事では距離センサ3つのみという構成で、ニューラルネットワークによる自動走行が可能であることをシミュレーションを通して検証した。また、TensorFlow Lite for microcontrollersを利用することで、ESP32を搭載した実機のラジコンカーで自動走行が可能であることを検証した。今回は単純な制御則でも十分に制御できるが、コースを逸脱しないように走行するという抽象的表現を人がプログラミングでルールとして落とし込むのではなく、データから演繹的に自動的にルールを作り出せること、さらにそれを「マイコンレベルで実現できる」という点が非常に面白い。今後マイコンを利用したセンサデータと複雑なルールの組み合わせが単純だが高度なことができるデバイスの登場を予感させる。

Jetson Nanoで動く深層強化学習を使ったラジコン向け自動運転ソフトウェアの紹介

この記事について

 深層強化学習を利用してAI RC Carの走行を学習させるソフトウェアをGithub上で公開しています。learnign_racerと命名しました。2020年4月29日の時点でのバージョンはv1.0.0です。この記事ではこのソフトウェアについて紹介していきます。

github.com

なお、NVIDIAのJetson Community Projectsのページでも紹介されています。

developer.nvidia.com

learning_racerの概要

 このソフトウェアは10分から15分程度の学習時間で、コースに沿って走行する方法を獲得することができます。従来のAI RC Carでは人間のお手本操作をDNNに覚えこませる手法が一般的です1。ですが、本手法は教師なし学習の手法である強化学習をベースとしているため、教師データとなる人間のお手本操作は必要ありません。さらにJetson Nanoを搭載したJetBot2やJetRacer3といったAI RC Carに対応しています。下の動画は実際にJetBotで学習させた様子です。


Deep reinforcement learning with Jetbot( on the Jetson nano) in real world.

さらに、実機だけでなく、AI RC CarのシミュレータであるDonkeySIMを使い、WindowsUbuntu, macOSといったプラットフォームで実行することが可能です。そのため実機がなくてもこのソフトウェアを利用することが可能です。


Deep reinforcement learning with DonkeySIM.

 深層強化学習をエッジで学習させる仕掛け

 今回の手法はイギリスのベンチャーWAYVE4の手法からAntonio Raffine5さんがさらに発展させたものです。私はこれをJetBotやJetRacerで動くように実装を行いました。この手法がエッジでの深層強化学習に適している工夫が行われています。

Learning to Drive Smoothly in Minutes - Towards Data Science

 DQNに代表される深層強化学習教師なし学習で様々なタスクを解くことができる手法として注目されています6強化学習に必要な関数を深層学習で近似したこの手法では都度得られるデータをもとにDNNをゼロから学習させます。そのため、学習にはそれなりのリソースが必要になり時間がかかると言えます。

 一方でJetson Nanoのような非力なエッッジ側でAIを学習するにはさらに多くの時間がかかります。そのため、あらかじめ学習したAIを少量のデータで学習し直し目的のタスクに適応させる、転移学習と呼ばれる手法を利用することが一般的です7。しかし、今回の深層強化学習では転移学習の元となるモデルを用意することができません。

 そこで今回は2つの仕掛けを導入して、エッジでの深層強化学習を実現します。一つは深層強化学習の手法としてSAC(Soft Actor Critic)8を利用しました。もう一つの仕掛けはVAE(Variational Auto Encoder)を利用した状態表現学習(State Representation Learning)9です。それぞれについて説明します。

 平易に書くため、専門的には誤解が多いところがあるかもしれませんが、その場合はコメント欄で優しく指摘してください。より詳細な情報を知りたい場合はRaffineさんのMidiamを参照ください。

SAC(Soft Actor Critic)

強化学習は、多くの場合、シミュレーター上で試行を繰りすことで学習を進めていきます。エージェントと呼ばれるソフトウェアが、方策(Policy)と呼ばれるルールに従って、行動を行い、環境を変化させます。環境の変化から報酬を計算し、行動良し悪しを評価します。この結果得られたエージェントの行動、変化した環境の状態と報酬をデータとして集めて、行動全体の報酬の総和である価値が最大になるように方策を学習します。今回は実機のため、このエージェントのデータ集めは実時間で行わざる終えません。そのため、学習効率の良い、深層強化学習アルゴリズムであるSAC(Soft Actor Critic)を採用しました。

 SACはoff-policy型の深層強化学習のState of The Artな手法です。off-policyな方策型の深層強化学習は学習によって方策が変わっても過去のデータを活用することが可能です。そのため、新たな方策を学習しても少量の試行を行うだけで、過去に取得したデータと合わせて学習を行うことができます。そのため、効率的に方策の学習を進めることができます。

状態表現学習

 もう一つの仕掛けである状態表現学習(State Representation Learnig)は環境の状態をあらかじめ学習しておくことで、深層強化学習の学習効率を高める手法です。環境の状態は車両に搭載されたカメラで撮影した映像になります。状態表現学習を利用しない場合はこの画像を環境情報として深層強化学習の入力に与えます。画像という巨大な入力を学習する必要があるためマシンパワーが必要になります。しかし、状態表現学習を利用すると画像から圧縮した特徴を入力として利用することが可能です。今回は状態表現学習として千から1万程度の画像をVAEで学習しています。VAEの中間層から出力される潜在空間を32次元の特徴量としてSACへ入力にしています。下記画像の左側がカメラからの入力です。右側の画像は潜在空間からVAEが生成した画像です。潜在空間はカラーバーとして表示されています。

f:id:masato-ka:20200429143631p:plain
状態表現学習に使ったVAEの実行結果

 VAEの学習自体はGoogle Colaboratoryなどを利用して学習させています。集める画像の量が多いように感じますが、教師ありのAI RC CARよりもデータ量としては少なく、また教師データは必要ないため、収集は容易です10

 このようにエッッジでの学習が難しそうな手法でも、効率の良いアルゴリズムを採用することと、AIの学習全体を見直し、あらかじめクラウドで学習できる部分を切り出しておくことで、エッジ側で学習できるようになります。今回の場合は効率のよいSACの採用と深層強化学習の畳み込み層の計算をVAEとして切り出しクラウドであらかじめ学習しておくことがキモになります。 

ソフトウェアのセットアップ

続いて、learning_racerの利用方法を説明します。ソフトウェアはPythonで実装されpipコマンドでインストールすることができます。JetsonNanoでは環境固有の問題により、用意しているシェルスクリプトを利用してインストールします。

JetBot,JetRacer実機へのインストール

JetPack4.2以上のイメージではJetBotまたはJetRacerのソフトウェアを構築します。構築後、以下のコマンドでインストールします。

$ cd ~/ && git clone -b v1.0.0 https://github.com/masato-ka/airc-rl-agent.git
$ cd airc-rl-agent
$ sh install_jetpack.sh

DonkeySIMを用いたシミュレータ環境での実行

以下のサイトから環境に合わせてDonkeySIMをダウンロードします。Unityで作られた実行形式のバイナリです。

Releases · tawnkramer/gym-donkeycar · GitHub

以下のコマンドでインストールします。[choose platform]にはwindows, windows-gpu, osx, ubuntuのいずれか利用しているプラットフォームに合わせて実行します。windows-gpuを選択した場合、TensorFlow 1.15.0とPytorchが対応しているCUDA10.0がインストールされている必要があります。

$ cd ~/ && git clone -b v1.0.0 https://github.com/masato-ka/airc-rl-agent.git
$ cd airc-rl-agent
$ sudo pip3 install .\[choose platform\]

利用方法

詳細はこちらもREADMEを読んでいただければと思います。簡単に説明すると実機、シミュレータとも大きく3つのステップで学習します。

1. VAEモデルの作成

 まずはVAEモデルの学習を行います。実機やシミュレータを利用してコースの画像をくまなく撮影します。単純に1周するだけではなく、ジグザグ走行や、コースアウト、白線の上を走行など、様々なパターンを取得するのがコツです。学習の中でこのデータ集めが一番難易度が高い部分です。しかし、後段の深層強化学習の結果に大きく響くのでしっかりとデータを集めます。

 実機のデータに必要なツールはnotebooksフォルダに入っているためそちらを利用します。シミュレータの場合は画像を保存する機能があるため、そちらを利用します。学習用のJupyterノートブックもColaboratory向けに用意しています。

 VAEモデルの作成が面倒な場合は以下の学習ずみモデルも利用可能です。JetBotのデモでよく利用されるLEGO CITYの道路プレート用のモデルとDonkeySIMのGenerate-track-v1のモデルを用意しました。

2. パラメータ設定

基本的に、学習パラメータを変更しなくてもデフォルトのセッティングで学習はうまくいきます。その代わり機体に合わせて以下のパラメータを変更します。変更はconfig.ymlファイルに記載します。

パラメータ名 説明 設定の目安
MIN_STEERING ステアリング命令の最小値 -1のまま固定
MAX_STEERING ステアリング値の最大値 1のまま固定
MIN_THROTTLE 最小スロットル値 停止やバックしないようにJetBotやJetRacerの司令値の下限に合わせる
MAX_THROTTLE 最大スロットル値 JetRacerの場合は速度が出過ぎないように司令値に注意する
MAX_STEERING_DIFF 1ステップで変更できる最大ステアリング値 値を大きくすると大きく蛇行する0.35-0.2がベター

3. 深層強化学習の実行

 以下のコマンドで学習を開始します。<robot> はjetbot, jetracer, simのいずれかを指定します。また<device>はcpuかgpuを使っているマシンに合わせて指定します。シミュレータの場合はコマンド実行前に、シミュレータを実行しておきます。

$ racer train -vae vae.path -robot <robot> -device <device> -steps 10000 

 コマンド実行後しばらくすると車両が動き始めます。実機の場合はnotebookに入っているuser_interface_without_gamepad.ipynbを使い、車両がコースアウトする直前で停止させ、車両の位置を手で戻して、もう一度走行させます。

 シミュレータの場合は自動的に学習が進んでいくので、シミュレータの画面を眺めているだけでOKです。

まとめ

 この記事では深層強化学習を用いたAI RC Car向けのソフトウェアを紹介しました。紹介した内容としてはSACとVAEを組み合わせた深層強化学習の手法、実装したソフトウェアの構築方法と簡単な利用方法となります。現在、AI RC Carの世界では実機のレースはもとより、シミュレータを利用したバーチャルレースが始まっています。アメリカのAI Robocarコミュニティーが中心となり開催しています11。初代チャンピオンはなんと日本人のnaisyさんです。また、日本のAI RC Carコミュニティーでもすでにレースが開催されています12。今後このソフトウェアをもとにこういったレースへ参加していきたいです[]。

Variational Auto Encoderを利用したAI RC Carの教師なし学習の検討

この記事について

この記事はAI RC Car アドベントカレンダー22日目の記事です。今回はAI RC Carの教師なし学習について考察します。

AI RC CarのDLモデル学習とそれぞれの課題

AI RC Carが走行に利用する学習モデルは基本的に教師あり学習で行われます。AI RC Carから撮影したコースの画像と、それぞれの画像に紐づけられた教師ラベルです。この教師ラベルの良し悪しによってAI RC Carの走行特性が大きく変わります。例えばDonkeyCarのような模倣学習を行なった場合、ドライバーがコース上を綺麗に走行することで、推論走行時にコースを綺麗に走行できます。また、JetRacerのようにマニュアルでアノテーションを付ける場合はアノテーションの付け方によって走行時の特性が決定します。

 これらの教師あり学習に共通して言えることは、教師ラベルの付け方が最終的な走行性能に影響を当たるという点です。これはAI RC Carを扱う人がラベルの付け方を訓練してコツをつかむ必要があることを意味します。

 しかし、画像のみからコースを走行するために必要な情報を学習できれば、人によるラベル付けに影響を受けません。そこで、今回はラベルを必要としない教師なし学習でAI RC Carの走行用モデルを学習させる方法を検討します。

VAEによる教師なし学習

DLの教師なし学習のモデルとして、Auto Encoderと呼ばれるモデルがあります。今回はAuto Encoderの発展系のモデルであるVariational Auto Encoder(VAE)を利用します。Variational Auto Encoderそのものについての詳細は他の詳しいサイトを参照ください。

qiita.com

簡単にいうとAutoEncoderは与えられた画像と同じ画像を出力するように学習させたCNNです。ネットワークの構造は大きく3つに別れています。まずはじめにEncoder層です。この層は一般的なCNNと同様に、画像を畳み込みフィルタで圧縮します。次に、中間層と呼ばれる層ではEncoderで画像から圧縮された 特徴を保持します。最後の層はDecoder層ですこの層はEncoderと逆向きのネットワーク構造を持っています。中間層から受け取った特徴から元の画像を再構成します。学習はEncoderに入力した画像を同じ画像がDecoderから出力されるように学習を行います。このように学習すると、中間層の特徴変数(潜在変数)は元の画像の情報を十分に表現した特徴を抽出していると言えます。このAEを拡張したVAEでは中間層の潜在変数が正規分布に従うように正則化して学習を行います。実はこうすることで、潜在変数の個々の変数に意味を持たせることができるようです。

VAEによるAI RC Carのステアリング制御

VAEでAI RC Carのステアリングを制御する方法については以下の論文を参考にしました。この論文では実車の車載カメラから撮影した画像をVAEで学習を行い自動運転をするというものです。VAEの潜在変数のうち、道路の曲率を説明する変数を使いステアリングの制御に活用することを提案しています。実はこの論文の中ではステアリングの制御については詳細は説明されていません。ですので、ここから先はどのように実現するか検討する必要があります。

http://people.csail.mit.edu/rosman/papers/iros-2018-variational.pdf

本記事のゴールは、AI RC CarでもVAEを使いコース上のカーブの曲率を説明する特徴を抽出できることを検証し、それ以降は今後考えていきます。

VAEの実装と実験

VAEの実装はこちらのPytorchの実装を参考にしました。

github.com

ただし、このままの実装だとLoss関数が思わしくなかったため、Loss関数部分をこちらの実装に置き換えています。

www.sambaiz.net

ハイパーパラメータとして、バッチサイズは32、エポック数は300を与えています。画像サイズは64x64です。また、VAEでは潜在変数の数を決定します。潜在変数が少なければ表現力が失われ、変数が多くなれば変数を扱うときに不便です。今回は変数 5, 10,25で100エポックづつ学習させて再構成された画像を比較してみたところ、10程度で十分と考えました。かなり小さい画像ですが左が入力画像、右が再構成された(最終的に出力された)画像になります。

f:id:masato-ka:20191217000304p:plain
5変数
f:id:masato-ka:20191217000347p:plain
10変数
f:id:masato-ka:20191217000411p:plain
25変数

変数5では背景が多少ぼやけています。10, 25ではそんなに変化がないように見受けられます。

結果と考察

実際に1300毎程度の画像を300エポック学習させました。どのくらいが適正な画像枚数なのかわかりませんが、1300枚は学習データとしては少ない枚数だと思います。まずは学習後、実際に再構成された画像は以下です。これは学習ずみのVAEにコース画像を入力し、デコーダから出力された画像を並べたものです。かなりぼやけてはいますが、人目に見て、コースだということがわかるくらいには表現されています。まず学習としては成功と言えると考えられます。

f:id:masato-ka:20191217001223p:plain
再構成画像

続いて、各変数の値の意味を確認していきます。実は各変数がデコーダの出力を通ってどのように画像に影響を与えるかを解析的に求める方法はありません。(もし手法があるなら教えて欲しい)上記の参考文献でも各変数のうち1つのパラメータを変動させてデコーダ出力を観察すると記載されています。 そこで、上記再構成画像の1枚めの画像の潜在変数をサンプリングし、10個の潜在変数をそれぞれ独立に-20か20の間で1づつ変化させた画像を作成しました。つまり画像の生成を行なっています。

f:id:masato-ka:20191217001741p:plain

小さい画像で見にくいですが、パラメータを変えることで、画像が変化していっていることがわかります。このうち、上から7列めの画像では右から左にかけて白線の傾きが変わっているように見えます。拡大して、GIF画像にしたのが以下のものです。

f:id:masato-ka:20191217232040g:plain

このように、白線の傾きを1つの潜在変数で再現することが可能となりました。同様に、白線が傾いて見えるカーブの画像を入れると、この変数の値は白線の傾き具合、つまりカーブの曲率を出力されると考えられます。これで画像のみからコースに関する知識を抽出することが可能となりました。

課題

今回はVAEを使い、コース画像のみから、教師データなどの事前知識なしに、どういった画像がカーブなのかというコースに関する知識を抽出することができました。しかし、これで実際にAI RC Carを走らせるには課題があります。

  1. 潜在変数の取り扱い

VAEでは学習を行うたびに潜在変数が表す値が変わってきます。これを固定する方法は現在のところありません。また、今回はたまたま曲率を表すパラメータを抽出できましたが、必ずカーブの曲率を学習するという保証もないと思っています。潜在変数の評価方法と、最適な潜在変数のパラメータ数を追求する必要がありそうです。

  1. 実機での制御

カーブの曲率を表す潜在変数を見つけたとしてここから制御値にどのように落とし込むかはまだ課題です。値の変化がカーブの曲率に対して比例していれば、PID制御など単純な制御でうまくいきそうな気もします。ただし、必ずしも潜在変数がカーブの曲率を表現する保証がありません。先行事例では潜在変数を使った強化学習により、制御を獲得する方法もあります。

https://towardsdatascience.com/learning-to-drive-smoothly-in-minutes-450a7cdb35f4

まとめ

今回はVAEによる教師なし学習でコースに関する知識を学習させることができました。今後は潜在変数による制御方法の獲得を目指したいと思います。