【はじめてのパターン認識】第7章 パーセプトロン型学習規則

2クラスの線形識別関数を求める古典的な手法に、パーセプトロンの学習規則がある。 識別関数のパラメータを逐次的に求めるこのアルゴリズムは、2クラスの学習データが線形分離可能であれば収束することが知られている。 これをパーセプトロンの収束定理という。

パーセプトロンの学習や誤差逆伝播法などは、 ゼロから作るディープラーニングや深層学習などで散々学んできたので今回はメモしないことにした。

【はじめてのパターン認識】第6章 線形識別関数

はじめてのパターン認識を読んでいる。
その個人的メモ。

はじめてのパターン認識

はじめてのパターン認識

線形判別分析

1次元に写像されたとき、クラス間の分布ができるだけ重ならないような写像方向を見つけるのが線形判別分析。

線形識別関数と線形判別関数の違いは、 線形識別関数がクラス分けを目的としているのに対して、線形判別関数はクラス分離をよくすることを目的として考えられていること。

フィッシャーの線形判別関数

このフィッシャーの線形判別関数の節で少し詰まったので丁寧に書き下していきたい。

フィッシャーの方法の基本的な考え方は、
クラス間変動のクラス内変動に対する比、クラス内変動・クラス間変動比を最大にする一次元軸を求めることにある。
すなわち、変換後の空間において2つのクラスがよく分離するために、変換ベクトルを求めるわけである。

例として、2クラス({(C_1, C_2)})の問題について考える。
{\mu_i}はクラス{C_i}の変換前の平均、
{n_i}はクラス{C_i}のデータサンプル数とする。

ここで変換前のクラス内変動行列{S_W}とクラス間変動行列{S_B}を以下のようにして求める。 {} $$ \begin{align} S_{W} &= \sum_{i=1,2} \sum_{x} (x-\mu_{i})(x-\mu_{i})^{t} \\ S_{B} &= \sum_{i=1,2} n_{i} (\mu_{i} - \mu)(\mu_{i} - \mu)^{t} \\ &= \frac{n_{1} n_{2}}{n} (\mu_{1} - \mu_{2})(\mu_{1} - \mu_{2})^{t} \end{align} $$

さて、{y = w^{t} x}で変換後のクラスの平均を{m_{i}}とすると、 {} $$ \begin{align} m_{i} &= \frac{1}{n_{i}} \sum_{y} y \\ &= \frac{1}{n_{i}} \sum_{x} w^{t} x \\ &= w^{t} \mu_{i} \end{align} $$ となる。 変換後の空間でのクラス内変動行列{\tilde{S}_{W}}、クラス間変動行列{\tilde{S}_{B}}も同様に求めることができる。 {} $$ \begin{align} \tilde{S}_{W} &= \sum_{i=1,2} \sum_{y} (y - \tilde{m}_{i})^{2} \\ &= w^{t} S_{W} w \\ \tilde{S}_{B} &= \sum_{i=1,2} \sum n_{i} (m_{i} - m)^{2} \\ &= \frac{n_{1} n_{2}}{n} (m_{1} - m_{2})^{2} \\ &= w^{t} S_{B} w \end{align} $$

上に述べたように、フィッシャーの判別ではクラス内変動とクラス間変動の比が大きくなるように変換{w}を定める。 これを定式化すると、 {} $$ J(w) = \frac{w^{T} S_{B} w}{w^{T} S_{W} w} $$ となる。 この式を最大化する解を求める。 そのために{w}偏微分すると、 {} $$ \frac{\partial}{\partial w} \frac{w^{T} S_{B} w}{w^{T} S_{W} w} = \frac{2}{(w^{T} S_{W} w)^{2}} \left[ (w^{T} S_{W} w) S_{B}w - (w^{T} S_{B} w) S_{W} w \right] $$ が0になればよいので、満たしておかなければならない条件は、 {} $$ (w^{T} S_{B} w) S_{W} w = (w^{T} S_{W} w) S_{B}w $$ となる。 ちなみに、線形変換{w^{T}}は方向のみに興味があるので、大きさの情報に意味はない。 二次形式の部分はスカラー値なので、無視してよい。 すなわち以下のように評価をしても良いということになる。 {} $$ S_{W} w \propto S_{B} w $$ 両辺に{S_{W}^{-1}}を左からかけて、 さらに{S_{B}w}{m_{1} - m_{2}}に比例することを用いると、 {} $$ w \propto S_{W}^{-1} S_{B} w \propto S_{W}^{-1} (m_{1} - m_{2}) $$ となる。これがフィッシャーの基準による最適な{w}となる。 この意味するところは、結局、 クラスごとの平均の差を求めて、クラス内変動行列{S_{W}}を計算して、 その逆行列を求めれば{w}は決まるということ。

{w}によって変換された特徴空間は,クラス内変動,クラス間変動比を最大にする一次元空間となる. 線形判別法で注意が必要な点は,線形判別法によって決まるのは空間のみであって,軸上に設けるべき識別のための境界は定まらないという点である. このような場合に軸上に決定境界を決定する方法は,

  1. 変換後のクラス平均の中点を境界とする方法
  2. 変換後の各クラスごとの分散で内分する方法
  3. 事前確率も考慮して内分する方法

などがある.

フィッシャーの方法の一般化

フィッシャーの方法をより一般的な形で表現するため, 変動行列の代わりに共分散行列と事前確率{C_{i}}を用いた定式化を試みる. クラス{C_{i}}の共分散行列{\Sigma_{i}}はクラス{C_{i}}に属するパターンの共分散行列であり, {} $$ Σ_{i} = \frac{1}{n_{i}} \sum (x-m_{i}) (x-m_{i})^{t} = \frac{1}{n_{i}} S_{i} $$ で定義される. クラス内共分散行列{Σ_{W}},クラス間共分散行列{Σ_{B}}{} $$ \begin{align} Σ_{W} &= \sum P(C_{i}) Σ_{i} \\ &= \sum ( P(C_{i}) \frac{1}{n_{i}} \sum (x-m_{i}) (x-m_{i})^{t} ) \\ Σ_{B} &= \sum P(C_{i}) (m_{i} - m) (m_{i} - m)^{t} \\ &= P(C_{1}) P(C_{2}) (m_{1} - m_{2}) (m_{1} - m_{2})^{t} \end{align} $$ と定義する. 変換行列{w}によって変換した空間上でも, {\tilde{Σ}_{W}}{\tilde{Σ}_{B}}を求めることができ,

{} $$ \begin{align} \tilde{Σ}_{W} &= P(C_{1}) \tilde{σ}_{1}^{2} + P(C_{2}) \tilde{σ}_{2}^{2} \end{align} $$

Numpy array演算操作メモ

numpy arrayの演算操作についての自分用メモ

numpy arrayのサイズ

numpyにおけるarrayのサイズはなかなか直感的に理解するまで時間がかかると思う。
ベクトルとそれ以外でとりあえず分ければよいのだなと感じた.

import numpy as np

a = np.array([1])
a.shape
# (1,)

b = np.array([1,2])
b.shape
# (2,)

c = np.array([[1]])
c.shape
# (1, 1)

d = np.array([[0], [2]])
d.shape
# (2, 1)

転置

a = np.zeros((3,7))
at = a.T
print(at.shape)
# (7, 3)

ただし以下のようにnumpyでは列ベクトルと行ベクトルを意識していない。
そのため、転置はされない。

x = np.zeros(10)
xt = x.T
print(xt.shape)
# (10,)

スライス

a = np.array([[1,2],[3,4],[5,6]])
print(a[0])
# [1 2]

print(a[::-1])
# [[5 6]
#  [3 4]
#  [1 2]]

print(a[:,::-1])
# [[2 1]
#  [4 3]
#  [6 5]]

print(a[::-1, ::-1])
# [[6 5]
#  [4 3]
#  [2 1]]

Advanced Indexing

Advanced indexingには2つ種類がある。

Integer array indexing

抽出する行と列を別々に指定するのがAdvanced indexingと理解

x = np.array([[1,2], [3,4], [5,6]])
print(x)
# [[1 2]
#  [3 4]
#  [5 6]]

print(x[[0,1,2], [0,1,0]])
# [1 4 5]

print(x[[0]])
# [[1 2]]

print(x[[0,1],[1]])
# [2 4]
Boolean array indexing
x = np.array([1, -1, -2, 3])
print(x[x < 0])
# [-1 -2]

x[x<0] += 20
print(x)
# [ 1 19 18  3]

集約演算

# 集約演算
x = np.array([[1,2], [3,4], [5,6]])

print(x)
# [[1 2]
#  [3 4]
#  [5 6]]

print(x.sum())
# 21

print(x.sum(axis=0))
# [0,:]と[1,:]と[2,:]の和
# [ 9 12]

print(x.sum(axis=1))
# [:,0]と[:,1]の和
# [ 3  7 11]

テンソル

もういちどだけ内積・外積 [物理のかぎしっぽ] テンソル積については別にまとめる必要がある。

【はじめてのパターン認識】第5章 k最近傍法

はじめてのパターン認識を読んでいる。
その個人的メモ。

はじめてのパターン認識

はじめてのパターン認識

漸近仮定とkNN誤り率の期待値

2クラス問題を考える。
条件付きベイズ誤り率は、事後確率の小さい方 {} $$ \epsilon (x) = \min{ [ P(C_1|x), P(C_2|x) ]} $$ である。
ベイズ誤り率は、その期待値が {} $$ \epsilon \ast = \int \epsilon (x) p (x) dx $$ で表される。
入力{x}の最近傍鋳型を{x_{1NN}}とする。
N個の鋳型の集合を{T_{N}}とする。
このとき以下の漸近仮定が成り立てば、 {} $$ \lim_{N \to \inf} T_{N} \Rightarrow d(x, x_{1NN}) \to 0 $$ kNN誤り率とベイズ誤り率の間には、次の関係が成り立つ。 {} $$ \frac{1}{2} \epsilon \ast \geq \epsilon_{2NN} \geq \epsilon_{4NN} \geq \cdots \geq \epsilon \ast \geq \cdots \geq \epsilon_{3NN} \geq \epsilon_{1NN} \geq \epsilon_{1NN} \geq 2 \epsilon \ast $$

教科書にはこの証明が載っていてかなり面白い。

LSTMで三角関数を組み合わせた周期関数を予測してみる

簡単な周期関数をLSTMネットワークに学習させ、予測させてみる。

環境
  • python:3.6.0 (Anaconda 4.3.1)
  • keras:2.0.1
  • tensorflow: 1.0.1

予測させる周期関数

今回予測させる周期的な関数は、 周期の異なるsinとcosの和で作る。 (下図上段のオレンジと黄の曲線)

この周期関数 {y} は以下の式で表される。 {} $$ y = \sin(\frac{\pi x}{20}) + \cos(\frac{\pi 3 x}{20}) $$ (下図中段の赤い曲線)

f:id:yusuke_ujitoko:20170411205932p:plain

なお、そのまま学習させるとあまりにも簡単なので、
ノイズを混ぜた曲線を学習させることにした。
(上図下段の青い曲線)

データ作成
from random import random
import numpy as np

x = np.arange(0, 2000)
data = np.sin(np.pi*x/20) + np.cos(np.pi*x*3/20) # 予測させたい周期関数
data_noised = [d * (0.75+0.5*random()) for d in data] # ノイズを混ぜる

モデル

  • 入力層
    • 過去100の時刻のデータ(ベクトル)を入れる。
  • 中間層は一層のみ(30ユニット)。
    • 非常に単純な構造。
  • 出力層
    • スカラ値

イメージとしては、過去の時刻から現在までの全てのデータを入力したら、 現在の1つ先のデータが出力される感じ。

from keras.models import Sequential  
from keras.layers.core import Dense, Activation  
from keras.layers.recurrent import LSTM

in_out_neurons = 1
hidden_neurons = 30

model = Sequential()
model.add(LSTM(hidden_neurons, return_sequences=False,
               input_shape=(None, in_out_neurons)))
model.add(Dense(in_out_neurons, input_dim=hidden_neurons))  
model.add(Activation("linear"))  
model.compile(loss="mean_squared_error", optimizer="rmsprop")  
________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
lstm_18 (LSTM)               (None, 30)                3840      
_________________________________________________________________
dense_18 (Dense)             (None, 1)                 31        
_________________________________________________________________
activation_18 (Activation)   (None, 1)                 0         
=================================================================
Total params: 3,871.0
Trainable params: 3,871.0
Non-trainable params: 0.0
_____________________________

学習

誤差の変化

60epoch程度で収束している。

f:id:yusuke_ujitoko:20170411214721p:plain

予測した曲線の変化

5 epochのときの予測曲線

f:id:yusuke_ujitoko:20170411214432p:plain

10 epochのときの予測曲線

f:id:yusuke_ujitoko:20170411214454p:plain

20 epochのときの予測曲線

f:id:yusuke_ujitoko:20170411214548p:plain

40 epochのときの予測曲線

f:id:yusuke_ujitoko:20170411214613p:plain

60 epochのときの予測曲線

f:id:yusuke_ujitoko:20170411214649p:plain

LSTMで周期関数の予測ができた。 次はもう少し複雑な系列データを予測してみたい。