福くんと鈴木一真さんの画像分類問題をCNNで解く(データセット作成編)

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

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

2クラス分類なので課題としては簡単。
この二人に似ている人が他にいれば教えてください。

データセット作成

肝はこのフェーズ。
福くんと鈴木一真さんの画像をそれぞれ集める。

今回はGoogle Custom Search APIを使った。
無料使用の範囲では、一日100クエリまでしか検索できないという制約があるが、 今回はそこまで量を集めるわけではないので問題なかった。

インポートするライブラリ
import urllib.request
from urllib.parse import quote
import httplib2
import json 
import os
import time
import copy

API_KEY = "×××××××××××"
CUSTOM_SEARCH_ENGINE = "○○○○○○○○○○○"
Google Custom Search APIで画像URLを取得
# 画像URLを取得する
def getImageUrl(search_item, total_num):
    img_list = []
    i = 0

    while i < total_num:
        # クエリを作成
        query_img = "https://www.googleapis.com/customsearch/v1?key=" + API_KEY + "&cx=" + CUSTOM_SEARCH_ENGINE + "&num=" + str(10 if(total_num-i)>10 else (total_num-i)) + "&start=" + str(i+1) + "&q=" + quote(search_item) + "&searchType=image"
        res = urllib.request.urlopen(query_img)
        data = json.loads(res.read().decode('utf-8'))

        # URLを取得
        for j in range(len(data["items"])):
            img_list.append(data["items"][j]["link"])
        i=i+10
    return img_list
画像ダウンロード
# URLから画像をダウンロードする
def getImage(search_item, img_list):
    opener = urllib.request.build_opener()
    http = httplib2.Http(".cache")
    for i in range(len(img_list)):
        try:
            fn, ext = os.path.splitext(img_list[i])
            response, content = http.request(img_list[i])

            # ディレクトリがない場合、ディレクトリ作成
            if not (os.path.exists(search_item)):
                os.mkdir(search_item)
            
            # 画像保存
            with open(search_item+"/"+search_item+str(i)+ext, 'wb') as f:
                f.write(content)
        except:
            print("failed to download images.")
            continue

if __name__ == "__main__":
    search_word = "福くん"
    img_list = getImageUrl(search_word, 100)
    getImage(search_word, img_list)
顔部分のみ切り抜き

OpenCVのカスケード型分類器を使って、画像から顔部分を検出して切り抜く。 切り抜いた後、128*128のサイズにリサイズしている。

import cv2
import time
import copy
from matplotlib import pyplot as plt
# 顔部分を切り抜く
def cropFace(src_name, dst_name, imsize):
    # Haarcascade分類器を読み込む
    path = os.environ['HOME'] + "/anaconda3/share/OpenCV/haarcascades/" + "haarcascade_frontalface_default.xml"
    face_cascade = cv2.CascadeClassifier(path) 

    # 切り抜いた画像を保存するディレクトリを作成
    dir = os.getcwd() + '/Crop_' + dst_name
    if not (os.path.exists(dir)):
        os.mkdir(dir)
    img_paths = os.listdir(os.getcwd() + "/" + src_name) 
    img_paths = [src_name + "/" + img_path  for img_path in img_paths]

    # 画像ごとにループ
    for img_path in img_paths:
        root, ext = os.path.splitext(img_path)
        if not ext in ['.jpg', '.jpeg', '.png', '.tiff', '.tif','.bmp', 'JPG', 'JPEG']:
            continue
        img = cv2.imread(img_path)
        gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) # grayに変換
        faces = face_cascade.detectMultiScale(gray, 1.3, 5)

        # 検出した顔ごとに切り抜き
        for num in range(len(faces)):
            cropImg = copy.deepcopy(img[faces[num][1]:faces[num][1]+faces[num][3], faces[num][0]:faces[num][0]+faces[num][2]])
            # 画像リサイズ
            resizeImg = cv2.resize(cropImg, (imsize, imsize)) 

            t = time.ctime().split(' ')
            if t.count('') == 1:
                t.pop(t.index(''))
            timestr = t[1] + t[2] + t[0] + '_' + t[4] + '_' + t[3].split(':')[0] + t[3].split(':')[1] + t[3].split(':')[2]
            filename = dir + "/" + dst_name + '_' + timestr + "_" + str(num + 1) + '.tif'
            cv2.imwrite(filename, resizeImg)

        # 顔検出を可視化して目視チェック
        for (x, y, w, h) in faces:
            cv2.rectangle(img, (x,y), (x+w, y+h), (255,0,0), 2)
        plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
        plt.show()

これらの処理で集めた画像群

上記の処理により、福くん、鈴木一真さんの顔切り抜き画像を50個ずつ集めることができた。 集めた画像の一部を下に示す。

福くん

f:id:yusuke_ujitoko:20170409180542p:plain

鈴木一真さん

f:id:yusuke_ujitoko:20170409180747p:plain

思ったよりもデータは集まらない。
もっと簡単に集められると思っていた。 それぞれ100個は画像集めよう、と事前に考えていたが、最終的にはなんとか50個に到達する程度となってしまった。

ネット上の画像は同じ画像が拡散されているパターンが多いのだなあと感じる。
一見異なる画像でも、同じ画像からの切り抜きだったりする。

あと福くんの画像探してクエリ入れても、鈴木一真さんの画像が混じっていたりして笑ってしまった。

… 集めた画像を眺めて気づいたのだが、
データセットで比較すると、福くんと鈴木一真さんは全然似てない!!
誰だ似てるとか言った奴は!
これはニューラルネット使うまでもなく分類できる!笑

せっかくデータセットを作ったので分類まではやることにする。
長くなってしまったのでCNNで分類するのは次の記事に分ける。

yusuke-ujitoko.hatenablog.com

【はじめてのパターン認識】第4章 確率モデルと識別関数

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

はじめてのパターン認識

はじめてのパターン認識

標準化

学習データを構成する個々の特徴は、測定単位のとり方で大きな値になったり小さな値になったりするので、 分布の形状もそれに伴って大きく変化する。

測定単位の影響を取り除く1つの方法が、個々の特徴を平均0、分散1に標準化すること。
天下り的に記述してしまうが、 {x}の平均{\mu}標準偏差{\sigma}を用いた線形変換 $$ z = \frac{x - \mu}{σ} $$ を考えると、 {} $$ E \left\{ z \right\} = E\left\{ \frac{x - \mu}{σ}\right\} = \frac{1}{σ} (E \left\{x\right\} - \mu) = 0 \\ Var \left\{ z \right\} = E\left\{ \left( \frac{x - \mu}{σ} \right)^{2} \right\} = \frac{1}{σ^{2}} (E \left\{ (x - \mu)^{2} \right\} = 1 $$ このように、{z}の平均は0、分散は1となる。
特徴ごとに標準化を行うことで、測定単位の影響がない特徴ベクトルを構成することができる。

無相関化

観測データの特徴間の相関をなくす処理(無相関化)は主成分分析と深い関係がある。
こちらも天下り的に定義していく。

観測データから作られた共分散行列{\Sigma}固有値問題 {} $$ Σ s = \lambda s $$ を解いて得たd個の固有値{\lambda_1 \geq \lambda_2 \geq \cdots \geq \lambda_d}、 対応する固有ベクトル{s_1, s_2, \cdots , s_d}とする。 これらの固有ベクトルを並べて行列 {} $$ S = (s_1, s_2, \cdots , s_d) $$ を定義する。共分散行列は実対称行列なので固有ベクトルは正規直交基底となっている。

観測データ{x}{S^{T}}で線形変換することを考える。 線形変換されたデータは{y = S^{T} x}で与えられ、その平均値と共分散行列は、 {} $$ E \left\{ y \right\} = E \left\{ S^{T} x \right\} = S^{T} \mu \\ \begin{align} Var \left\{ y \right\} &= E\left\{ \left(y - E \left\{ y \right\} \right) \left(y - E \left\{ y \right\} \right)^{T} \right\} \\ &= S^{-1} E\left\{ \left(y - E \left\{ y \right\} \right) \left(y - E \left\{ y \right\} \right)^{T} \right\} S \\ &= S^{-1} Σ S \end{align} $$

となる。 最後の式は行列の対角化の式であり、 {} $$ S^{-1} Σ S = \Lambda = \begin{bmatrix} \lambda_1 & 0 & 0 \\ 0 & \cdots & 0 \\ 0 & 0 & \lambda_d \end{bmatrix} $$ のように表される。 これにより各特徴間の相互相関が0になる。 これが観測データの無相関化という。

(参考) 直交行列とは(定義,性質)

白色化

無相関化により各特徴間の相関はなくなるが、 固有値に相当する分だけ、特徴量の標準偏差に違いが残る。 この違いをなくして、全ての特徴量の標準偏差を1に正規化し、かつ中心化を行う操作を白色化(whitening)という。

白色化後の座標系を{u = (u_1, \cdots, u_d)^{T}}とすれば、

{} $$ u = \Lambda^{-½} S^{T} (x - \mu) $$ で与えられる。
{\Lambda ^{-\frac{1}{2}}}{\Lambda}の各対角要素の平方根をとった行列の逆行列。 この{u}の共分散行列が単位行列となっている。 このことを示していく。

{} $$ \begin{align} E \left\{ u \right\} &= \Lambda^{-½} S^{T}(E \left\{ x \right\} - \mu) \\ &= \Lambda^{-½} S^{T}(\mu - \mu) \\ &= 0 \end{align} $$

となるので、{u}の共分散行列は、

{} $$ \begin{align} Var \left\{ u \right\} &= E \left\{ uu^{T} \right\} \\ &= E \left\{ \Lambda^{-½} S^{T} \left(x - \mu \right) \left(x - \mu \right)^{T} S \Lambda^{-T/2} \right\} \\ &= \Lambda^{-½} S^{-1} E \left\{ (x - \mu)(x - \mu)^{T} \right\} S \Lambda^{-T/2} \\ &= \Lambda^{-½} S^{-1} Σ S \Lambda^{-T/2} \\ &= \Lambda^{-½} \Lambda \Lambda^{-T/2} \\ &= I \end{align} $$ となり、{u}の共分散行列が単位行列となることが示された。

標準化の場合は、それぞれの特徴の標準偏差が独立に1に正規化されるのに対して、 白色化の場合は、回転と中心化を行った後に各軸の標準偏差が1に正規化される。 したがって、どの方向に対してもデータ分布の標準偏差が単位超球上に乗るようになる。

パラメトリックモデルとノンパラメトリックモデル

学習データの分布を表現する場合、以下の2つに分けられる。

  • パラメトリックモデル
    • 学習データから推定した統計量(パラメータ)を用いて構成した確率モデルで分布を表現する
  • ノンパラメトリックモデル
    • 特定の確率モデルを仮定せず、学習データそのものを用いてデータの分布を表現する

正規分布関数

一次元正規分布関数は次のように定義される。 {} $$ \mathcal{N} (x|\mu,σ^{2}) = \frac{1}{\sqrt{2 \pi} σ} exp \left( - \frac{(x - \mu)^2}{2 σ^{2}}\right) $$

d次元の正規分布関数の場合は次のように定義される。 {} $$ \mathcal{N} (x|\mu, Σ) = \frac{1}{{2 \pi}^{d/2} |Σ|^{½}} exp \left( - \frac{1}{2} (x - \mu)^T Σ^{-1} (x - \mu) \right) $$ ここで、{\mu}は平均ベクトル、Σは共分散行列を表す。
正規分布関数の指数部は、任意の点{x}と平均ベクトル{\mu}との間の距離、 {} $$ d(x, \mu) = \sqrt{(x - \mu)^T Σ^{-1} (x - \mu)} $$ を表している。これをマハラノビス距離という。 ユークリッド距離を共分散行列で割り算しているので、分布の広がり方を考慮に入れた距離となっている。 平均ベクトルからのマハラノビス距離が同じでも、分布の広がりが大きな方向の点までのユークリッド距離は、小さな方向の点までのユークリッド距離より大きくなる。

正規分布から導かれる識別関数

i番目のクラスのクラス条件付き確率が次の正規分布をしていると仮定して、 ベイズの誤り率最小識別規則を満たす識別関数を求める。 {} $$ P(x|C_{i}) = \frac{1}{{2 \pi}^{d/2} |Σ_{i}|^{½}} exp \left( - \frac{1}{2} (x - \mu_{i})^T Σ_{i}^{-1} (x - \mu_{i}) \right) $$ クラスの事前確率を{P(C_{i}}とすれば、事後確率は、 {} $$ P(C_{i}|x) ∝ \frac{P(C_{i})}{{2 \pi}^{d/2} |Σ_{i}|^{½}} exp \left( - \frac{1}{2} (x - \mu_{i})^T Σ_{i}^{-1} (x - \mu_{i}) \right) $$ となる。対数を取り、 {} $$ \ln(P(C_{i})) - \frac{d}{2} \ln(2 \pi) - \frac{1}{2} \ln(|Σ_{i}|) - \frac{1}{2} (x - \mu_{i})^T Σ_{i}^{-1} (x - \mu_{i}) $$ となる。これを整理して以下のように{g_{i}(x)}とおく。 {} $$ g_{i}(x) = (x - \mu_{i})^T Σ_{i}^{-1} (x - \mu_{i}) + \frac{1}{2} \ln(|Σ_{i}|) - 2 \ln(P(C_{i})) $$ この{g_{i}(x)}が最小となるクラスを選択すれば、誤り最小基準のベイズの識別規則が得られる。

【はじめてのパターン認識】第3章 ベイズの識別規則

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

はじめてのパターン認識

はじめてのパターン認識

本章では、ベイズの識別規則とROC曲線についての説明がメインになされている。

ベイズの識別規則

観測データを{x}、識別クラスを{C_i}とする。
ベイズの識別規則は次式で定義される事後確率が最大なクラスに観測データを分類する、というもの。

  • 事後確率 {P(C_i | x)}
  • クラス条件付き確率(尤度) {p(x|C_i)}
  • 周辺確率 {p(x)}
  • 事前確率 {P(C_i)} として、 {} $$ P(C_i | x) = \frac{p(x|C_i)}{p(x)} \times P(C_i) = (修正項) \times (事前確率) $$

この式から、事前確率が尤度と周辺確率で修正されて事後確率が得られることがわかる。
具体的には、

クラスを指定した時のxの生起確率である尤度{p(x|C_i)}が、 クラスを指定しない時の生起確率{p(x)}よりも

  • 大きければ、事後確率は事前確率より大きくなり、
  • 小さければ、事後確率は事前確率より小さくなる

クラス{C_i}とクラス{C_j}の識別境界は、事後確率が等しくなるところ、すなわち、 {} $$ P(C_i|x) = P(C_i | x) = \frac{p(x|C_i)}{p(x)} \times P(C_i) = \frac{p(x|C_j)}{p(x)} \times P(C_j) = P(C_j | x) $$ が成り立つところとなる。 式からわかるように周辺確率は{p(x)}はどちらのクラスにも共通に現れているため、識別規則に含める必要はない。 したがって、ベイズの識別規則は、(識別クラス) = {arg max p(x|C_i)P(C_i)}で与えられる。

ベイズの識別規則は誤り率最小

2クラス{C_1, C_2}の識別問題を用いて、ベイズの識別規則が誤り率を最小にすることを示す。

ベイズの識別規則によれば、クラス{C_2}の事後確率がクラス{C_2}の事後確率より

  • 大きい時、入力ベクトル{x}をクラス{C_1}に識別する
  • 小さい時、入力ベクトル{x}をクラス{C_2}に識別する

したがって、ベイズの識別規則のもとでの誤り率{\epsilon(x)}は事後確率の小さい方になるので、 {} $$ \epsilon(x) = min[P(C_1|x), P(C_2,x)] $$ となる。

受信者動作特性曲線(ROC曲線:receiver operator characteristics curve)

ROCに関しては下記の記事が詳しく、大変参考になる。
【統計学】ROC曲線とは何か、アニメーションで理解する。 - Qiita

【はじめてのパターン認識】第2章 識別規則と学習法の概要

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

はじめてのパターン認識

はじめてのパターン認識

識別規則の構成法

  1. 事後確率による方法
  2. 距離による方法
  3. 関数値による方法
  4. 決定木による方法
事後確率による方法

パターン空間に確率分布を仮定し、事後確率が最大のクラスに分類する。 ベイズの最大事後確率法が代表例。 学習データから確率分布のパラメータを推定する必要があり、様々な方法が提案されている。

距離による歩法

入力ベクトルxと各クラスの代表ベクトルとの距離を計算し、一番近い代表ベクトルのクラスに分類する。 最近傍法が代表例。

関数値による方法

関数{f(x)}の正負、あるいは最大値でクラスを決める。 バーセプトロン型学習回路やサポートベクトルマシンがその代表例。 識別のために用いられる関数{f(x)}を識別関数という。

決定木による方法

識別規則の審議に応じて次の識別規則を順次適用し、決定木の形でクラスを決める。 学習データから決定木を自動的に構成する手法が提案されている。

手元にあるデータを学習用とテスト用に分割する手法

  1. ホールダウト法(holdout法)
  2. 交差確認法(cross validation法)
  3. 一つ抜き法(leave-one-out法)
  4. ブートストラップ法(bootstrap法)
ホールドアウト法

手元のデータを二つ二分割し、一方を学習に使い、もう一方はテストのために取り置いておき、誤り率を推定するために使用する。

ホールドアウト法は、有限のデータを学習用とテスト用に分割するので、 学習用を多くすればテスト用が減り、汎化性能の評価精度が落ちる。 逆にテスト用を多くすれば学習用が減少するので、学習そのものの精度が悪くなる。 したがって、手元のデータが大量にある場合を除いて、良い性能評価を与えないという欠点がある。

交差確認法(cross validation法)

ホールドアウト法の欠点を補うものとして使用される。 手元のデータをm個のグループに分割して、m-1個のグループのデータを用いて識別器を学習し、残りの1つのグループのデータでテストを行う。 これをm回繰り返し、それらの誤り率の平均を性能予測値とする。

1つ抜き法

交差確認法において、データの数とグループの数を等しくした場合を言う。 1つのデータを除いた全てのデータで学習し、除いた1つでテストすることをデータ数分繰り返す。 ジャックナイフ法とも呼ばれている。

ブートストラップ法

手元のデータから、N回復元抽出してブートストラップサンプルを作る。 このブートストラップサンプルを学習に使用する。 そしてテストデータとして、ブートストラップサンプルを使う場合と、もともとのデータ集合を使う場合の誤認識率の差をバイアスとして記憶しておく。

今度はもともとのデータ集合を使って学習させて、もともともデータ集合でテストした誤認識率にこのバイアス分を足すと、真の誤認識率の予測値が精度良く得られる。

汎化誤差を評価

識別関数の形やパラメータの要素が識別関数の複雑さを決める。 これらを変えながら、交差確認法やブートストラップ法により汎化誤差を推定すればよい。 これらの手法はデータの分布を統計モデルで記述する必要がないので、多くの研究者が使用している。

ちなみに、 データの分布に統計モデルを仮定した場合は、AIC赤池情報量基準)やBICベイズ情報量基準)、MDL(最小距離基準)などで解析的に汎化誤差を評価できる。

【読書メモ】データ解析のための統計モデリング入門のまとめ

データ解析のための統計モデリング入門を読んだ。

って何それという状態だったので大変勉強になった。
筆致が軽く、数学の素養が必要ないため読みやすく、誰にでも薦められる。
この本を読む前に線形モデルだけは勉強しておくべき。

その読書メモのまとめ。