しんさんのブログ

科学や技術のこと読書のことなど

機械学習基礎の基礎: パーセプトロンで線形分離を実装してみる

今度は、パーセプトロンを使って線形分離をしてみます。
適当に線形分離できそうなデータを以下のように作ってみました。
データは2次元平面上の点の座標(x, y)とそれぞれの正解ラベル1 or -1をつけています。
10, 20, 1
11, 30, 1
15, 30, 1
17, 35, 1
5, 25, 1
25, 35, 1
50, 40, 1
44, 38, 1
33, 30, 1
35, 35, 1
15, 8, -1
20, 2, -1
23, 11, -1
31, 22, -1
40, 35, -1
42, 29, -1
45, 39, -1
47, 37, -1
50, 39, -1
35, 20, -1
これを、テキストファイルとして読み込んで教師データとしています。
r = (x, y), 正解 t = 1or -1として、2入力、1出力のパーセプトロンをつくります。
重みw = (w0, w1)に対して、
出力層への入力は u = dot(w, r)となります。
識別関数fは
f = 1 ( if u >= 0)
f = -1 ( if u< 0)
とします。
教師データと出力が一致しない場合は
w := w + r* t
として重みw を更新します。
コードは以下のようになりました。
学習は120エポックとしました。

###############
# パーセプトロンの実装
###############
import numpy as np
import matplotlib.pyplot as plt

### (1) データ読み込み ###
trainData = np.loadtxt('linerSeparateData.txt', delimiter=',', skiprows=0)
trainDataX = trainData[:, 0:2] # (x, y)
trainDataY = trainData[:, 2] # 正解ラベル

# 重みwの初期化
w = np.random.rand(2)

# activation function
def f(x):
	if np.dot(w, x) >= 0:
		return 1
	else:
		return -1

num_epoch = 120 #エポック数
countNum = 0 #カウンター

### 学習 ###
for _ in range(num_epoch):
	for x, y in zip(trainDataX, trainDataY):
		if f(x) != y: # 予想が間違えたとき
			w = w + y*x # 重みを更新
		# print result
	countNum+=1
	print('{}: w = {}' .format(countNum, w))

# 結果の表示
x0 = np.arange(0, 50)

plt.plot(trainDataX[trainDataY == 1, 0], trainDataX[trainDataY == 1, 1], 'o')
plt.plot(trainDataX[trainDataY == -1, 0], trainDataX[trainDataY == -1, 1], 'x')
plt.plot(x0, -w[0]/w[1] * x0)
plt.show()

見た感じはうまく分離できているように見えます。
f:id:wshinya:20180325221236p:plain

機械学習基礎の基礎: 確率的勾配降下法の実装

前回のデータとコードを改造してStochastic gradient descent method(SGD)を実装してみました。
と言っても、大した改造ではなくパラメータの更新を1つの訓練サンプルごとに行うようにしただけです。
np.random.permutation(X.shape[0])
で、いったんランダムにデータを並べなおして、そこから一つずつデータを取り出してデータの数の回数だけパラメータを更新します。
最後に、平均誤差をプロットして学習が進むごとに誤差が小さくなっていることを確認してみました。

###############
# SGDの実装
###############
import numpy as np
import matplotlib.pyplot as plt

### (1) データ読み込み ###
trainData = np.loadtxt('kodomoShinchouTaijyu.txt', delimiter=',', skiprows=0)
trainDataX = trainData[:, 0]
trainDataY = trainData[:, 1]

### (2) データの標準化
mu = trainDataX.mean() # 平均
sigma = trainDataX.std() # 標準偏差

def standaradize(x):
	return (x - mu)/sigma

trainDataXSTD = standaradize(trainDataX)

# fitting 関数を2次関数にする
# y = theta0 + theta1 * x + theta2 * x^2

# 学習データをマトリクスとしてまとめる
# 2次関数でfittingするので2次まで
def toMatrix(x):
	return np.vstack([np.ones(x.shape[0]), x, x**2]).T

X = toMatrix(trainDataXSTD)

# パラメータの初期値
theta = np.random.rand(3)

### 線形fitting ###
# fitting関数 (線形回帰)
def f(x):
	return np.dot(x, theta)

# MSE関数(最小2乗の損失関数)
def E(x, y):
	return 0.5 * np.sum( (y - f(x)) ** 2)

# 平均2乗誤差
def MSE(x, y):
	return (1.0/x.shape[0] * np.sum((y - f(x))**2 ))

### 学習 ###
eta = 1e-2 #学習率
diff = 1 # 誤差

# 平均誤差を記録する
errors = []

# 学習する
errors.append(MSE(X, trainDataY))

while diff > 1e-3: # 損失関数の変化が小さくなるまでループする
	# 学習データをランダムにシャッフルする
	n = np.random.permutation(X.shape[0])
	# ランダムにシャッフルした学習データの中から一つ抜き出してパラメータを更新していく(確率的勾配降下法)
	for x, y in zip(X[n, :], trainDataY[n]):
		theta = theta - eta * (f(x) - y) * x
	# 平均誤差を配列に追加
	errors.append(MSE(X, trainDataY))
		# 1つ前のステップの誤差との差分
	diff = errors[-2] - errors[-1]

### 結果をグラフにする
x = np.linspace(-2, 2, 100)
plt.plot(trainDataXSTD, trainDataY, 'o')
plt.plot(x, f(toMatrix(x)))
plt.show()

# 平均誤差をグラフにする
x = np.arange(len(errors))
plt.plot(x, errors)
plt.show()

平均誤差の推移は以下のようになりました。20回ほどのイテレーションでほぼ収束していることがわかります。
f:id:wshinya:20180325183703p:plain

機械学習基礎の基礎: 非線形fitting

前回の線形fittingでは、身長が低いところでfittingのずれが大きくなっていましたので、今回は2次関数でfittingすることにします。
同時に、入力データやパラメータをmatrixで扱うことでコードをシンプルにしています。
前回との差分は、fitting関数を
y = theta0 + theta1 * x + theta2 * x^2
に変更したことです。

具体的なコードは以下のようになります。
matrixを使ったことでコードがシンプルになりました。

###############
# 多項式回帰の実装
###############

import numpy as np
import matplotlib.pyplot as plt

### (1) データ読み込み ###
trainData = np.loadtxt('kodomoShinchouTaijyu.txt', delimiter=',', skiprows=0)

trainDataX = trainData[:, 0]
trainDataY = trainData[:, 1]

#plt.plot(trainDataX, trainDataY, 'o')
#plt.show()

### (2) データの標準化
mu = trainDataX.mean() # 平均
sigma = trainDataX.std() # 標準偏差

def standaradize(x):
	return (x - mu)/sigma

trainDataXSTD = standaradize(trainDataX)

# fitting 関数を2次関数にする
# y = theta0 + theta1 * x + theta2 * x^2

# 学習データをマトリクスとしてまとめる
# 2次関数でfittingするので2次まで
def toMatrix(x):
	return np.vstack([np.ones(x.shape[0]), x, x**2]).T

X = toMatrix(trainDataXSTD)
#print(X)
# パラメータの初期値
theta = np.random.rand(3)

### 線形fitting ###
# fitting関数 (線形回帰)
def f(x):
	return np.dot(x, theta)

# MSE関数(最小2乗の損失関数)
def E(x, y):
	return 0.5 * np.sum( (y - f(x)) ** 2)

### 学習 ###
ncount = 0 # 更新回数
eta = 1e-2 #学習率
diff = 1 # 誤差

# 学習する
error = E(X, trainDataY)
while diff > 1e-3: # 損失関数の変化が小さくなるまでループする
	# 勾配降下法の実行
	theta = theta - eta * np.dot(f(X) - trainDataY, X)[f:id:wshinya:20180325151001p:plain]
	# 1つ前のステップの誤差との差分
	currentError = E(X, trainDataY)
	diff = error - currentError
	error = currentError
	# 途中経過を出力
	ncount += 1
	#log = '{}: t0 = {:.3f} t1 = {:.3f}, diff={:.4f}'
	#print(log.format(ncount, t0, t1, diff) )


### 結果をグラフにする
x = np.linspace(-2, 2, 100)
plt.plot(trainDataXSTD, trainDataY, 'o')
plt.plot(x, f(toMatrix(x)))
plt.show()

実行結果は以下のようなグラフになりました。

f:id:wshinya:20180325151001p:plain
2次関数でfittingした結果
直線で近似した時よりもxが小さいところでよくfitしていることがわかります。

機械学習基礎の基礎: データ読み込みから線形fittingまで

機械学習の基本である線形回帰のコードを書いてみます。

流れの説明:
1) データファイルの読み込み:
データファイルはtextデータで
x0, y0
x1, y1
 ...

のように格納しています。

今回以下のような ダミーの(身長, 体重)データを使用しました.
85, 16.8
90.5, 17.2
100.0, 17.5
110.3, 18.9
116.5, 21.4
122.5, 27.2
128.2, 30.5
133.5, 34.2
139.0, 38.2
145.0, 44.0
152.8, 49.0
160.0, 53.9
165.3, 58.9
168.2, 60.6
169.9, 62.6

2) データの標準化
読み込んだデータをパラメータの収束を早めるために標準化します。
ここでは、データの平均をゼロ、標準偏差を1にするように標準化します。

3) 線形fittingの実行
x = 身長
y = 体重
として、
線形関数
y = t0 + t1 * x
でデータをfittingします。
具体的には、パラメータt0, t1をすこしずつ変化させながら、最小2乗誤差
E = 0.5 ( y_予想 - y_正解)^2
が最小になる場所を求めます。
Eが最小になる場所ではEのt0, t1の微分がゼロになることから、パラメータを変化させたときのEの変化が小さくなったらEのlocal minimumになったと判定して、パラメータ更新のループを終了させます。
パラメータの更新の方法は勾配降下法を採用します。これは、誤差関数をパラメータで微分してその点での傾きに応じて傾きの方向に徐々に勾配を下っていくという方法です。
パラメータと誤差の変化の途中経過は毎ループprintするようにします。

4) 結果をグラフに書く
最後に、元のデータとfitting結果をグラフに描画します

以上の内容を具体的にPythonのコードで書いたものが以下になります.

###############
# 線形回帰の実装
###############

import numpy as np
import matplotlib.pyplot as plt

### (1) データ読み込み ###
trainData = np.loadtxt('kodomoShinchouTaijyu.txt', delimiter=',', skiprows=0)

trainDataX = trainData[:, 0]
trainDataY = trainData[:, 1]

#plt.plot(trainDataX, trainDataY, 'o')
#plt.show()

### (2) データの標準化
mu = trainDataX.mean() # 平均
sigma = trainDataX.std() # 標準偏差

def standaradize(x):
	return (x - mu)/sigma

trainDataXSTD = standaradize(trainDataX)

#plt.plot(trainDataXSTD, trainDataY, 'o')
#plt.show()

### 線形fitting ###
# fitting関数 (線形回帰)
def f(x):
	return t0 + t1 * x

# MSE関数(最小2乗の損失関数)
def E(x, y):
	return 0.5 * np.sum( (y - f(x)) ** 2)

### 学習 ###
ncount = 0 # 更新回数
eta = 1e-2 #学習率
diff = 1 # 誤差

# パラメータの初期値
t0 = np.random.rand()
t1 = np.random.rand()

# 学習する
error = E(trainDataXSTD, trainDataY)
while diff > 1e-3: # 損失関数の変化が小さくなるまでループする
	# 勾配降下法の実行
	tmp0 = t0 - eta * np.sum( (f(trainDataXSTD) - trainDataY))
	tmp1 = t1 - eta * np.sum( (f(trainDataXSTD) - trainDataY) * trainDataXSTD)
	t0 = tmp0
        t1 = tmp1
       # 1つ前のステップの誤差との差分
	currentError = E(trainDataXSTD, trainDataY)
	diff = error - currentError
	error = currentError[f:id:wshinya:20180325141117p:plain]
	# 途中経過を出力
	ncount += 1
	log = '{}: t0 = {:.3f} t1 = {:.3f}, diff={:.4f}'
	print(log.format(ncount, t0, t1, diff) )


### 結果をグラフにする
x = np.linspace(-2, 2, 100)
plt.plot(trainDataXSTD, trainDataY, 'o')
plt.plot(x, f(x))
plt.show()

上記コードを実行すると、私の環境ですと48回のイテレーションで結果が収束し、以下のようなグラフが表示されました。

f:id:wshinya:20180325141117p:plain
線形回帰の結果

身長が小さい時にあえて線形からずれるようにデータを作りましたのでxが小さい時にだいぶずれていますね。
それから、横軸は標準化したままのデータを描画しています。

jupyterlabをインストールしてみた

jupyter notebookの次期バージョンとしてjupyterlabが予定されているらしいです。
以下の記事を読み、試してみたいと思いインストールしてみました。
http://www.publickey1.jp/blog/18/webidejupyterlabpythonrscala.html

インストール方法
jupyterlabのgithubに詳しいインストール方法が書いてありましたのでそれに従ってインストールしました。
インストール環境はWindows10 64bitでcondaを使用してインストールしました。

https://github.com/jupyterlab/jupyterlab

インストールする前に、jupyter notebook のバージョンを確認します。

jupyter notebook --version

Jupyter notebook のバージョンは4.3以上ならjupyterlabが使用できます。
ただしバージョンが5.3より低いときにはインストール後に以下のコマンドを実行しなければいけないようです。

jupyter serverextension enable --py jupyterlab --sys-prefix

バージョンの確認が終わりましたら、以下のコマンドでインストールします.

conda install -c conda-forge jupyterlab

すると必要なパッケージを自動でダウンロードしてくれます.
サイズが大きくてダウンロードするのにかなり時間がかかりましたが、正常にインストールができました.

コンソールで

jupyter lab

と打ち込むと実行できます。
起動にはそれほど時間もかからず、思ったよりサクサクと動作します.
先日書いたpython+opencvの顔認識のコードも問題なく動きました.
今後がたのしみです.

以下のサイトにインストール方法、使い方、特徴がまとめられています。
qiita.com

Python + OpenCV3で顔認識

先日, Python + OpenCV3の環境を構築しましたので, この環境でWeb camからの映像を使用して顔認識してみました.

初めにanacondaでopnecvの仮想環境をactivate して jupyter notebookを立ち上げます.
以下jupyter notebookのコードと解説を書きます.

顔認識用の特徴量ファイルの読み込み
まずは顔認識用特徴量ファイルを読み込み, opencvにセットします。
今回は、正面顔と目の位置を認識させたいと思いますので2種類の特徴量ファイルをセットします。
ファイルのパスは私のWindows Anaconda環境では以下のようになっていました。
("someone"の部分は自分のユーザーネームに置き換える)

import cv2 # OpenCV libraryのインポート
import numpy as np
# 特徴量ファイルのパスを指定する. 
FACE_CASCADE_FILE = "c:/Users/someone/Anaconda3/pkgs/opencv3-3.1.0-py35_0/Library/etc/haarcascades/haarcascade_frontalface_default.xml"
EYE_CASCADE_FILE = "c:/Users/someone/Anaconda3/pkgs/opencv3-3.1.0-py35_0/Library/etc/haarcascades/haarcascade_eye.xml"
# cascade classifierをセット
face_cascadeClassifier = cv2.CascadeClassifier(FACE_CASCADE_FILE)
eye_cascadeClassifier = cv2.CascadeClassifier(EYE_CASCADE_FILE)

Web cameraの準備と書き込みファイルの設定
Web から読み込んだ画像を認識させますので、Web camをオープンしておきます。
(1行目のcap = cv2.VideoCapture("moviefile/hoge.mp4")を使えばhoge.mp4を入力にすることもできます)
さらに、顔と目の部分を長方形で囲ってその結果の画像を動画でセーブするために書き込みの設定も行います。
書き込む動画のfpsは適当に10.0にしておきました。この値を30.0とするとセーブ後の動画が早送りになってしまいます。
おそらくメインループの回転速度が30.0fpsより遅いのが原因だと思われます。

# Web camera の準備
#cap = cv2.VideoCapture("moviefile/hoge.mp4")
cap = cv2.VideoCapture(0)
#cap.set(cv2.CAP_PROP_FPS, 30)
#fps = cap.get(cv2.CAP_PROP_FPS)
#print(fps)
fps = 10.0 #顔認識に時間がかかるので記録画像のframe rateは遅めにしておく
ret, frame = cap.read()
h, w, ch = frame.shape
# 動画の書き込みの設定を行う
fourcc = cv2.VideoWriter_fourcc(*"XVID") # codec はXVIDを使用
writeFrameDst = cv2.VideoWriter("output/faceDetect.avi", fourcc, fps, (w,h)) # codecはfourcc
  1. メインループ

メインループではループごとに動画から1フレーム抜き出して、その画像をグレー画像に変換後、顔と目を認識させます。
認識した顔と目の場所に赤と緑で枠を書いて、その結果を画面に表示するとともにファイルにセーブします。

while True:
    ret, frame = cap.read()
    if ret == False: # カメラからの映像がないとき
        break
    img = frame.astype(np.uint8) # 顔認識用に現在のフレームの画像をキャプチャ
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # グレー画像に変換
    faceArea = face_cascadeClassifier.detectMultiScale(img_gray) # 顔認識 faceArea = [x座標, y座標, xサイズ, yサイズ]
    #cv2.imshow("img", frame.astype(np.uint8)) # astype(np.uint8)は必要ないが念のため
    
    # 顔の領域に赤い四角を描く
    for x, y, w, h in faceArea:
        cv2.rectangle(img, (x, y), (x+w, y+h), (0,0, 255), 2)
    eyesArea = eye_cascadeClassifier.detectMultiScale(img_gray) # 目認識 eyesArea = [x座標, y座標, xサイズ, yサイズ]
    
    # 目の領域に緑色の四角を描く
    for (ex,ey,ew,eh) in eyesArea:
        cv2.rectangle(img, (ex,ey), (ex+ew,ey+eh), (0,255,0), 2)
    
    cv2.imshow("img", img) # 入力画像と顔エリアをマージした画像を表示
    writeFrameDst.write(img) # 動画1コマ書き込み
    
    if cv2.waitKey(1) == 27:
        break

終了処理
メインループでESCキーが押されたら、書き込みファイルとWeb cameraの終了処理を行い、開いているwindowを閉じます.

cap.release()
writeFrameDst.release()
cv2.destroyAllWindows()

目の認識の精度があまり高くないようで、いろんな場所を誤認識してしまいます.
コードの途中でエラーが出て、修正後再実行するときにはjupyter notebookのカーネルをリスタートしないとWeb cameraがオープンされたままになっているので再びプログラムを起動したときにエラーがでますのでご注意ください。
顔認識に関しては以下のブログを参考にしました。
qiita.com

数理統計の勉強のための資料

機械学習ビッグデータを勉強していると根本的な数理統計の知識が足りないことに気づきました。
数理統計の勉強のための参考書をリストアップします。

「Introduction to Mathematical Statistics」

Introduction to Mathematical Statistics: Pearson New International Edition

Introduction to Mathematical Statistics: Pearson New International Edition

  • 作者: Robert V. Hogg,Joseph W. McKean,Allen T. Craig
  • 出版社/メーカー: Pearson Education Limited
  • 発売日: 2013/07/26
  • メディア: ペーパーバック
  • この商品を含むブログを見る

「 現代数理統計学の基礎」

現代数理統計学の基礎 (共立講座 数学の魅力)

現代数理統計学の基礎 (共立講座 数学の魅力)

「数理統計学―基礎から学ぶデータ解析」

数理統計学―基礎から学ぶデータ解析

数理統計学―基礎から学ぶデータ解析

「数理統計学 (数学シリーズ)」

数理統計学 (数学シリーズ)

数理統計学 (数学シリーズ)

「 入門・演習 数理統計」

入門・演習 数理統計

入門・演習 数理統計


「数理統計学の基礎」
数理統計学の基礎

数理統計学の基礎

「現代数理統計学

現代数理統計学 (創文社現代経済学選書)

現代数理統計学 (創文社現代経済学選書)

数理統計とは違いますが絶版になった統計学の本が電子化されてPDFで読めるようになっています.
「統計科学のための電子図書館」というサイトです.
統計科学のための電子図書システム