LoginSignup
40
58

More than 5 years have passed since last update.

Pythonデータ分析勉強会 第3回資料

Posted at

今回は第3回目です。

Pythonデータ分析勉強会 第1回資料
Pythonデータ分析勉強会 第2回資料
Pythonデータ分析勉強会 第3回資料

第1回、第2回の資料を見れば、Pythonを使ってデータ分析が進められると思います。

今回は、MT法の説明がメインです。機械学習に興味がある方は、本稿を飛ばして「scikit-learn」を、
ディープラーニングに興味がある方は「Keras」を勉強すると、理解が進むと思います。

※ 塩尻市内でPythonデータ分析勉強会を開催します。初心者の方も是非参加してください!

figure_1.png

第2回 演習の答え

その1

import numpy as np
import matplotlib.pyplot as plt

A = np.sin(2*np.pi*np.array(range(100))/100)

plus, minus = [], []

for i in A:
    if i > 0:
        plus.append(i)
    else:
        minus.append(i)

np.savetxt("All.csv", A, delimiter=",")
np.savetxt("plus.csv", plus, delimiter=",")
np.savetxt("minus.csv", minus, delimiter=",")

plt.figure()
plt.title("All")
plt.plot(A)
plt.show()

plt.figure()
plt.title("Plus")
plt.plot(plus)
plt.show()

plt.figure()
plt.title("Minus")
plt.plot(minus, c="red")
plt.show()

その2

import numpy as np
import matplotlib.pyplot as plt

def plot_csv(name):
    x = np.loadtxt(name + ".csv", delimiter=",")

    plt.figure()
    plt.title(name)
    plt.plot(x)
    plt.show()

plot_csv("All")
plot_csv("Plus")
plot_csv("Minus")

MT法

概要

MT法は、一言でいうと「変数間の相関を考慮した距離を出し、その距離を見て異常かどうか判定する」
手法といえます。図にすると以下のようになります。

figure_5.png

正常データの平均値を出して、平均値からの距離で異常検知することを考えます。
通常の距離(ユークリッド距離)を使うと左図にあるように、同心円状に広がっていきます。
ところが、変数間の相関を考慮した距離(マハラノビス距離)を使うと、右図のように楕円上に
広がる距離となります。

MT法では、マハラノビス距離が大きければ「異常」、小さければ「正常」と判定します。

理論

MT法の理論は、線形代数の知識が必要になります。ただ、理論を理解しなくても使うことはできるので、
お急ぎの方は、コード全文をご覧ください。MT法の数式は以下の資料が参考になります。

http://kaz7227.art.coocan.jp/mt-system.pdf
入門MTシステム

MT法は以下の流れで行っています。

無題.jpg

まずは、左図にあるように正常データで平均値、標準偏差、相関行列を求め、単位空間を作成します。
そして、テストデータでマハラノビス距離を求め、その距離で異常かどうか判断します。

ここで、テストデータXが以下のように与えられたとします。

X=[X_{1},X_{2},・・・,X_{n}]

Xを使って、以下のようにマハラノビス距離が求められます。

\tilde{X} = [\frac{X_{1}-\bar{X_{1}}}{\sigma_{1}},\frac{X_{2}-\bar{X_{2}}}{\sigma_{2}},・・・,\frac{X_{n}-\bar{X_{n}}}{\sigma_{n}}]\\

D^2 = \frac{1}{n}\tilde{X}R^{-1}\tilde{X}^{T}

ただし、$\bar{X}$は各状態量の平均値、$\sigma$は各状態量の標準偏差、$R$は相関行列、$D$はマハラノビス距離です。

例題

ここでは、以下のデータが与えられたとして単位空間を作ってみます。

X Y
データ1 10 20
データ2 11 18
データ3 9.5 22

XとYは状態量です。例えば、Xは機械の温度、Yは機械にかかる力と見なしても良いでしょう。

平均値を求める

最初に各状態量の平均値を求め、その平均値で引くという作業が必要になります。
表にすると、以下のとおりです。

・平均値を求める

X Y
データ1 10 20
データ2 11 18
データ3 9.5 22
平均値(avg) 10.2 20

・平均値で引く

XX YY
データ1 10-10.2=-0.2 20-20=0
データ2 11-10.2=0.8 18-20=-2
データ3 9.5-10.2=-0.7 22-20=2

上の表で、新たな変数XX、YYを作っています。

コードにすると以下のとおりです。

import numpy as np

xx = np.copy(x)
avg = np.zeros(2)

#各状態量から平均値を引く
for i in range(2):
    avg[i] = np.mean(x[:,i])
    for j in range(3):
        xx[j,i] = x[j,i] - avg[i]

・xというのは、データが入ったnp配列(3行、2列)です。
・3行目でxをコピーしています。
・4行目で平均値配列(avg)の形を宣言しています。
・7行目以降はfor文です。rangeに2を与えているのは、xの列の数が「2」だからです。
・8行目で各列(状態量)毎に平均値を求めています。
・9行目以降もfor文になっています。7行目で、rangeに3を与えているのは、xの行数が「3」だからです。
・最後の行で、xから各平均値で引き、XXとYYを求めています。

標準偏差を求める

次に、前項で求めた値を「各状態量の標準偏差で割る」という作業が必要です。
表にすると、以下のとおりです。

・標準偏差を求める

X Y
データ1 10 20
データ2 11 18
データ3 9.5 22
標準偏差(std) 0.62 1.63

Xの標準偏差

\sqrt{\frac{(10-10.2)^2+(11-10.2)^2+(9.5-10.2)^2}{3}}=0.62

Yの標準偏差

\sqrt{\frac{(20-20)^2+(18-20)^2+(22-20)^2}{3}}=1.63

・標準偏差で割る

XX YY
データ1 -0.2/0.62=-0.32 0/1.63=0
データ2 0.8/0.62=1.29 -2/1.63=-1.23
データ3 -0.7/0.62=-1.13 2/1.63=1.23

コードにすると、以下のとおりです。

std = np.zeros(2)

#標準偏差で割る
for i in range(2):
    std[i] = np.std(x[:,i])
    for j in range(3):
        xx[j,i] = xx[j,i] / std[i]

・やっていることは、平均値の作業と同じです。「np.mean」が「np.std」になっただけです。
・標準偏差はnp.std()の1行で求められます。これは便利!

相関行列を求める

相関行列は、以下の式で与えられます。

R = \begin{pmatrix}
1&r_{yx}\\
r_{xy}&1
\end{pmatrix}

ただし、$r_{xy}$と$r_{yx}$はxとyの相関係数です。マハラノビス距離を求める際は、逆行列$R^{-1}$を使います。
コードにすると以下のとおりです。

R = np.corrcoef(x.transpose())
invR = np.linalg.inv(R)

ただし、np.corrcoef()に渡す配列は、横長の形にしないといけないため、transpose()で
転置しています。$R^{-1}$は以下のように、求まります。

array([[28.        , 27.49545417],
       [27.49545417, 28.        ]])

これで、マハラノビス距離を求める準備が整いました。
次に、正常データでマハラノビス距離を求めてみます。

マハラノビス距離を求める

試しに、データ1のマハラノビス距離を求めてみます。

XX YY
データ1 -0.32 0

マハラノビス距離は前述したとおり、以下の式で与えられます。

D^2 = \frac{1}{n}\tilde{X}R^{-1}\tilde{X}^{T}

データ1の数値を入れると、以下のとおりです。

D^2=\frac{1}{2}

\begin{pmatrix}
-0.32&0
\end{pmatrix}

\begin{pmatrix}
28&27.495\\
27.495&28\\
\end{pmatrix}

\begin{pmatrix}
-0.32\\
0
\end{pmatrix}

=1.4336

コードで書くと、以下のとおりです。

#MD^2の計算
d0 = np.array([-0.32, 0])
d1 = np.dot(d0,invR)
d2 = np.dot(d1,d0)/2

等確率楕円

グラフで出てくる楕円は「等確率楕円」と呼ばれ、以下の資料(P2)が参考になります。
http://civilyarou.web.fc2.com/WANtaroHP_html5_win/f90_STAT/dir_SREG/TeX_ellipse.pdf

コードは以下のとおりです。

curve_c = np.zeros((2,51))

low = np.corrcoef(data[:,0],data[:,1])[0,1]

for i in range(div+1):
    r = (-2*(1-low**2)*np.log(1-0.95)/(1-2*low*np.sin(i*2*np.pi/50)*np.cos(i*2*np.pi/50)))**0.5
    curve_c[0,i] = avg[0] + std[0]*r*np.cos(i*2*np.pi/50)
    curve_c[1,i] = avg[1] + std[1]*r*np.sin(i*2*np.pi/50)

・1行目で、楕円カーブの座標が入る箱を用意しています。
・3行目で相関係数を求め、for文以降は参考資料のとおりに計算しています。

Python上達のコツ

・Python上達の近道は、実装して動かしてみることです。ネットには、先人達が書いた素晴らしい
 コードがたくさん落ちています。そのコードをいじってデータを使い練習しましょう。
 使うデータは、乱数でもフリーのデータでも何でもOKです。

・Pythonのコードを書いて、初めて動かすときは、必ずバグがあると思ってください。
 最初は、エラーが出るのが当たり前です。エラーが出なくなっても、print文で
 「データ数」や「出力値」を出して、論理的に間違っていないか確認をとってください。

・論理的な確認をとったら、数種類のダミーデータで確認をとってください。
 コードは完璧!と思っていても思わぬ見落としがあったりします。

コード全文

ちなみに、コード中の「if name == 'main':」というのは、ここからコードが始まりますよ!という
宣言です。無くても良いですが、見やすさのために書いています。

import numpy as np
import matplotlib.pyplot as plt

divide = 2 #状態量の数
R = np.zeros((divide,divide))    #相関行列
invR = np.zeros((divide,divide)) #相関行列の逆行列
avg = np.zeros(divide)           #平均値
std = np.zeros(divide)           #標準偏差
make = 0

p = 0.95        #マハラノビス距離p=0.95で2σ
md_sikii = 2.448#MDの閾値95%で2.448
div = 50        #Mt楕円の分割数

def maha(x):
    global make, R, invR, avg, std

    N, _ = x.shape #Nはデータ数
    xx = np.copy(x)
    xx = np.array(xx,dtype="float32")
    x_return = []

    #各状態量から平均値を引く
    for i in range(divide):
        if make == 0:
            avg[i] = np.mean(x[:,i])
        for j in range(N):
            xx[j,i] = xx[j,i] - avg[i]

    #各状態量を標準偏差で割る
    for i in range(divide):
        if make == 0:
            std[i] = np.std(x[:,i])
        for j in range(N):
            xx[j,i] = xx[j,i] / std[i]

    #make=0のときだけ計算
    if make == 0:
        R = np.corrcoef(xx.transpose())
        invR = np.linalg.inv(R)
        make = 1

    #MD^2の計算
    for i in range(N):
        d0 = xx[i,:]
        d1 = np.dot(d0,invR)
        d2 = np.dot(d1,d0)/divide
        x_return.append(d2)

    return x_return

if __name__ == '__main__':

    curve_c = np.zeros((2,div+1))

    #正常データの作成
    x1 = np.random.normal(1, 0.3, (1, 50))
    y1 = np.random.normal(1, 0.3, (1, 50))
    x2 = np.random.normal(1.5, 0.3, (1, 50))
    y2 = np.random.normal(1.5, 0.3, (1, 50))

    #テストデータの作成
    test1 = np.array([1.02,1.5])
    test1 = test1.reshape((1,2))
    test2 = np.array([0.5,2])
    test2 = test2.reshape((1,2))

    #正常データの形を整える
    data = []
    data.append(x1)
    data.append(x2)
    data.append(y1)
    data.append(y2)
    data = np.array(data)
    data = data.reshape(2,100)
    data = data.transpose()
    print(data.shape)

    #単位空間の作成
    _ = maha(data)

    #テストデータのマハラノビス距離
    md_1 = maha(test1)
    md_2 = maha(test2)

    #楕円のデータ
    low = np.corrcoef(data[:,0],data[:,1])[0,1]

    for i in range(div+1):
        r = (-2*(1-low**2)*np.log(1-p)/(1-2*low*np.sin(i*2*np.pi/div)*np.cos(i*2*np.pi/div)))**0.5
        curve_c[0,i] = avg[0] + std[0]*r*np.cos(i*2*np.pi/div)
        curve_c[1,i] = avg[1] + std[1]*r*np.sin(i*2*np.pi/div)

    #可視化
    plt.figure()

    plt.subplot(1,2,1)
    plt.scatter(x1, y1, c="green", s=50)
    plt.scatter(x2, y2, c="green", s=50)
    plt.scatter(test1[:,0],test1[:,1],c="m", s=50)
    plt.scatter(test2[:,0],test2[:,1],c="red", s=50)
    plt.xlabel("T")
    plt.ylabel("KN")

    plt.plot(curve_c[0],curve_c[1],c="c",label="MD^2=2.448")
    plt.legend()

    plt.subplot(1,2,2)
    plt.bar([1,2], [md_1[0], md_2[0]], align="center")
    plt.grid(True)
    plt.xticks([1,2], ["Purple", "Red"])
    plt.ylabel("MD^2")
    plt.grid(True)

    plt.show()

コードを実行すると、冒頭で出た散布図と棒グラフが出力されます。

40
58
1

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
40
58