【はじめてのパターン認識】第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で周期関数の予測ができた。 次はもう少し複雑な系列データを予測してみたい。

福くんと鈴木一真さんの画像分類問題をCNNで解く(訓練・テスト編)

これまではMNISTやIris等の既成のデータセットをもとにして問題を解いてきたが、 機械学習で一番苦労する部分は「データセットを作るところ」と噂でよく聞くので、 今回はデータセットを自分で作って分類問題を解いてみようと思う。

鈴木福くんと鈴木一真さんの画像をCNNに分類させてみることにした。下記にもあるように、福くんと鈴木一真さんは容姿が似ていることで有名なので、今回はこのお二方のサンプルを集めて分類させるところまでいきたい。
(参考:俳優・鈴木一真(44)と鈴木福くん(9)がソックリ過ぎる)

前記事で作成したデータセット(各カテゴリ50サンプルずつ)を使う。
ランダムに抽出した5サンプル↓はこんな感じ。 (上が福くん、下が鈴木一真さん)

f:id:yusuke_ujitoko:20170409204504p:plain f:id:yusuke_ujitoko:20170409204542p:plain

事前処理

事前処理としては標準化とテスト用サンプルの切り出しを行った。 各カテゴリごとに50サンプルあるが、 そのうち40サンプルずつを訓練に使い、残りの10サンプルでテストすることにした。

訓練に使ったモデル

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_1 (Conv2D)            (None, 128, 128, 32)      896       
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 126, 126, 32)      9248      
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 63, 63, 32)        0         
_________________________________________________________________
dropout_1 (Dropout)          (None, 63, 63, 32)        0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 63, 63, 32)        9248      
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 61, 61, 32)        9248      
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 30, 30, 32)        0         
_________________________________________________________________
dropout_2 (Dropout)          (None, 30, 30, 32)        0         
_________________________________________________________________
flatten_1 (Flatten)          (None, 28800)             0         
_________________________________________________________________
dense_1 (Dense)              (None, 128)               3686528   
_________________________________________________________________
dropout_3 (Dropout)          (None, 128)               0         
_________________________________________________________________
dense_2 (Dense)              (None, 2)                 258       
=================================================================
Total params: 3,715,426.0
Trainable params: 3,715,426.0
Non-trainable params: 0.0
_________________________________________________________________

訓練結果(LossとAccuracy)

Loss

始めの方は順調に学習が進んでいるのがわかる。 また250エポック位から徐々に過学習の傾向が見られる。

f:id:yusuke_ujitoko:20170409203803p:plain

Accuracy

訓練データのAccuracyはほぼ1となっている。
一方、テストデータのAccuracyは一番良い時で0.85~0.9程度であり、過学習が進むと0.8程度に近づいていった。

f:id:yusuke_ujitoko:20170409203825p:plain

過学習を緩和するために、各層にL2正則化項を入れておいたが、効果は薄かったのかもしれない。
データセットがもう少し豊富であれば汎化性能をもっと上げられると考えられる。

福くんか鈴木一真さんか予測してみる

f:id:yusuke_ujitoko:20170409234417p:plain

福くんの画像は福くんと予測

f:id:yusuke_ujitoko:20170409234423p:plain

こちらの画像も正しく鈴木一真さんを予測

f:id:yusuke_ujitoko:20170409234429p:plain

コミカドは福くん

f:id:yusuke_ujitoko:20170409234435p:plain

太陽の男は鈴木一真さんのよう。

まとめ

データセットを作成するところを初めてやったらやはり大変。

  • ネット上の画像は重複が多い
  • 結局目視でゴミが混じってないか確認する必要がある。目が疲れる。

今回は各カテゴリ50個ずつしかサンプルを集められなかったが、サンプル増やせば汎化性能はよくなるはず。 あまりやりたくはないけど…

参考

ハリーポッターの組み分け帽子をCNNで実装してみた - Qiita
ちゅん顔の認識 - uphy’s tech blog

yusuke-ujitoko.hatenablog.com