pandas

pandasの基本操作を一通り試してみます。
まずは単純にpandasでcsvファイルを読み込んでみます。
[csvファイル]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
date,station,temp
2018/9/1,札幌,19.5
2018/9/1,青森,21.5
2018/9/1,仙台,21.5
2018/9/1,東京,25.9
2018/9/1,福岡,25.5
2018/9/1,鹿児島,26.8
2018/9/1,那覇,27.6
2018/9/1,波照間,27.3
2018/9/1,秋田,22.2
2018/9/1,盛岡,22.0
2018/9/1,山形,20.8
2018/9/1,福島,21.9
2018/9/1,水戸,23.7
2018/9/1,千葉,27.5
2018/9/1,宇都宮,23.2
2018/9/1,さいたま,24.8
(以下略)

[コード]

1
2
3
4
5
# pandasでcsvファイルを読み込む
import pandas

df = pandas.read_csv('data.csv', encoding="Shift_JIS")
df

[結果]
結果
print関数を使わずJupyter上でdataframe型の変数を表示するといい感じのテーブルで表示してくれます。

dataframe型データの先頭数行を表示する場合はhead関数を使います。
(引数にデータ数を指定することもできます。)

1
df.head()

[結果]
結果
末尾数行を表示する場合はtail関数を使います。
(引数にデータ数を指定することもできます。)

1
df.tail()

[結果]
結果

次に変数dfをインデックス指定で分割します。
範囲指定の数字はデータ自体の順番を考えるよりもデータとデータの間の順番と考えるとわかりやすいと思います。

1
2
df_sliced = df.iloc[2:7]
df_sliced

[結果]
結果

分割したデータをさらに分割することもできます。

1
df_sliced.iloc[2:4]

[結果]
結果

今度は行ごとに指定ではなく、列ごとに指定します。
列を指定する場合は、単純に列名(カラム名)を使用します。

1
df['temp']

[結果]
結果
カラム名で指定したデータはシリーズデータになっていて最大値、最小値、合計、平均を簡単に取得することができます。

1
2
3
4
print('最大値', df['temp'].max())
print('最小値', df['temp'].min())
print('合計', df['temp'].sum())
print('平均', df['temp'].mean())

[結果]
結果
また欠損値がある場合はfillna関数を使って補完することができます。
下記は平均値で補完するサンプルコードとなります。

1
2
m = df['temp'].mean()
df['temp'] = df['temp'].fillna(m)

(Google Colaboratoryで動作確認しています。)

画像OCR(連続文字の認識)

数字がどこに書かれているかを見つける処理を実装してみます。
まずは数字が10個表示されている下記の画像を入力ファイルとします。
10個の数字(numbers.png)

実装する処理は下記の通りです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import sys
import numpy as np
import cv2

# 画像の読み込み
im = cv2.imread('numbers.png')
# グレイスケールに変換しぼかした上で二値化する
gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (5, 5), 0)
thresh = cv2.adaptiveThreshold(blur, 255, 1, 1, 11, 2)

# 輪郭を抽出
contours = cv2.findContours(
thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)[1]

# 抽出した領域を繰り返し処理する
for cnt in contours:
x, y, w, h = cv2.boundingRect(cnt)
if h < 20: # 小さすぎるのは飛ばす
continue
red = (0, 0, 255)
cv2.rectangle(im, (x, y), (x+w, y+h), red, 2)

cv2.imwrite('result1.png', im)

結果1(result1.png)
問題なく見つけた数字を赤枠で囲むことができています。

次に100個の数字が表示されている画像を入力にしてみます。
6行目の入力ファイル名を変更するだけです。
100個の数字(numbers100.png)

結果2
結果は上記のようになり数字の中まで赤枠で囲ってしまっています。
これを改善するためには、cv2.RETR_LISTというパラメータをcv2.RETR_EXTERNAL(15行目)に変更します。
このパラメータは領域の一番外側だけを検出するという意味になります。
下記に修正したソースの全体を記載しておきます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import sys
import numpy as np
import cv2

# 画像の読み込み
im = cv2.imread('numbers100.png')
# グレイスケールに変換しぼかした上で二値化する
gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (5, 5), 0)
thresh = cv2.adaptiveThreshold(blur, 255, 1, 1, 11, 2)

# 輪郭を抽出
contours = cv2.findContours(
thresh,
cv2.RETR_EXTERNAL, # ☆☆☆領域の一番外側だけを検出☆☆☆
cv2.CHAIN_APPROX_SIMPLE)[1]

# 抽出した領域を繰り返し処理する
for cnt in contours:
x, y, w, h = cv2.boundingRect(cnt)
if h < 20: # 小さすぎるのは飛ばす
continue
cv2.rectangle(im, (x, y), (x+w, y+h), (0,0,255), 2)
cv2.imwrite('result3.png', im)

結果3(result3.png)

今度は適切に全ての数字を認識することができました。

(Google Colaboratoryで動作確認しています。)

OpenCVで顔認識

OpenCVを使って顔認識を試してみます。
まずは、画像に写っている顔の範囲を調べます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import cv2
import sys

# 入力ファイルを指定する
image_file = "./face1.jpg"

# カスケードファイルのパスを指定 ---OpenCVをインストールするとshareディレクりに格納される
cascade_file = "haarcascade_frontalface_alt.xml"

# 画像の読み込み
image = cv2.imread(image_file)
# グレースケールに変換
image_gs = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# 顔認識用特徴量ファイルを読み込む
cascade = cv2.CascadeClassifier(cascade_file)
# 顔認識の実行
face_list = cascade.detectMultiScale(image_gs,
scaleFactor=1.1,
minNeighbors=1,
minSize=(150,150)) # 顔認識の最小範囲(これ以下は無視)

if len(face_list) > 0:
# 認識した部分を囲む
print(face_list)
color = (0, 0, 255)
for face in face_list:
x,y,w,h = face
cv2.rectangle(image, (x,y), (x+w, y+h), color, thickness=8)
# 描画結果をファイルに書き込む
cv2.imwrite("facedetect-output.png", image)
else:
print("no face")

入力ファイル
顔認識結果

正常に顔の範囲が認識できました。
次に顔にモザイクをかけてみます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import cv2, sys, re

# 入力ファイル
image_file = "./face1.jpg"

# 出力ファイル名
output_file = re.sub(r'\.jpg|jpeg|png$', '-mosaic.jpg', image_file)
mosaic_rate = 30

# カスケードファイルのパスを指定
cascade_file = "haarcascade_frontalface_alt.xml"

# 画像の読み込み
image = cv2.imread(image_file)
image_gs = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # グレイスケール変換

# 顔認識を実行
cascade = cv2.CascadeClassifier(cascade_file)
face_list = cascade.detectMultiScale(image_gs,
scaleFactor=1.1,
minNeighbors=1,
minSize=(100,100))

if len(face_list) == 0:
print("no face")
quit()

# 認識した部分にモザイクをかける
print(face_list)
color = (0, 0, 255)
for (x,y,w,h) in face_list:
# 顔を切り抜く
face_img = image[y:y+h, x:x+w]
# 切り抜いた画像を指定倍率で縮小
face_img = cv2.resize(face_img, (w//mosaic_rate, h//mosaic_rate))
# 縮小した画像を元のサイズに戻す
face_img = cv2.resize(face_img, (w, h),
interpolation=cv2.INTER_AREA)
# 元の画像に貼り付ける
image[y:y+h, x:x+w] = face_img
# 描画結果をファイルに書き込む
cv2.imwrite(output_file, image)

モザイク結果

(Google Colaboratoryで動作確認しています。)

ニューラルネットワーク(3)

前回使用したテストデータは10種ですが、次にもっと大きなデータを使ってテストするときのために正解率を表示できるようにしておきます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 結果判定リスト
score = []
for data in test_data:
val = data.split(',')
answer = int(val[0])
res = n_network.query((numpy.asfarray(val[1:]) / 255.0 * 0.99) + 0.01)
# 最大値のものを算出した答えとする
res_max = numpy.argmax(res)
print('正解', answer, '算出した答え', res_max, '=&gt;', '〇' if answer == res_max else '×')

score.append(1 if answer == res_max else 0)

# 正解率
print('# 正解率 # {:5.2f}%'.format(sum(score) / len(score) * 100))

【結果】
結果

現状7割の正解率ですが学習データを増やしたり学習回数、学習率を調整してみます。

これまでは学習データ100個、テストデータ10個で簡単に動作確認してきましたが、今回は学習データ60,000個、テストデータ10,000個を使ってどのくらい正確に手書き文字を認識するかテストします。
おさらいとして、自作した完成版のニューラルネットワークを確認します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import numpy
import scipy.special
# ニューラルネットワーク(完成版)
class neural_network:
# 【初期化】
# 入力層、隠れ層、出力層のノード数を設定する。
def __init__(self, in_node, hid_node, out_node, learn_rate):
self.in_node = in_node # 入力層
self.hid_node = hid_node # 隠れ層
self.out_node = out_node # 出力層
self.learn_rate = learn_rate # 学習率

# 重み行列(処理の核となる)
# 正規分布の平均、標準偏差、配列の大きさを設定
self.weight_in_hid = numpy.random.normal(0.0, pow(self.hid_node, -0.5), (self.hid_node, self.in_node))
self.weight_hid_out = numpy.random.normal(0.0, pow(self.out_node, -0.5), (self.out_node, self.hid_node))

# 活性化関数はシグモイド関数
self.activation_func = lambda x: scipy.special.expit(x)

# 【学習】
# 学習データから重みを調整する。
def train(self, in_list, target_list):
# 入力データ(1次元)を2次元化して転置をとる。
#(横長の配列が縦長になる)
in_matrix = numpy.array(in_list, ndmin=2).T
target_matrix = numpy.array(target_list, ndmin=2).T
# -------------- 重みをかけて発火させる --------------
# 入力層→隠れ層の計算
hid_in = numpy.dot(self.weight_in_hid, in_matrix)
hid_out = self.activation_func(hid_in)

# 隠れ層→出力層の計算
final_in = numpy.dot(self.weight_hid_out, hid_out)
final_out = self.activation_func(final_in)
# -------------- 誤差の計算 --------------
# 出力層の誤差(目標出力 - 最終出力)
out_err = target_matrix - final_out
# 隠れ層の誤差は出力層の誤差をリンクの重みの割合で分配
hid_err = numpy.dot(self.weight_hid_out.T, out_err)
# -------------- 重みの更新(処理の核) --------------
# 隠れ層と出力層の間のリンクの重みを更新
self.weight_hid_out += self.learn_rate * numpy.dot((out_err * final_out * (1.0 - final_out)), numpy.transpose(hid_out))

# 入力層と隠れ層の間のリンクの重みを更新
self.weight_in_hid += self.learn_rate * numpy.dot((hid_err * hid_out * (1.0 - hid_out)), numpy.transpose(in_matrix))

# 【照会】
# 入力に対して出力層からの答えを返す。
def query(self, input_list):
# 入力リストを行列に変換
# 1次元配列は2次元配列に変換し転置をとる。
#(横長の配列が縦長になる)
in_matrix = numpy.array(input_list, ndmin=2).T

# 隠れ層に入ってくる信号の計算(入力層に重みをかける)
hid_in = numpy.dot(self.weight_in_hid, in_matrix)
# 隠れ層で結合された信号を活性化関数(シグモイド関数)により出力
# (閾値を超えたものが発火する)
hid_out = self.activation_func(hid_in)

# 出力層に入ってくる信号の計算(隠れ層に重みをかける)
final_in = numpy.dot(self.weight_hid_out, hid_out)
# 出力層で結合された信号を活性化関数(シグモイド関数)により出力
# (閾値を超えたものが発火する)
final_out = self.activation_func(final_in)

return final_out

次に今回使用する学習データ60,000個とテストデータ10,000個をダウンロードしておきます。

1
2
!wget https://www.pjreddie.com/media/files/mnist_train.csv
!wget https://www.pjreddie.com/media/files/mnist_test.csv

60,000個の学習データを学習率0.2、学習回数(エポック)1回で学習させます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 60,000個のデータを学習
import numpy
import matplotlib.pyplot
%matplotlib inline

in_node = 784 # 入力層のノード数(28 * 28)
hid_node = 200 # 隠れ層のノード数
out_node = 10 # 出力層のノード数(0~9を表す)

learn_rate = 0.2 # 学習率

# ニューラルネットワークのインスタンス生成
n_network = neural_network(in_node, hid_node, out_node, learn_rate)

# 学習データファイルを読み込んでリスト化
with open('mnist_train.csv', 'r') as f:
train_data = f.readlines()

epochs = 1 # 学習回数
for e in range(epochs):
# 学習データすべてに対して実行
for record in train_data:
val = record.split(',')
# 入力値のスケールとシフト
in_data = (numpy.asfarray(val[1:]) / 255.0 * 0.99) + 0.01
# 目標配列の生成(ラベル位置0.99、残り0.01)
target = numpy.zeros(out_node) + 0.01
target[int(val[0])] = 0.99
n_network.train(in_data, target)

10,000個のテストデータで正解率を算出します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 10,000個のテストデータで正解率を算出
with open('mnist_test.csv', 'r') as f:
test_data = f.readlines()

# 結果判定リスト
score = []
for data in test_data:
val = data.split(',')
answer = int(val[0])
res = n_network.query((numpy.asfarray(val[1:]) / 255.0 * 0.99) + 0.01)
# 最大値のものを算出した答えとする
res_max = numpy.argmax(res)

score.append(1 if answer == res_max else 0)

# 正解率
print('# 正解率 # {:5.2f}%'.format(sum(score) / len(score) * 100))

【結果】

1
# 正解率 # 95.38%

正解率をあげるため学習率や学習回数(エポック)を調整しようと考えていたのですが、もうすでに正解率95%以上と十分な正解率(認識率)となっています。
次に正解率がどう変動するのかを確認したいと思います。

学習処理と検証処理を関数化します。引数に学習率を設定すると、その学習率での正解率が返ります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import numpy

# 学習率を変えて、学習・検証ができるように関数化する。
def train_test(learn_rate):
in_node = 784 # 入力層のノード数(28 * 28)
hid_node = 200 # 隠れ層のノード数
out_node = 10 # 出力層のノード数(0~9を表す)

# ニューラルネットワークのインスタンス生成
n_network = neural_network(in_node, hid_node, out_node, learn_rate)

# 学習データファイルを読み込んでリスト化
with open('mnist_train.csv', 'r') as f:
train_data = f.readlines()

epochs = 1 # 学習回数
for e in range(epochs):
# 学習データすべてに対して実行
for record in train_data:
val = record.split(',')
# 入力値のスケールとシフト
in_data = (numpy.asfarray(val[1:]) / 255.0 * 0.99) + 0.01
# 目標配列の生成(ラベル位置0.99、残り0.01)
target = numpy.zeros(out_node) + 0.01
target[int(val[0])] = 0.99
n_network.train(in_data, target)

# 10,000個のテストデータで正解率を算出
with open('mnist_test.csv', 'r') as f:
test_data = f.readlines()

# 結果判定リスト
score = []
for data in test_data:
val = data.split(',')
answer = int(val[0])
res = n_network.query((numpy.asfarray(val[1:]) / 255.0 * 0.99) + 0.01)
# 最大値のものを算出した答えとする
res_max = numpy.argmax(res)
score.append(1 if answer == res_max else 0)

# 正解率
return sum(score) / len(score) * 100

上記で定義した関数を学習率を変化させながら実行し、その結果を折れ線グラフに表示します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
%matplotlib inline
import matplotlib.pyplot as plt

x_data = []
y_data = []
# 0.1から0.9までの学習率で、正解率を算出する。
for i in numpy.arange(0.1, 1, 0.1):
x_data.append(i)
y_data.append(train_test(i))

plt.xlabel("learn_rate") # 学習率
plt.ylabel("accuracy_rate(%)") # 正解率

plt.plot(x_data, y_data, marker='o')
plt.show()

X軸に学習率、Y軸に正解率が表示されます。
結果

上記の結果から学習率が0.1の場合が一番正解率が高いことがわかりました。
次回は学習率を0.1に固定し、学習回数(エポック)を変化させると正解率がどのように変化するかを調べてみます。

さきほど関数化したものの引数に学習回数を追加します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import numpy

# 学習率と学習回数を変えて、学習、検証ができるように関数化する。
def train_test(learn_rate, epochs):
in_node = 784 # 入力層のノード数(28 * 28)
hid_node = 200 # 隠れ層のノード数
out_node = 10 # 出力層のノード数(0~9を表す)

# ニューラルネットワークのインスタンス生成
n_network = neural_network(in_node, hid_node, out_node, learn_rate)

# 学習データファイルを読み込んでリスト化
with open('mnist_train.csv', 'r') as f:
train_data = f.readlines()

for e in range(epochs):
# 学習データすべてに対して実行
for record in train_data:
val = record.split(',')
# 入力値のスケールとシフト
in_data = (numpy.asfarray(val[1:]) / 255.0 * 0.99) + 0.01
# 目標配列の生成(ラベル位置0.99、残り0.01)
target = numpy.zeros(out_node) + 0.01
target[int(val[0])] = 0.99
n_network.train(in_data, target)

# 10,000個のテストデータで正解率を算出
with open('mnist_test.csv', 'r') as f:
test_data = f.readlines()

# 結果判定リスト
score = []
for data in test_data:
val = data.split(',')
answer = int(val[0])
res = n_network.query((numpy.asfarray(val[1:]) / 255.0 * 0.99) + 0.01)
# 最大値のものを算出した答えとする
res_max = numpy.argmax(res)
#print('正解', answer, '算出した答え', res_max, '=&gt;', '〇' if answer == res_max else '×')

score.append(1 if answer == res_max else 0)

# 正解率
#print('# 正解率 # {:5.2f}%'.format(sum(score) / len(score) * 100))
return sum(score) / len(score) * 100

上記で定義した関数を学習回数を変化させながら実行し、その結果を折れ線グラフに表示します。
(学習率は前回もっとも結果のよかった0.1を指定します。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
%matplotlib inline
import matplotlib.pyplot as plt

x_data = []
y_data = []
# 0.1から0.9までの学習率(で、正解率を算出する。
for i in numpy.arange(0.1, 1, 0.1):
x_data.append(i)
y_data.append(train_test(i))

# データをグラフに設定
plt.xlabel("learn_rate") # 学習率
plt.ylabel("accuracy_rate(%)") # 正解率

plt.plot(x_data, y_data, marker='o')
plt.show()

X軸に学習回数、Y軸に正解率が表示されます。
結果

もともと学習回数が1回でも正解率95.75%となかなかの精度がでているのですが、学習回数をふやすとやや結果がよくなっていっているのがわかります。
ただ2%以内の増減なので、処理時間がすごく増える割には効果があるとは思えませんでした。
次回は学習率を0.1、学習回数を5回に固定し、隠れ層の数を変化させると正解率がどのように変化するかを調べてみます。

関数化したものの引数に隠れ層の数を追加します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import numpy

# 学習率と学習回数を変えて、学習、検証ができるように関数化する。
def train_test(learn_rate, epochs, hid_node):
in_node = 784 # 入力層のノード数(28 * 28)
#hid_node = 200 # 隠れ層のノード数
out_node = 10 # 出力層のノード数(0~9を表す)

# ニューラルネットワークのインスタンス生成
n_network = neural_network(in_node, hid_node, out_node, learn_rate)

# 学習データファイルを読み込んでリスト化
with open('mnist_train.csv', 'r') as f:
train_data = f.readlines()

for e in range(epochs):
# 学習データすべてに対して実行
for record in train_data:
val = record.split(',')
# 入力値のスケールとシフト
in_data = (numpy.asfarray(val[1:]) / 255.0 * 0.99) + 0.01
# 目標配列の生成(ラベル位置0.99、残り0.01)
target = numpy.zeros(out_node) + 0.01
target[int(val[0])] = 0.99
n_network.train(in_data, target)

# 10,000個のテストデータで正解率を算出
with open('mnist_test.csv', 'r') as f:
test_data = f.readlines()

# 結果判定リスト
score = []
for data in test_data:
val = data.split(',')
answer = int(val[0])
res = n_network.query((numpy.asfarray(val[1:]) / 255.0 * 0.99) + 0.01)
# 最大値のものを算出した答えとする
res_max = numpy.argmax(res)
#print('正解', answer, '算出した答え', res_max, '=&gt;', '〇' if answer == res_max else '×')

score.append(1 if answer == res_max else 0)

# 正解率
#print('# 正解率 # {:5.2f}%'.format(sum(score) / len(score) * 100))
return sum(score) / len(score) * 100

上記で定義した関数を隠れ層を変化させながら実行し、その結果を折れ線グラフに表示します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 隠れ層と正解率の関係
# 引数に隠れ層数追加 0.1 5 隠れ層 100~700
# 隠れ層をX軸に、正解率をY軸にした折れ線グラフを作成
%matplotlib inline
import matplotlib.pyplot as plt

x_data = []
y_data = []
# 0.1から0.9までの学習率(で、正解率を算出する。
for i in range(100, 701, 100):
x_data.append(i)
y_data.append(train_test(0.1, 5, i))

# データをグラフに設定
plt.xlabel("hidden layer") # 学習回数
plt.ylabel("accuracy_rate(%)") # 正解率

plt.plot(x_data, y_data, marker='o')
plt.show()

X軸に隠れ層の数、Y軸に正解率が表示されます。
結果
100層から200層で1%弱の情報がありますがそれ以降はあまり変化がありません。
これまでに、学習率・学習回数・隠れ層の数と正解率の関係を見てきましたがもともとの正解率が高かったこともありほんとに微調整といった感じです。
このあたりのパラメータ調整は、正解率が低い場合には調整する意味合いが大きくなるかと思います。

(Google Colaboratoryで動作確認しています。)

ニューラルネットワーク(2)

学習メソッドtrainを実装します。前回実装した照会メソッドqueryと似ています。
重みをかけて発火させたあとに目標出力との誤差を算出しそれを学習率に応じて重みに反映する・・・これがニューラルネットワークの最重要ポイントかと思われます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import numpy
import scipy.special

class neural_network:
# 【初期化】
# 入力層、隠れ層、出力層のノード数を設定する。
def __init__(self, in_node, hid_node, out_node, learn_rate):
self.in_node = in_node # 入力層
self.hid_node = hid_node # 隠れ層
self.out_node = out_node # 出力層
self.learn_rate = learn_rate # 学習率

# 重み行列(処理の核となる)
# 正規分布の平均、標準偏差、配列の大きさを設定
self.weight_in_hid = numpy.random.normal(0.0, pow(self.hid_node, -0.5), (self.hid_node, self.in_node))
''' ↓こんな感じの配列ができる
[[ 0.37395332 0.07296579 0.36696637]
[-0.1570748 0.28908756 0.99958053]
[-0.09054778 -0.20084478 0.31981826]]
'''
self.weight_hid_out = numpy.random.normal(0.0, pow(self.out_node, -0.5), (self.out_node, self.hid_node))
''' ↓こんな感じの配列ができる
[[ 0.93304259 0.02641947 0.29506316]
[-0.74275445 0.9010841 -0.47840667]
[ 0.04494529 0.49177323 1.13985481]]
'''

# 活性化関数はシグモイド関数
self.activation_func = lambda x: scipy.special.expit(x)

# 【学習】
# 学習データから重みを調整する。
def train(self, in_list, target_list):
# 入力データ(1次元)を2次元化して転置をとる。
#(横長の配列が縦長になる)
in_matrix = numpy.array(in_list, ndmin=2).T
target_matrix = numpy.array(target_list, ndmin=2).T
# -------------- 重みをかけて発火させる --------------
# 入力層→隠れ層の計算
hid_in = numpy.dot(self.weight_in_hid, in_matrix)
hid_out = self.activation_func(hid_in)

# 隠れ層→出力層の計算
final_in = numpy.dot(self.weight_hid_out, hid_out)
final_out = self.activation_func(final_in)
# -------------- 誤差の計算 --------------
# 出力層の誤差(目標出力 - 最終出力)
out_err = target_matrix - final_out
# 隠れ層の誤差は出力層の誤差をリンクの重みの割合で分配
hid_err = numpy.dot(self.weight_hid_out.T, out_err)
# -------------- 重みの更新(処理の核) --------------
# 隠れ層と出力層の間のリンクの重みを更新
self.weight_hid_out += self.learn_rate * numpy.dot((out_err * final_out * (1.0 - final_out)), numpy.transpose(hid_out))

# 入力層と隠れ層の間のリンクの重みを更新
self.weight_in_hid += self.learn_rate * numpy.dot((hid_err * hid_out * (1.0 - hid_out)), numpy.transpose(in_matrix))

# 【照会】
# 入力に対して出力層からの答えを返す。
def query(self, input_list):
# 入力リストを行列に変換
# 1次元配列は2次元配列に変換し転置をとる。
#(横長の配列が縦長になる)
in_matrix = numpy.array(input_list, ndmin=2).T

# 隠れ層に入ってくる信号の計算(入力層に重みをかける)
hid_in = numpy.dot(self.weight_in_hid, in_matrix)
# 隠れ層で結合された信号を活性化関数(シグモイド関数)により出力
# (閾値を超えたものが発火する)
hid_out = self.activation_func(hid_in)

# 出力層に入ってくる信号の計算(隠れ層に重みをかける)
final_in = numpy.dot(self.weight_hid_out, hid_out)
# 出力層で結合された信号を活性化関数(シグモイド関数)により出力
# (閾値を超えたものが発火する)
final_out = self.activation_func(final_in)

return final_out

これで自作ニューラルネットワークが完成しました。
このクラスを使ってMNISTデータの判定を行ってみます。

まず、学習データ(100種類)とテストデータ(10種類)をダウンロードします。

1
2
!wget https://raw.githubusercontent.com/makeyourownneuralnetwork/makeyourownneuralnetwork/master/mnist_dataset/mnist_test_10.csv
!wget https://raw.githubusercontent.com/makeyourownneuralnetwork/makeyourownneuralnetwork/master/mnist_dataset/mnist_train_100.csv</pre>

データを読み込んで、どんなデータか表示してみます。

1
2
3
4
5
6
7
8
9
10
import numpy
import matplotlib.pyplot
%matplotlib inline

with open('mnist_train_100.csv', 'r') as f:
data_list = f.readlines()

val = data_list[7].split(',') # データを選ぶ(0-99の間)
img = numpy.asfarray(val[1:]).reshape((28, 28))
matplotlib.pyplot.imshow(img, cmap='Greys', interpolation='None')

結果

データを学習し、テストデータの1つを選んでどの数字と合致する確率が高いかを表示します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import numpy
import matplotlib.pyplot
%matplotlib inline

in_node = 784 # 入力層のノード数(28 * 28)
hid_node = 200 # 隠れ層のノード数
out_node = 10 # 出力層のノード数(0~9を表す)

learn_rate = 0.2 # 学習率

# ニューラルネットワークのインスタンス生成
n_network = neural_network(in_node, hid_node, out_node, learn_rate)

# 学習データファイルを読み込んでリスト化
with open('mnist_train_100.csv', 'r') as f:
train_data = f.readlines()

# 全学習データを10回学習
epochs = 10 # 学習回数
for e in range(epochs):
# 学習データすべてに対して実行
for record in train_data:
val = record.split(',')
# 入力値のスケールとシフト
in_data = (numpy.asfarray(val[1:]) / 255.0 * 0.99) + 0.01
# 目標配列の生成(ラベル位置0.99、残り0.01)
target = numpy.zeros(out_node) + 0.01
target[int(val[0])] = 0.99
n_network.train(in_data, target)

# テストデータファイルを読み込んでリスト化
with open('mnist_test_10.csv', 'r') as f:
test_data = f.readlines()

val = test_data[0].split(',') # テストデータを選択(0-9の間)
img = numpy.asfarray(val[1:]).reshape((28, 28))
matplotlib.pyplot.imshow(img, cmap='Greys', interpolation='None')

# 選択したデータをニューラルネットワークで照会
res = n_network.query((numpy.asfarray(val[1:]) / 255.0 * 0.99) + 0.01)
for a,b in enumerate(res):
print('{}の可能性 {:5.2f}%'.format(a,b[0] * 100))

結果

今回は[7]の合致率が97%以上ときちんと認識しているようです。
10種類あるテストデータをすべて試してみましたが、なかなかよい結果がでてました。
ただ人間でもよくわからないデータ(手書き数字)だとはっきり認識するのは難しいようです。(あたり前か・・・)

隠れ層のノード数、学習率、学習回数の範囲をいろいろ試してみて認識率がどうかわるかを試すのもよいかと思います。

(Google Colaboratoryで動作確認しています。)

ニューラルネットワーク(1)

ニューラルネットワークを試してみます。
まずは、基本メソッド、各層の数と学習率をもつニューラルネットワーククラスを定義します。
中身はなにもありませんが、これがニューラルネットワーク・クラスの骨格となります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# ニューラルネットワークを表すクラス
class neural_network:
# 【初期化】
# 入力層、隠れ層、出力層のノード数を設定する。
def __init__(self, in_node, hid_node, out_node, learn_rate):
self.in_node = in_node # 入力層
self.hid_node = hid_node # 隠れ層
self.out_node = out_node # 出力層
self.learn_rate = learn_rate # 学習率

# 【学習】
# 学習データから重みを調整する。
def train():
pass

# 【照会】
# 入力に対して出力層からの答えを返す。
def query():
pass

このクラスに少しずつ実装していきます。

初期化のところで重みを生成し、活性化関数にシグモイド関数を設定してます。
また照会メソッドで、入力に対し入力・隠れ層の重みをかけシグモイド関数で発火させます。
さらにその結果に隠れ・出力層の重みをかけシグモイド関数で発火させ、その結果を返しています。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import numpy
import scipy.special
# Pythonでニューラルネットワーク(3)
class neural_network:
# 【初期化】
# 入力層、隠れ層、出力層のノード数を設定する。
def __init__(self, in_node, hid_node, out_node, learn_rate):
self.in_node = in_node # 入力層
self.hid_node = hid_node # 隠れ層
self.out_node = out_node # 出力層
self.learn_rate = learn_rate # 学習率

# 重み行列(処理の核となる)
# 正規分布の平均、標準偏差、配列の大きさを設定
self.weight_in_hid = numpy.random.normal(0.0, pow(self.hid_node, -0.5), (self.hid_node, self.in_node))
''' ↓こんな感じの配列ができる
[[ 0.37395332 0.07296579 0.36696637]
[-0.1570748 0.28908756 0.99958053]
[-0.09054778 -0.20084478 0.31981826]]
'''
self.weight_hid_out = numpy.random.normal(0.0, pow(self.out_node, -0.5), (self.out_node, self.hid_node))
''' ↓こんな感じの配列ができる
[[ 0.93304259 0.02641947 0.29506316]
[-0.74275445 0.9010841 -0.47840667]
[ 0.04494529 0.49177323 1.13985481]]
'''

# 活性化関数はシグモイド関数
self.activation_func = lambda x: scipy.special.expit(x)

# 【学習】
# 学習データから重みを調整する。
def train():
pass

# 【照会】
# 入力に対して出力層からの答えを返す。
def query(self, input_list):
# 入力リストを行列に変換
# 1次元配列は2次元配列に変換し転置をとる。
#(横長の配列が縦長になる)
in_matrix = numpy.array(input_list, ndmin=2).T

# 隠れ層に入ってくる信号の計算(入力層に重みをかける)
hid_in = numpy.dot(self.weight_in_hid, in_matrix)
# 隠れ層で結合された信号を活性化関数(シグモイド関数)により出力
# (閾値を超えたものが発火する)
hid_out = self.activation_func(hid_in)

# 出力層に入ってくる信号の計算(隠れ層に重みをかける)
final_in = numpy.dot(self.weight_hid_out, hid_out)
# 出力層で結合された信号を活性化関数(シグモイド関数)により出力
# (閾値を超えたものが発火する)
final_out = self.activation_func(final_in)

return final_out

上記で定義したクラスが問題なく動くか、テストするコードを簡単に書いて実行してみます。

1
2
3
4
5
6
7
8
9
10
11
in_node = 3       # 入力層のノード数
hid_node = 3 # 隠れ層のノード数
out_node = 3 # 出力層のノード数

learn_rate = 0.3 # 学習率

# ニューラルネットワークのインスタンス生成
n_network = neural_network(in_node, hid_node, out_node, learn_rate)

# まだ学習してないけど照会してみる
n_network.query([1.0, 0.5, -1.5])

【結果】

1
2
3
array([[0.55799198],
[0.51430992],
[0.30202192]])

まだ学習するメソッドを実装していないので意味のない出力ではありますが、エラーなしで動作することを確認できました。
次回は学習メソッドを実装していきます。

(Google Colaboratoryで動作確認しています。)

機械学習に関する用語集

機械学習に関する用語

用語 英語 説明
教師あり学習 Supervised Learning 正解となるデータをもとに機械学習を行う手法。データの分類や数値の予測などに使用する。
教師なし学習 Unsupervised Learning 正解が用意されていないデータに対して行う手法。データのクラスタリングなどに使用する。
強化学習 Reinforcement Learning ある環境の中での行動に対して報酬を与えて学習させる手法。ゲームや自動運転などにおいて、振る舞いを最適化するために使用する。
分類 Classification 教師あり学習でデータがどのグループに属するか(ラベル)を予測すること。
回帰 Rgression 教師あり学習でデータに対して数値を予測すること。
クラスタリング Clustering 教師なし学習で、似ているデータをグループ化すること。分類とは異なり正解が存在しない。
アルゴリズム Algorithm 機械学習ではそれぞれの機械学習を行うための手順のことを指す。主要なアルゴリズムはscikit-learnで用意されている。
アンサンブル学習 Ensemble Learning 複数のモデルの結果を組み合わせて多数決などで決定する手法。
ラベル Label 分類で、データの正解を表す値。
モデル Model 機械学習アルゴリズムが作成した予測を行うためのパラメータの集まり。予測プログラムで使用する。

教師あり学習の主な手法

用語 英語 説明
線形回帰 Linear Regression 回帰に使用するアルゴリズムの1つ。
ロジスティック回帰 Logistic Regression アルゴリズムの名前には回帰が付いているが、主に分類に使用するアルゴリズム。
サポートベクターマシン Support Vector Machine : SVM 分類、回帰に使用できるアルゴリズム。
決定木 Decision Tree データを分類するルールを定義して分類を行うアルゴリズム。
ランダムフォレスト Random Forest 複数の決定木の予測結果から、多数決で予測を行うアルゴリズム。アンサンブル学習の1つ。

精度に関する用語

用語 英語 説明
学習データ Data 学習済みモデルを作成するための機械学習アルゴリズムの入力に使用するデータの集まり。あらかじめ用意したデータを学習データとテストデータに分割する。教師データ、訓練データともいう。
テストデータ Test Data モデルの精度評価を行うために使用するデータ。
混合行列 Confusion Matrix 分類の制度を計算するために予測と正解の組み合わせを集計した表。
陽性 Positive 分類で目的としているデータの持つ性質。
陰性 Negative 分類で目的としていないデータの持つ性質。
真陽性 True Positive : TP 陽性と予測して(Positive)、予測が当たった(True)データの性質。
偽陽性 False Positive : FP 陽性と予測して(Positive)、予測が外れた(False)データの性質。
偽陰性 False Negative : FN 陰性と予測して(Negative)、予測が外れた(False)データの性質。
真陰性 True Negative : TN 陰性と予測して(Negative)、予測が当たった(True)データの性質。
正解率 Accuracy 全体のうち予測当たった割合。(TP + TN) / (TP + FP + FN + TN)
適合性 Precision 陽性と予測したうち実際に陽性だった割合。TP / (TP + FP)
再現率 Recall 陽性のデータのうち、陽性と予測した割合。TP / (TP + FN)
F値 F-Value 適合率と再現率のバランスをとった値。適合率と再現率の調和平均で求める。

折れ線グラフ表示

Pandasのデータフレームにデータを読み込んでおくとMatplotlibで簡単にグラフを描けます。

1
2
3
4
5
6
7
8
9
10
import os
import pandas as pd
from bokeh.io import output_notebook

base_url = 'https://raw.githubusercontent.com/practical-jupyter/sample-data/master/anime/'
csv_data = os.path.join(base_url, 'anime_stock_returns.csv')
df = pd.read_csv(csv_data, index_col=0, parse_dates=['Date'])
output_notebook() # 出力先をNotebook上にする

df.plot.line() # matplotlibでグラフ表示

[結果]
Matplotlibでグラフ表示

pandas_bokehというパッケージをインストールしておくと同じように簡単にBokehでグラフを描けます。
まず次のパッケージをインストールしておきます。

1
!pip install pandas_bokeh

コードとしてはpandas_bokehをimportしてplot関数をplot_bokeh関数に置き換えるだけとなります。

1
2
3
4
5
6
7
8
9
10
11
12
import os
import pandas as pd
import pandas_bokeh # 追加
from bokeh.io import output_notebook

base_url = 'https://raw.githubusercontent.com/practical-jupyter/sample-data/master/anime/'
csv_data = os.path.join(base_url, 'anime_stock_returns.csv')
df = pd.read_csv(csv_data, index_col=0, parse_dates=['Date'])
output_notebook() # 出力先をNotebook上にする

#df.plot.line() # コメントアウト
df.plot_bokeh(figsize=(800, 450)) # 追加

[結果]
Bokehでグラフ表示
こんな少ないコード量でこれだけのグラフを描けるのは素晴らしいと思います。

(Google Colaboratoryで動作確認しています。)

chainer-goghでAI画像作成

chainer-goghを使ってある画像をスタイル画像に合わせて合成してみます。
(結構時間がかかります・・・)

まず、作成する画像を出力するディレクトリを作成しておきます。

1
!mkdir result

次に画像合成に必要な関数を定義します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import chainer
from chainer import cuda
import chainer.functions as F
from chainer.links import caffe
from chainer import Variable, optimizers

class NIN:
def __init__(self, fn="nin_imagenet.caffemodel", alpha=[0,0,1,1], beta=[1,1,1,1]):
print ("load model... %s"%fn)
self.model = caffe.CaffeFunction(fn)
self.alpha = alpha
self.beta = beta
def forward(self, x):
y0 = F.relu(self.model.conv1(x))
y1 = self.model.cccp2(F.relu(self.model.cccp1(y0)))
x1 = F.relu(self.model.conv2(F.average_pooling_2d(F.relu(y1), 3, stride=2)))
y2 = self.model.cccp4(F.relu(self.model.cccp3(x1)))
x2 = F.relu(self.model.conv3(F.average_pooling_2d(F.relu(y2), 3, stride=2)))
y3 = self.model.cccp6(F.relu(self.model.cccp5(x2)))
x3 = F.relu(getattr(self.model,"conv4-1024")(F.dropout(F.average_pooling_2d(F.relu(y3), 3, stride=2))))
return [y0,x1,x2,x3]

class VGG:
def __init__(self, fn="VGG_ILSVRC_16_layers.caffemodel", alpha=[0,0,1,1], beta=[1,1,1,1]):
print ("load model... %s"%fn)
self.model = caffe.CaffeFunction(fn)
self.alpha = alpha
self.beta = beta
def forward(self, x):
y1 = self.model.conv1_2(F.relu(self.model.conv1_1(x)))
x1 = F.average_pooling_2d(F.relu(y1), 2, stride=2)
y2 = self.model.conv2_2(F.relu(self.model.conv2_1(x1)))
x2 = F.average_pooling_2d(F.relu(y2), 2, stride=2)
y3 = self.model.conv3_3(F.relu(self.model.conv3_2(F.relu(self.model.conv3_1(x2)))))
x3 = F.average_pooling_2d(F.relu(y3), 2, stride=2)
y4 = self.model.conv4_3(F.relu(self.model.conv4_2(F.relu(self.model.conv4_1(x3)))))
return [y1,y2,y3,y4]

class VGG_chainer:
def __init__(self, alpha=[0,0,1,1], beta=[1,1,1,1]):
from chainer.links import VGG16Layers
print ("load model... vgg_chainer")
self.model = VGG16Layers()
self.alpha = alpha
self.beta = beta
def forward(self, x):
feature = self.model(x, layers=["conv1_2", "conv2_2", "conv3_3", "conv4_3"])
return [feature["conv1_2"], feature["conv2_2"], feature["conv3_3"], feature["conv4_3"]]

class I2V:
def __init__(self, fn="illust2vec_tag_ver200.caffemodel", alpha=[0,0,0,1,10,100], beta=[0.1,1,1,10,100,1000]):
print ("load model... %s"%fn)
self.model = caffe.CaffeFunction(fn)
self.alpha = alpha
self.beta = beta
self.pool_func = F.average_pooling_2d

def forward(self, x):
y1 = self.model.conv1_1(x)
x1 = self.pool_func(F.relu(y1), 2, stride=2)
y2 = self.model.conv2_1(x1)
x2 = self.pool_func(F.relu(y2), 2, stride=2)
y3 = self.model.conv3_2(F.relu(self.model.conv3_1(x2)))
x3 = self.pool_func(F.relu(y3), 2, stride=2)
y4 = self.model.conv4_2(F.relu(self.model.conv4_1(x3)))
x4 = self.pool_func(F.relu(y4), 2, stride=2)
y5 = self.model.conv5_2(F.relu(self.model.conv5_1(x4)))
x5 = self.pool_func(F.relu(y5), 2, stride=2)
y6 = self.model.conv6_4(F.relu(F.dropout(self.model.conv6_3(F.relu(self.model.conv6_2(F.relu(self.model.conv6_1(x5))))),train=False)))
return [y1,y2,y3,y4,y5,y6]

class GoogLeNet:
def __init__(self, fn="bvlc_googlenet.caffemodel", alpha=[0,0,0,0,1,10], beta=[0.00005, 5, 50, 50, 5000, 500000]):
print ("load model... %s"%fn)
self.model = caffe.CaffeFunction(fn)
self.alpha = alpha
self.beta = beta
self.pool_func = F.average_pooling_2d

def forward(self, x):
y1 = self.model['conv1/7x7_s2'](x)
h = F.relu(y1)
h = F.local_response_normalization(self.pool_func(h, 3, stride=2), n=5)
h = F.relu(self.model['conv2/3x3_reduce'](h))
y2 = self.model['conv2/3x3'](h)
h = F.relu(y2)
h = self.pool_func(F.local_response_normalization(h, n=5), 3, stride=2)
out1 = self.model['inception_3a/1x1'](h)
out3 = self.model['inception_3a/3x3'](F.relu(self.model['inception_3a/3x3_reduce'](h)))
out5 = self.model['inception_3a/5x5'](F.relu(self.model['inception_3a/5x5_reduce'](h)))
pool = self.model['inception_3a/pool_proj'](self.pool_func(h, 3, stride=1, pad=1))
y3 = F.concat((out1, out3, out5, pool), axis=1)
h = F.relu(y3)

out1 = self.model['inception_3b/1x1'](h)
out3 = self.model['inception_3b/3x3'](F.relu(self.model['inception_3b/3x3_reduce'](h)))
out5 = self.model['inception_3b/5x5'](F.relu(self.model['inception_3b/5x5_reduce'](h)))
pool = self.model['inception_3b/pool_proj'](self.pool_func(h, 3, stride=1, pad=1))
y4 = F.concat((out1, out3, out5, pool), axis=1)
h = F.relu(y4)

h = self.pool_func(h, 3, stride=2)

out1 = self.model['inception_4a/1x1'](h)
out3 = self.model['inception_4a/3x3'](F.relu(self.model['inception_4a/3x3_reduce'](h)))
out5 = self.model['inception_4a/5x5'](F.relu(self.model['inception_4a/5x5_reduce'](h)))
pool = self.model['inception_4a/pool_proj'](self.pool_func(h, 3, stride=1, pad=1))
y5 = F.concat((out1, out3, out5, pool), axis=1)
h = F.relu(y5)

out1 = self.model['inception_4b/1x1'](h)
out3 = self.model['inception_4b/3x3'](F.relu(self.model['inception_4b/3x3_reduce'](h)))
out5 = self.model['inception_4b/5x5'](F.relu(self.model['inception_4b/5x5_reduce'](h)))
pool = self.model['inception_4b/pool_proj'](self.pool_func(h, 3, stride=1, pad=1))
y6 = F.concat((out1, out3, out5, pool), axis=1)
h = F.relu(y6)

return [y1,y2,y3,y4,y5,y6]

いよいよ合成処理を実行します。
139行目から147行目で入力画像や各パラメータを設定しています。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
import argparse
import os
import sys

import numpy as np
from PIL import Image

import chainer
from chainer import cuda
import chainer.functions as F
import chainer.links
from chainer.links import caffe
from chainer import Variable, optimizers

import pickle

def subtract_mean(x0):
x = x0.copy()
x[0,0,:,:] -= 120
x[0,1,:,:] -= 120
x[0,2,:,:] -= 120
return x
def add_mean(x0):
x = x0.copy()
x[0,0,:,:] += 120
x[0,1,:,:] += 120
x[0,2,:,:] += 120
return x

def image_resize(img_file, width):
gogh = Image.open(img_file)
orig_w, orig_h = gogh.size[0], gogh.size[1]
if orig_w>orig_h:
new_w = width
new_h = width*orig_h//orig_w
gogh = np.asarray(gogh.resize((new_w,new_h)))[:,:,:3].transpose(2, 0, 1)[::-1].astype(np.float32)
gogh = gogh.reshape((1,3,new_h,new_w))
print("image resized to: ", gogh.shape)
hoge= np.zeros((1,3,width,width), dtype=np.float32)
hoge[0,:,width-new_h:,:] = gogh[0,:,:,:]
gogh = subtract_mean(hoge)
else:
new_w = width*orig_w//orig_h
new_h = width
gogh = np.asarray(gogh.resize((new_w,new_h)))[:,:,:3].transpose(2, 0, 1)[::-1].astype(np.float32)
gogh = gogh.reshape((1,3,new_h,new_w))
print("image resized to: ", gogh.shape)
hoge= np.zeros((1,3,width,width), dtype=np.float32)
hoge[0,:,:,width-new_w:] = gogh[0,:,:,:]
gogh = subtract_mean(hoge)
return xp.asarray(gogh), new_w, new_h

def save_image(img, width, new_w, new_h, it):
def to_img(x):
im = np.zeros((new_h,new_w,3))
im[:,:,0] = x[2,:,:]
im[:,:,1] = x[1,:,:]
im[:,:,2] = x[0,:,:]
def clip(a):
return 0 if a<0 else (255 if a>255 else a)
im = np.vectorize(clip)(im).astype(np.uint8)
Image.fromarray(im).save(args['out_dir']+"/im_%05d.png"%it)

if args['gpu']>=0:
img_cpu = add_mean(img.get())
else:
img_cpu = add_mean(img)
if width==new_w:
to_img(img_cpu[0,:,width-new_h:,:])
else:
to_img(img_cpu[0,:,:,width-new_w:])

def get_matrix(y):
ch = y.data.shape[1]
wd = y.data.shape[2]
gogh_y = F.reshape(y, (ch,wd**2))
gogh_matrix = F.matmul(gogh_y, gogh_y, transb=True)/np.float32(ch*wd**2)
return gogh_matrix

class Clip(chainer.Function):
def forward(self, x):
x = x[0]
ret = cuda.elementwise(
'T x','T ret',
'''
ret = x<-120?-120:(x>136?136:x);
''','clip')(x)
return ret

def generate_image(img_orig, img_style, width, nw, nh, max_iter, lr, img_gen=None):
mid_orig = nn.forward(Variable(img_orig))
style_mats = [get_matrix(y) for y in nn.forward(Variable(img_style))]

if img_gen is None:
if args['gpu'] >= 0:
img_gen = xp.random.uniform(-20,20,(1,3,width,width),dtype=np.float32)
else:
img_gen = np.random.uniform(-20,20,(1,3,width,width)).astype(np.float32)
img_gen = chainer.links.Parameter(img_gen)
optimizer = optimizers.Adam(alpha=lr)
optimizer.setup(img_gen)
for i in range(max_iter):
img_gen.zerograds()

x = img_gen.W
y = nn.forward(x)

L = Variable(xp.zeros((), dtype=np.float32))
for l in range(len(y)):
ch = y[l].data.shape[1]
wd = y[l].data.shape[2]
gogh_y = F.reshape(y[l], (ch,wd**2))
gogh_matrix = F.matmul(gogh_y, gogh_y, transb=True)/np.float32(ch*wd**2)

L1 = np.float32(args['lam']) * np.float32(nn.alpha[l])*F.mean_squared_error(y[l], Variable(mid_orig[l].data))
L2 = np.float32(nn.beta[l])*F.mean_squared_error(gogh_matrix, Variable(style_mats[l].data))/np.float32(len(y))
L += L1+L2

if i%100==0:
print(i,l,L1.data,L2.data)

L.backward()
img_gen.W.grad = x.grad
optimizer.update()

tmp_shape = x.data.shape
if args['gpu'] >= 0:
img_gen.W.data += Clip().forward(img_gen.W.data).reshape(tmp_shape) - img_gen.W.data
else:
def clip(x):
return -120 if x<-120 else (136 if x>136 else x)
img_gen.W.data += np.vectorize(clip)(img_gen.W.data).reshape(tmp_shape) - img_gen.W.data

if i%50==0:
save_image(img_gen.W.data, W, nw, nh, i)

# 各パラメータを設定
args = {}
args['orig_img'] = 'cat.png' # オリジナルファイル
args['style_img'] = 'style_6.png' # スタイルファイル
args['out_dir'] = 'result' # 出力ディレクトリ
args['model'] = 'nin_imagenet.caffemodel' # 学習済みモデルファイル
args['width'] = 435 # 出力画像の幅
args['iter'] = 5000 # 繰り返し回数
args['gpu'] = -1
args['lam'] = 0.005
args['lr'] = 4.0

if args['gpu'] >= 0:
cuda.check_cuda_available()
chainer.Function.type_check_enable = False
cuda.get_device(args['gpu']).use()
xp = cuda.cupy
else:
xp = np

if 'nin' in args['model']:
nn = NIN()
elif 'vgg' == args['model']:
nn = VGG()
elif 'vgg_chainer' == args['model']:
nn = VGG_chainer()
elif 'i2v' in args['model']:
nn = I2V()
elif 'googlenet' in args['model']:
nn = GoogLeNet()
else:
print ('invalid model name. you can use (nin, vgg, vgg_chainer, i2v, googlenet)')
if args['gpu']>=0:
nn.model.to_gpu()

W = args['width']
img_content,nw,nh = image_resize(args['orig_img'], W)
img_style,_,_ = image_resize(args['style_img'], W)

generate_image(img_content, img_style, W, nw, nh, img_gen=None, max_iter=args['iter'], lr=args['lr'])

[入力ファイル] ※あらかじめGoogle Colaboratoryにアップロードしておきます。

入力ファイル 内容
cat.png オリジナルファイル
style_6.png スタイルファイル(オリジナルファイルをこのファイルっぽく画像合成する)
nin_imagenet.caffemodel 学習済みモデルファイル(ネットに落ちてます)

[合成された画像] ※50ファイル出力されるので、そのうち5ファイルをピックアップしてます。

im_01000.png
im_02000.png
im_03000.png
im_04000.png
im_04950.png

合成時間がかかるものの写真をマンガ風にしたり、ゴシック調にしたりとなにかに使えるような気がしないでもありません。。。

(Google Colaboratoryで動作確認しています。)

学習モデルを使って手書き数字を判定

前回作成した学習モデルを使って手書き数字を判定します。

判定したい画像ファイルを36行目に指定して実行します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
from __future__ import print_function
import argparse

import chainer
import chainer.functions as F
import chainer.links as L
import chainer.initializers as I
from chainer import training
from chainer.training import extensions
from PIL import Image
import numpy as np

class MLP(chainer.Chain):
def __init__(self, n_units, n_out):
w = I.Normal(scale=0.05) # モデルパラメータの初期化
super(MLP, self).__init__(
conv1=L.Convolution2D(1, 16, 5, 1, 0), # 1層目の畳み込み層(フィルタ数は16)
conv2=L.Convolution2D(16, 32, 5, 1, 0), # 2層目の畳み込み層(フィルタ数は32)
l3=L.Linear(None, n_out, initialW=w), #クラス分類用
)
def __call__(self, x):
h1 = F.max_pooling_2d(F.relu(self.conv1(x)), ksize=2, stride=2) # 最大値プーリングは2×2,活性化関数はReLU
h2 = F.max_pooling_2d(F.relu(self.conv2(h1)), ksize=2, stride=2)
y = self.l3(h2)
return y
"""
自分で用意した手書き文字画像をモデルに合うように変換する処理
"""
def convert_cnn(img):
data = np.array(Image.open(img).convert('L').resize((28, 28)), dtype=np.float32) # ファイルを読込み,リサイズして配列に変換
data = (255.0 - data) / 255.0 # 白黒反転して正規化
data = data.reshape(1, 1, 28, 28) # データの形状を変更
return data

def main():
inputimage = '3.png' # 入力する画像
modelfile = 'result/MLP.model' # 学習済みモデルファイル
unit = 1000 # ユニット数

print('自分の手書き文字を学習したモデルで評価してみるプログラム')
print('# 入力画像ファイル: {}'.format(inputimage))
print('# 学習済みモデルファイル: {}'.format(modelfile))
print('')

# モデルのインスタンス作成
model = L.Classifier(MLP(unit, 10))
# モデルの読み込み
chainer.serializers.load_npz(modelfile, model)

# 入力画像を28x28のグレースケールデータ(0-1に正規化)に変換する
img = convert_cnn(inputimage)
x = chainer.Variable(np.asarray(img)) # 配列データをchainerで扱う型に変換

y = model.predictor(x) # フォワード
c = F.softmax(y).data.argmax()
print('判定結果は{}です。'.format(c))

if __name__ == '__main__':
main()

200x200の画像ファイルに手書きで数字をかいた画像ファイルを3つ用意して判定しました。

[入力ファイル]
手書きの3
[結果]

1
2
3
4
5
自分の手書き文字を学習したモデルで評価してみるプログラム
# 入力画像ファイル: 3.png
# 学習済みモデルファイル: result/MLP.model

判定結果は3です。

[入力ファイル]
手書きの5
[結果]

1
2
3
4
5
自分の手書き文字を学習したモデルで評価してみるプログラム
# 入力画像ファイル: 5.png
# 学習済みモデルファイル: result/MLP.model

判定結果は5です。

[入力ファイル]
手書きの9
[結果]

1
2
3
4
5
自分の手書き文字を学習したモデルで評価してみるプログラム
# 入力画像ファイル: 9.png
# 学習済みモデルファイル: result/MLP.model

判定結果は3です。

1問不正解となりました。。。最後の手書き数字はどうみても9ですよね。

(Google Colaboratoryで動作確認しています。)