本日はお忙しいところ、お集まりいただきありがとうございました。本日は以下の点について解説します。
これらを学ぶことで、以下のような具体的課題に適用可能です。
ご存知の方も多いと思いますが、人工知能とはただの最適化数学の応用でしかありません。以下の例を考えてみてください。
最近あちこちで人工知能、人工知能と呼ばれていますが、上のの手続きをやっているにすぎません。 人工知能の分野では、(★)、(▲)、(■)に、以下のような専門用語がついています。
記号 | 呼び方 | 意味 | 具体的な方法 |
---|---|---|---|
★ | モデル | 知能の形状みたいなもの | ニューラルネット、SVM、決定木、深層学習など |
▲ | 評価関数 | 頭の良さの定義づけ | 平均二乗誤差、適合率、再現率、F値など |
■ | 学習 | 頭が良くなるように$a, b$を変更 | 最急勾配降下法、モーメンタム、遺伝的アルゴリズムなど |
人工知能に興味があって自分で勉強している人などは、具体的な方法に示したことを知っていると思います。 これを点で理解していると、どれがなんなのかわかりませんが、点と点を結んだ線として理解すると、★、▲、■の関係になります。 人工知能そのものの研究をしている人たちは、★、▲、■に新しい方法を提案しています (今回はあまり関係ありませんが、大前は(▲)を専門としています)。
人工知能を作ったことがある人もない人も、作る場合には、
という点は、最低限意識する必要はあります。
今回は具体例として、慣性センサから機械の異常状態を検出するという課題を扱ってみます。
一般的な慣性センサは、3軸加速度と3軸角速度を取得することができます。 センサデータを扱うときは、サンプリング周波数をきちんと記録することが必要です。 サンプリング周波数とは、1秒間に何個データを測定するかという意味です。 例えば、サンプリング周波数が100Hzの場合は、1秒間に100個データを取る、となります。 データ点数が500個あれば、5秒間の測定データだとわかるわけです。 しかし、サンプリング周波数を記録していなければ、何秒のデータなのかわからなくなってしまうので、データの取り直しになってしまいます。
今回、事例としてあるモータの横に、慣性センサをつけて、サンプリング周波数100Hzとして、150例のデータを取得しました。 取得したデータの内訳は以下の通りです。
人工知能の分野において、分類したい状態のことをクラスと呼びます。今回で言えば、N, A1, A2がクラスに相当するわけです。 そして、入力したセンサデータからどのクラスに属するのか分類する問題を、クラス分類問題と呼びます。
Signal_N: クラスNの波形(正常)
Signal_A1: クラスA1の波形(異常1)
Signal_A2: クラスA2の波形(異常2)
CSVデータの中には、以下のようなセンサデータが入っているイメージです。
Seq | Xacc | Yacc | Zacc | Xang | Yang | Zang |
---|---|---|---|---|---|---|
1 | 1.3 | 2.4 | 3.2 | 3.6 | 2.4 | 5.1 |
2 | 3.4 | 2.5 | 1.3 | 3.2 | 2.2 | 5.7 |
... | ... | ... | ... | ... | ... | ... |
100 | 0.1 | 0.3 | 1.4 | 1.2 | 4.2 | 5.7 |
import numpy as np
import random
# 正常クラスの信号読み込み
Signal_Class_N_temp=[]
for i in range(101, 151):
Signal_Class_N_temp.append(np.loadtxt("sensdat/dat/" + str(i) + ".csv", delimiter=",", skiprows=1, usecols=range(1,8)))
# 異常1クラスの信号読み込み
Signal_Class_A1_temp=[]
for i in range(51, 101):
Signal_Class_A1_temp.append(np.loadtxt("sensdat/dat/" + str(i) + ".csv", delimiter=",", skiprows=1, usecols=range(1,8)))
# 異常2クラスの信号読み込み
Signal_Class_A2_temp=[]
for i in range(1, 51):
Signal_Class_A2_temp.append(np.loadtxt("sensdat/dat/" + str(i) + ".csv", delimiter=",", skiprows=1, usecols=range(1,8)))
# 信号長を1秒に固定
SigLength = 100
Signal_Class_N = []
Signal_Class_A1 = []
Signal_Class_A2 = []
for i in range(0,50):
Signal_Class_N.append(Signal_Class_N_temp[i][0:SigLength,:])
Signal_Class_A1.append(Signal_Class_A1_temp[i][0:SigLength,:])
Signal_Class_A2.append(Signal_Class_A2_temp[i][0:SigLength,:])
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(20, 4))
ViewID=0
# クラスN 正常
plt.subplot(1,3,1)
plt.plot(Signal_Class_N[ViewID][:,1:4])
plt.title('Class N')
plt.xlabel('Sequence')
plt.ylabel('Amplitude')
plt.legend(['Xacc', 'Yacc', 'Zacc', 'Xang', 'Yang', 'Zang'])
plt.ylim(-2000, 13000)
# クラスA1 異常
plt.subplot(1,3,2)
plt.plot(Signal_Class_A1[ViewID][:,1:4])
plt.title('Class A1')
plt.xlabel('Sequence')
plt.ylabel('Amplitude')
plt.legend(['Xacc', 'Yacc', 'Zacc', 'Xang', 'Yang', 'Zang'])
plt.ylim(-2000, 13000)
# クラスA2 異常
plt.subplot(1,3,3)
plt.plot(Signal_Class_A2[ViewID][:,1:4])
plt.title('Class A2')
plt.xlabel('Sequence')
plt.ylabel('Amplitude')
plt.legend(['Xacc', 'Yacc', 'Zacc', 'Xang', 'Yang', 'Zang'])
plt.ylim(-2000, 13000)
この後の処理は、深層学習か、古典的な機械学習かで、微妙に処理が異なってきますが、いずれにせよ、人工知能の中身にあるイメージを理解することは大変重要です。
人工知能をブラックボックスで使わないための入り口は、以下の言葉を理解することだと考えています。
ややこしいと思うので、$n=2, 3$で考えてみます。
もうちょっと噛み砕くと、以下のようになります。
やはりわかりにくいと思うので、イラストで考えてみます。
上の例では、2次元の空間に1次元の識別境界を生成しています。この1次元こそがAIであることはイメージがついたでしょうか。 これを抽象化させると、
となります。
それでは、$6\times100$の信号を特徴量に変換してみます。上で述べたとおり、特徴量には、
の3種類があることがわかりました。周波数成分まで踏み込むとややこしくなるので、ひとまず時間領域特徴量の特に平均と分散だけで考えてみます(実際にやる場合は、高周波成分のスペクトル密度なども、特徴量として採用したほうがいいです)。
平均値(Mean)
分散値(Variance)
この2つを採用するので、具体的に算出する特徴量は、以下の12個となります。
これにより、600個の数値で表現される一回の観測結果が、12個の数字で表現することができます。
以下のコードは、信号を平均・分散の特徴量に変換するコードです。
NumSignal = 50 # 信号の数(1クラス50信号)
NumAx = 6 # 軸の数(3軸加速度・角速度)
# クラスN(正常データ)の平均値と分散値を算出
Features_N=[[]]
for i in range(0, NumSignal):
for j in range(1, NumAx+1):
Features_N[i].append(np.round(np.mean(Signal_Class_N[i][:,j]),3))
for j in range(1, NumAx+1):
Features_N[i].append(np.round(np.var(Signal_Class_N[i][:,j]),3))
if i != NumSignal-1:
Features_N.append([])
# クラスA1(異常1データ)の平均値と分散値を算出
Features_A1=[[]]
for i in range(0, NumSignal):
for j in range(1, NumAx+1):
Features_A1[i].append(np.round(np.mean(Signal_Class_A1[i][:,j]),3))
for j in range(1, NumAx+1):
Features_A1[i].append(np.round(np.var(Signal_Class_A1[i][:,j]),3))
if i != NumSignal-1:
Features_A1.append([])
# クラスA2(異常2データ)の平均値と分散値を算出
Features_A2=[[]]
for i in range(0, NumSignal):
for j in range(1, NumAx+1):
Features_A2[i].append(np.round(np.mean(Signal_Class_A2[i][:,j]),3))
for j in range(1, NumAx+1):
Features_A2[i].append(np.round(np.var(Signal_Class_A2[i][:,j]),3))
if i != NumSignal-1:
Features_A2.append([])
# 各クラス、一本の信号だけ特徴量を表示
Factor = ['1 :Xacc平均', '2 :Yacc平均', '3 :Zacc平均', # 3軸加速度の平均
'4 :Xang平均', '5 :Yang平均', '6 :Zang平均', # 3軸角速度の平均
'7 :Xacc分散', '8 :Yacc分散', '9 :Zacc分散', # 3軸加速度の分散
'10:Xang分散', '11:Yang分散', '12:Zang分散'] # 3軸角速度の分散
print(' *** 各クラスの0番目の信号の特徴量(12次元) ***')
print('特徴量名 | クラスN | クラスA1 | クラスA2')
for i in range(0,12):
print(Factor[i], ' | ', Features_N[0][i], ' | ', Features_A1[0][i], ' | ', Features_A2[0][i])
各クラス(正常、異常1、異常2)の0番目の信号の特徴量だけを表示してみました。
というように、600次元の信号が12次元の特徴量に変化したことはわかるでしょうか。このように、人工知能を構築するには、低次元化を行う必要があります。
人工知能の分野では、データセットを2つ、教師データ、テストデータに分割します。 これはそれぞれ以下の意味を持ちます。
人工知能を構築する様々な手法は、通常、教師データを正しく正答させるような学習を行います。ただし、教師データを良く当てるようなモデルを作ったとしても、それが直ちに良いモデルだということができません。人間の学力調査において、教材の問題はよく解けるが、期末テストではボロボロであることを通常、頭が良いとは言いません。教師データの精度が良いとはすなわち、教材の問題をよく解けることと等価で、期末テストでどのくらい解けるかを意味するわけではありません。人工知能の分野でも、期末テストを用意して、本当の頭の良さを測ってあげる必要があります。このためのデータセットのことを、テストデータと言います。
今回は150信号(1クラス50信号、3クラス)なので、以下のようにデータセットを分割します。
以下のコードは、教師データ、テストデータを取得するコードです。それぞれ以下の意味を持ちます。
# 正答ラベルの付与
ClassLabel_N=[]
for i in range(0, NumSignal):
ClassLabel_N.append("N")
ClassLabel_A1=[]
for i in range(0, NumSignal):
ClassLabel_A1.append("A1")
ClassLabel_A2=[]
for i in range(0, NumSignal):
ClassLabel_A2.append("A2")
# 教師データセット(1クラス0〜29番目の30信号、3クラスのため、合計90信号)
X_train = np.concatenate([Features_N[0:30], Features_A1[0:30], Features_A2[0:30]])
Y_train = np.concatenate([ClassLabel_N[0:30], ClassLabel_A1[0:30], ClassLabel_A2[0:30]])
# テストデータセット(1クラス30〜49番目の20信号、3クラスのため、合計60信号)
X_test = np.concatenate([Features_N[30:49], Features_A1[30:49], Features_A2[30:49]])
Y_test = np.concatenate([ClassLabel_N[30:49], ClassLabel_A1[30:49], ClassLabel_A2[30:49]])
さて、モータの異常状態を検出したいとき、どんな情報が必要でしょうか。横揺れのばらつき、縦揺れのばらつき、高周波成分の大きさ……、色々と必要そうです。 当然、情報は多ければ多いほど、有利です。 人間が何らかの知的な判断を行うときも、情報は少ないよりも多い方が有利です。 そう考えると、たくさんの情報、すなわち、たくさんの特徴量を採用して人工知能を構築することは、一見良さそうに思えます。
実はこれ、一概に正しいとは言えないのです。 より具体的に言うと、たくさんの特徴量を採用することには、メリットとデメリットが混在しているのです。 これをイメージした図を以下に示します。
このように、人工知能に与える情報を増やしすぎたとき、教師データが莫大にければ良い精度が出せなくなる現象を、次元の呪いと言います。 すなわち、今回用意した12次元の特徴量をすべて使用するのではなく、何らかの形で12次元の特徴量の中から、不要なもの、有効なものを分別させることが必要になります。
大量の特徴量の中から人工知能の精度向上に寄与する優れた特徴量を探索すると言う問題は、Feature Engineering と呼ばれる人工知能の学術分野となります。これは、数ある人工知能の研究領域の中で、極めて重要な部分として知られており、世界中の研究者がいろいろな議論を行なっています。代表的な方法としては、
などがあります。すべて説明すると大変なので、上の2つだけ、以下で解説します。
fig = plt.figure(figsize=(12, 4))
plt.subplot(1,2,1)
plt.scatter(X_train[ 0:30,0], X_train[ 0:30,1], s=30, c='blue', marker='s', linewidths=0.5, edgecolors='black')
plt.scatter(X_train[30:60,0], X_train[30:60,1], s=30, c='orange', marker='s', linewidths=0.5, edgecolors='black')
plt.scatter(X_train[60:90,0], X_train[60:90,1], s=30, c='red', marker='s', linewidths=0.5, edgecolors='black')
plt.title("Mean(Xacc)-Mean(Yacc)")
plt.xlabel("Mean(Xacc)")
plt.ylabel("Mean(Yacc)")
plt.legend(["Class N", "Class A1", "Class A2"], loc="upper right")
plt.grid(True)
plt.subplot(1,2,2)
plt.scatter(X_train[ 0:30,4], X_train[ 0:30,5], s=30, c='blue', marker='s', linewidths=0.5, edgecolors='black')
plt.scatter(X_train[30:60,4], X_train[30:60,5], s=30, c='orange', marker='s', linewidths=0.5, edgecolors='black')
plt.scatter(X_train[60:90,4], X_train[60:90,5], s=30, c='red', marker='s', linewidths=0.5, edgecolors='black')
plt.title("Mean(Yang)-Mean(Zang)")
plt.xlabel("Mean(Yang)")
plt.ylabel("Mean(Zang)")
plt.legend(["Class N", "Class A1", "Class A2"], loc="upper right")
plt.grid(True)
試しに以下の2つの特徴量を用いて、3クラスの散布図を描いてみました。
どちらの空間も、Class A1(異常1)の検出はできそうな感じはしますね。 このような作業をいろいろな特徴量の組み合わせで行うと、Class N(正常)、A1(異常1)、A2(異常2)の3クラスをうまく分離できる特徴量が何となく見えてきます。
とはいえ、目で見て探すのは大変ですし、今回は2次元の空間(あるいはせいぜい3次元)しか可視化することができないので、限界もあります。例えば今回は、平均、分散というわずか2つの特徴量しか採用していないため、12次元(2種類$\times$6軸)となりますが、歪度や周波数領域の特徴量を採用したりすると、100次元、1000次元と、どんどん特徴量の数が増えてしまいます。このような大量の特徴量から、良い特徴量を目で見て探すことは、ほぼ不可能になってしまいます。
ここでは、Out-of-Bag Error法と呼ばれる、有効な特徴量の探索方法について述べます。 これは、以下の手順を踏みます。
注意: かなりざっくり書いています。詳細を知りたい人はブートストラップ法などを勉強すると良いです。
pythonでは、scikit-learn上でこの手法がパッケージ化されているので、簡単に実装できます。
from sklearn.ensemble import RandomForestClassifier
RF = RandomForestClassifier(n_estimators=300, random_state=1)
RF.fit(X_train, Y_train)
FeatureImportance = RF.feature_importances_
label = ["Mean(Xacc)", "Mean(Yacc)", "Mean(Zacc)", "Mean(Xang)", "Mean(Yang)", "Mean(Zang)",
"Var(Xacc)", "Var(Yacc)", "Var(Zacc)", "Var(Xang)", "Var(Yang)", "Var(Zang)"]
left = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
plt.barh(left, width=FeatureImportance, tick_label=label, align="center")
plt.xlabel("Value")
plt.ylabel("Feature's name")
plt.title("Feature importances")
plt.show()
値が大きいほど、その特徴量は有効であることを示しています。 このように、目で見て探す方法とは異なり、一瞬で、有効な特徴量は何か、定量的に知ることができます。
今回は12次元の特徴量なので、あまり恩恵を感じにくいのですが、100次元、1000次元といった莫大な特徴量の中から有効な特徴量を探索しなければならないときに、大変大きな恩恵を感じることができます。一般的には、重要度が高い特徴量上位6〜7本を使おう、というように、ある程度の次元を保持させます。 ただし今回はわかりやすさ重視のため、2個の特徴量のみを使用することにします。すなわち、
だけを使ってみます。さて、実際にこの2つの特徴量で正常、異常1、異常2の状態が分類できそうか、散布図により確認してみます。
import matplotlib.ticker as ptick
fig = plt.figure(figsize=(12, 4))
ax=plt.subplot(1,2,1)
plt.scatter(X_train[ 0:30,6], X_train[ 0:30,7], s=30, c='blue', marker='s', linewidths=0.5, edgecolors='black')
plt.scatter(X_train[30:60,6], X_train[30:60,7], s=30, c='orange', marker='s', linewidths=0.5, edgecolors='black')
plt.scatter(X_train[60:90,6], X_train[60:90,7], s=30, c='red', marker='s', linewidths=0.5, edgecolors='black')
plt.title("Var(Xacc)-Var(Yacc)")
plt.xlabel("Var(Xacc)")
plt.ylabel("Var(Yacc)")
plt.legend(["Class N", "Class A1", "Class A2"], loc="upper right")
plt.grid(True)
ax.yaxis.set_major_formatter(ptick.ScalarFormatter(useMathText=True))
ax.xaxis.set_major_formatter(ptick.ScalarFormatter(useMathText=True))
ax.ticklabel_format(style="sci", axis="y",scilimits=(0,0))
ax.ticklabel_format(style="sci", axis="x",scilimits=(0,0))
# 左下を拡大した散布図
ax=plt.subplot(1,2,2)
plt.scatter(X_train[ 0:30,6], X_train[ 0:30,7], s=30, c='blue', marker='s', linewidths=0.5, edgecolors='black')
plt.scatter(X_train[30:60,6], X_train[30:60,7], s=30, c='orange', marker='s', linewidths=0.5, edgecolors='black')
plt.scatter(X_train[60:90,6], X_train[60:90,7], s=30, c='red', marker='s', linewidths=0.5, edgecolors='black')
plt.title("Var(Xacc)-Var(Yacc)")
plt.xlabel("Var(Xacc)")
plt.ylabel("Var(Yacc)")
plt.legend(["Class N", "Class A1", "Class A2"], loc="upper left")
plt.grid(True)
plt.xlim(0, 900000)
plt.ylim(0, 900000)
ax.yaxis.set_major_formatter(ptick.ScalarFormatter(useMathText=True))
ax.xaxis.set_major_formatter(ptick.ScalarFormatter(useMathText=True))
ax.ticklabel_format(style="sci", axis="y",scilimits=(0,0))
ax.ticklabel_format(style="sci", axis="x",scilimits=(0,0))
見てみると、Class N(正常)、A1(異常1)、A2(異常2)がよく分離されていることが確認できます。Out-of-Bag Error法により、有効な特徴量を発見できたことが確認されました。そのため、有効な特徴量を取り出します。
注意: 今回は簡単な例なので、わざわざこんなことをしなくても、分散に差がありそう、ということは信号を見ればわかる人はわかるかもしれませんね。
X_train_optimal = X_train[:,6:8]
X_test_optimal = X_test[:,6:8]
# pythonの仕様につき、6:8とすると、6列目以上8列目未満、すなわち、6列目と7列目の2列分が取り出されます。
# 6列目: X軸加速度の分散
# 7列目: Y軸加速度の分散
分離に有効な特徴量を2つ発見できたので、試しにこの2つの特徴量を用いて、人工知能を構築してみます。いろんな種類がありますが、今回は以下の手法を用いてみます。
決定木
ニューラルネットワーク
サポートベクターマシン
これらはすべて、n次元の空間に対し、$n-1$次元の識別境界を生成する手法となります。数理的な解釈としては、それ以外のなにものでもありません。今回は特徴量を2つ使用するので、2次元の空間に対し、1次元の線を生成する手法となります。
# 決定木の学習
from sklearn import tree
Model_Tree_01 = tree.DecisionTreeClassifier(min_samples_leaf=1, random_state=15)
Model_Tree_01 = Model_Tree_01.fit(X_train_optimal, Y_train)
# 木構造の可視化
import pydotplus
dot_data = 'Tree.dot'
tree.export_graphviz(Model_Tree_01, out_file=dot_data)
graph = pydotplus.graphviz.graph_from_dot_file(dot_data)
from IPython.display import Image
Image(graph.create_png())
valueというのは、ソートされたクラスラベル、左から順に(異常1, 異常2, 正常)の数となります。 一番上をルートノードと言います。今回は教師データが30信号$\times$3クラス=90信号分なので、value=[30, 30, 30]となっています。 これを、特徴量とその閾値による条件がTrue(正しい)かFalse(正しくない)の分岐により、90信号がどんどん分岐されていきます。
を意味します。ちょっとわかりにくいと思うので、構築した決定木の分岐基準を言語化してみます。
このように、人間がわかりやすい判断基準を得ることができました。 これでもわかりやすいのですが、特徴量空間に実際にどのような識別境界が得られたのか可視化してみます。
Xmin, Ymin, Xmax, Ymax = 0, 0, 900000, 900000 # 空間の最小最大値
resolution = 10000
x_mesh, y_mesh = np.meshgrid(np.arange(Xmin, Xmax, resolution),
np.arange(Ymin, Ymax, resolution))
MeshDat = np.array([x_mesh.ravel(), y_mesh.ravel()]).T
z = Model_Tree_01.predict(MeshDat) # 決定木に判別させる
z = np.reshape(z, (len(x_mesh), len(y_mesh))) # 空間のためにデータ整形
# 決定木の判定結果
ax = plt.subplot(1,1,1)
plt.scatter(x_mesh[z=='N'], y_mesh[z=='N'], s=5, alpha=0.3, c='blue')
plt.scatter(x_mesh[z=='A1'], y_mesh[z=='A1'], s=5, alpha=0.3, c='orange')
plt.scatter(x_mesh[z=='A2'], y_mesh[z=='A2'], s=5, alpha=0.3, c='red')
# 教師データの散布図
plt.scatter(X_train[ 0:30,6], X_train[ 0:30,7], s=30, c='blue', marker='s', linewidths=0.5, edgecolors='black')
plt.scatter(X_train[30:60,6], X_train[30:60,7], s=30, c='orange', marker='s', linewidths=0.5, edgecolors='black')
plt.scatter(X_train[60:90,6], X_train[60:90,7], s=30, c='red', marker='s', linewidths=0.5, edgecolors='black')
plt.title("Var(Xacc)-Var(Yacc)")
plt.xlabel("Var(Xacc)")
plt.ylabel("Var(Yacc)")
plt.legend(["Class N", "Class A1", "Class A2"], loc="upper left")
plt.grid(True)
plt.xlim(0, 900000)
plt.ylim(0, 900000)
ax.yaxis.set_major_formatter(ptick.ScalarFormatter(useMathText=True))
ax.xaxis.set_major_formatter(ptick.ScalarFormatter(useMathText=True))
ax.ticklabel_format(style="sci", axis="y",scilimits=(0,0))
ax.ticklabel_format(style="sci", axis="x",scilimits=(0,0))
一つ一つのプロットが、教師データです。カラフルな領域が、教師データを使用して生成した識別境界となります。 2次元の空間上(面)に1次元の識別境界(線)ができていますね。 それはともかく、決定木により生成した識別境界は、教師データをすべて正しく分類できています。 とはいえ、X軸方向の揺れのばらつきが小さければ、Y軸方向の揺れのばらつきがいくら大きくても正常と判定されるようです。 ちょっと微妙な感じもしますね。
続いてニューラルネットワークです。こちらも設定すべきハイパーパラメータがたくさんありますが、今回は学習回数のみをセットします。 そして、以下の設定でモデルを学習させてみます。
簡単にいうと、入力された2つの特徴量が、別の2次元に変換され、正常、異常1、異常2のどれらしいかを3次元ベクトルとして出力する2段階の関数となります。変換自体は高校3年生の数学で学ぶ程度のことで、ただの足し算、掛け算、割り算、指数関数の組み合わせなので、かなり簡単です。パラメータの学習は大学1年生くらいの数学で学ぶ内容がベースです(偏微分を使います)。
直にニューラルネットワークをプログラムで書かせる場合、200行程度必要ですが、pythonでは簡単にプログラムを書くことができます。
from sklearn.neural_network import MLPClassifier
import warnings
warnings.filterwarnings('ignore')
Model_NN_01 = MLPClassifier(hidden_layer_sizes=(2, ), solver="sgd", activation="logistic", learning_rate_init=0.01,
random_state=1, max_iter=5000, alpha=0.1, tol = pow(10, -10), verbose=False)
Model_NN_01 = Model_NN_01.fit(X_train_optimal/100000, Y_train)
これで学習が終わりです。決定木のときと同様に、2次元の空間に対し、どんな1次元が生成されたのか、確認してみます。
Xmin, Ymin, Xmax, Ymax = 0, 0, 900000, 900000 # 空間の最小最大値
resolution = 10000
x_mesh, y_mesh = np.meshgrid(np.arange(Xmin, Xmax, resolution),
np.arange(Ymin, Ymax, resolution))
MeshDat = np.array([x_mesh.ravel(), y_mesh.ravel()]).T
MeshDat = MeshDat / 100000
z = Model_NN_01.predict(MeshDat)
z = np.reshape(z, (len(x_mesh), len(y_mesh)))
# ニューラルネットの判定結果
ax = plt.subplot(1,1,1)
plt.scatter(x_mesh[z=='N'], y_mesh[z=='N'], s=5, alpha=0.3, c='blue')
plt.scatter(x_mesh[z=='A1'], y_mesh[z=='A1'], s=5, alpha=0.3, c='orange')
plt.scatter(x_mesh[z=='A2'], y_mesh[z=='A2'], s=5, alpha=0.3, c='red')
# 教師データの散布図
plt.scatter(X_train[ 0:30,6], X_train[ 0:30,7], s=30, c='blue', marker='s', linewidths=0.5, edgecolors='black')
plt.scatter(X_train[30:60,6], X_train[30:60,7], s=30, c='orange', marker='s', linewidths=0.5, edgecolors='black')
plt.scatter(X_train[60:90,6], X_train[60:90,7], s=30, c='red', marker='s', linewidths=0.5, edgecolors='black')
plt.title("Var(Xacc)-Var(Yacc)")
plt.xlabel("Var(Xacc)")
plt.ylabel("Var(Yacc)")
plt.legend(["Class N", "Class A1", "Class A2"], loc="upper left")
plt.grid(True)
plt.xlim(0, 900000)
plt.ylim(0, 900000)
ax.yaxis.set_major_formatter(ptick.ScalarFormatter(useMathText=True))
ax.xaxis.set_major_formatter(ptick.ScalarFormatter(useMathText=True))
ax.ticklabel_format(style="sci", axis="y",scilimits=(0,0))
ax.ticklabel_format(style="sci", axis="x",scilimits=(0,0))
今回はどうでしょうか。決定木での識別境界のデメリットが大きく緩和されていますね。 どちらの分散も小さいときのみ、正常判定がなされるという、割と妥当な識別境界を得ることができました。
ニューラルネットワークの学習は、数式内部にある頭の良さを操作する変数を、微分により、プラスマイナスどちらにずれしたらいいか?という判断を行って、実際にその方向に変化させる、という処理の反復により実現されます。すなわちちょっとずつ頭が良くなる、ということです。これを動画してみます。
以下のコードは、ニューラルネットワークの学習状況、すなわち識別境界の変化を動画ファイルにして、出力するコードです。印刷された紙では特に意味がありませんので、ご注意ください。
import matplotlib.animation as animation
NN_Frame = []
NN_Movie = []
fig = plt.figure()
ax = plt.subplot(1,1,1)
for i in range(1,50):
NN_Movie.append(MLPClassifier(hidden_layer_sizes=(2, ), solver="sgd", activation="logistic",
learning_rate_init=0.01, random_state=1, max_iter=i*10, alpha=0.1,
tol = pow(10, -10), verbose=False))
NN_Movie[i-1].fit(X_train_optimal/100000, Y_train)
z = NN_Movie[i-1].predict(MeshDat)
z = np.reshape(z, (len(x_mesh), len(y_mesh)))
# ニューラルネットの判定結果
im1 = plt.scatter(x_mesh[z=='N'], y_mesh[z=='N'], s=5, alpha=0.3, c='blue')
im2 = plt.scatter(x_mesh[z=='A1'], y_mesh[z=='A1'], s=5, alpha=0.3, c='orange')
im3 = plt.scatter(x_mesh[z=='A2'], y_mesh[z=='A2'], s=5, alpha=0.3, c='red')
# 教師データの散布図
im4 = plt.scatter(X_train[ 0:30,6], X_train[ 0:30,7], s=30, c='blue', marker='s', linewidths=0.5, edgecolors='black')
im5 = plt.scatter(X_train[30:60,6], X_train[30:60,7], s=30, c='orange', marker='s', linewidths=0.5, edgecolors='black')
im6 = plt.scatter(X_train[60:90,6], X_train[60:90,7], s=30, c='red', marker='s', linewidths=0.5, edgecolors='black')
plt.title("Var(Xacc)-Var(Yacc)")
plt.xlabel("Var(Xacc)")
plt.ylabel("Var(Yacc)")
plt.legend(["Class N", "Class A1", "Class A2"], loc="upper left")
plt.grid(True)
plt.xlim(0, 900000)
plt.ylim(0, 900000)
ax.yaxis.set_major_formatter(ptick.ScalarFormatter(useMathText=True))
ax.xaxis.set_major_formatter(ptick.ScalarFormatter(useMathText=True))
ax.ticklabel_format(style="sci", axis="y",scilimits=(0,0))
ax.ticklabel_format(style="sci", axis="x",scilimits=(0,0))
# フレームを積む
NN_Frame.append([im1, im2, im3, im4, im5, im6])
ani = animation.ArtistAnimation(fig, NN_Frame, interval=500)
ani.save('NN_Movie.mp4', writer="ffmpeg")
続いて、サポートベクターマシンに移ります。こちらは、クラス間の真ん中を通るように識別境界を生成するモデルです。この特性により、教師データが少なくても比較的良い識別境界を生成することができます。サポートベクターマシンには、大きく分けて2つのモデルがあります。
線形サポートベクターマシン: Linear SVM
非線形サポートベクターマシン Non-linear SVM
どっちがいいんだということは自分が作りたいモデルによって異なるので、今回は両方作ってみます。
from sklearn import svm
# 線形サポートベクターマシン
SVMModel_Lin01 = svm.SVC(kernel='linear', C=0.1)
SVMModel_Lin01 = SVMModel_Lin01.fit(X_train_optimal, Y_train)
#非線形サポートベクターマシン
SVMModel_RBF01 = svm.SVC(kernel='rbf', C=10, gamma=0.00001)
SVMModel_RBF01 = SVMModel_RBF01.fit(X_train_optimal/1000, Y_train)
これで学習が終わりです。これまでと同じように、どのような1次元が生成されたのか、みてみます。
Xmin, Ymin, Xmax, Ymax = 0, 0, 900000, 900000 # 空間の最小最大値
resolution = 10000
x_mesh, y_mesh = np.meshgrid(np.arange(Xmin, Xmax, resolution),
np.arange(Ymin, Ymax, resolution))
MeshDat = np.array([x_mesh.ravel(), y_mesh.ravel()]).T
# メッシュデータの推定
z_Lin01 = SVMModel_Lin01.predict(MeshDat) # モデル1
z_RBF01 = SVMModel_RBF01.predict(MeshDat/1000) # モデル2
# データ整形
z_Lin01 = np.reshape(z_Lin01, (len(x_mesh), len(y_mesh)))
z_RBF01 = np.reshape(z_RBF01, (len(x_mesh), len(y_mesh)))
fig = plt.figure(figsize=(12, 4))
# 線形SVMの可視化
ax = plt.subplot(1,2,1)
plt.scatter(x_mesh[z_Lin01=='N'], y_mesh[z_Lin01=='N'], s=5, alpha=0.3, c='blue')
plt.scatter(x_mesh[z_Lin01=='A1'], y_mesh[z_Lin01=='A1'], s=5, alpha=0.3, c='orange')
plt.scatter(x_mesh[z_Lin01=='A2'], y_mesh[z_Lin01=='A2'], s=5, alpha=0.3, c='red')
plt.scatter(X_train[ 0:30,6], X_train[ 0:30,7], s=30, c='blue', marker='s', linewidths=0.5, edgecolors='black')
plt.scatter(X_train[30:60,6], X_train[30:60,7], s=30, c='orange', marker='s', linewidths=0.5, edgecolors='black')
plt.scatter(X_train[60:90,6], X_train[60:90,7], s=30, c='red', marker='s', linewidths=0.5, edgecolors='black')
plt.title("Linear SVM: Var(Xacc)-Var(Yacc)")
plt.xlabel("Var(Xacc)")
plt.ylabel("Var(Yacc)")
plt.legend(["Class N", "Class A1", "Class A2"], loc="upper left")
plt.grid(True)
plt.xlim(0, 900000)
plt.ylim(0, 900000)
ax.yaxis.set_major_formatter(ptick.ScalarFormatter(useMathText=True))
ax.xaxis.set_major_formatter(ptick.ScalarFormatter(useMathText=True))
ax.ticklabel_format(style="sci", axis="y",scilimits=(0,0))
ax.ticklabel_format(style="sci", axis="x",scilimits=(0,0))
# 非線形SVMの可視化
ax = plt.subplot(1,2,2)
plt.scatter(x_mesh[z_RBF01=='N'], y_mesh[z_RBF01=='N'], s=5, alpha=0.3, c='blue')
plt.scatter(x_mesh[z_RBF01=='A1'], y_mesh[z_RBF01=='A1'], s=5, alpha=0.3, c='orange')
plt.scatter(x_mesh[z_RBF01=='A2'], y_mesh[z_RBF01=='A2'], s=5, alpha=0.3, c='red')
plt.scatter(X_train[ 0:30,6], X_train[ 0:30,7], s=30, c='blue', marker='s', linewidths=0.5, edgecolors='black')
plt.scatter(X_train[30:60,6], X_train[30:60,7], s=30, c='orange', marker='s', linewidths=0.5, edgecolors='black')
plt.scatter(X_train[60:90,6], X_train[60:90,7], s=30, c='red', marker='s', linewidths=0.5, edgecolors='black')
plt.title("Non-linear SVM: Var(Xacc)-Var(Yacc)")
plt.xlabel("Var(Xacc)")
plt.ylabel("Var(Yacc)")
plt.legend(["Class N", "Class A1", "Class A2"], loc="upper left")
plt.grid(True)
plt.xlim(0, 900000)
plt.ylim(0, 900000)
ax.yaxis.set_major_formatter(ptick.ScalarFormatter(useMathText=True))
ax.xaxis.set_major_formatter(ptick.ScalarFormatter(useMathText=True))
ax.ticklabel_format(style="sci", axis="y",scilimits=(0,0))
ax.ticklabel_format(style="sci", axis="x",scilimits=(0,0))
どうでしょうか。意見が割れそうです。なんとなく、非線形サポートベクターマシンの方が良さそうですね(右側)。あと、ニューラルネットワークよりも良さそうな識別境界が得られていますね。先ほどサポートベクターマシンの説明で行なった、あるクラスと別のクラスの、ちょうど真ん中に識別境界が生成されている、ということがわかるでしょうか。一般に、サポートベクターマシンの方がニューラルネットワークよりも良い識別境界が得られると言われる所以が、この考え方となります。
ところで、先ほど非線形サポートベクターマシンは複雑な識別境界を引けるので、デメリットとして過学習が生じやすいと言いましたね。実際に、わざと過学習を起こさせたモデルを作ってみます。
# 非線形サポートベクターマシンの学習(過学習)
SVMModel_RBF02_OverLearning = svm.SVC(kernel='rbf', C=10, gamma=0.001)
SVMModel_RBF02_OverLearning = SVMModel_RBF02_OverLearning.fit(X_train_optimal/1000, Y_train)
Xmin, Ymin, Xmax, Ymax = 0, 0, 900000, 900000 # 空間の最小最大値
resolution = 10000
x_mesh, y_mesh = np.meshgrid(np.arange(Xmin, Xmax, resolution),
np.arange(Ymin, Ymax, resolution))
MeshDat = np.array([x_mesh.ravel(), y_mesh.ravel()]).T
MeshDat = MeshDat / 1000
z = SVMModel_RBF02_OverLearning.predict(MeshDat)
z = np.reshape(z, (len(x_mesh), len(y_mesh)))
# ニューラルネットの判定結果
ax = plt.subplot(1,1,1)
plt.scatter(x_mesh[z=='N'], y_mesh[z=='N'], s=5, alpha=0.3, c='blue')
plt.scatter(x_mesh[z=='A1'], y_mesh[z=='A1'], s=5, alpha=0.3, c='orange')
plt.scatter(x_mesh[z=='A2'], y_mesh[z=='A2'], s=5, alpha=0.3, c='red')
# 教師データの散布図
plt.scatter(X_train[ 0:30,6], X_train[ 0:30,7], s=30, c='blue', marker='s', linewidths=0.5, edgecolors='black')
plt.scatter(X_train[30:60,6], X_train[30:60,7], s=30, c='orange', marker='s', linewidths=0.5, edgecolors='black')
plt.scatter(X_train[60:90,6], X_train[60:90,7], s=30, c='red', marker='s', linewidths=0.5, edgecolors='black')
plt.title("Var(Xacc)-Var(Yacc)")
plt.xlabel("Var(Xacc)")
plt.ylabel("Var(Yacc)")
plt.legend(["Class N", "Class A1", "Class A2"], loc="upper left")
plt.grid(True)
plt.xlim(0, 900000)
plt.ylim(0, 900000)
ax.yaxis.set_major_formatter(ptick.ScalarFormatter(useMathText=True))
ax.xaxis.set_major_formatter(ptick.ScalarFormatter(useMathText=True))
ax.ticklabel_format(style="sci", axis="y",scilimits=(0,0))
ax.ticklabel_format(style="sci", axis="x",scilimits=(0,0))
どうでしょうか。何か様子がおかしいことがわかると思います。
果たして、実際にこのモデルを使用したとして、異常2をうまく検出できるでしょうか?
おそらくですが、できない場合が多いと思います。
ですがこのモデル、学習に使用した教師データをすべて正しく回答しています。これを見ればわかるように、人工知能の分野においては、学習に使用した教師データに正答することは、あまり意味のあることではないのです。
ここではわざと、サポートベクターマシンで過学習を発生させました。しかし、決定木でもニューラルネットワークでも過学習は発生させられます。なので、サポートベクターマシンは他のモデルより劣っているとは決して思わないようにしてください。
分類問題における精度評価の指標として、一般に、適合率、再現率、F値を用います。
適合率と再現率、一見似ていますがまったく別物の指標です。少し、頭の中で以下の事例を考えてみましょう。 糖尿病か否かを判定するモデルがあります。8人の被験者に対し、このモデルの実測と予測を比較したところ、以下の結果となりました。0が糖尿病ではない、1が糖尿病である、ということを意味します。「適合率・再現率」は「高い・低い」、どうなるでしょうか。
ターゲットクラスを「糖尿病である」すなわち「1」と考えてみます。こうした場合、予測モデルが糖尿病だと出力した値は、100%信用できますね。したがって、「このモデルの適合率は高い」と判断できます。一方、実際の糖尿病患者は4名おり、3名取り漏らしています。ゆえに、「このモデルの再現率は低い」と判断できます。では、以下の事例ではどうでしょうか。
超極端な例です。この予測モデルの糖尿病だという回答は、全然信用できません。ゆえに「このモデルの適合率は低い」と判断されます。一方、実際の糖尿病患者を取り漏らしているでしょうか。一切取り漏らしていません。ゆえに「このモデルの再現率は高い」と判断できます。
では、適合率と再現率、どちらが高いモデルがいいのでしょうか。これは扱う問題の性質によって異なります。具体例として、火災報知器を例に考えてみましょう。あれは適合率と再現率、どちらが高く、どちらが低いでしょうか。火災報知器がブーブー鳴っても、実際に火事であることはあまりない気がします。しかし火災報知器は、実際の火事を取り漏らすことはほとんどありません。つまり、適合率が低く、再現率が高い、というのが正解です。詳しいことはわかりませんが、ああいう類の問題は、再現率を極めて高くしよう、という思想が備わっています。このように、適合率と再現率、重視すべきはいずれかという問題は、状況によって異なることを覚えておくといいでしょう。
ところで、察しのいい人は気がつくかもしれませんが、再現率を高めようとすると、適合率が下がります。逆に、適合率を高めようとすると、再現率が下がります。実は、適合率と再現率にはトレードオフの関係があります。科学というのはなかなか辛辣で、うまい話はないのです。 ...とは言ったものの、火災報知器のような特殊な例を除いて、適合率も再現率も、どちらも重要です。したがって、特にどちらを大事にしたいという考えがない場合には、適合率と再現率の相乗平均であるF値をモデルの精度評価指標として採用することが普通です。論文などでどのモデルがいいのか議論したい場合には、F値を使うといいでしょう。
とはいえ、何でもかんでもF値を見るのではなく、適合率・再現率もしっかりと観察するようにしましょう。知能が有する個性を明瞭に知る一つの手がかりとなります。
ところで、先ほど教師データの精度には重要な意味はないと言いました。通常、人工知能の精度評価には学習には使用しなかったテストデータを使用します。それでは、テストデータを用いて、今まで構築してきたモデルの適合率、再現率、F値を算出してみます。
from sklearn.metrics import precision_recall_fscore_support
# 決定木の精度評価
Y_Tr = Model_Tree_01.predict(X_test_optimal) # <- 今回はテストデータを使用
Res_Tr = precision_recall_fscore_support(Y_Tr, Y_test, average='weighted')
Res_Trw = []
Res_Trw.append(np.round(Res_Tr[0], 3))
Res_Trw.append(np.round(Res_Tr[1], 3))
Res_Trw.append(np.round(Res_Tr[2], 3))
print("決定木: 適合率P =",Res_Trw[0], "/再現率R =",Res_Trw[1], "/F値 =",Res_Trw[2])
# ニューラルネット
Y_NN = Model_NN_01.predict(X_test_optimal/100000) # <- 今回はテストデータを使用
Res_NN = precision_recall_fscore_support(Y_NN, Y_test, average='weighted')
Res_NNw = []
Res_NNw.append(np.round(Res_NN[0], 3))
Res_NNw.append(np.round(Res_NN[1], 3))
Res_NNw.append(np.round(Res_NN[2], 3))
print("ニューラルネット: 適合率P =",Res_NNw[0], "/再現率R =",Res_NNw[1], "/F値 =",Res_NNw[2])
# 線形SVM
Y_SVML = SVMModel_Lin01.predict(X_test_optimal) # <- 今回はテストデータを使用
Res_SVML = precision_recall_fscore_support(Y_SVML, Y_test, average='weighted')
Res_SVMLw = []
Res_SVMLw.append(np.round(Res_SVML[0], 3))
Res_SVMLw.append(np.round(Res_SVML[1], 3))
Res_SVMLw.append(np.round(Res_SVML[2], 3))
print("線形SVM: 適合率P =",Res_SVMLw[0], "/再現率R =",Res_SVMLw[1], "/F値 =",Res_SVMLw[2])
# 非線形SVM
Y_SVMR = SVMModel_RBF01.predict(X_test_optimal/1000) # <- 今回はテストデータを使用
Res_SVMR = precision_recall_fscore_support(Y_SVMR, Y_test, average='weighted')
Res_SVMRw = []
Res_SVMRw.append(np.round(Res_SVMR[0], 3))
Res_SVMRw.append(np.round(Res_SVMR[1], 3))
Res_SVMRw.append(np.round(Res_SVMR[2], 3))
print("非線形SVM: 適合率P =",Res_SVMRw[0], "/再現率R =",Res_SVMRw[1], "/F値 =",Res_SVMRw[2])
# 非線形SVM 過学習
Y_SVMROL = SVMModel_RBF02_OverLearning.predict(X_test_optimal/1000) # <- 今回はテストデータを使用
Res_SVMROL = precision_recall_fscore_support(Y_SVMROL, Y_test, average='weighted')
Res_SVMROLw = []
Res_SVMROLw.append(np.round(Res_SVMROL[0], 3))
Res_SVMROLw.append(np.round(Res_SVMROL[1], 3))
Res_SVMROLw.append(np.round(Res_SVMROL[2], 3))
print("非線形SVM(過学習):適合率P =",Res_SVMROLw[0], "/再現率R =",Res_SVMROLw[1], "/F値 =",Res_SVMROLw[2])
各指標とも、最良が1、最悪が0です。過学習させたモデルだけ、精度が少し低い状態にあります。他のモデルはすべて同い精度で、値も高いですね。本当は差をつけたかったのですが、今回は異常値検出としての問題が簡単すぎたので、すべて高い精度となってしまいました。セミナーとしてはあまり説明しやすい結果ではありませんが、自然にやるとこんな感じである、ということでしょうか。
これまで、いわゆる古典的方法による人工知能の構築方法を説明しました。次に、最近流行りの深層学習について述べます。深層学習と古典的な人工知能の間には、以下の違いがあります。
違いがわかったでしょうか。 深層学習のメリット、デメリットは以下の点にあります。
実際に使うようになるとわかりますが、古典的な人工知能の方が良い性能を持つことも多いです。
古典的な人工知能と深層学習の違いがわかったところで、深層学習を構築してみます。ノートパソコンなので、かなり単純なモデルにしてみます。詳しい人からは怒られそうなくらい単純です。
# 深層学習用の教師データ整形
InputDat_N=[]
InputDat_A1=[]
InputDat_A2=[]
for i in range(0, 30):
InputDat_N.append(np.reshape(Signal_Class_N[i][0:,1:3], [1, 2*100]).ravel())
InputDat_A1.append(np.reshape(Signal_Class_A1[i][0:,1:3], [1, 2*100]).ravel())
InputDat_A2.append(np.reshape(Signal_Class_A2[i][0:,1:3], [1, 2*100]).ravel())
X_DL = np.concatenate([InputDat_N, InputDat_A1, InputDat_A2])
X_DL = X_DL/100
# 深層学習モデルの構築
Model_DeepLearning = MLPClassifier(hidden_layer_sizes=(5, 2, ), solver="sgd", activation="logistic", learning_rate_init=0.05,
random_state=10, max_iter=700, alpha=0.01, tol = pow(10, -10), verbose=False)
Model_DeepLearning = Model_DeepLearning.fit(X_DL, Y_train)
#plt.plot(Model_DeepLearning.loss_curve_)
これで学習が終わりました。さて、どのような特徴量が学習されたでしょうか。今回は、わかりやすさ重視なので、深層学習で2つの特徴量を学習させました(普通は300個の特徴量を学習するなど、かなり数が多いです)。2つだと、2次元空間として可視化できます。実際に見てみましょう。
import matplotlib.animation as animation
DL_Frame = []
DLM = []
fig = plt.figure()
# 学習回数を25回ずつ増やしながら、深層学習構築、特徴抽出を繰り返す
for i in range(1, int(700/25)):
plt.clf() # ★ 動画ファイルを作成する場合にはコメントアウト
# 深層学習モデルの構築
DLM.append(
MLPClassifier(hidden_layer_sizes=(5, 2, ), solver="sgd", activation="logistic", learning_rate_init=0.05,
random_state=10, max_iter=(i-1)*25+1, alpha=0.01, tol = pow(10, -10), verbose=False)
)
DLM[i-1].fit(X_DL, Y_train)
# 学習済みモデルの特徴抽出
DLF=[]
for j in range(0, 90):
temp = 1/(1+np.exp(-1 * np.dot(DLM[i-1].coefs_[0].T, X_DL[j]) + DLM[i-1].intercepts_[0]))
DLF.append(1/(1+np.exp(-1 * np.dot(DLM[i-1].coefs_[1].T, temp) + DLM[i-1].intercepts_[1])))
DLF = np.array(DLF)
# 特徴量空間の可視化
im1 = plt.scatter(DLF[0:30, 0], DLF[0:30, 1], s=30, c='blue', marker='s', linewidths=0.5, edgecolors='black')
im2 = plt.scatter(DLF[30:60, 0], DLF[30:60, 1], s=30, c='orange', marker='s', linewidths=0.5, edgecolors='black')
im3 = plt.scatter(DLF[60:90, 0], DLF[60:90, 1], s=30, c='red', marker='s', linewidths=0.5, edgecolors='black')
plt.title("Deep Feature Space")
plt.xlabel("Deep Feature 1")
plt.ylabel("Deep Feature 2")
plt.legend(["Class N", "Class A1", "Class A2"], loc="upper right")
plt.grid(True)
# フレームを積む
DL_Frame.append([im1, im2, im3])
#aniDL = animation.ArtistAnimation(fig, DL_Frame, interval=500)
#aniDL.save('DL_Movie.mp4', writer="ffmpeg")
さて、学習した2つの特徴量はどのくらい重要なのでしょうか。古典的な人工知能の方で説明した平均や分散よりも優れた特徴量を獲得できたのでしょうか。実はこれ、(諸般の理由によりあまり適切ではありませんが。。。)先ほどやったOut-of-Bag Error法で定量的に測定することができます。 今計算している特徴量は、
この14個です。これら全部で、Out-of-Bag Error法を適用します:
# 平均・分散による12次元の空間と、深層学習の2次元空間を結合させる。
X_train_DL = np.concatenate([X_train, DLF],1)
# OOB
RFDL = RandomForestClassifier(n_estimators=300, random_state=1)
RFDL.fit(X_train_DL, Y_train)
FeatureImportanceDL = RFDL.feature_importances_
# 棒グラフ
label = ["Mean(Xacc)", "Mean(Yacc)", "Mean(Zacc)", "Mean(Xang)", "Mean(Yang)", "Mean(Zang)",
"Var(Xacc)", "Var(Yacc)", "Var(Zacc)", "Var(Xang)", "Var(Yang)", "Var(Zang)",
"DL Feature 1", "DL Feature 2"]
left = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
plt.barh(left, width=FeatureImportanceDL, tick_label=label, align="center")
plt.xlabel("Value")
plt.ylabel("Feature's name")
plt.title("Feature importances")
plt.show()
上ふたつが深層学習由来の特徴量の重要度です。かなり重要だと判断されるようです。ただ、よく見るとY軸加速度の分散値の方が有効みたいです。この結果からもわかるように、深層学習は古典的な人工知能構築手法よりも優れている、ということは一概に言えないわけです。
とはいえ、深層学習の特徴量は、Y軸加速度の分散という最重要な特徴量に迫ることができました。このように、深層学習は手軽にいい特徴量を獲得することができます。これはかなりいい点でしょうね。
ところで、よく深層学習と古典的な人工知能の構築は、対立する手法だと思う人も多いかもしれません。しかし、ここ1-2年の人工知能の理論研究では、平均、分散といったこれまで使われてきた特徴量と、深層学習により獲得された特徴量を混ぜた空間を構築することで、どちらか一方のみを使うよりも良い性能が得られるということがわかってきています。このように、深層学習と古典的な手法は、別に対立したりはしないのです。例えば、上の棒グラフを見ると、 - DL Feature 1: 深層学習により得た特徴量 - DL Feature 2: 深層学習により得た特徴量 - Y軸加速度の分散値: 古典的な手法による特徴量 という3次元空間で人工知能を構築すると、すごい良い性能が出そう、というのは簡単に想像できると思います。
さて、深層学習による特徴量学習について説明しました。 深層学習はただ単に、入力の次元を下げ、空間を作る方法だということは理解できたでしょうか。 同時に、これまで解説した決定木、サポートベクターマシン、ニューラルネットワークなどが、空間上に識別境界を生成する方法だということを理解していれば、深層学習とこれまでの手法が連携できることがわかると思います。 これを目で見て確認するため、以下のモデルを構築してみます。
import matplotlib.animation as animation
DL_Frame = []
DLM = []
fig = plt.figure()
# 学習回数を25回ずつ増やしながら、深層学習構築、特徴抽出を繰り返す
for i in range(1, int(700/25)):
plt.clf() # ★注意: 動画にしたい場合には、コメントアウトすること!!
# 深層学習モデルの構築
DLM.append(
MLPClassifier(hidden_layer_sizes=(5, 2, ), solver="sgd", activation="logistic", learning_rate_init=0.05,
random_state=10, max_iter=(i-1)*25+1, alpha=0.01, tol = pow(10, -10), verbose=False)
)
DLM[i-1].fit(X_DL, Y_train)
# 学習済みモデルの特徴抽出
DLF=[]
for j in range(0, 90):
temp = 1/(1+np.exp(-1 * np.dot(DLM[i-1].coefs_[0].T, X_DL[j]) + DLM[i-1].intercepts_[0]))
DLF.append(1/(1+np.exp(-1 * np.dot(DLM[i-1].coefs_[1].T, temp) + DLM[i-1].intercepts_[1])))
DLF = np.array(DLF)
# 深層学習による特徴量空間上にニューラルネットワークで識別境界を生成
DeepLearningNN = MLPClassifier(hidden_layer_sizes=(2, ), activation="logistic", learning_rate_init=0.05,
max_iter = 1000, random_state=1)
DeepLearningNN = DeepLearningNN.fit(DLF, Y_train)
# ニューラルネットによる識別境界の可視化
Xmin, Ymin, Xmax, Ymax = 0, 0, 1, 1 # 空間の最小最大値
resolution = 0.01
x_mesh, y_mesh = np.meshgrid(np.arange(Xmin, Xmax, resolution),
np.arange(Ymin, Ymax, resolution))
MeshDat = np.array([x_mesh.ravel(), y_mesh.ravel()]).T
z = DeepLearningNN.predict(MeshDat)
z = np.reshape(z, (len(x_mesh), len(y_mesh)))
im1 = plt.scatter(x_mesh[z=='N'], y_mesh[z=='N'], s=5, alpha=0.3, c='blue')
im2 = plt.scatter(x_mesh[z=='A1'], y_mesh[z=='A1'], s=5, alpha=0.3, c='orange')
im3 = plt.scatter(x_mesh[z=='A2'], y_mesh[z=='A2'], s=5, alpha=0.3, c='red')
# 深層学習による特徴量空間プロットの可視化
im4 = plt.scatter(DLF[0:30, 0], DLF[0:30, 1], s=30, c='blue', marker='s', linewidths=0.5, edgecolors='black')
im5 = plt.scatter(DLF[30:60, 0], DLF[30:60, 1], s=30, c='orange', marker='s', linewidths=0.5, edgecolors='black')
im6 = plt.scatter(DLF[60:90, 0], DLF[60:90, 1], s=30, c='red', marker='s', linewidths=0.5, edgecolors='black')
plt.title("Deep Learning + NN")
plt.xlabel("Deep Feature 1")
plt.ylabel("Deep Feature 2")
plt.legend(["Class N", "Class A1", "Class A2"], loc="upper right")
plt.grid(True)
# フレームを積む
DL_Frame.append([im1, im2, im3, im4, im5, im6])
plt.show()
#aniDLNN = animation.ArtistAnimation(fig, DL_Frame, interval=500)
#aniDLNN.save('DLNN_Movie.mp4', writer="ffmpeg")
import matplotlib.animation as animation
DL_Frame = []
DLM = []
fig = plt.figure()
# 学習回数を25回ずつ増やしながら、深層学習構築、特徴抽出を繰り返す
for i in range(1, int(700/25)):
plt.clf() # ★注意: 動画にしたい場合には、コメントアウトすること!!
# 深層学習モデルの構築
DLM.append(
MLPClassifier(hidden_layer_sizes=(5, 2, ), solver="sgd", activation="logistic", learning_rate_init=0.05,
random_state=10, max_iter=(i-1)*25+1, alpha=0.01, tol = pow(10, -10), verbose=False)
)
DLM[i-1].fit(X_DL, Y_train)
# 学習済みモデルの特徴抽出
DLF=[]
for j in range(0, 90):
temp = 1/(1+np.exp(-1 * np.dot(DLM[i-1].coefs_[0].T, X_DL[j]) + DLM[i-1].intercepts_[0]))
DLF.append(1/(1+np.exp(-1 * np.dot(DLM[i-1].coefs_[1].T, temp) + DLM[i-1].intercepts_[1])))
DLF = np.array(DLF)
# 深層学習による特徴量空間上にサポートベクターマシンで識別境界を生成
DeepLearningSVM = svm.SVC(kernel='linear', C=100000)
DeepLearningSVM = DeepLearningSVM.fit(DLF, Y_train)
# サポートベクターマシンによる識別境界の可視化
Xmin, Ymin, Xmax, Ymax = 0, 0, 1, 1 # 空間の最小最大値
resolution = 0.01
x_mesh, y_mesh = np.meshgrid(np.arange(Xmin, Xmax, resolution),
np.arange(Ymin, Ymax, resolution))
MeshDat = np.array([x_mesh.ravel(), y_mesh.ravel()]).T
z = DeepLearningSVM.predict(MeshDat)
z = np.reshape(z, (len(x_mesh), len(y_mesh)))
im1 = plt.scatter(x_mesh[z=='N'], y_mesh[z=='N'], s=5, alpha=0.3, c='blue')
im2 = plt.scatter(x_mesh[z=='A1'], y_mesh[z=='A1'], s=5, alpha=0.3, c='orange')
im3 = plt.scatter(x_mesh[z=='A2'], y_mesh[z=='A2'], s=5, alpha=0.3, c='red')
# 深層学習による特徴量空間プロットの可視化
im4 = plt.scatter(DLF[0:30, 0], DLF[0:30, 1], s=30, c='blue', marker='s', linewidths=0.5, edgecolors='black')
im5 = plt.scatter(DLF[30:60, 0], DLF[30:60, 1], s=30, c='orange', marker='s', linewidths=0.5, edgecolors='black')
im6 = plt.scatter(DLF[60:90, 0], DLF[60:90, 1], s=30, c='red', marker='s', linewidths=0.5, edgecolors='black')
plt.title("Deep Learning + SVM")
plt.xlabel("Deep Feature 1")
plt.ylabel("Deep Feature 2")
plt.legend(["Class N", "Class A1", "Class A2"], loc="upper right")
plt.grid(True)
# フレームを積む
DL_Frame.append([im1, im2, im3, im4, im5, im6])
plt.show()
#aniDLSVM = animation.ArtistAnimation(fig, DL_Frame, interval=500)
#aniDLSVM.save('DLSVM_Movie.mp4', writer="ffmpeg")
import matplotlib.animation as animation
DL_Frame = []
DLM = []
fig = plt.figure()
# 学習回数を25回ずつ増やしながら、深層学習構築、特徴抽出を繰り返す
for i in range(1, int(700/25)):
plt.clf() # ★注意: 動画にしたい場合には、コメントアウトすること!!
# 深層学習モデルの構築
DLM.append(
MLPClassifier(hidden_layer_sizes=(5, 2, ), solver="sgd", activation="logistic", learning_rate_init=0.05,
random_state=10, max_iter=(i-1)*25+1, alpha=0.01, tol = pow(10, -10), verbose=False)
)
DLM[i-1].fit(X_DL, Y_train)
# 学習済みモデルの特徴抽出
DLF=[]
for j in range(0, 90):
temp = 1/(1+np.exp(-1 * np.dot(DLM[i-1].coefs_[0].T, X_DL[j]) + DLM[i-1].intercepts_[0]))
DLF.append(1/(1+np.exp(-1 * np.dot(DLM[i-1].coefs_[1].T, temp) + DLM[i-1].intercepts_[1])))
DLF = np.array(DLF)
# 深層学習による特徴量空間上に決定木で識別境界を生成
DeepLearningTr = tree.DecisionTreeClassifier(min_samples_leaf=1, random_state=15)
DeepLearningTr = DeepLearningTr.fit(DLF, Y_train)
# 決定木による識別境界可視化
Xmin, Ymin, Xmax, Ymax = 0, 0, 1, 1 # 空間の最小最大値
resolution = 0.01
x_mesh, y_mesh = np.meshgrid(np.arange(Xmin, Xmax, resolution),
np.arange(Ymin, Ymax, resolution))
MeshDat = np.array([x_mesh.ravel(), y_mesh.ravel()]).T
z = DeepLearningTr.predict(MeshDat)
z = np.reshape(z, (len(x_mesh), len(y_mesh)))
im1 = plt.scatter(x_mesh[z=='N'], y_mesh[z=='N'], s=5, alpha=0.3, c='blue')
im2 = plt.scatter(x_mesh[z=='A1'], y_mesh[z=='A1'], s=5, alpha=0.3, c='orange')
im3 = plt.scatter(x_mesh[z=='A2'], y_mesh[z=='A2'], s=5, alpha=0.3, c='red')
# 深層学習による特徴量空間プロットの可視化
im4 = plt.scatter(DLF[0:30, 0], DLF[0:30, 1], s=30, c='blue', marker='s', linewidths=0.5, edgecolors='black')
im5 = plt.scatter(DLF[30:60, 0], DLF[30:60, 1], s=30, c='orange', marker='s', linewidths=0.5, edgecolors='black')
im6 = plt.scatter(DLF[60:90, 0], DLF[60:90, 1], s=30, c='red', marker='s', linewidths=0.5, edgecolors='black')
plt.title("Deep Learning + Tree")
plt.xlabel("Deep Feature 1")
plt.ylabel("Deep Feature 2")
plt.legend(["Class N", "Class A1", "Class A2"], loc="upper right")
plt.grid(True)
# フレームを積む
DL_Frame.append([im1, im2, im3, im4, im5, im6])
plt.show()
#aniDLTr = animation.ArtistAnimation(fig, DL_Frame, interval=500)
#aniDLTr.save('DLTr_Movie.mp4', writer="ffmpeg")
本日はお疲れ様でした。本日は、加速度・角速度という時系列信号から、異常状態を検出する様々な手法について説明しました。また、わかりやすさを優先にするため、単純な問題、単純なモデルを主体として解説しました。
簡単なモデルを作る分には、今回の内容で十分です。 ぜひ、いろいろな問題解決に応用していただければと思います。