【機械学習】識別器の学習に使用するデータの数による識別精度の比較
イントロ
機械学習では、識別器を作成するのに学習データを使用します。もちろん学習データは多いほうがいいですが、最低どのくらい必要か気になったので実験していきます。
本記事では、生体信号の筋電波形をMyoで計測したデータの中から、学習データに使用するデータ数を1、2、3…と順に増やしていき、各学習データ数における識別率を比較していきます。
使用したデータのリンク(Mendeley Data - EMG data of the Myo Armband)
方法
学習データ数を1、2、3…と増やしていき、最大数を7として各学習データ数における識別率を計測します。
学習データをtrainデータとtestデータに分割します。
識別率の比較には、
・trainデータに対して交差検証を行った時の識別率
・trainデータで学習した識別器でtestデータを分類したときの識別率
・50データ中の学習データ以外の未知データに対して分類したときの識別率
をグラフにプロットして比較します。
実装
1. データのファイルパスの設定
import numpy as np import random # 各ジェスチャのファイルパス path1 = "../8hand_gesture_dataset/HandGesture01.txt" path2 = "../8hand_gesture_dataset/HandGesture02.txt" path3 = "../8hand_gesture_dataset/HandGesture03.txt" path4 = "../8hand_gesture_dataset/HandGesture04.txt" path5 = "../8hand_gesture_dataset/HandGesture05.txt" path6 = "../8hand_gesture_dataset/HandGesture06.txt" path7 = "../8hand_gesture_dataset/HandGesture07.txt" path8 = "../8hand_gesture_dataset/HandGesture08.txt" path = [path1, path2, path3, path4, path5, path6, path7, path8]
2. データを呼び出すための関数の定義
以下のpath_to_data関数ではジェスチャのファイルパスとデータ番号を引数として渡すことで、引数に該当する筋電波形のデータのリストを取得できる。
# 取得したいジェスチャのデータをファイルパスとデータ番号から筋電データを取得する def path_to_data(path, rand_num): # 8hand_gesture_dataset内のテキストファイルの読み込み with open(path) as f: s = f.read() # テキストファイルの内容は全て文字列型なので、Numpy配列の機械学習しやすいように変換する s_l = s.split('\n\n') del s_l[0] all_data = np.array([]) k = 0 #1周目のループを判断するためのブール値 # 分割した50データはまだ文字列型の配列でまとめられているから、機械学習がしやすいようにデータを変換して、int型の配列に変換 # rand_numにリスト型で指定した値で取得するデータを指定 for num in rand_num: # 1データをセンサ数で分割 data = s_l[num].split("\n") del data[0] # サンプルを8センサで読み取った、1センサあたり100サンプルの8×100のint型配列に変換 sensor8 = [] for i in range(len(data)): data[i] = data[i].replace('{', '').replace('}', '').replace(' ', '') data_str = data[i].split(",") for j in range(len(data_str)): if data_str[j] == '': del data_str[j] data_int = [int(s) for s in data_str] sensor8.append(data_int) # 8×100を100×8に転置 arr_sensor8 = np.array(sensor8).T if len(arr_sensor8) == 100: if k != 0: all_data = np.concatenate([all_data, arr_sensor8], 0) elif k == 0: all_data = arr_sensor8 k = 1 # 変換したデータを連結した配列を返す return all_data
以下のdata_load関数では、50データから任意のデータ番号をリストで指定することで、該当するデータ番号のサンプルのリストとサンプルとマッチするクラスラベルのリストを取得できる。
# データの読み込み # rand_num: データ番号リスト def data_load(rand_num): gesture = 1 # 動作のクラス値の変数 all_data = [] # 全ての動作データの保存配列 for p in path: # print(p) # データの取得 data = path_to_data(p, rand_num) # 全ての特徴データとクラスデータを1つの配列にまとめる処理 # ループ1周目の処理 if p == path1: all_data = data gesture_array = np.full(len(data), gesture) # 1周目以降の処理 else: all_data = np.concatenate([all_data, data]) gesture_array = np.concatenate([gesture_array, np.full(len(data), gesture)]) # rand_num_ls.append(rand_num) gesture += 1 #print(len(all_data)) # emgの特徴量[sensor1, sensor2, ... , sensor8] emg_data = all_data # classラベル # gesture_array return emg_data, gesture_array
以下のload_train_test()関数では、random_stateで乱数シード値を指定して、kで学習データに使用したいデータの数を指定することで、50データから学習データと未知データを生成する。
# データの読み込み def load_train_test(random_state = 0, k = 2): random.seed(random_state) train_num = random.sample(range(50), k = k) # 50データ中から学習データに使用するデータ番号を選択 # 学習データの読み込み emg_train, gesture_train = data_load(train_num) print(train_num) # 未知データの番号リスト test_num = list(range(50)) for num in train_num: test_num.remove(num) print(test_num) # テストデータの読み込み emg_test, gesture_test = data_load(test_num) return emg_train, emg_test, gesture_train, gesture_test
3. 識別器を生成するための関数の定義
from sklearn.svm import SVC # SVMライブラリ from sklearn.discriminant_analysis import LinearDiscriminantAnalysis # 線形判別分析ライブラリ from sklearn.neighbors import KNeighborsClassifier # k近傍法ライブラリ from sklearn.model_selection import train_test_split # データ分割ライブラリ from sklearn.model_selection import cross_val_score # 交差検証ライブラリ # 各識別モデル生成用の関数 def generate_dicriminater(x_trainval, y_trainval): x_train, x_test, y_train, y_test = train_test_split(x_trainval, y_trainval, test_size=0.3, random_state=0) # 識別器の保存リスト model_list = [] # 識別器名リスト name_list = ["svm_linear", "svm_poly", "svm_rbf", "svm_sigmoid", "lda", "knn"] # score_list val_score_list = [] test_score_list = [] # SVM識別器の生成 kernel_str = ["linear", "poly", "rbf", "sigmoid"] param_list = [0.001, 0.01, 0.1, 1, 10, 100] for k in kernel_str: print(k) best_score = 0 best_parameters = {} # 交差検証を用いたグリッドサーチ for gamma in param_list: for C in param_list: # SVCのインスタンス生成 model = SVC(kernel = k, gamma=gamma, C=C, random_state=None) # 交差検証 パラメータcvで分割方法を設定 scores = cross_val_score(model, x_train, y_train, cv=5) # 交差検証による評価値の平均 score = np.mean(scores) if score>best_score: best_score = score best_parameters = {'gamma' : gamma, 'C' : C} # もっともスコアの高いパラメータで識別器をインスタンス model = SVC(kernel=k, **best_parameters) # モデルの保存 model_list.append(model) # best_parametersにおける識別器を作成 model.fit(x_train, y_train) # testデータの評価 test_score = model.score(x_test, y_test) # スコアの保存 val_score_list.append(best_score) test_score_list.append(test_score) print('Best score on validation set: {}'.format(best_score)) print('Best parameters: {}'.format(best_parameters)) print('Test set score with best parameters: {}\n'.format(test_score)) # 線形判別分析の識別器生成 lda = LinearDiscriminantAnalysis() model_list.append(lda) # 交差検証 scores = cross_val_score(lda, x_train, y_train, cv=5) val_score = np.mean(scores) # testデータの評価 lda.fit(x_train, y_train) test_score = lda.score(x_test, y_test) # スコアの保存 val_score_list.append(val_score) test_score_list.append(test_score) print("LDA") print("Score on validation set: {}".format(val_score)) print("test_set score: {}\n".format(test_score)) # k近傍法の識別器生成 knn = KNeighborsClassifier(n_neighbors = 3) model_list.append(knn) # 交差検証 scores = cross_val_score(knn, x_train, y_train, cv=5) val_score = np.mean(scores) # testデータの評価 knn.fit(x_train, y_train) test_score = knn.score(x_test, y_test) # スコアの保存 val_score_list.append(val_score) test_score_list.append(test_score) print("knn") print("Score on validation set: {}".format(val_score)) print("test_set score: {}\n".format(test_score)) return model_list, val_score_list, test_score_list, name_list
4. 識別率のリストを生成する
# データ数による識別率の変動の比較 index_list = np.arange(1,9) del_index = np.where(index_list == 5) index_list = np.delete(index_list, del_index) for k in index_list: print("Number {}\n".format(k)) # emgとgestureの学習データと未知データの呼び出し emg_train, emg_unknown, gesture_train, gesture_unknown = load_train_test(random_state=1, k=k) # 識別器の生成と生成時の識別率の呼び出し model_list, val_score_list, test_score_list, name_list = generate_dicriminater(emg_train, gesture_train) un_score_list = [] for m in model_list: m.fit(emg_train, gesture_train) un_score = m.score(emg_unknown, gesture_unknown) un_score_list.append(un_score) # list型からnp.array型に変換 val_score_arr = np.array(val_score_list) test_score_arr = np.array(test_score_list) un_score_arr = np.array(un_score_list) if k == index_list[0]: val_score_summary = val_score_arr test_score_summary = test_score_arr un_score_summary = un_score_arr elif k > index_list[0]: val_score_summary = np.vstack([val_score_summary, val_score_arr]) test_score_summary = np.vstack([test_score_summary, test_score_arr]) un_score_summary = np.vstack([un_score_summary, un_score_arr]) print(val_score_summary) print(test_score_summary) print(un_score_summary) print("Finish") # 転置処理 val_summ = val_score_summary.T test_summ = test_score_summary.T un_summ = un_score_summary.T print("valiation score") print(val_summ) print("\n") print("test score") print(test_summ) print("\n") print("unknown score") print(un_summ)
5. 識別率をグラフにプロット
# 学習データの数による識別率の違いをグラフ化 import matplotlib.pyplot as plt data_num = np.arange(1,len(val_summ[0])+1) fig = plt.figure(figsize = (13,15)) ax1 = fig.add_subplot(311) for i in range(len(val_summ)): # ドットの配置 ax1.scatter(data_num, val_summ[i], label=name_list[i]) # ラインの配置 ax1.plot(data_num, val_summ[i]) # グラフタイトル plt.title("cross validation score") # 軸ラベルの表示 plt.xlabel("Number of trials") plt.ylabel("Identification rate") # 横軸のtickを変更 plt.xticks(data_num) plt.yticks(np.arange(0, 1.1, 0.1)) # グリッド線 plt.grid() # 凡例の表示とグラフの表示 plt.legend(loc='lower right') ax2 = fig.add_subplot(312) for i in range(len(val_summ)): # ドットの配置 ax2.scatter(data_num, test_summ[i], label=name_list[i]) # ラインの配置 ax2.plot(data_num, test_summ[i]) plt.title("test score") # 軸ラベルの表示 plt.xlabel("Number of trials") plt.ylabel("Identification rate") # 横軸のtickを変更 plt.xticks(data_num) plt.yticks(np.arange(0, 1.1, 0.1)) # グリッド線 plt.grid() # 凡例の表示とグラフの表示 plt.legend(loc='lower right') ax3 = fig.add_subplot(313) for i in range(len(val_summ)): # ドットの配置 ax3.scatter(data_num, un_summ[i], label=name_list[i]) # ラインの配置 ax3.plot(data_num, un_summ[i]) plt.title("unknown score") # 軸ラベルの表示 plt.xlabel("Number of trials") plt.ylabel("Identification rate") # 横軸のtickを変更 plt.xticks(data_num) plt.yticks(np.arange(0, 1.1, 0.1)) # グリッド線 plt.grid() # 凡例の表示とグラフの表示 plt.legend(loc='lower right')
実行結果
cross validation score:学習データを分割した内のtrainデータで交差検証をした時の識別率
test score:学習データを分割した内のtestデータを識別した時の識別率
unknown score:50データの内、学習データ以外の未知データを識別した時の識別率
学習データ数が識別率に影響を与えているのは、unknown scoreの未知データに対するグラフでsvm_sigmoid以外は学習データ数が増加するにしたがって、識別率が上昇している。
svm_linear、svm_poly、lda、knnに関してはデータ数が2~3あたりから識別率がおおよそ0.95で変化していないため、学習データ数は2、3でいいと考えられる。