機械学習に詳しくなりたいブログ

機械学習や数学について勉強した内容を中心に書きます。100%趣味です。記事は数学的に厳密でなかったり誤りを含んでいるかもしれません。ご指摘頂ければ幸いです。

MNISTをCLAFIC法で識別する

今回はCLAFIC法と呼ばれる、KL展開を用いた手法でMNISTを識別してみたいと思います。これまでに、KL展開は分散最大基準と平均二乗誤差最小基準の2種類を見てきました。(参考:KL展開 平均二乗誤差最小基準KL展開 分散最大基準) 目的が分類の場合は平均二乗誤差最小基準が用いられるようです。おそらく分散最大基準だと、仮に空間上の中心が違うだけでほぼ同じ分布形状をしているクラスが複数あったとすると、その固有ベクトルは同じ傾向が出てしまうため、分類に向かないのだと思います。ここでそれぞれの基準におけるMNISTの最大固有値に対応する固有ベクトルを確認してみます。(参考:MNISTをKL展開して部分空間を図示

分散最大基準

f:id:opabinia2:20180702205543p:plain




平均二乗誤差最小基準

f:id:opabinia2:20180702205546p:plain

それぞれの求め方で、輪郭は似通ってますがけっこう違いますね。この違いを理屈で説明したいんですが、、、わかるような、わからないような。

さて、射影後のデータを元の次元で見るで、データ\mathbf xの次元を行列\mathbf Aによって削減した後のベクトル\mathbf{A}^{T} \mathbf{x}を、元の次元で表現すると \mathbf{A}\mathbf{A}^{T} \mathbf{x}であることを確認しました。(変換行列についてはこちら:KL展開 分散最大基準 解の導出) このことは、\mathbf xの次元D^{\prime}に対する直交射影行列が\mathbf{A}\mathbf{A}^{T}であると捉えることができ、これを


\mathbf{P}=\mathbf{A}\mathbf{A}^{T} \tag{1}

とします。また、射影行列の性質として以下の等式が成り立ちます。


\begin{eqnarray*}
\mathbf{P}\mathbf{P}&=&\mathbf{P} \tag{2} \\
\mathbf{P}^{T}&=&\mathbf{P} \tag{3} 
\end{eqnarray*}

さて、データをある部分空間へ正射影することを考えます。このとき、その正射影ベクトルのノルムが大きければ、その部分空間が持つ特徴に似ていると捉えられます。よって識別対象全ての部分空間へ射影し、そのノルムが最大であるものが識別結果と考えることができます。このように分類する手法をCLAFIC法を呼びます。

\mathbf Pは直交射影行列ですから、データ\mathbf xD次元空間→D^{\prime}次元空間の正射影は\mathbf P \mathbf xとなり、このノルムの二乗は、式(3)を使えば


\begin{eqnarray*}
\| \mathbf{P} \mathbf{x} \|^{2} &=& (\mathbf{P}\mathbf{x})^{T}\mathbf{P}\mathbf{x}\tag{4} \\
&=&\mathbf{x}^{T}  \mathbf{P}\mathbf{P}\mathbf{x}\tag{5} \\
&=& \mathbf{x}^{T}  \mathbf{P}\mathbf{x}\tag{6}
\end{eqnarray*}

と書けます。(参考:ベクトルのノルムの式(5)、転置行列の定理) この式(6)をMNISTの部分空間全て対して計算し、最大値を識別結果とします。

CLAFIC法で実際にMNISTを識別してみました。今回は平均二乗誤差最小基準、分散最大基準、それぞれで計算した部分空間を用います。また、部分空間の次元は適当にふって傾向を見てみました。結果は以下のグラフです。

f:id:opabinia2:20180702205548p:plain


平均二乗誤差最小基準のほうが精度が高く、95.7%くらいの正解率が出ています。*1 部分空間の次元を増やすにつれて、どちらの基準でも精度は悪くなっていきます。次元を増やしていくと、どの部分空間でも元の画像が表現できてしまいますから、各部分空間で差が表れにくいのでしょう。(参考:KL展開で得られた基底で画像を表現する) CLAFIC法は50年近く前に提案された手法だそうですが、この時代の手法でこれだけの精度が出るのは驚きました。でもMNISTの識別精度をいろいろネットで見て回ると、畳込みニューラルネットワークで実験した結果はよく目に付き、99.7%とかそれくらいの精度が出るみたいです。この数%の差が大きいんでしょうね。ただ問題の状況と求める精度によっては、ニューラルネットワークなんか使わなくても50年前の手法でも十分、ということもあるかもしれません。

今回のコードです。 MNISTのデータは、http://yann.lecun.com/exdb/mnist/に、訓練データ用、テスト用と分けられたものが公開されていましたのでこちらを使いました。また、式(6)は、以下のように変形して計算したほうが効率が良いそうなのでこちらを使っています。


\begin{eqnarray*}
\displaystyle \mathbf{x}^{T}  \mathbf{P}\mathbf{x} &=&\mathbf{x}^{T} \left( \sum_{j=1}^{d} (\mathbf{u}_j\mathbf{u}_j^{T}) \right)\mathbf{x}\tag{7} \\
\displaystyle &=& \sum_{j=1}^{d} (\mathbf{x}^{T}\mathbf{u}_j)^2 \tag{8} \\
\end{eqnarray*}

部分空間の次元が小さいときは計算の速さを体感できましたが、次元が大きくなるとそうでもないような?(こういうのはきちんと計算量を見積もって理解したほうが良いと思いますが、、、。)なお\mathbf{u}などの文字の意味はKL展開 分散最大基準 解の導出で定義してあるものと同じです。

# CLAFIC法でMNISTを識別
import numpy as np

# テストデータを読み込み
data = np.load("./mnist/test_data.npy")
label = np.load("./mnist/test_label.npy")

# データ数
N = data.shape[0]

# ラベル数
C = 10

# 次元数
D = data.shape[1]

# 各クラスの固有ベクトルを読み込み(固有値降順にソート済み)
A = np.empty([C, D, D])
for i in range(C):
    A[i, :, :] = np.load("./mnist/mnist_eig_{0}.npy".format(i))

# CLAFIC法(式(8)の計算)
out = np.empty(C)
for d in range(0, 200, 20):
    count_ok = 0
    for i in range(N):
        for j in range(C):
            norm = 0
            for k in range(d):
                norm += np.dot(data[i, :], A[j, :, k])**2
            out[j] = norm
        if np.argmax(out) == label[i]:
            count_ok += 1

    # 正解率
    print(count_ok/N * 100, d)


参考 MNISTデータをnumpyで扱える形式に変換

# http://yann.lecun.com/exdb/mnist/ でダウンロードしたMNISTをndarrayに変換して保存
import numpy as np
import gzip

with gzip.open("./mnist/t10k-images-idx3-ubyte.gz", "rb") as f:
    # 最初の16バイトは画像数などの情報
    x = np.frombuffer(f.read(), dtype=np.uint8, offset=16)
    x = x.reshape(-1,784)

np.save("./mnist/test_data", x)

with gzip.open("./mnist/train-images-idx3-ubyte.gz", "rb") as f:
    x = np.frombuffer(f.read(), dtype=np.uint8, offset=16)
    x = x.reshape(-1,784)

np.save("./mnist/train_data", x)

with gzip.open("./mnist/t10k-labels-idx1-ubyte.gz", "rb") as f:
    # 最初の8バイトはラベル数などの情報
    x = np.frombuffer(f.read(), dtype=np.uint8, offset=8)

np.save("./mnist/test_label", x)

with gzip.open("./mnist/train-labels-idx1-ubyte.gz", "rb") as f:
    x = np.frombuffer(f.read(), dtype=np.uint8, offset=8)

np.save("./mnist/train_label", x)

*1:CLAFIC法でMNISTを識別した結果があまりネットになく、結果が合っているのかちょっと不安