ニューラルネットワークの最適化方法を比較

いくつかある最適化方法のうち下記の3つを比較します。

  • SGD
  • MomentumSGD
  • Adam

まずは必要なモジュールをインポートします。

1
2
3
4
5
6
7
8
import chainer.optimizers as Opt
import chainer.functions as F
import chainer.links as L
from chainer import Variable, Chain, config

import numpy as np
import matplotlib.pyplot as plt
import sklearn.datasets as ds

アヤメデータを読み込み、訓練データと学習データに分けます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# アヤメデータを読み込む
Iris = ds.load_iris()
xdata = Iris.data.astype(np.float32)
tdata = Iris.target.astype(np.int32)

D, N = xdata.shape # D=150, N=4

# 訓練データとテストデータに分ける
Dtrain = D // 2
index = np.random.permutation(range(D))
xtrain = xdata[index[0:Dtrain],:]
ttrain = tdata[index[0:Dtrain]]
xtest = xdata[index[Dtrain:D],:]
ttest = tdata[index[Dtrain:D]]

グラフ表示関数を定義します。

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
# グラフ表示関数
def show_graph():
# 学習記録の表示(誤差)
Tall = len(train_loss)
plt.figure(figsize=(8, 6))
plt.plot(range(Tall), train_loss, label='train_loss')
plt.plot(range(Tall), test_loss, label='test_loss')
plt.title('loss function in training and test')
plt.xlabel('step')
plt.ylabel('loss function')
plt.xlim([0, Tall])
plt.ylim(0, 4)
plt.legend()
plt.show()

# 学習記録の表示(正解率)
plt.figure(figsize=(8, 6))
plt.plot(range(Tall), train_acc, label='train_acc')
plt.plot(range(Tall), test_acc, label='test_acc')
plt.title('accuracy in training and test')
plt.xlabel('step')
plt.ylabel('accuracy')
plt.xlim([0, Tall])
plt.ylim(0, 1.0)
plt.legend()
plt.show()

学習用関数を定義します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 学習用関数
def training(T):
for time in range(T):
config.train = True
optNN.target.zerograds()
ytrain = model(xtrain)
loss_train = F.softmax_cross_entropy(ytrain, ttrain)
acc_train = F.accuracy(ytrain, ttrain)
loss_train.backward()
optNN.update()

config.train = False
ytest = model(xtest)
loss_test = F.softmax_cross_entropy(ytest, ttest)
acc_test = F.accuracy(ytest, ttest)

train_loss.append(loss_train.data)
train_acc.append(acc_train.data)
test_loss.append(loss_test.data)
test_acc.append(acc_test.data)

最後に最適化方法を変えながら学習・結果表示を行います。

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
# 最適化方法を変えながら学習・結果表示を行う
for optNN in [Opt.SGD(), Opt.MomentumSGD(), Opt.Adam()]:
# 3層のニューラルネットワークを作成
C = np.max(tdata) + 1
NN = Chain(l1=L.Linear(N, 3), l2=L.Linear(3, 3), l3=L.Linear(3, C))

# 3層ニューラルネットワークの関数化
def model(x):
h = NN.l1(x)
h = F.sigmoid(h)
#h = F.relu(h)
h = NN.l2(h)
h = F.sigmoid(h)
#h = F.relu(h)
y = NN.l3(h)
return y

# 最適化手法を設定
# optNN = Opt.SGD()
# optNN = Opt.MomentumSGD()
# optNN = Opt.Adam()
optNN.setup(NN)

# 学習の記録用
train_loss = []
train_acc = []
test_loss = []
test_acc = []

training(3000) # 引数は学習回数

show_graph() # グラフ表示

実行結果は比較しやすいように表形式にまとめました。

SGD MomentumSGD Adam
初歩的な最適化 勢いをつけた最適化 適応的モーメント勾配法
誤差 誤差 誤差
正解率 正解率 正解率

(適応的勾配法は、勾配の大きさが小さくなってくると勾配に従って調整する大きさを多くします。)

非線形関数にReLUを使用すると実行結果が安定しなかった(すぐ最適化されたりまったくされなかったり・・・)のでシグモイドを使用しています。

今回のケースではAdamが一番はやく正解率が安定しています。
最適化方法によってだいぶ結果が変わるので、データやケースによって最適化方法をいろいろ試すしかなさそうです。。。

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

ニューラルネットワークでアヤメ分類

アヤメデータの分類をニューラルネットワークで行います。
まずは必要なモジュールをインポートします。

1
2
3
import numpy as np
import matplotlib.pyplot as plt
import sklearn.datasets as ds

アヤメデータを読み込みます。

1
2
3
4
5
6
# アヤメデータを読み込む
Iris = ds.load_iris()
xdata = Iris.data.astype(np.float32) # アヤメデータの特徴量
tdata = Iris.target.astype(np.int32) # アヤメの種類(答え)

D, N = xdata.shape # D=150, N=4 # 4種類の特徴量が150データ分

読み込んだデータを訓練データとテストデータに分けます。半分ずつにします。

1
2
3
4
5
6
7
# 訓練データとテストデータに分ける
Dtrain = D // 2 # 75
index = np.random.permutation(range(D)) # ランダムなインデックスを作成
xtrain = xdata[index[0:Dtrain],:] # 訓練データの入力
ttrain = tdata[index[0:Dtrain]] # 訓練データのラベル(正解)
xtest = xdata[index[Dtrain:D],:] # テストデータの入力
ttest = tdata[index[Dtrain:D]] # テストデータのラベル(正解)

Chainerを使えるように宣言します。

1
2
3
4
5
# Chainerの宣言
import chainer.optimizers as Opt
import chainer.functions as F
import chainer.links as L
from chainer import Variable, Chain, config

3層のニューラルネットワークを作成します。

1
2
3
# 3層のニューラルネットワークを作成
C = np.max(tdata) + 1
NN = Chain(l1=L.Linear(N, 3), l2=L.Linear(3, 3), l3=L.Linear(3, C))

3層ニューラルネットワークを関数化します。

①ニューラルネットワークの関数化
1
2
3
4
5
6
7
8
# 3層ニューラルネットワークの関数化
def model(x):
h = NN.l1(x)
h = F.sigmoid(h)
h = NN.l2(h)
h = F.sigmoid(h)
y = NN.l3(h)
return y

最適化手法を設定します。

②最適化手法を設定
1
2
3
# 最適化手法を設定
optNN = Opt.SGD()
optNN.setup(NN)

学習結果を記録する変数を定義します。

1
2
3
4
5
# 学習の記録用
train_loss = []
train_acc = []
test_loss = []
test_acc = []

学習処理を実装します。学習回数は1000回です。

③学習処理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
T = 1000
for time in range(T):
config.train = True
optNN.target.zerograds()
ytrain = model(xtrain)
loss_train = F.softmax_cross_entropy(ytrain, ttrain)
acc_train = F.accuracy(ytrain, ttrain)
loss_train.backward()
optNN.update()

config.train = False
ytest = model(xtest)
loss_test = F.softmax_cross_entropy(ytest, ttest)
acc_test = F.accuracy(ytest, ttest)

train_loss.append(loss_train.data)
train_acc.append(acc_train.data)
test_loss.append(loss_test.data)
test_acc.append(acc_test.data)

結果の誤差をグラフ表示します。

④誤差のグラフ表示
1
2
3
4
5
6
7
8
9
10
11
12
# 学習記録の表示(誤差)
Tall = len(train_loss)
plt.figure(figsize=(8, 6))
plt.plot(range(Tall), train_loss, label='train_loss')
plt.plot(range(Tall), test_loss, label='test_loss')
plt.title('loss function in training and test')
plt.xlabel('step')
plt.ylabel('loss function')
plt.xlim([0, Tall])
plt.ylim(0, 4)
plt.legend()
plt.show()

誤差
ほとんど変わっていないようにみえます。
正解率もグラフ化します。

⑤正解率のグラフ表示
1
2
3
4
5
6
7
8
9
10
11
# 学習記録の表示(正解率)
plt.figure(figsize=(8, 6))
plt.plot(range(Tall), train_acc, label='train_acc')
plt.plot(range(Tall), test_acc, label='test_acc')
plt.title('accuracy in training and test')
plt.xlabel('step')
plt.ylabel('accuracy')
plt.xlim([0, Tall])
plt.ylim(0, 1.0)
plt.legend()
plt.show()

正解率
ぜんぜん正解率があがりません。あきらめずにもう10,000回実行してみます。
先ほどの1,000回と合わせてトータルで11,000回実行することになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
T = 10000   # 学習回数を10倍に変更
for time in range(T):
config.train = True
optNN.target.zerograds()
ytrain = model(xtrain)
loss_train = F.softmax_cross_entropy(ytrain, ttrain)
acc_train = F.accuracy(ytrain, ttrain)
loss_train.backward()
optNN.update()

config.train = False
ytest = model(xtest)
loss_test = F.softmax_cross_entropy(ytest, ttest)
acc_test = F.accuracy(ytest, ttest)

train_loss.append(loss_train.data)
train_acc.append(acc_train.data)
test_loss.append(loss_test.data)
test_acc.append(acc_test.data)

④誤差のグラフ表示と⑤正解率のグラフ表示を再び実行します。
誤差
誤差は緩やかながらだんだん減少しているようです。
正解率
正解率は2段階にわけて正解率がぐんとあがることがあり、最終的にはほぼ100%の正解率になってます。あきらめずに続けるって大切ですね。

しかしもっと効率よく学習する方法があります。
それは非線形変換をシグモイドからReLUに変更することです。複雑なニューラルネットワークを利用するときはReLUの方がよいとのことです。
①ニューラルネットワークの関数化を下記のように変更します。

①ニューラルネットワークの関数化を変更
1
2
3
4
5
6
7
8
# 非線形変換をシグモイドからReLUに変更
def model(x):
h = NN.l1(x)
h = F.relu(h)
h = NN.l2(h)
h = F.relu(h)
y = NN.l3(h)
return y

ニューラルネットワークをもう一度生成し、最適化手法を再設定、学習の記録用データをリセットしたあとに、③学習処理(1,000回)と④誤差のグラフ表示、⑤正解率のグラフ表示を実行してみます。

誤差

正解率
確かにシグモイドよりReLUを使った方が早く誤差、正解率ともに改善していることが分かります。

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

ニューラルネットワークでデータ分類

ニューラルネットワークでデータの分類をしてみます。
まずは、chainerの宣言をします。

1
2
3
4
import chainer.optimizers as Opt
import chainer.functions as F
import chainer.links as L
from chainer import Variable, Chain, config

次に乱数を生成します。100行2列のデータとなります。

1
2
3
4
5
6
import numpy as np
import matplotlib.pyplot as plt

D = 100
N = 2
xdata = np.random.randn(D * N).reshape(D, N).astype(np.float32)

次にデータを分割する関数とその関数で分類したデータを準備します。

1
2
3
4
def f(x):
return x * x

tdata = (xdata[:,1] > f(xdata[:,0])).astype(np.int32)

1層のニューラルネットワーク作成します。
2種類のデータが入力され、2種類のデータ出力される単純なニューラルネットワークとなります。

1
2
3
# 1層のニューラルネットワーク作成
C = 2 # 結果が2種類あるという意味
NN = Chain(l1=L.Linear(N, C))

このニューラルネットワークを使った関数を定義します。

1
2
3
def model(x):
y = NN.l1(x)
return y

上記で定義した関数でデータを分類します。

1
2
ydata = model(xdata)
ydata

乱数で生成したデータ(一部略)
正解率を検証します。

1
2
3
# 精度の検証
acc = F.accuracy(ydata, tdata)
acc

最初の正解率
結果は0.21(21%)ととても低いですが、まったく学習をしていない状態なので気にする必要はありません。

ニューラルネットワークを最適化する手法を設定します。

1
2
3
# 最適化手法の設定
optNN = Opt.SGD()
optNN.setup(NN)

結果を確認するために、誤差と正解率を記録するエリアを定義します。

1
2
3
# 学習の記録用
loss_series = []
acc_series = []

いよいよ学習してみます。処理に関してはソースコメントを参照してください。

1
2
3
4
5
6
7
8
9
10
11
12
T = 5000
for time in range(T):
config.train = True # 学習モードにする
optNN.target.zerograds() # 初期化(全ての勾配を0にする)
ydata = model(xdata) # 誤差を計算する
loss = F.softmax_cross_entropy(ydata, tdata) # 成績チェック
acc= F.accuracy(ydata, tdata) # 誤差逆伝搬
loss.backward() # ニューラルネットワークを調整
optNN.update()
# 結果の記録
loss_series.append(loss.data)
acc_series.append(acc.data)

誤差がどのように変化しているかをグラフ化します。

1
2
3
4
5
6
7
8
9
10
# 学習記録の表示(誤差)
Tall = len(loss_series)
plt.figure(figsize=(8, 6))
plt.plot(range(Tall), loss_series)
plt.title('loss function in training')
plt.xlabel('step')
plt.ylabel('loss function')
plt.xlim([0, Tall])
plt.ylim(0, 1)
plt.show()

誤差(1層のニューラルネットワーク)
0.3付近までは順調に誤差が減っていますがそこからは改善していないようです。
次に、正解率の遷移をグラフ化します。

1
2
3
4
5
6
7
8
9
10
# 学習記録の表示(正解率)
Tall = len(acc_series)
plt.figure(figsize=(8, 6))
plt.plot(range(Tall), acc_series)
plt.title('accuracy in training')
plt.xlabel('step')
plt.ylabel('accuracy')
plt.xlim([0, Tall])
plt.ylim(0, 1)
plt.show()

正解率(1層のニューラルネットワーク)
0.85(85%)付近までは上昇しますが、そこが限界のようです。

さらなる精度情報のために2層のニューラルネットワークにしてみます。
下記では、入力が2種類、中間層が4層、出力が2種類となるニューラルネットワークを定義しています。

1
2
3
# 2層のニューラルネットワークにする
C = 2
NN = Chain(l1=L.Linear(N, 4), l2=L.Linear(4 , C))

2層のニューラルネットワークの関数化ですが2つの線形変換の間に非線形変換をいれるとよいとのことです。
シグモイド関数をはさんでみました。

1
2
3
4
5
6
# 2層のニューラルネットワークの関数化
def model(x):
h = NN.l1(x) # 線形変換
h = F.sigmoid(h) # 非線形変換(シグモイド関数)
y = NN.l2(h) # 変形変換
return y

あらたにニューラルネットワークを定義したので、最適化手法を再設定します。
学習記録もいったん初期化し、学習を2万回に増やして実行します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 最適化手法の設定(再設定)
optNN = Opt.SGD()
optNN.setup(NN)

# 学習の記録用(初期化)
loss_series = []
acc_series = []

# 学習
T = 20000
for time in range(T):
config.train = True
optNN.target.zerograds()
ydata = model(xdata)
loss = F.softmax_cross_entropy(ydata, tdata)
acc= F.accuracy(ydata, tdata)
loss.backward()
optNN.update()
# 結果の記録
loss_series.append(loss.data)
acc_series.append(acc.data)

結果は下記のとおりです。
誤差(2万回)
正解率(2万回)

誤差の結果をみるとまだまだ改善するような感じなのでもう2万回 追加で学習をしてみます。
再実行するのは、ループの[学習]のところと[学習記録の表示(誤差)][学習記録の表示(正解率)]です。

誤差(4万回)

正解率(4万回)

だんだん結果がよくなっているので、もう2万回追加で実行します。

誤差(6万回)

正解率(6万回)

ほぼ100%の正解率にすることができました。
誤差が改善する見込みがある場合は、学習回数を単純に増やしていくといいみたいです。

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

numpyで乱数生成・データ分類・分類したデータをグラフ表示

numpyで生成した乱数を、特定関数で分類し、その分類したデータをグラフに表示します。
まずは必要なモジュールをimportします。

1
2
3
# 基本モジュールの宣言
import numpy as np
import matplotlib.pyplot as plt

次に乱数を生成します。縦100横2の配列にreshapeしています。

1
2
3
4
5
# 乱数生成(縦100,横2の配列分の乱数を生成)
D = 100
N = 2
xdata = np.random.randn(D * N).reshape(D, N).astype(np.float32)
xdata

結果(一部略)
生成した配列をx,yを表した100個のデータとみなして散布図を表示します。

1
2
3
4
5
# 散布図を表示
# xdata[:,0] <= 1列目に縦に並んでいる数字の全て
# xdata[:,1] <= 2列目に縦に並んでいる数字の全て
plt.scatter(xdata[:,0], xdata[:,1])
plt.show()

結果
分類用の関数を定義します。ここでは単純にxを2乗した数値を返しています。

1
2
3
# 関数の定義
def f(x):
return x * x

乱数の配列に対して、定義した関数の上にくるか下にくるかを判定します。
上にくる場合は1を、下にくる場合は0が返ります。
(ループで1つずつ処理しなくても入力の配列に対して、結果の配列が返ってくるのがnumpyの便利なところです。)

1
2
3
# 条件にあてはまるものを探す
tdata = (xdata[:,1] > f(xdata[:,0])).astype(np.int32)
tdata

結果
結果がTrue(1)となる配列のインデックスをndata0に、False(0)となる配列のインデックスをndata1に格納します。

1
2
3
4
5
6
# 乱数のデータを2つのグループに分ける
# True(1)とFalse(0)の場所を調べる
ndata0 = np.where(tdata==0)
ndata1 = np.where(tdata==1)
print(ndata0)
print(ndata1)

結果
最後にグループ分け関数と分類されたデータを1つのグラフとして表示します。

1
2
3
4
5
6
7
8
# 2つの種類のデータを図に示す
x = np.linspace(-2.0, 2.0, D) # -2.0から2.0の範囲でD=100個の点を用意する
plt.plot(x, f(x))

plt.scatter(xdata[ndata0, 0], xdata[ndata0, 1], marker='x')
plt.scatter(xdata[ndata1, 0], xdata[ndata1, 1], marker='o')

plt.show()

結果

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

3Dグラフ表示

matplitlibとjupyterで3Dグラフを書いてみます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 3Dグラフ
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
%matplotlib inline

t = np.linspace(-2 * np.pi, 2 * np.pi)
x, y = np.meshgrid(t, t)
R = np.sqrt(x ** 2 + y ** 2)
z = np.sin(R)

fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(1, 1, 1, projection='3d')

ax.plot_surface(x, y, z)
plt.show()

[結果]
結果

次に曲面を描画した3Dグラフを書いてみます。

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

from mpl_toolkits.mplot3d import Axes3D
%matplotlib inline

x = y= np.linspace(-5, 5)
X, Y = np.meshgrid(x, y)
Z = np.exp(-(X ** 2 + Y ** 2) / 2) / (2 * np.pi)

fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(1, 1, 1, projection='3d') # 3D描画機能を持ったサブプロット作成
ax.plot_surface(X, Y, Z) # 曲面を描画した3Dグラフを表示

plt.show()

結果

今度は3Dヒストグラムを書いてみます。
描画関数としてはbar3dを使用します。

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 numpy as np
import matplotlib.pyplot as plt

from mpl_toolkits.mplot3d import Axes3D
%matplotlib inline

fig = plt.figure(figsize=(8, 6))

ax = fig.add_subplot(111, projection="3d")

# x, y, zの位置を決める
xpos = [i for i in range(10)]
ypos = [i for i in range(10)]
zpos = np.zeros(10)

# x, y, zの増加量を決める
dx = np.ones(10)
dy = np.ones(10)
dz = [i for i in range(10)]

# 3Dヒストグラムを作成
ax.bar3d(xpos, ypos, zpos, dx, dy, dz)

plt.show()

結果

3D散布図を書いてみます。
描画関数としてはscatter3Dを使用します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import numpy as np
import matplotlib.pyplot as plt

from mpl_toolkits.mplot3d import Axes3D
%matplotlib inline

fig = plt.figure(figsize=(8, 6))

np.random.seed(0) # 乱数を固定する(毎回同じ乱数が出るようになる)
X = np.random.randn(1000)
Y = np.random.randn(1000)
Z = np.random.randn(1000)

ax = fig.add_subplot(1, 1, 1, projection='3d')
# 1次元に変換
x = np.ravel(X)
y = np.ravel(Y)
z = np.ravel(Z)

ax.scatter3D(x, y, z)
plt.show()

結果

最後にグラフの点が通る座標に応じて表示する色を変えてみます。
plot_surface関数にcmap=cm.coolwarmを指定するだけです。

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

from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D
%matplotlib inline

t = np.linspace(-2 * np.pi, 2 * np.pi)
x, y = np.meshgrid(t, t)
r = np.sqrt(x ** 2 + y ** 2)
z = np.sin(r)

fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(1, 1, 1, projection="3d")
ax.plot_surface(x, y, z, cmap=cm.coolwarm) # カラーマップを適用

plt.show()

結果

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

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で動作確認しています。)