概要
「実践! Chainerとロボットで学ぶディープラーニング」には、実はシミュレーション環境が用意[1]されており、実機を購入しなくても教材の内容を楽しむことができます! 今回は、そのシミュレーション環境でどんな事ができるか試してみました。
「実践! Chainerとロボットで学ぶディープラーニング」とは
「実践! Chainerとロボットで学ぶディープラーニング」[2]は、Preferred Networks、株式会社アフレル、山梨大学が共同で開発した教材で、教育版レゴマインドストームEV3を使って、Pythonによるプログラミングから、オープンソースの深層学習フレームワークChainerまで学習することができる教材です。
【発表】プログラミング教材 「実践!Chainerとロボットで学ぶディープラーニング」をアフレルさんと共同開発しました。
— Preferred Networks JP (@PreferredNetJP) October 7, 2019
深層学習の正しい知識の学習機会をより広く提供し、実務領域への活用を促進するため無料公開しています。高校・大学の授業や自主学習にご利用ください!
この教材自体は無料なのですが、動かすための実機を購入するとなると結構なお値段になります(税込 ¥78,980)。気軽に試すにはちょっとお高いお値段で私も購入するのをためらっていました。
しかし!!!実は以下のようなシミュレーション環境も教材には用意されています。
これを使えばサンプルを動かして、購入するか検討できますね。
環境設定
シミュレーション環境はpfnet-research/chainer-ev3/document/simulator_setup.mdに用意されております。リンク先の手順に従って設定してみましょう。
サンプル実行
実行
環境が設定できたら早速サンプルコードを動かしてみましょう。
linetrace_controller1.ipynb
にルールベースでライントレースするnotebookが用意されています。ドキュメント[1]に書いてあるとおり、以下の手順でサンプルコードを実行してみましょう。
-
Jupyter Labの左側のリストから
linetrace_controller1.ipynb
を開く。 -
Jupyter Lab上で
linetrace_controller1.ipynb
を実行。 -
シミュレーターのウインドウ上で、
t
キーを押し、シミュレーションを開始。
すると以下のようにルールベースのライントレースのシミュレーションが動くはずです。
シミュレーションを停止したいときは再度、t
キーを押しましょう。
コードの説明
# タッチセンサーを押して離すとスタート.
ev3.lcd_draw_string('Push to start.', 0)
while not ev3.touch_sensor_is_pressed(touch_port):
pass
while ev3.touch_sensor_is_pressed(touch_port):
pass
ev3.lcd_draw_string('Go!', 0)
- ここを見ると、タッチセンサーが押されるまで後続の処理に進まないことがわかります。
- シミュレーターでは、
t
キーがタッチセンサーに相当するので、t
キーを押すことで動き出すことがわかります。
# 制御ループ
while True:
# タッチセンサーが押されたら終了.
if ev3.touch_sensor_is_pressed(touch_port):
break
# P制御でステアリング値を計算.
white = 70 # 白面上の反射値.
black = 5 # 黒面上の反射値.
midpoint = (white + black) / 2 # 平均値を計算.
color = ev3.color_sensor_get_reflect(color_port)
steer = 1.5 * (color - midpoint) # p * (測定値 - 平均値)
# EV3へ制御値を送信.
ev3.motor_steer(lmotor_port, rmotor_port, 10, int(steer))
- ここは、実際のライントレースの処理部分になります。
- ここで、
ev3.color_sensor_get_reflect(ポート番号)]
はカラーセンサーから反射光の値を取得する関数です。- 引数: 右表のポート番号を指定(今回は変数内に指定)
- 戻り値: 反射光の値(0~100)
- ここの値が大きいほど白いものがカラーセンサーに写っていることがわかります。
- なので、カラーセンサーに写っている色が黒いほど正の方向(左)、白いほど負の方向(右)に舵を切ることがわかります。
Chainerを使った学習と推論
テキスト自体もこちらpfnet-research/chainer-robotcar-textで公開されています。現在の最新版はchainer-robotcar-text-v1.1.0.pdfのようです。ここの手順にしたがって、Chainerをつかったライントレースをしてみます。
上記テキストのp.133から課題の説明がありますが、以下の順序で機械学習ベースのライントレースを行います。
- 訓練データセットの作成
- ルールベースのライントレースプログラムを動かすことで、カメラ画像(特徴量)と制御角度(ラベル)の組を作成する
- モデルの訓練
- 作成したデータセットを使用して、モデルを訓練します。
- モデルによるライントレースの実行
- 訓練したモデルを使用して、カメラ画像から制御角度を予測し、ev3を動かします。
訓練データセットの作成
機械学習を行うには入力に使う特徴量とそれの出力に対応するラベルが必要になります。ルールベースのライントレースプログラムを動かし、その時のカメラ画像(特徴量)と制御角度(ラベル)をロギングすることで訓練に使用するデータセットを作成します。
実行
プログラムはml_linetrace_logger.ipynb
に用意されています。中身を見てみると、上記で説明したlinetrace_controller1.ipynb
とほとんど変わらないことがわかりますね。違いは以下です。
- 白と黒の反射光の値をカラーセンサーから取得して定義していること
- カメラ画像と制御角度を保存していること
大体3000サンプルのデータが必要なので5周くらいさせてみましょう。データセットはml_linetrace_data/(日付)-(時刻)
ディレクトリ以下に保存されます。
モデルの訓練
プログラムはml_linetrace_trainer.ipynb
に用意されています。実行する前に、input_dir
を先程作成したデータセットの場所にしましょう。
だいたい、30epochほど訓練すると十分なようです。作成したモデルはml_linetrace_model/mychain.model
に保存されます。
import os
import numpy as np
from PIL import Image
import chainer
from chainer.datasets import LabeledImageDataset
from chainer.datasets import TransformDataset
import chainer.links as L
import chainer.functions as F
from chainer import serializers
from chainer import training
from chainer.training import extensions
# Network definition
class MLP(chainer.Chain):
def __init__(self):
super(MLP, self).__init__()
with self.init_scope():
self.l1 = L.Linear(300, 256) # 300(20*15) -> 256 units
self.l2 = L.Linear(256, 256) # 256 units -> 256 units
self.l3 = L.Linear(256, 1) # 256 units -> 1
def forward(self, x):
h1 = F.relu(self.l1(x))
h2 = F.relu(self.l2(h1))
return self.l3(h2)
# Preprocess data
def preprocess(in_data):
img, label = in_data
img = img / 256. # normalization
img = img.reshape(300) # 2-dim (20,15) -> 1-dim (300)
label = label.reshape(1) # scalar -> 1-dim
return img, label
input_dir = 'ml_linetrace_data/20191129-173344'
out_dir = 'ml_linetrace_model'
batchsize = 100
epoch = 30
# Set up a neural network to train
model = L.Classifier(MLP(),
lossfun=F.mean_squared_error,
accfun=F.mean_absolute_error)
# Setup an optimizer
optimizer = chainer.optimizers.Adam()
optimizer.setup(model)
# Load the dataset
dataset = LabeledImageDataset(os.path.join(input_dir, 'list.txt'),
os.path.join(input_dir, 'images'),
label_dtype=np.float32)
# Divide dataset into training and validation
threshold = np.int32(len(dataset) * 0.8)
train = TransformDataset(dataset[0:threshold], preprocess)
val = TransformDataset(dataset[threshold:], preprocess)
# Set iterators
train_iter = chainer.iterators.SerialIterator(train, batchsize)
val_iter = chainer.iterators.SerialIterator(val, batchsize,
repeat=False, shuffle=False)
# Setup an Updater
updater = training.updaters.StandardUpdater(train_iter, optimizer)
# Setup a Trainer
trainer = training.Trainer(updater, (epoch, 'epoch'), out='result')
trainer.extend(extensions.LogReport())
trainer.extend(extensions.Evaluator(val_iter, model))
trainer.extend(extensions.PrintReport(['epoch', 'main/loss', 'main/accuracy', 'validation/main/loss', 'validation/main/accuracy', 'elapsed_time']))
# Run the Trainer
trainer.run()
# Save the model, the optimizer and config.
if not os.path.exists(out_dir):
os.makedirs(out_dir)
print('save the model')
serializers.save_npz('{}/mlp.model'.format(out_dir), model.predictor)
print('save the optimizer')
serializers.save_npz('{}/mlp.state'.format(out_dir), optimizer)
モデルによるライントレースの実行
プログラムはml_linetrace_controller.ipynb
に用意されています。ml_linetrace_model/mychain.model
に保存されたモデルを使って実行します。
すると、こんな感じでちゃんと学習できているような様子が見られます。
import time
import numpy as np
from PIL import Image
import chainer
from chainer import configuration
import chainer.links as L
import chainer.functions as F
from chainer import serializers
from lib.ev3 import EV3
from lib.vstream import VideoStream
touch_port = EV3.PORT_2
lmotor_port = EV3.PORT_B
rmotor_port = EV3.PORT_C
# Network definition
class MLP(chainer.Chain):
def __init__(self):
super(MLP, self).__init__()
with self.init_scope():
self.l1 = L.Linear(300, 256) # 300(20*15) -> 256 units
self.l2 = L.Linear(256, 256) # 256 units -> 256 units
self.l3 = L.Linear(256, 1) # 256 units -> 1
def forward(self, x):
h1 = F.relu(self.l1(x))
h2 = F.relu(self.l2(h1))
return self.l3(h2)
# Set up a neural network of trained model
predictor = MLP()
# Load the model
serializers.load_npz('ml_linetrace_model/mlp.model', predictor)
# Run VideoStream by setting image_size and fps
vs = VideoStream(resolution=(20, 15),
framerate=10,
colormode='binary').start()
ev3 = EV3()
ev3.enable_watchdog_task()
ev3.motor_config(lmotor_port, EV3.LARGE_MOTOR)
ev3.motor_config(rmotor_port, EV3.LARGE_MOTOR)
ev3.sensor_config(touch_port, EV3.TOUCH_SENSOR)
print("Push the touch sensor to start the linetracer")
while not ev3.touch_sensor_is_pressed(touch_port):
pass
# Confirm the touch sensor is released.
while ev3.touch_sensor_is_pressed(touch_port):
pass
# Enable evaluation mode for faster inference.
with configuration.using_config('train', False), chainer.using_config('enable_backprop', False):
while True:
# Break this loop when the touch sensor was pressed.
if ev3.touch_sensor_is_pressed(touch_port):
break
im = vs.read() # Get a current image in PIL format.
im = np.asarray(im, dtype=np.float32) # Convert to numpy array.
x = im / 255. # Normalization
x = x.reshape(1, 300) # (20, 15) -> (1, 300)
y = predictor(x) # Predict steer value from x.
steer = y.data[0, 0]
print("predicted steer = {}".format(steer))
ev3.motor_steer(lmotor_port, rmotor_port, 10, int(steer))
vs.stop()
ev3.motor_steer(lmotor_port, rmotor_port, 0, 0)
ev3.close()
おまけ
シミュレータで作ったモデルを実機で動かしたところ、無事動きました!
Chainerとシミュレーターを使ってモデルを作り、それを実機で動かしたバージョン pic.twitter.com/hoCpQAeYZ4
— くめざわ (Chainer Evangelist) (@kumezawa_) November 30, 2019