end0tknr's kipple - 新web写経開発

http://d.hatena.ne.jp/end0tknr/ から移転しました

HOG特徴量 + ナイーブベイズ による ジェスチャー(手話)画像認識

先日は、k近傍による 画像認識を行いましたが、今回は、ナイーブベイズを使用。

HOG特徴量 + KNN(k近傍) による ジェスチャー(手話)画像認識 - end0tknr's kipple - 新web写経開発

以下は、更に以前の主成分分析に関するエントリ

固有値、固有ベクトルからの主成分分析 オレオレ入門 (PCA: Principal Component Analysis) - end0tknr's kipple - 新web写経開発

PIL / Pillow , numpy による画像処理入門 その3 - 平板化(1次元化)と主成分分析(principal component analysis; PCA) - end0tknr's kipple - 新web写経開発

ただし、単純にベイズ分類器を使用せず、主成分分析(PCA)により次元数を 50に削減し、実行。

まっ、いつもの

実践コンピュータビジョン サンプルプログラム

の写経です

#!/usr/local/bin/python
# -*- coding: utf-8 -*-

from PIL import Image
import numpy
import os
import bayes
import math

def main():
    # 訓練データとtestデータのload
    features, labels = read_gesture_features_labels('train/')
    test_features, test_labels = read_gesture_features_labels('test/')

    classnames = numpy.unique(labels)
    print "CATEGORIES:",
    print classnames

    # 主成分分析 - 返り値:写像行列(次元の重要度順), 分散, 平均
    V, S, m = my_pca(features)
    # 重要な次元を50コ残す
    V = V[:50]

    # numpy.dot():内積
    features = numpy.array([numpy.dot(V, f - m) for f in features])
    test_features = numpy.array([numpy.dot(V, f - m) for f in test_features])

    # ベイズ分類器
    bc = BayesClassifier()
    blist = [features[numpy.where(labels == c)[0]] for c in classnames]
    bc.train(blist, classnames)
    res = bc.classify(test_features)[0]
    acc = sum(1.0 * (res == test_labels)) / len(test_labels)
    print 'Accuracy:', acc
    print_confusion(res, test_labels, classnames)


def read_gesture_features_labels(path):
    # *.dsift のfile名list
    featlist = [
        os.path.join(path, f) for f in os.listdir(path) if f.endswith('.dsift')
    ]
    # 特徴量をload
    features = []
    for featfile in featlist:
        l, d = read_features_from_file(featfile)
        features.append(d.flatten())
    features = numpy.array(features)
    # ラベルを生成する
    labels = [featfile.split('/')[-1][0] for featfile in featlist]
    return features, numpy.array(labels)


def print_confusion(res, test_labels, classnames):
    n = len(classnames)
    # 混同行列
    class_ind = dict([(classnames[i], i) for i in range(n)])
    confuse = numpy.zeros((n, n))
    for i in range(len(test_labels)):
        confuse[class_ind[res[i]], class_ind[test_labels[i]]] += 1
    print 'Confusion matrix for'
    print classnames
    print confuse


# 特徴量を読込み行列形式で返す
def read_features_from_file(filename):
    f = numpy.loadtxt(filename)
    return f[:, :4], f[:, 4:]  # 特徴点の配置と記述子


# 主成分分析
#   入力:X, 訓練データを平板化した配列を行として格納した行列
#   出力:写像行列(次元の重要度順), 分散, 平均
def my_pca(X):
    # 次元数を取得
    num_data, dim = X.shape
    # データをセンタリング
    mean_X = X.mean(axis=0)
    X = X - mean_X
    if dim > num_data:
        # PCA - 高次元のときはコンパクトな裏技を用いる
        M = numpy.dot(X, X.T)  # 共分散行列
        e, EV = numpy.linalg.eigh(M)  # 固有値と固有ベクトル
        tmp = numpy.dot(X.T, EV).T  # ここがコンパクトな裏技
        V = tmp[::-1]  # 末尾の固有ベクトルほど重要なので、反転する
        S = numpy.sqrt(e)[::-1]  # 固有値の並びも反転する
        for i in range(V.shape[1]):
            V[:, i] /= S
    else:
        # PCA - 低次元なら特異値分解を用いる
        U, S, V = numpy.linalg.svd(X)
        V = V[:num_data]  # 最初のnum_dataの分だけが有用
    # 写像行列と、分散、平均を返す
    return V, S, mean_X


class BayesClassifier(object):
    def __init__(self):
        self.labels = []  # クラスのラベル
        self.mean = []    # クラスの平均
        self.var = []     # クラスの分散
        self.n = 0        # クラスの数

        
    # data (n*dimの配列のリスト)で学習。
    # labelsはオプションで、デフォルトは 0...n-1 
    def train(self, data, labels=None):
        if labels == None:
            labels = range(len(data))
        self.labels = labels
        self.n = len(labels)
        for c in data:
            self.mean.append(numpy.mean(c, axis=0))
            self.var.append(numpy.var(c, axis=0))  # var():分散

    # 各クラスの確率を計算し確率の高いラベルを返すことでpointsを分類
    def classify(self, points):
        # 各クラスの確率を計算する
        est_prob = numpy.array(
            [gauss(m, v, points) for m, v in zip(self.mean, self.var)])
        # 最も確率の高いindex noを求め、クラスのラベルを返す
        ndx = est_prob.argmax(axis=0)
        est_labels = numpy.array([self.labels[n] for n in ndx])
        return est_labels, est_prob


# 独立した平均m分散vの点を、xの行として持つ d次元ガウス分布を評価
def gauss(m, v, x):
    if len(x.shape) == 1:
        n, d = 1, x.shape[0]
    else:
        n, d = x.shape
        
    # 共分散行列を求め、xから平均を引く
    S = numpy.diag(1 / v)  # diag():行列の対角成分
    x = x - m
    # 確率の積,  exp():ネイピア数(e)の?乗, dot():内積
    y = numpy.exp(-0.5 * numpy.diag(numpy.dot(x, numpy.dot(S, x.T))))
    # 正規化して返す, prod():配列の積
    return y * (2 * numpy.pi)**(-d / 2.0) / (numpy.sqrt(numpy.prod(v)) + 1e-6)


if __name__ == '__main__':
    main()

↑こう書くと↓こう表示されます

$ python 0_8.2.1.bayes_g.py 
CATEGORIES: ['A' 'B' 'C' 'F' 'P' 'V']
0_8.2.1.bayes_g.py:87: RuntimeWarning: invalid value encountered in sqrt
  S = numpy.sqrt(e)[::-1]  # 固有値の並びも反転する
0_8.2.1.bayes_g.py:109: FutureWarning: comparison to `None` will result in an elementwise object comparison in the future.
  if labels == None:
Accuracy: 0.744680851064
Confusion matrix for
['A' 'B' 'C' 'F' 'P' 'V']
[[ 23.   1.   3.   2.   0.   0.]
 [  0.  22.   0.   3.   2.   1.]
 [  0.   1.  28.   1.   1.   1.]
 [  1.   0.   0.  22.   0.   0.]
 [  0.   5.   2.   0.  23.   5.]
 [  5.   0.   3.   1.  10.  22.]]

srcではガウス確率分布(正規分布)モデルを使ってベイズ分類器を実装していますが、 未だ私の理解は今ひとつ