[technology][deep learning][chainer]COZMOを内蔵カメラを利用してDeep Learning(Chainer+python)で自律走行させる。Part.2-学習編-
rc30-popo.hatenablog.com
の続きです。
前回、COZMOのカメラ画像と操作(前進、左旋回、右旋回)の紐付けデータが取れたので、これをDeep Learningで学習させます。
画像を畳み込みニューラルネットワークに読み込ませ、操作ラベル(前進、左旋回、右旋回)を教師データにします。
畳み込みニューラルネットワークの実装には国産Deep LearningフレームワークのChainerを利用しています。
ニューラルネットワークの設計
一応学習に使うマシンにはGeForceが載っているのですが、GPUに読ませるデータはGPU内蔵2GBとメインメモリから2GBの計4GBしか使えないので、なるべくメモリサイズが小さくなる様にあまり大規模なものは組まない様にしています。
ニューラルネットワークの入力は180x120とCOZMOのQVGA(320x240)画像を更に縦横1/2に縮小します。前回取得したカメラ画像を手動で何段階か縮小した感触として、このくらいのサイズなら画像から特徴抽出して操作ラベルとの紐付け学習ができるかな?と直感で決めたサイズです。
畳み込みとMaxプーリングを2段重ねて、全結合層へ。全結合層は中間層を1024ノードとしましたが、これもたいした根拠はなく適当です。
畳み込みのチャンネル数は一段目が16,二段目が32チャンネルを出力しています。中間層のノード数と合わせて、これも適当に決めましたが、結果としては上手く行っています。
もう少し各パラメータ小さい値でも良かったかもしれません。
学習
ミニバッチサイズ50,エポック数は10でやりました。
これでGPU内のメモリ使用量(nvidia-smiコマンドで確認)は1.6GBくらいだったので、バッチサイズは100でも良かったかもしれません。
通常は集めたデータの一部を検証用に残すのですが、今回は全データ学習させて、後から100個ほどランダムに抽出してフィッティングの具合だけ確認しました。
学習が終わったらモデルデータをファイルに保存します。
畳み込みニューラルネットワークの定義: cozmo_dnn.py
# -*- Coding: utf-8 -*- # Donkey COZMO # Convolution network definition # Copyright (C) RC30-popo,2019 import chainer import chainer.links as L import chainer.functions as F from chainer import Chain, optimizers, Variable # chainer.config.train = False class czCnn(Chain): def __init__(self): super(czCnn, self).__init__( conv1 = L.Convolution2D(in_channels=1, out_channels=16, ksize=4, stride=1, pad=1), conv2 = L.Convolution2D(in_channels=16, out_channels=32, ksize=4, stride=1, pad=1), l0 = L.Linear(None,1024), l1 = L.Linear(None,3), bncv1 = L.BatchNormalization(16), bncv2 = L.BatchNormalization(32), bn0 = L.BatchNormalization(1024), ) def forward(self, x,ratio=0.5): h = F.reshape(x,(len(x),1,160,120)) h = F.max_pooling_2d(F.relu(self.bncv1(self.conv1(h))),2) h = F.max_pooling_2d(F.relu(self.bncv2(self.conv2(h))),2) h = F.dropout(F.relu(self.bn0(self.l0(h))),ratio=ratio) h = self.l1(h) return h
学習走行Script: cozmo_dnn_train.py
# -*- Coding: utf-8 -*- # Donkey COZMO # Convolution network training # Copyright (C) RC30-popo,2019 # OS import os # OpenCV import cv2 # Numpy import numpy as np # Chainer import chainer import chainer.links as L import chainer.functions as F from chainer import Chain,optimizers,Variable from chainer import serializers from chainer import cuda # random import random xp = cuda.cupy from cozmo_dnn import czCnn # Files DONKEY_COZMO_DATAFILE = 'data/donkey_cozmo.dat' DONKEY_COZMO_MDLFILE = 'donkey_cozmo_mdl.npz' DONKEY_COZMO_OPTFILE = 'donkey_cozmo_opt.npz' img_x = 160 img_y = 120 x_train_data = [] t_train_data = [] # Read data and convert to vector with open(DONKEY_COZMO_DATAFILE, mode='r', encoding='utf-8') as f: for line in f: imgfile,label = line[:-1].split(',') print('imgfile = '+imgfile+',label = '+label) img = cv2.imread(imgfile) img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) img = cv2.resize(img,(img_x,img_y),interpolation=cv2.INTER_AREA) # print('img shape = ',img.shape) img_gs = img.flatten() x_train_data.append(img_gs) t_train_data.append(int(label)) x_train = xp.array(x_train_data,dtype=xp.float32) x_train /= 255 t_train = xp.array(t_train_data,dtype=xp.int32) total_datacount = len(x_train) print('Total number of training data = ',total_datacount) # Initialize Neural Network model = czCnn() chainer.config.train = True optimizer = optimizers.Adam() optimizer.setup(model) # GPU setup gpu_device = 0 cuda.get_device(gpu_device).use() model.to_gpu(gpu_device) # number of epochs n_epoch = 10 # batch size batch_size = 50 # Training main loop for epoch in range(n_epoch): sum_loss = 0 perm = np.random.permutation(total_datacount) for i in range(0,total_datacount,batch_size): if i + batch_size <= total_datacount: local_batch_size = batch_size else: local_batch_size = total_datacount - i x = Variable(x_train[perm[i:i+local_batch_size]]) t = Variable(t_train[perm[i:i+local_batch_size]]) y = model.forward(x,ratio=0.3) model.cleargrads() loss = F.softmax_cross_entropy(y, t) loss.backward() optimizer.update() # sum_loss += loss.data*local_batch_size sum_loss += float(cuda.to_cpu(loss.data)) * local_batch_size print("epoch: {0}, mean loss: {1}".format(epoch,sum_loss/total_datacount)) # Check accuracy perm = np.random.permutation(total_datacount) x = Variable(x_train[perm[0:100]]) t = t_train[perm[0:100]] chainer.config.train = False y = model.forward(x) cnt = 0 for i in range(100): ti = t[i] yi = xp.argmax(y.data[i]) print('[%d] t = %d, y = %d' % (i,ti,yi)) if ti == yi: cnt += 1 # Display Result # print(cnt) print("accuracy: {}".format(cnt/(100))) model.to_cpu() serializers.save_npz(DONKEY_COZMO_MDLFILE, model) # serializers.save_npz(DONKEY_COZMO_OPTFILE, optimizer)
[technology][deep learning][chainer]COZMOを内蔵カメラを利用してDeep Learningで自律走行させる。Part.3-自律走行編-
学習が終わったので、早速自律走行させてみます。
以下がその動画。スクリプトのバグ取りのみで、Deep Learningの学習部分は一発で上手くいきました。
カメラ画像だけで判断して自律走行ができています。
ちなみに原理上、新しいコースを走らせる場合は、新しいコースで再度、データ取りと学習をやり直す必要があります。
自律走行にはデータ取りに使ったスクリプトを改造します。
データ取りでは人間が操作をするために、キー入力を拾って前進、左旋回、右旋回のコマンドをCOZMOに送っていましたが、このキー入力を拾う部分をChainerで作成したCNNに置き換え、カメラ画像をCNNに渡して、出てきた操作ラベルに従ってCOZMOにコマンドを送るだけです。
COZMO自律走行スクリプト: donky_cozmo_run.py
#!/usr/bin/env python3 # -*- Coding: utf-8 -*- # Donkey COZMO # autonomous run by deep learning result # Copyright (C) RC30-popo,2019 import sys import time import termios import os import cv2 from PIL import Image import numpy as np import cozmo from cozmo.util import degrees, distance_mm, speed_mmps # Chainer import chainer import chainer.links as L import chainer.functions as F from chainer import Chain,optimizers,Variable from chainer import serializers from chainer import cuda # random import random xp = cuda.cupy from cozmo_dnn import czCnn # Initial settings DONKEY_COZMO_FACE_INIT_ANGLE = 0.0 # -25.0 degree to 44.5 degree DONKEY_COZMO_TURN_ANGLE = 10.0 DONKEY_COZMO_FORWARD_DIST = 20.0 DONKEY_COZMO_SPEED = 50.0 # COZMO actions DONKEY_COZMO_ACTION_FORWARD = 0 DONKEY_COZMO_ACTION_LEFT_TURN = 1 DONKEY_COZMO_ACTION_RIGHT_TURN = 2 # Files DONKEY_COZMO_MDLFILE = 'donkey_cozmo_mdl.npz' img_x = 160 img_y = 120 # Decide action by Deep Learning prediction from COZMO's camera image def cozmo_decide_action(cur_img): global img_x global img_y global model img = cv2.resize(cur_img,(img_x,img_y),interpolation=cv2.INTER_AREA) img_gs = img.flatten() x_data = [] x_data.append(img_gs) x = xp.array(x_data,dtype=np.float32) x /= 255 x = Variable(x) y = model.forward(x) # print('y = ',y) res = xp.argmax(y.data[0]) # print('res = ',res) return res # Let COZMO go forward def cozmo_go_forward(robot: cozmo.robot.Robot): global DONKEY_COZMO_FORWARD_DIST global DONKEY_COZMO_SPEED robot.drive_straight(distance_mm(DONKEY_COZMO_FORWARD_DIST), speed_mmps(DONKEY_COZMO_SPEED)).wait_for_completed() # Let COZMO turn left def cozmo_left_turn(robot: cozmo.robot.Robot): global DONKEY_COZMO_TURN_ANGLE robot.turn_in_place(degrees(DONKEY_COZMO_TURN_ANGLE)).wait_for_completed() # Let COZMO turn right def cozmo_rigth_turn(robot: cozmo.robot.Robot): global DONKEY_COZMO_TURN_ANGLE robot.turn_in_place(degrees(-1.0 * DONKEY_COZMO_TURN_ANGLE)).wait_for_completed() # Record COZMO camera image with action(go straight, left turn, right turn) def cozmo_donkey_run(robot: cozmo.robot.Robot): global DONKEY_COZMO_FACE_INIT_ANGLE robot.camera.image_stream_enabled = True # Enable COZMO camera capture lift_action = robot.set_lift_height(0.0, in_parallel=False) lift_action.wait_for_completed() head_action = robot.set_head_angle(cozmo.util.Angle(degrees=DONKEY_COZMO_FACE_INIT_ANGLE),in_parallel=False) head_action.wait_for_completed() firstTime = True try: while True: duration_s = 0.1 latest_image = robot.world.latest_image if latest_image != None: gray_image = latest_image.raw_image.convert('L') cv_image = np.asarray(gray_image) # print('cv_image shape = ',cv_image.shape) if firstTime: height, width = cv_image.shape[:2] print('*** Start captureing COZMO camera') print('image height = ',height) print('image width = ',width) firstTime = False cv2.imshow('frame',cv_image) cv2.waitKey(1) action = cozmo_decide_action(cv_image) if action == DONKEY_COZMO_ACTION_FORWARD: print('*** Go FORWARD ***') cozmo_go_forward(robot) elif action == DONKEY_COZMO_ACTION_LEFT_TURN: print('*** Turn LEFT ***') cozmo_left_turn(robot) elif action == DONKEY_COZMO_ACTION_RIGHT_TURN: print('*** Turn RIGHT ***') cozmo_rigth_turn(robot) # time.sleep(duration_s) except KeyboardInterrupt: print('Keyboard Interrupt!!') print('Exit Cozmo SDK') cv2.destroyAllWindows() pass # Initialize Neural Network model = czCnn() optimizer = optimizers.Adam() optimizer.setup(model) chainer.config.train = False serializers.load_npz(DONKEY_COZMO_MDLFILE, model) # GPU setup gpu_device = 0 cuda.get_device(gpu_device).use() model.to_gpu(gpu_device) # Run main code cozmo.run_program(cozmo_donkey_run)