SVM決定境界線プロットとSVMのAccuracyをみると、元データ(教師となるトレーニングデータ)を何にするかが原因であったことが判りました。フタをあければ、当たり前ですが、XYZの加速度成分を分類するのに、XYZが同時に動く振り方(ジェスチャー)をしているのでそれぞれのジェスチャー内に重複するデータが多数発生するのが、分類ミスが多発する原因でした。
●元のトレーニングデータ(教師となるデータ)
BNO055のLinearAcc(laX,laY,laZ)を採用したのですが、Linearだと重力加速度は除外しているので、姿勢差がでません。下記生データ時系列図では、左からX方向往復振り、中央がY方向往復振り、右がZ方向往復振りです。
それぞれの方向の波形が大きなピークをだしてますが、低い値では他の成分も加速が入ってしまってます。
ですので、テストデータが低い値の場合は、どの振り回し方だか分類がつかないでミスが発生します。
●機械学習の評価指標でみると
SVM分類のトレーニングデータの正解率(Accuracy)69.4%しかでてませんでした。
F1値(F値は対照的な特徴を持つ適合率と再現率の調和平均)が69.9%でした。
●境界線グラフ(seaborn-analyzer使用)
左図:縦laY-横laX 中図:縦laZ-横laY 右図:縦laX-laZ
入り混じっているので正解率が69%なのは納得です。
●元々の原作者の意図
魔法の杖のジェスチャーを機械学習で判定させてみた
の作者の振り回し方は姿勢を変えながらトレーニングデータをサンプリングさせていたのだと思います。
ですので、私がLinear加速度を使ったら、姿勢がでないので、SVMで分類できないトレーニングデータであっ
ということです。 Gravity加速度で、与えるか一般の加速度でトレーニングしたほうが、正解率はあがるはずです。
●IMUを使った機械学習の論文調査
剣道の技をIMUを複数個身体にとりつけて、つき技を分類した論文がありました。
剣道上達支援のためのIMUを用いた打突動作
ここでは、技毎に統計値を目いっぱいとって、機械学習のアルゴリズムを試してますが、一番良い正解率が
Randum Forestで94%でした。特徴量として、統計値 時間、周波数でとってます。
この論文の引用文献でさかのぼって、人間の行動をスマホIMUで判ありました。
有料と無料のダウンロードがありますが、無料でもダウンロードできます。
Multi-stage activity inference for locomotion and transportation analytics of mobile users
ここで使っているデータと特徴量のテーブルだけ抜粋しました。
結果として 下記のツリーで分類ができるようです。
●私のやりたいこと
時系列グラフだけみて、どんな動作をしているかは、人間の目でも難しいので、上記論文のやり方を学習して
IMUの新しい使い方をやってみたいと思います。
●プログラムの備忘録
元プログラムは、IMUからのデータをリストにアペンドしながらトレーニングデータを作成してますが、
csvファイルから読みとって scikit-learn用にデータを変換する方法でプログラムを作り直しました。
#coding utf-8 import datetime import pickle # モデルをファイルとして使用する用 import keyboard import numpy as np # numpyで2次元配列を扱うため import serial # シリアル通信を行うため from sklearn.svm import SVC, SVR # 学習用 import csv import os import pandas as pd import matplotlib.pyplot as plt import seaborn as sns import time #----------Global variable---------------- X = [] Y = [] def serTch(filenamei,length:int,gestures:int,cmN): #X = [] #Y = [] wr=[] print("serTch INN") path = os.getcwd() print(path) print(type(path)) #length = 100 #ジェスチャの長さ(連続で受信するデータの個数) #gestures = 3 #ジェスチャの種類の数 times = 1 #各ジェスチャを行う回数 #==================================================== #now = datetime.datetime.now() #name = now.strftime('%m%d_%H%M') #filenamei ='imu.csv' #filename = 'model_' + name + '.sav' # 入力した名前の学習モデルのファイル名 #filename = 'model.sav' # 入力した名前の学習モデルのファイル名 print('Connecting...') ser = serial.Serial(cmN,115200,timeout=None) #ポート番号19からシリアル受信 print("-----Input c:CreateModel,g:Gesture Measuring +ENTER") print("filenamei="+filenamei+"length="+str(length)+"gestures="+str(gestures)) #mx = [] # 各軸ごとの配列を初期化 #my = [] #mz = [] with open(filenamei, 'w', newline='') as f: writer = csv.writer(f) for i in range(gestures * times): cnt = 0 #受信したデータのカウント用 gesture_num = i % gestures + 1 #ジェスチャ番号を指定 print('Push a then Do gesture No.', gesture_num) while True: line=ser.readline()#Binary linestr= line.decode('utf8')#バイナリーをASCII変換 strarr=linestr.split(',') datano=strarr[0] x=strarr[1] y=strarr[2] z=strarr[3] tim=strarr[4] if keyboard.is_pressed('a'): print("key hit") switch = "a" else: switch='' if switch =='a' and cnt == 0: #クリックされたらカウント開始 print("cnt=1") cnt = 1 if cnt != 0: #ジェスチャ中a #print("In gestureNo"+str(gesture_num)+" datacnt="+str(cnt)) print("In gestureNo"+str(gesture_num)+'datanp=' + datano + ',x=' + x + 'y=' + y+ 'z=' + z + 'tim=' + tim) #mx.append(x.rstrip('\r\n')) # シリアルから受け取ってたデータのゴミを取って配列へ追加 #my.append(y.rstrip('\r\n')) #mz.append(z.rstrip('\r\n')) cnt += 1 if cnt == length: #lengthまでいったら次のジェスチャ待ちへ print("length Up") break print("CSV:gnum="+str(gesture_num)+"x="+x+"y="+y+"z="+z) wr = [gesture_num,x, y, z] writer.writerow(wr) #Y.append(str(gesture_num)) # 学習ラベル用リストへ追加 #m = mx + my + mz #三軸の加速度を横一列にする #X.append(m) #学習データ用リストへ追加 #print("Xappend:Gesture_num="+str(gesture_num)) #print(X) print("serial closed") ser.close() # シリアルを閉じ f.close() # isn't actually needed #print("def result:") #print(X) # print(Y) #return [X,Y] # -----------main--------------------------------------- while True: print("-----Input c:CreateModel,g:Gesture Measuring +ENTER") keys = input() millisec = time.time() * 1000 if keys == 'c': serTch('imu.csv',100,3,'COM28') #X=result[0] #X=X[0] #Y=result[1] # CSVからデータ作成 #with open('imu.csv', 'w', newline='') as f: df = pd.read_csv("imu.csv", sep=",", header=None, names=['gnum', 'lx', 'ly', 'lz']) print(df) # データフレームを行列へ変換 arr = df.to_numpy() print(arr) X=arr[:,1:] Y=arr[:,0] print("---X=",end='') print(X) print("---Y=",end='') print(Y) #X = np.array(X).reshape(-1, 1) print("X=",end='') print(X) # SVM start #X = np.array(X).reshape(-1, 1) model = SVC(kernel = 'linear', C=1, gamma=1) #学習モデルのパラメータを指定 #model.fit(X,np.ravel(Y)) #学習を行う model.fit(X, Y) # 学習を行う # %% サポートベクターマシン分類して性能評価指標算出 from sklearn.svm import SVC from sklearn.metrics import accuracy_score, f1_score # X = iris[['petal_width', 'petal_length']].values # 説明変数 # y = iris['species'].values # 目的変数(3種類のアヤメ種類) #clf = SVC() # SVM分類用インスタンス #clf.fit(X, Y) # 学習 y_pred = model.predict(X) # 推論 print(f'Accuracy={accuracy_score(Y, y_pred)}') # 正解率を表示 print(f'F1={f1_score(Y, y_pred, average="macro")}') # F1-Macroを表示 from seaborn_analyzer import classplot classplot.class_separator_plot(model, ['lx','ly'],'gnum', df) classplot.class_separator_plot(model, ['ly', 'lz'], 'gnum', df) classplot.class_separator_plot(model, ['lz', 'lx'], 'gnum', df) plt.show() # CSV読み込みして散布図プロット #df = pd.read_csv("imu.csv", sep=",", header=None, names=['gnum', 'gyX', 'gyY', 'gyZ']) #df = pd.read_csv("imu.csv", sep=",", header=None, names=['gnum', 'acX', 'acY', 'acZ']) print(df) print(len(df.columns)) print(df.info()) sns.pairplot(df, hue='gnum') plt.tight_layout() plt.show() sns.lineplot(df) plt.show() #df = pd.DataFrame(model) #sns.pairplot(df, height=1.5) with open('model.sav', 'wb') as fp_model: #学習モデルを保存するためのファイルを開く pickle.dump(model, fp_model) #モデルを保存 print('Model created.') #-----------------------------start gesture measuring----------------------------------------------- elif keys=='g': print("=================Test Input MODE================ ") serTch('imut.csv', 10, 1, 'COM28') with open('model.sav', 'rb') as fp_model: # 学習モデルファイルを開く loaded_model = pickle.load(fp_model) # モデルをロード print('Do any gesture!') with open('imut.csv', 'rb') as fs: writer = csv.writer(fs) dfs = pd.read_csv("imut.csv", sep=",", header=None, names=['gnum', 'lx', 'ly', 'lz']) print(dfs) arr2=dfs.to_numpy() print("arr2=", end='') print(arr2) test_arr=arr2[:,1:] print("test_arr=",end='') print(test_arr) pre = loaded_model.predict(test_arr) # ジェスチャの判定 print('********************This gesture is No.', pre) sns.lineplot(dfs) plt.show() plt.show() |
①IMUサンプリング
IMUのサンプリング関数でジェスチャー毎にサンプリングして、CSVファイルに保存する。
gnum,lax,lay,laz の4列を1行とするCSVファイルgnumがジェスチャー番号で
3つのジェスチャーを一つのCSVファイルimu.csvに収納します。
1 | -8.79 | 5.02 | -3.01 |
1 | 3.13 | 4.37 | -1.12 |
1 | 13.53 | 3.21 | -0.52 |
1 | 33.54 | -4.75 | 4.99 |
1 | 35.86 | -10.85 | 4.63 |
1 | 16.28 | 0.78 | 0.22 |
1 | 1.61 | 6.81 | 5.1 |
1 | -22.05 | 12.4 | 1.87 |
②scikit-leanトレーニングデータへのCSVデータ変換
scikit-learnは、pandasのデータフレームは受け入れてくれませんので、
行列フォーマットに変換しないといけません。
arr = df.to_numpy() #ここで、pandas DataFrameからnumpyの行列データ arrに変換しました。
CSVからpandas DataFrame dfへ | 変換 | numpy の行列データarr |
gnum lx ly lz 0 1 -2.39 -3.48 -1.57 1 1 -6.91 -2.71 -1.95 2 1 -28.57 2.07 -4.87 3 1 -27.46 3.36 -3.93 4 1 -22.68 1.74 -3.15 |
df.to_numpy() | [[ 1. -2.39 -3.48 -1.57] [ 1. -6.91 -2.71 -1.95] [ 1. -28.57 2.07 -4.87] … [ 3. 5.08 -1.25 6.16] [ 3. 3.02 -0.98 2.22] [ 3. 1.61 -0.44 -7.39]] |
データ行列とラベル行列にarrをスライスします。
X=arr[:,1:]
X=[[-2.390e+00 -3.480e+00 -1.570e+00] [-6.910e+00 -2.710e+00 -1.950e+00] [-2.857e+01 2.070e+00 -4.870e+00] [-2.746e+01 3.360e+00 -3.930e+00] [-2.268e+01 1.740e+00 -3.150e+00] [-9.850e+00 -1.540e+00 -6.100e-01] |
Y=arr[:,0]
Y=[1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3.] |
●トレーニングデータX YをSVMにいれてFITTING
model = SVC(kernel = ‘linear’, C=1, gamma=1) #学習モデルのパラメータを指定
#model.fit(X,np.ravel(Y)) #学習を行う
model.fit(X, Y) # 学習を行う
●感想
判らなかったのは、トレーニングデータの加工方法で、WEB上では、既成データセットを使っている事例が多く
一からデータを作るやり方が少ないので、わからないままいたので、SVMのデータ入力エラーが多発しました。
要するに、scikit-leranは、行列データしか受け入れてくれないので、行列データをnumpyで作成する以外に方法が
無いということがどこにも書いてなくて、さまよい続けました。ここのサイト様に感謝です。
CSVファイルをpandasで読み込み、scikit-learnで学習させる
https://kakedashi-engineer.appspot.com/2020/03/21/pandas-sklearn/?utm_source=pocket_saves
●以後
IMUのlinear加速度でのピーク判別視点で、トレーニングデータを作成して、学習させてみます。
正解率98%以上でないと、機械学習の効果が感じられないので、粘ってみます。