本記事の内容はベータ公開としているので内容が変更される場合があります。
本記事では、教育版 LEGO® MINDSTORMS EV3(以降EV3)とPython環境を利用して色の識別を分類を用いて行っていく。
環境構築に関しては環境構築編の記事を参照してほしい。
また、前回の記事ではライントレースと線形回帰を合わせた内容を行っている。
EV3で機械学習その1 環境構築編:こちら
EV3で機械学習その2 線形回帰編:こちら
EV3で機械学習その3 分類編:本記事
参考
本記事の内容は以下の書籍を参考にしている。
Pythonを使ったEV3の基本的な制御等については以下に網羅されている。
ロボットではじめるAI入門
上記書籍との内容の関係は以下の通りになる。
・概要
・PC,EV3のセットアップ ⬅ NEW(初回)
・プログラム作成方法 ⬅ NEW(初回)
・モーター,センサーの利用
・ライントレース制御
・ライントレースフィードバック
・線形回帰 ⬅ NEW(前回)
・分類 ⬅ NEW(本記事)
本記事内での環境
-
PC
Windows10
Python 3.7.3
開発環境 VisualStudioCode(以降VSCode) -
EV3
python-ev3dev2(以降ev3dev)
目次
- 分類手法
- やること
- EV3のモデルと読み取る色
- プログラムの作成
- プログラムの実行
- 実行結果
- まとめ
分類手法
SVM(サポートベクターマシン)
データの散布があったとき、それらを分けるための境界線を引くための手法。
境界線を引く=分類の予測をする ということになるが、予測には過去のデータを利用する。
その際に外れ値のような余計なデータまで使ってしまうと精度が下がる可能性がある。
そこでSVMは本当に予測に必要となる一部のデータのみ利用する。予測に必要となるデータのことをサポートベクトルと呼び、サポートベクトルを用いた機械学習法がサポートベクターマシンとなる。
やること
今回はカラーセンサーでRGB(赤緑青の度合い)と何色かのラベル番号をセットで採取する。EV3のカラーセンサーはRGB値は0~255の数値で取得できる。採取したデータ群をもとにそのデータが何色なのか境界を定めさせデータをもとに推論を行う。
EV3のモデルと読み取る色
前回使用したコースの端にあるカラーを読み取っていく。プログラム内でラベルの定義を変更することで、ある程度の色を学習データとして利用することは可能なので好きな色を分類させることができる。
また今回も前回と同様のEV3のモデル「ベースロボ」を利用する。今回はモーターを動かすわけではないのでインテリジェントブロックとカラーセンサーさえあれば問題ないが、カラーセンサーは読み取る面から0.5cm~1cm程度の隙間が空いている必要があるため安定してカラーセンサーを固定できるモデルを利用する。
プログラムの作成
今回は以下の2つのプログラムを作成する。
- EV3側プログラム
data_get_color.py
- PC側プログラム
Classification.py
前回同様データの処理や推論自体はPC側プログラムで行い、EV3側ではカラーのRGB値の取得や送信を行う。
それぞれのプログラムの関係性を以下の図に示す。
EV3側プログラム
EV3側プログラムdata_get_color.py
はVSCode上のワークスペースで作成する。ワークスペースの作成やEV3への転送方法については以下の記事を参照してほしい。
EV3×Pyrhon 機械学習その1 環境構築編
import time
import socket
import sys
from ev3dev2.button import Button
from ev3dev2.sensor import INPUT_3
from ev3dev2.sensor.lego import ColorSensor
def dataget(color_dic, color_num):
_rgb_data = color.raw
_rgb_data = list(_rgb_data)
_rgb_data.append(color_num)
_rgb_data_str = ','.join(map(str, _rgb_data))
s.send(_rgb_data_str.encode())
print('\n'+'rgb_color = {}'.format(_rgb_data_str))
def predict():
_rgb_data = color.raw
_rgb_data = list(_rgb_data)
_rgb_data_str = ','.join(map(str, _rgb_data))
s.send(_rgb_data_str.encode())
pre_color = s.recv(1024).decode()
print('\n'+'predict_color = {}'.format(color_dic[int(pre_color[0])]))
# sensors&motors definition
button = Button()
color = ColorSensor(INPUT_3)
# gyro initialize
color.mode = 'RGB-RAW'
# variable initialize
color_dic = {
1: 'RED',
2: 'GREEN',
3: 'BLUE'
}
color_num = 1
color_max = len(color_dic)
# get gyrodate and into array
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect(('169.254.85.105', 50010)) # your PC's Bluetooth IP & PORTpy
for cnt in range(color_max):
s.send((color_dic[cnt+1]).encode())
time.sleep(0.1)
s.send(('END').encode())
print('Start program...')
while not(button.backspace):
if button.up:
color_num += 1
time.sleep(0.1)
if color_num > color_max:
color_num = color_max
elif button.down:
color_num -= 1
time.sleep(0.1)
if color_num < 1:
color_num = 1
elif button.right:
msg = 'save'
s.send(msg.encode())
dataget(color_dic, color_num)
elif button.left:
msg = 'predict'
s.send(msg.encode())
predict()
print('\r'+'save_color = {} '.format(color_dic[color_num]), end='')
time.sleep(0.1)
print('\n'+'End program')
sys.exit()
※EV3側環境は日本語でコメントアウトすると文字コードの性質上エラーが発生するので注意が必要。
後半に記述しているs.connect(('169.254.207.161', 50010))は、前回と同様に環境に応じて書き換える。
以下のRED、GREEN、BLUEの箇所を変更することでデータとして記録する際のラベル名を変更できる。
今回は赤、緑、青を読み取るので以下のままにする。
# variable initialize
color_dic = {
1: 'RED',
2: 'GREEN',
3: 'BLUE'
}
PC側プログラム
PC側プログラムではEV3から送られてくるカラーの値とラベルをセットにCSVファイルに記録するのと、EV3側から推論のメッセージが送られてきたらデータ群を元に現在見ている色が何色か推論を行い結果を表示する。
前回同様program
フォルダにClassification.py
をテキストドキュメントとして作成し、以下の内容を記述する。
import socket
import sys
import csv
import numpy as np
import pandas as pd
import os.path
from sklearn.model_selection import train_test_split
from sklearn.svm import SVC
from sklearn.multiclass import OneVsRestClassifier
from sklearn.ensemble import RandomForestClassifier as RFC
from sklearn.metrics import accuracy_score
# setting svm
C = 1.
kernel = 'rbf'
gamma = 0.01
estimator = SVC(C=C, kernel=kernel, gamma=gamma)
clf = OneVsRestClassifier(estimator)
x_data = np.zeros(0)
y_data = np.zeros(0)
color_elements = None
color_dic = {}
color_cnt = 1
# データファイルの作成
if os.path.exists('color_data.csv') == False:
writedata = ['red', 'green', 'blue', 'color']
f = open('color_data.csv', 'w', newline='') # ファイルを開く
writer = csv.writer(f)
writer.writerow(writedata) # データの書き込み
f.close()
data = pd.read_csv("color_data.csv", sep=",") # csvファイルの読み込み
# 読み込んだデータを入力データとラベルに分ける
x_data = data.loc[:, ["red", "green", "blue"]]
y_data = data.loc[:, "color"]
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(('169.254.85.105', 50010)) # your PC's Bluetooth IP & PORT
s.listen(1)
print('Start program...')
while True:
conn, addr = s.accept()
with conn:
# EV3側のプログラムで作成した色の辞書と同じものを作成
while True:
color_elements = conn.recv(1024).decode()
if color_elements == 'END':
break
color_dic[color_cnt] = color_elements
color_cnt += 1
print('color_dic = {}'.format(color_dic))
# メッセージによって動作を変化
while True:
rgb_data = conn.recv(1024).decode()
if not rgb_data:
break
# 溜めたデータを用いて、推論後、値をEV3に送信
elif rgb_data == 'predict':
train_x, test_x, train_y, test_y = train_test_split(x_data,
y_data)
clf.fit(train_x, train_y)
y_pred = clf.predict(test_x)
acc_score = accuracy_score(test_y, y_pred)
print('acc_score = {}'.format(acc_score))
rgb_data = conn.recv(1024).decode()
rgb_data = rgb_data.split(',')
pre_color = clf.predict([rgb_data])
print('predict_color = {}'.format(color_dic[pre_color[0]]))
conn.send(str(pre_color[0]).encode())
# EV3から送られてきたデータを保存
elif rgb_data == 'save':
rgb_data = conn.recv(1024).decode()
rgb_data = rgb_data.split(',')
print('rgb_data = {}'.format(rgb_data))
np.append(y_data, rgb_data[0:2])
np.append(y_data, int(rgb_data[3]))
writedata = rgb_data
f = open('color_data.csv', 'a', newline='')
writer = csv.writer(f)
writer.writerow(writedata)
f.close()
print('End program')
sys.exit()
今回は3色の分類を行うためclf = OneVsRestClassifier(estimator)
で多クラス分類を行うモデルを利用する。
後半に記述しているs.bind(('169.254.207.161', 50010))
はEV3側プログラム同様環境に合わせて変更する。
環境の確認と変更方法は前回の記事もしくは以下を確認してほしい。
ソケット通信のIP設定
プログラムの実行
2つのプログラムを作成できたらそれぞれ実行していく。
- コマンドプロンプトから
cd Desktop\program
を実行する(\は¥マークと同義)
- 続けてコマンドプロンプトにて
python Classification.py
を実行する
※実行後Start program...と表示され待機状態になる
- VSCode上で接続しているEV3のSSHターミナルを開き
cd ev3workspace/
を実行する - SSHターミナルにて
python3 data_get_gyro.py
を実行する
- 上:保存する色(ラベル)を切り替える
- 下:保存する色(ラベル)を切り替える
- 右:ボタンが押されたら、その時のカラーセンサーの値とラベルをPCに送信する(データ採取)
- 左:ボタンがおされたら、その時のカラーセンサーの値をPCに送信する(推論)
上記のように操作が設定されているので、採取したい色に上下ボタンでラベルを合わせて、右ボタンを押してデータを採取する。
上下ボタンを押すとVSCode上で採取するラベルの名前が切り替わる。
右ボタンでカラーのデータを採取するとVSCode上とコマンドプロンプト上に以下のようにRGB値とラベルが出力され、、CSVファイルに保存される。
実行結果
ある程度データを採取できたらEV3の左上のボタンを押して一度プログラムを終了する。
再度同じ手順でPC側、EV3側のプログラムをそれぞれ実行し、
それぞれ採取した色の上で左ボタンを押して推論結果をPC側で確認する。
それぞれの色が判別できているのが確認できる。
また実際のカラーデータで判別する前に採取したデータ群の一部をテストデータとして用いて
正確率を表示している。
まとめ
実際にロボットから受け取ったデータで分類を行うことができた。今回はSVMの手法を用いたが、Scikit-learnのモデル指定の記述を変更することでランダムフォレストなど他の分類手法で機械学習を実装することも可能だ。
昨今の機械学習ではPC内で簡潔してしまうものが多いが、今回のようにエッジ(ロボット側)で実行結果を確認できる点や自分で採取したデータを利用する点においては身近に感じることができるのではないだろうか。
Appendix
3次元データのプロット
今回取得したRGBのような3次元のデータを以下のようなプログラムでプロットする。
プロットには前回の記事でインストールしたライブラリmatpolotlib
必要なので注意
color_data.csvがあるフォルダ下に3d_graph.py
を作成する。
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
from sklearn.svm import SVC
from sklearn.metrics import r2_score
import math
import itertools
from sklearn.multiclass import OneVsRestClassifier
from sklearn.metrics import accuracy_score
file = 'color_data.csv'
def main():
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
data = np.loadtxt(file,delimiter=",",skiprows=1)
print('data = {}'.format(data))
print('data max= {}'.format(np.amax(data,axis=0)))
print('data min= {}'.format(np.amin(data,axis=0)))
for xs, xy, zs, c in data:
#print('xs = {}, xy = {}, zs = {}, c = {}'.format(xs, xy, zs, c))
if c == 1:
color = "r"
marker="o"
elif c == 2:
color = "g"
marker="^"
elif c == 3:
color = "b"
marker=","
ax.scatter(xs, xy, zs, c=color, marker=marker)
ax.set_xlabel('red')
ax.set_ylabel('green')
ax.set_zlabel('blue')
plt.show()
if __name__ == '__main__':
main()
コマンドプロンプトよりcd コマンド
で作成したフォルダに移動する。
python 3d_graph.py
で実行するとcsvファイルを読み込み以下のように3次元のグラフをプロットすることができ、データの分布を確認することができる。