ニューラルネットワーク(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, '=>', '〇' 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, '=>', '〇' 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, '=>', '〇' 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で動作確認しています。)

画像認識に向いているCNN

畳み込みニューラル・ネットワーク(CNN)は画像処理に強いディープ・ラーニングとのことです。
CNNを使って手書きの文字を入力し文字を認識させてみます。

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
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

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 main():
parser = argparse.ArgumentParser(description='Chainer example: MNIST')
parser.add_argument('--batchsize', '-b', type=int, default=100, help='Number of images in each mini-batch')
parser.add_argument('--epoch', '-e', type=int, default=20, help='Number of sweeps over the dataset to train')
parser.add_argument('--gpu', '-g', type=int, default=-1, help='GPU ID (negative value indicates CPU)')
parser.add_argument('--out', '-o', default='result', help='Directory to output the result')
parser.add_argument('--resume', '-r', default='', help='Resume the training from snapshot')
parser.add_argument('--unit', '-u', type=int, default=1000, help='Number of units')
#args = parser.parse_args()
args = parser.parse_args(args=[]) # Jupyter用

print('GPU: {}'.format(args.gpu))
print('# unit: {}'.format(args.unit))
print('# Minibatch-size: {}'.format(args.batchsize))
print('# epoch: {}'.format(args.epoch))
print('')

train, test = chainer.datasets.get_mnist(ndim=3) # ndim=3を引数で与えるだけでOK
model = L.Classifier(MLP(args.unit, 10), lossfun=F.softmax_cross_entropy)
if args.gpu >= 0:
chainer.cuda.get_device(args.gpu).use()
model.to_gpu()
optimizer = chainer.optimizers.Adam()
optimizer.setup(model)
train_iter = chainer.iterators.SerialIterator(train, args.batchsize)
test_iter = chainer.iterators.SerialIterator(test, args.batchsize, repeat=False, shuffle=False)
updater = training.StandardUpdater(train_iter, optimizer, device=args.gpu)

trainer = training.Trainer(updater, (args.epoch, 'epoch'), out=args.out)
trainer.extend(extensions.Evaluator(test_iter, model, device=args.gpu))
trainer.extend(extensions.dump_graph('main/loss'))
trainer.extend(extensions.snapshot(), trigger=(args.epoch, 'epoch'))
trainer.extend(extensions.LogReport())
trainer.extend( extensions.PlotReport(['main/loss', 'validation/main/loss'], 'epoch', file_name='loss.png'))
trainer.extend( extensions.PlotReport(['main/accuracy', 'validation/main/accuracy'], 'epoch', file_name='accuracy.png'))
trainer.extend(extensions.PrintReport( ['epoch', 'main/loss', 'validation/main/loss', 'main/accuracy', 'validation/main/accuracy', 'elapsed_time']))
trainer.extend(extensions.ProgressBar())

if args.resume:
chainer.serializers.load_npz(args.resume, trainer)

trainer.run()
model.to_cpu()

modelname = args.out + "/MLP.model"
print('save the trained model: {}'.format(modelname))
chainer.serializers.save_npz(modelname, model)

if __name__ == '__main__':
main()

[実行結果]

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
GPU: -1
# unit: 1000
# Minibatch-size: 100
# epoch: 20

Downloading from http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz...
Downloading from http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz...
Downloading from http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz...
Downloading from http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz...
epoch main/loss validation/main/loss main/accuracy validation/main/accuracy elapsed_time
1 0.231759 0.0724735 0.935317 0.9783 92.431
2 0.0670751 0.0424591 0.980317 0.9862 244.515
3 0.0477893 0.0391225 0.98565 0.9865 396.184
4 0.0395797 0.0346431 0.987767 0.9892 547.208
5 0.0323916 0.035994 0.99 0.9883 698.513
6 0.0277908 0.032813 0.99155 0.9892 788.491
7 0.0233976 0.0334056 0.9929 0.9896 878.809
8 0.0202218 0.0281953 0.9938 0.9912 1029.3
9 0.0174586 0.0332045 0.994483 0.9901 1179.88
10 0.0150536 0.0278408 0.995333 0.9919 1269.85
11 0.0141825 0.0325213 0.995583 0.991 1360.04
12 0.0118445 0.0382767 0.996233 0.9893 1509.41
13 0.0107907 0.0331525 0.996683 0.9905 1659.55
14 0.00933434 0.0442279 0.9967 0.9885 1809.8
15 0.00836822 0.0357421 0.997333 0.9895 1932.74
16 0.00697497 0.0377525 0.9977 0.9896 2022.73
17 0.0075223 0.0438979 0.997383 0.9875 2174.04
18 0.00604851 0.0376632 0.99795 0.9914 2327.5
19 0.00420569 0.0373403 0.998817 0.9913 2492.95
20 0.00607973 0.0395273 0.99795 0.9905 2650.21
save the trained model: result/MLP.model

各項目の意味は下記の通りです。

名称 内容
epoch 学習回数
main/loss 出力と学習データの誤差
validation/main/loss 出力とテスト・データの誤差
main/accuracy 学習データの正答率
validation/main/accuracy テスト・データの正答率
elapsed_time 経過時間(秒)

最終的なテスト・データの正答率(validation/main/accuracy)は99.05%とのかなり優秀な結果となりました。
エポック数と正答率(main/accuracy、validation/main/accuracy)の関係は下記のグラフのようになります。
(validationがテストデータの方を表します。)
正答率

エポック数と誤差(validation/main/loss)の関係は下記のグラフのようになります。
誤差

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

ループを使わない書き方

map関数

Pythonではfor文でのループよりもmap関数を使うのがいいコードとのことです。

例えば文字列を数字に変換する場合、for文で書くと次のようになります。

1
2
3
4
5
6
7
in_data = ["1", "2", "3"]

out_data = []
for d in in_data:
out_data.append(int(d))

print(out_data) # 出力 [1, 2, 3]

map関数で書き換えると下記のようになります。

1
2
3
4
5
in_data = ["1", "2", "3"]

out_data = map(int, in_data)

print(list(out_data)) # 出力 [1, 2, 3]

だいぶコンパクトなコードになりいい感じです。

上記は関数のとる引数が1つの場合ですが、引数を複数とる関数の場合はカンマで区切って別のリスト型変数を指定すればいいです。

1
2
3
4
5
in_data1 = [1, 2, 3]
in_data2 = [1, 2, 3]

out_data = map(lambda x,y:x+y, in_data1, in_data2)
print(list(out_data)) # 出力 [2, 4, 6]

引数のデータサイズ数が違う場合は自動で短い配列の方に合わせられるようです。

1
2
3
4
5
in_data1 = [1, 2, 3]
in_data2 = [1, 2]

out_data = map(lambda x,y:x+y, in_data1, in_data2)
print(list(out_data)) # 出力 [2, 4]

filter関数

リスト型データからある条件に合致するデータを抽出します。
filter関数の第一引数は抽出用の関数で、第二引数はデータとなります。

1
2
3
4
5
6
7
8
in_data = ['a.txt', 'b.doc', 'c.txt', 'd.jpg']

def is_txt(x):
return x.endswith('.txt')

out_data = filter(is_txt, in_data)

print(list(out_data)) # 出力 ['a.txt', 'c.txt']

関数を別定義するのはちょっとまどろっこしい感じがしますが、下記のようにlambdaを使うと1行で書けるようになり便利&シンプルです。

1
2
3
4
5
in_data = ['a.txt', 'b.doc', 'c.txt', 'd.jpg']

out_data = filter(lambda x: x.endswith('.txt'), in_data)

print(list(out_data)) # 出力 ['a.txt', 'c.txt']

reduce関数

配列のすべてのデータに関して順次処理を行います。
ポイントとしては第一引数には引数を2つとる関数を指定します。
Python3からはreduce関数を使うためにはimportを書かないといけないとのことです。

1
2
3
4
5
6
7
from functools import reduce

in_data = [1, 2, 3]

out_data = reduce(lambda x, y: x + y, in_data)

print(out_data) # 出力 6

上記は1+2した結果にさらに3を足して6になる・・・ということらしいです。

reduceの第3引数には下記のように初期値を設定することができます。

1
2
3
4
5
6
7
from functools import reduce

in_data = [1, 2, 3]

out_data = reduce(lambda x, y: x + y, in_data, 10)

print(out_data) # 出力 16

初期値10に1足して、それに2足して、最後に3を足して16となります。
実務処理で私はこの関数を使ったことがありません。。。ですが、機械学習とかで便利なことがあるのかもしれません・・・タブン。

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

教師あり学習でBMI肥満度分類

BMI値の算出式で導き出された肥満度分類を、算出式を使わずSVM(サポートベクターマシン)で分類してみます。

手順としては下記の通りです。
①乱数を使って10000万件の身長・体重データを生成し、BMI値を算出し肥満度分類を行う。
②身長・体重データと肥満度分類のデータを8割の学習データと2割のテストデータに分ける。
③8割のデータを学習させる。
④学習データをもとに2割のテストデータでデータ予測を行う。
⑤データ予測がどれだけ正しかったかの結果を表示する。

[データ作成]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import random

# BMIを算出し、体型を3パターンで返す
def calc_bmi(height, weight):
bmi = weight / (height / 100) ** 2
if bmi < 18.5:
return '痩せ'
elif bmi < 25:
return '普通'
else:
return '肥満'

# データ作成 ← 手順①
lst_label = []
lst_height_weight = []
for i in range(10000):
height = random.randint(100, 200) # 100cm~200cmの身長を生成
weight = random.randint(30, 100) # 30kg~60kgの体重を生成
label = calc_bmi(height, weight)

lst_height_weight.append([height, weight])
lst_label.append(label)

[SVMを使っての学習と予測]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from sklearn import model_selection, svm, metrics
import matplotlib.pyplot as plt
import pandas as pd

# 学習データとテストデータに分ける ← 手順②
# テストデータの割合はtest_sizeで指定(0.0~1.0)
data_train, data_test, label_train, label_test = \
model_selection.train_test_split(lst_height_weight, lst_label, test_size=0.2)

# データを学習 ← 手順③
clf = svm.SVC()
clf.fit(data_train, label_train)

# データを予測 ← 手順④
predict = clf.predict(data_test)

[結果表示]

1
2
3
# 結果確認 ← 手順⑤
print('正解率=', metrics.accuracy_score(label_test, predict))
print('レポート=\n', metrics.classification_report(label_test, predict))

[出力結果]

1
2
3
4
5
6
7
8
9
10
11
正解率= 0.994
レポート=
precision recall f1-score support

普通 0.99 0.98 0.98 391
痩せ 0.99 1.00 0.99 430
肥満 1.00 1.00 1.00 1179

accuracy 0.99 2000
macro avg 0.99 0.99 0.99 2000
weighted avg 0.99 0.99 0.99 2000

正解率は99.4%と十分に納得いく結果となりました。
レポートの見方は下記の通りです。

名称 内容
precision 予測が正だった中で、予測通り正答分類できた割合
recall 実際に正だった中で、予測も正答だった割合
f1-score precisionとrecallの調和平均
support データ数

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