masato-ka's diary

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

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

JetBotのroad_followingを使いやすくする。

この記事について

この記事はAI RC Car アドベントカレンダー17日目の記事です。この記事ではJetBotのroad_followingのデモを実行しやすくするTipsを紹介します。

JetBotのroad_followingのデモ

JetBotの公式リポジトリに置いてあるJetBotのデモはゲームコントローラを使い、ブラウザ上に表示された画像上の座標を入力することでアノテーションを行なっていきます。このままでももちろんデモとして成功します。しかし、ゲームコントローラで画像上の狙った場所にカーソールを持っていくのは至難の業です。さらに、ゲームパッドで入力された座標を逐次Pythonインタプリタで処理しているため、ブラウザとJetBotが通信を行なっています。そのため、通信環境が著しく悪くなる走行会や展示会場では問題になります。

JetRacerのinteractive_regressionを利用する

interactive_regressionはJetRacer用のPythonノートブックですが、学習させる内容は全くな同じため、JetBotにも利用することができます。interactive_regressionは画像をWEBブラウザ上でのクリックであつけることができるため、簡単に画像のアノテーションを行うことが可能です。さらに学習とバリデーションを同じノートブック上で行えるため、走行前に、コース上で学習が足りていな部分を簡単に見つけることができます。

github.com

JetBot向けinteractive_regression

JetBot向けにinteractive_regressionを修正するのはとても簡単にです。JetRacerとの差分はカメラモジュールの呼出部分のみです。カメラオブジェクトの呼びだし箇所を変更すれば大丈夫です。さらに3セル目で呼び出されているfrom jetcam.utils import bgr8_to_jpegは不要なため、コメントアウトしておきます。具体的にはオリジナルのinteracitivi_regressionの最初のセルを以下のように書き換えます。

from jetbot import Camera, bgr8_to_jpeg
camera = Camera(width=224, height=224)
camera.start()

jupyter_clickable_image_widgetをインストールする必要があります。JetBotにログイン後、以下のGitHubリポジトリのREADMEを参照し、インストールを行います。

github.com

インストール後はJetRacerと同様にデータ収集と学習を行えば大丈夫です。JetRacerの学習の練習にもなります。

まとめ

Jupyterノートブック上で画像クリックによるアノテーションから学習までできるのは非常に楽で良いです。jupyter_clickable_image_widgetは便利なので、自前のプロジェクトでも積極的に活用していきたいです。

AI RC Carは何を見て走行しているのか?

この記事について

この記事ではJetBotやJetRacerで学習させたAIモデルが推論結果を出力する際に、画像上のどこに着目したかを調べます。手法としてはResNet-18のモデルにGrad-CAMを使って推論時に重視した画像上の箇所を可視化させます。

AIの推論とその根拠について

ディープラーニングの推論結果に対して、なぜその出力が出たのか、入力データをどう解釈したかを調べることはAI研究のホットキーワドとなっているようです。詳細はXAIなどで検索されると多くの研究に当たります。AI RC Carにおいても、走行時にステアリング角や、画像座標を出力しますが、いったい画像上の何を見て出力しているかわかりません。もし推論の結果とともに、推論結果が画像上のどの部分に着目したのかを可視化できれば、学習後のモデルがどのくらい走行中の状況をしっかり見ているかがわかります。

Grad-CAM

Grad-CAMはCNNの推論根拠を可視化する手法の一つです。詳しくは詳細に書かれば他の記事を参考いただければと思います。端的に言うと、推論結果出力後、その誤差逆伝播を遡り、変化の激しい(勾配の大きい)パーセプトロンを見つけると言うものです。勾配の大きい箇所は推論結果の計算に大きく寄与した箇所なので、結果的に何を重要と考え、結果を出力できたかがわかるだろうという考え方です。

qiita.com

JetRacerやJetBotの転移学習に用いられるモデルはResNet-18で、PyTorchを利用して実装されています。ResNet-18でPyTorchを利用して可視化を行います。GradCAM自体の実装は以下のブログの内容を利用しました。こちらのブログではResNet-32を利用されていますが、Grad-CAMに渡すレイヤー名などは変わらないので、そのまま利用可能です。

tech.jxpress.net

モデルをGradCAMクラスでラップし、fowardを実行後の処理を変えます。参考にしているブログでは画像が出力となっていますが、AI RC Carの場合は2つの変数が出力になります。JetRacerの場合は出力のうち、推論された画像のx座標の値にのみ着目すればいいため、以下のように実行します。

# sample_image[None, ...]はモデルとロードした画像に合わせて変更、こちらはJetBotのサンプルでの実行方法
model_output = grad_cam.forward(sample_image[None, ...])
grad_cam.backward_on_target(model_output[0][1])

実際の可視化画像

実際に走行時の画像をGradCamで可視化した映像が以下になります。注意したいのは以下は実際にはJetBotで撮影した連続画像をオフラインで処理しています。また、このデータの場合は画像上の座標位置ではなく、ステアリング角を学習した場合のものです。ですが、JetRacerでも同様の方法で可視化が可能です。


Visualize Autopilot with Grad-CAM

この動画ではステアリング変更に、コースの白線が特徴として利用されていることがわかります。また、大きくカーブするシーンでは周囲の環境も特徴としてよく利用されているようです。JetRacerで画像座標を推論する場合もほぼ同じ結果が出ると考えています。

まとめ

GardCAMでの可視化は、直感的に走行に寄与しそうな画像上の特徴にモデルが着目しているかを確認することができます。これにより、学習したモデルがコース上を正確に走れるか検討ができそうです。

AI RC Carのプロポ信号をUSBゲームパッドに変換する

この記事について

この記事はAI RC Carアドベントカレンダー13日目の記事です。今回はRC Carのプロポの入力をUSBゲームパッドとして変換する内容を紹介します。

AI RC Carとプロポ

AI RC Carでは人がプロポを使って操作した内容をJetsonNanoなどのSBC(シングルボードコンピュータ)に記録する場合が多々あります。通常、スピコンやサーボへ接続するプロポ受信機のPWM信号をなんらかの方法でSBCへ渡すことで実現します。一般的なラジコンのPWM信号は1msから2msのパルス幅となっています。これをSBCで直接読み込む方法はありません。PWMの値を読む前にOSの割り込みなり入るため、専用ハードを用意する必要があります。RTOSなどを利用すればできるかもしれませんが、基本的にはSBCで実施するのは厳し目です。

マイコンを利用したPWM信号のデコード

こういった用途に非常に適しているのがマイコンです。一般的に多くのマイコンではこういったPWM信号のパルス幅を計測するための機能が用意されています。Arduinoなどにもようしてありますが、今回は格安マイコンのBluepill(STM32F103)を利用します。理由としては以前購入してそのまま放置していた。Amazonで格安で購入できる点です。

BluepillとSBCの接続

Bluepillはmicro USBを搭載しており、HIDにも対応しています。HIDはいわゆる、マウスやキーボード、ゲームパッドなどのインタフェースとしてホストPCへ接続できる機能です。今回はBuluepillをゲームパッドとしてSBCへ接続します。ゲームパッドジョイスティックとして認識されるためプログラムから制御可能です。Pythonであれば、PyGamesなどを使って制御できます。また、HTML5のGamePad APIも利用できます。簡単に試すにはPCへUSBで接続、以下のサイトを最新のChromeブラウザから開くとGamePadとして認識していることがわかります。

HTML5 Gamepad Tester

プログラム

Bluepillの詳しい開発方法は掲載していません。Arduino IDEで開発できるように環境を整えてください。実際に書き込みを行ったソースコードは以下のようになっています。HIDのライブラリであるUSBCompositeをあらかじめインストールしておきます。PB5とPB6にプロポの受信機のCH1, CH2をそれぞれ接続します。USBでホストに接続するとXBOX360のコントローラとして認識され、CH1が左ジョイスティックの縦操作、CH2が右ジョイスティックの横操作に対応します。

qiita.com

#include <USBComposite.h>

#define CHANNEL_NUM 2
#define DEBUG 1

const int WIDTH = 410;
byte PWM_PIN[CHANNEL_NUM] = {PB5,PB6};
int base[CHANNEL_NUM] = {0,0};
int value[CHANNEL_NUM] = {0,0};
float joystick[CHANNEL_NUM] = {0,0};
  
USBXBox360 XBox360;


void setup() {
  // put your setup code here, to run once:
  pinMode(PC13, OUTPUT);
  digitalWrite(PC13, HIGH);
  pinMode(PWM_PIN[0], INPUT);
  pinMode(PWM_PIN[1], INPUT);

  delay(100);

  base[0] = pulseIn(PWM_PIN[0], HIGH, 29412);
  base[1] = pulseIn(PWM_PIN[1], HIGH, 29412);

  XBox360.begin();
  digitalWrite(PC13, LOW);
  delay(1000);
  
}

void loop() {
  for(int i=0; i < CHANNEL_NUM; i++){
     value[i] = pulseIn(PWM_PIN[i], HIGH, 29412);
     joystick[i] = map(value[i], long(base[i] - WIDTH), long(base[i] + WIDTH), -30000, 30000);
  }
  XBox360.X(joystick[0]);
  XBox360.XRight(joystick[1]);
  
}

init内では入出力ピンの初期化処理とHIDデバイスの初期化を行っています。メインループの中ですが、逐次PWMピンの値を読み取り、-30000から30000の間に値をマッピングし直しているのみです。その後はXBox360のコントローラに対応した値をセットしてホスト側PCに入力を送っています。

まとめ

ラジコンプロポの信号をRaspberryPiやJetson Nanoで読み取る方法は以外にめんどくさく情報がありません。Arduinoで一度受け取ってからHIDで送る方法は大変お手軽で思ったよりも簡単に作れるので、AI RC Car作成の際に一つ用意しておくと良いでしょう。

AWS RoboMakerのJetbotチュートリアルをやってみる(シミュレーター編)

この記事について

この記事はAI RC Car アドベントカレンダー11日目の記事です。今日はAWS RoboMakerのJetbotチュートリアルをやってみます。このチュートリアルは2019年12月に開催された AWS re:Invent 2019でワークショップとして開催された内容です。実際のワークショップではAWSで用意されたアカウントを使いチュートリアルを進めていたようです。個人のアカウントで進めることもできましたが、実施には一手間必要だったのでその部分について触れていきます。AI RC Carと言いつつも、全然RCじゃないですし、むしろロボットっぽいですが、この分野はいずれ融合します。多分。。。

AWS RoboMakerとは

AWS RoboMakerはAWSのサービスの一つです。クラウド上でROSアプリケーションの開発とシミュレーションができます。ROSアプリケーションの開発はCloud9と呼ばれるWEBブラウザで動くIDEで開発を行います。AWS のコグニティブサービスやKinesisによるビデオストリーミングをサポートしています。また、開発後はGazeboでのシミュレーションをWEBブラウザ上で確認可能です。さらに、AWS Greengrass IoTと連携したロボットのフリート管理機能ではロボットへアプリケーションを直接デプロイすることが可能です。

aws.amazon.com

AWS RoboMakerとJetBot

AWS RoboMakerのチュートリアルではJetBotが題材となっています。チュートリアルのマテリアルにはJetBotのROSアプリケーションのサンプルとGazeboシミュレータで利用可能なJetBotの3Dモデルが含まれています。もちろん開発した内容は実機のJetBotへインターネット越しにデプロイできます。JetBotでROS開発を行う場合は強力なツールになりえます。

AWS RoboMakerのチュートリアル

対象とするAWS RoboMakerのチュートリアルJetBot Teleopチュートリアルです。

robomakerworkshops.com

このチュートリアルは3つの構成に分かれてます。一つはWorkshop setupチュートリアルを進めるのに必要なリソースをAWS Cloud Formationを使い自動構築します。その次はActivity #1: An Introduction to ROS Developmentで、AWS RoboMaker上でのROSアプリケーションのビルドとシミュレーションの実行を学習します。最後はActivity #2: Deploying ROS Applicationsです。実機へのROSアプリケーションのデプロイ方法を学習します。この記事では2つ目までを対象とします。また、チュートリアルの中ではROSのプログラミングやアプリケーションの基本構成については深く語られていませんので、そのあたりの知識は別途学習する必要があります。

AWS RoboMaker環境の構築

まずはチュートリアルのWorkshop setupを進めていきます。このチュートリアルではAWSがイベント向けに用意した環境を利用するか、自分のAWS環境で進めるかで手順が異なります。今回は自分のAWS環境を利用するので、No. I will use own AWS account.を選択します。

下の方に進んでいくとLaunch stack ボタンを押下すると、AWS CloudFormationの画面がブラウザ上に表示されます。AWSコンソールにログインしていない場合は認証画面が開くので、ログインしましょう。

右上でリージョンがオレゴンになっているので、東京に切り替えます。この後のCloud9IDEを立ち上げる際に、オレゴンだとm4.largeのインスタンスが利用できない旨エラーが出ました。

デザイナーでCloudFormationで作成されるリソースを確認しておきましょう。VPCと2つのサブネット、S3バケットAWS Robomaker用のロールが2つ作成されます。確認したら右上の「閉じる」を押して下の画面に戻り、右下の「次へ」を押下します。

画面が切り替わったら「スタックの名前」と「S3BucketName」に名前を入れます。いずれも任意の名前を入れれば大丈夫ですが。S3BucketNameだけはグローバルに一意な名前をつける必要があります。「次へ」を押して、次の画面に進みます。次の画面の下の方に、AWS Roleを自動で作成する旨の警告が表示されていますので、チェックボックを入れて確認しましょう。そのまま「作成」を押せばリソースの自動作成が始まります。しばらくすると作成が完了します。

RoboMakerによる開発

続いて、Activity #1: An Introduction to ROS Developmentです。まずは手順にしたがい、AWS RoboMakerを使って開発環境を立ち上げます。この時、選択するVPCはdefaultと表示されているものと、先ほどのCloudFormationで作成されたものの2つが表示されます。一旦私はCloudFormationで作成されたVPCを選択しました。subnetは適当に。

その後、CloudFormationでmod-xxxxxぽい名前を控えるとありますが、実際には環境構築で作成したS3が含まれるCloudFormationでつけた環境の名前で大丈夫です。Cloud9立ち上げ時にもう一つCloudFormationの環境が作られますが、そちらではありません。引き続き、手順にしたがってGitからサンプルコードをダウンロードとinstall_deps.shの実行まで進めます。

この後、ビルドを実行する手順になりますが、すぐに実行すると他のaptのプロセスがロックした状態で正常にビルドできません。しばらく待って実行するが、psコマンドでaptのプロセス番号を特定し、killコマンドで停止させるなどで回避可能です。

その後のsimulationの実行では実行時に権限がないと言われ、実行ができません。設定からIAMRoleを追加します。Add or Edit Configurationsを選択して設定画面を開きます。

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

設定画面で、SIMULATION=>JetBotCircle Simulation を選択して、IAM roleでrobomaker-simulation-roleを選択します。同様にteleopの方にも設定しておきましょう。

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

ここまでくれば後は手順通りに進めます。シミュレーション実行後、Gazeboの画面を開くと以下のようにJetBotが回転している状態が表示されます。Teleopの方もクライアントをダウンロードして、認証情報を設定すればローカルマシンからシミュレーション上のJetBotを操作することが可能です。

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

まとめ

JetBotの開発にROSを利用しようとするとAWS RoboMakerは選択肢になりえそうです。開発からシミュレーションまで環境が最初から準備されているのがマネージドサービスの強みですね。一方でちょっと遊んだだけで2ドルくらい取られるので、本当に使い始めたらいくらかかるのかちょっと心配です。また、AWSとROS両方の知識を求められるので、学習コストは非常に高い気がします。もし、ROS(Gazeboを)動かせるPCを運良く持っていたのであれば、大人くしROSの環境構築をしてしまった方が良いかもしれません。実機へのデプロイができたら続きを書きます。

JetsonNanoのカメラの色味を補正する。

この記事について

この記事はAI RC Car アドベントカレンダー6日目の記事です。この記事ではAI RC Carによく用いられるJetsonNanoに接続されるカメラの設定について自分への備忘録の意味を込めて紹介します。今回の情報はFacebookのコミュニティ「AIでRCカーを走らせよう!」でinachiさんから提供された情報をベースに記載しています。

SainSmart IMX219

AI RC Carではコースを広く写すことはもとより、推論時の手がかりとなるように周囲を広く撮影できるカメラが利用されます。よく利用されるが書くとしてFPV 160度の広角カメラが用いられることが多いように感じます。Jetson Nanoの場合はIMX 219 FPV160というカメラが利用可能です。Amazonでは3500円程度で販売されています。

こちらのカメラをJetsonNanoに接続すれば、OpenCVを通してすぐにカメラ画像にアクセスすることが可能です。しかしこのカメラを通して得られる画像は以下のように周囲が赤みがかった画像が表示されます。

f:id:masato-ka:20191206003542j:plain
色味のおかしい画像

カメラパラメータの調整

実はこのカメラパラメータの調整は簡単に調整することが可能です。次のブログに記載があります。

medium.com

Waveshareのサイトで公開されているパメラパラメータのチューニングファイルをダウンロードしてカメラのセッティングフォルダに配置するだけです。

wget https://www.waveshare.com/w/upload/e/eb/Camera_overrides.tar.gz
tar zxvf Camera_overrides.tar.gz 
sudo cp camera_overrides.isp /var/nvidia/nvcam/settings/
sudo chmod 664 /var/nvidia/nvcam/settings/camera_overrides.isp
sudo chown root:root /var/nvidia/nvcam/settings/camera_overrides.isp

念の為、camera_overrides.ispの中身をのぞいてみると、NVIDIAが公開したファイルだということがわかります。色相の設定やホワイトバランス、カラーなどの設定が記載されていますが、詳細はわかりません。

まとめ

ディープラーニングを利用した学習走行にカメラの色味がどこまで影響があるかわかりませんが、OpenCVを使った前処理などを行う場合は必ず影響を受けます。設定ファイルを配置するだけでクリアな画像を撮影できるため、当該の事象が出る方は是非設定したほうが良いでしょう。

JetBotのraod_followingサンプル学習時のGPUメモリ解放忘れについて

この記事について

この記事はAI RC Carアドベントカレンダー4日めの記事です。4日目の今日は小ネタ中の小ネタです。JetBotのサンプルに含まれるroad_followingの学習ノートブックの修正についてです。

road_followingは学習が遅い

JetBotのサンプルにはroad_followingと呼ばれる、コースを追従するためのサンプルがあります。モデルを学習するためのtrain_model.ipynbノートブックで学習を行おうとすると学習に時間がかかり、また500枚程度の画像で、GPUメモリが溢れてしまい学習ができなくなってしまいます。

loss値の解放忘れ

この現象の原因は学習時に各エポックごとのloss値の総和を求める際にlossの計算結果をGPUメモリに乗せたまま計算していることが原因と考えられます。 train_model.ipynbの最終ブロックをみてみると以下のようなコードをみることができます。

for images, labels in iter(train_loader):
        images = images.to(device)
        labels = labels.to(device)
        optimizer.zero_grad()
        outputs = model(images)
        loss = F.mse_loss(outputs, labels)
        train_loss += loss
        loss.backward()
        optimizer.step()
    train_loss /= len(train_loader)

この時、mse_lossの計算結果はGPUメモリ上に乗ったままになっています。ですので、上記コードの7行目をtrain_loss += float(loss)とします。これはfloatでキャストしているだけですが、Pytorchのloss.detach().cpu()と同じ効果を得られるようです。学習時とバリデーション時両方忘れずに修正しましょう。 この修正を夏頃プルリクエストで投げてみましたが、あまりにもニッチすぎるのか、それとも外部からのプルリクエストは受け付けない方針なのか、マージされません。ですので個別に修正することをお勧めします。

github.com

## まとめ

とても微妙な箇所ですが、実際にroad_followingの学習を初めてみると他のサンプルの学習に比べてすこい遅い感じがするので、road_followingを試す場合はこの修正をお勧めします。