読者です 読者をやめる 読者になる 読者になる

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

福くんと鈴木一真さんの画像分類問題を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