0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

LEGO Mindstorms×AI 機械学習その3 分類編

Last updated at Posted at 2020-11-04

本記事の内容はベータ公開としているので内容が変更される場合があります。


本記事では、教育版 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)

目次

  1. 分類手法
  2. やること
  3. EV3のモデルと読み取る色
  4. プログラムの作成
  5. プログラムの実行
  6. 実行結果
  7. まとめ

分類手法

SVM(サポートベクターマシン)

データの散布があったとき、それらを分けるための境界線を引くための手法。

境界線を引く=分類の予測をする ということになるが、予測には過去のデータを利用する。
その際に外れ値のような余計なデータまで使ってしまうと精度が下がる可能性がある。
そこでSVMは本当に予測に必要となる一部のデータのみ利用する。予測に必要となるデータのことをサポートベクトルと呼び、サポートベクトルを用いた機械学習法がサポートベクターマシンとなる。
Figure_1.png

やること

今回はカラーセンサーでRGB(赤緑青の度合い)と何色かのラベル番号をセットで採取する。EV3のカラーセンサーはRGB値は0~255の数値で取得できる。採取したデータ群をもとにそのデータが何色なのか境界を定めさせデータをもとに推論を行う。

以下は今回採取するデータの例
20.png

EV3のモデルと読み取る色

前回使用したコースの端にあるカラーを読み取っていく。プログラム内でラベルの定義を変更することで、ある程度の色を学習データとして利用することは可能なので好きな色を分類させることができる。
19.png      0930.png

また今回も前回と同様のEV3のモデル「ベースロボ」を利用する。今回はモーターを動かすわけではないのでインテリジェントブロックとカラーセンサーさえあれば問題ないが、カラーセンサーは読み取る面から0.5cm~1cm程度の隙間が空いている必要があるため安定してカラーセンサーを固定できるモデルを利用する。
0890.png     0891.png

プログラムの作成

今回は以下の2つのプログラムを作成する。

  • EV3側プログラムdata_get_color.py
  • PC側プログラムClassification.py

前回同様データの処理や推論自体はPC側プログラムで行い、EV3側ではカラーのRGB値の取得や送信を行う。
それぞれのプログラムの関係性を以下の図に示す。
18.png

EV3側プログラム

EV3側プログラムdata_get_color.pyはVSCode上のワークスペースで作成する。ワークスペースの作成やEV3への転送方法については以下の記事を参照してほしい。
EV3×Pyrhon 機械学習その1 環境構築編

data_get_color.py
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の箇所を変更することでデータとして記録する際のラベル名を変更できる。
今回は赤、緑、青を読み取るので以下のままにする。

data_get_color.py
# variable initialize
color_dic = {
            1: 'RED',
            2: 'GREEN',
            3: 'BLUE'
            }

PC側プログラム

PC側プログラムではEV3から送られてくるカラーの値とラベルをセットにCSVファイルに記録するのと、EV3側から推論のメッセージが送られてきたらデータ群を元に現在見ている色が何色か推論を行い結果を表示する。

前回同様programフォルダにClassification.pyをテキストドキュメントとして作成し、以下の内容を記述する。

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つのプログラムを作成できたらそれぞれ実行していく。

  1. コマンドプロンプトからcd Desktop\programを実行する(\は¥マークと同義)
10.png
  1. 続けてコマンドプロンプトにてpython Classification.pyを実行する
    ※実行後Start program...と表示され待機状態になる
11.png
  1. VSCode上で接続しているEV3のSSHターミナルを開きcd ev3workspace/を実行する
  2. SSHターミナルにてpython3 data_get_gyro.pyを実行する
12.png
  1. コースに付属している色の上にEV3を設置し、カラーセンサーでRGB値を取得しPCに送信する。EV3の各ボタンに操作が設定されているため応じてボタンを押下してデータを採取する。
    0933.png  17.png
  • 上:保存する色(ラベル)を切り替える
  • 下:保存する色(ラベル)を切り替える
  • 右:ボタンが押されたら、その時のカラーセンサーの値とラベルをPCに送信する(データ採取)
  • 左:ボタンがおされたら、その時のカラーセンサーの値をPCに送信する(推論)

上記のように操作が設定されているので、採取したい色に上下ボタンでラベルを合わせて、右ボタンを押してデータを採取する。
上下ボタンを押すとVSCode上で採取するラベルの名前が切り替わる。
12-1.png
12-2.png

右ボタンでカラーのデータを採取するとVSCode上とコマンドプロンプト上に以下のようにRGB値とラベルが出力され、、CSVファイルに保存される。
12-3.png
12-4.png

同じように各ラベルである程度データを集める。
12-5.png

実行結果

ある程度データを採取できたらEV3の左上のボタンを押して一度プログラムを終了する。
17-1.png

CSVが作成されているのを確認する。
12-6.png

再度同じ手順でPC側、EV3側のプログラムをそれぞれ実行し、
それぞれ採取した色の上で左ボタンを押して推論結果をPC側で確認する。
17-3.png    12-7.png

それぞれの色が判別できているのが確認できる。
また実際のカラーデータで判別する前に採取したデータ群の一部をテストデータとして用いて
正確率を表示している。

まとめ

実際にロボットから受け取ったデータで分類を行うことができた。今回はSVMの手法を用いたが、Scikit-learnのモデル指定の記述を変更することでランダムフォレストなど他の分類手法で機械学習を実装することも可能だ。
昨今の機械学習ではPC内で簡潔してしまうものが多いが、今回のようにエッジ(ロボット側)で実行結果を確認できる点や自分で採取したデータを利用する点においては身近に感じることができるのではないだろうか。

Appendix

3次元データのプロット

今回取得したRGBのような3次元のデータを以下のようなプログラムでプロットする。
プロットには前回の記事でインストールしたライブラリmatpolotlib必要なので注意

color_data.csvがあるフォルダ下に3d_graph.pyを作成する。

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次元のグラフをプロットすることができ、データの分布を確認することができる。
22.png

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?