バッチ規格化とバッチ再規格化の比較

ニューラルネットワークが複雑になり学習がうまくいかない場合にはバッチ規格化を行うといいです。
バッチ規格化を使うと途中の結果を整理しながら学習をすることができます。
またバッチ再規格化というものがあり、バッチ規格よりもよい性能を示すとのことなのでこの2つを比較してみます。

まずchainerよりMNISTデータを読み込みます。

1
2
3
4
import chainer.datasets as ds

# MNISTデータの読み込み
train, test = ds.get_mnist()

結果

次に読み込んだデータを訓練データとテストデータに分けます。
chainerにはconverくらすにconcat_examplesというメソッドがあり容易にデータの振り分けを行うことができます。

1
2
3
4
5
6
7
8
# 入力データとラベルデータの設定
import chainer.dataset.convert as con
# 訓練データとテストデータ分ける
xtrain, ttrain = con.concat_examples(train)
xtest, ttest = con.concat_examples(test)

print(xtrain.shape, ttrain.shape)
print(xtest.shape, ttest.shape)

結果

振り分けたデータの1つをmatplotlibを使って表示してみます。

1
2
3
4
5
6
7
8
import matplotlib.pyplot as plt

# データの形とMNIST originalの1列を表示する
Dtrain, N = xtrain.shape
plt.imshow(xtrain[0,:].reshape(28, 28))
plt.show()

print('ラベル(答え)=>', ttrain[0])

結果

本題のバッチ規格化を設定します。
まずニューラルネットワークを作成する際にbnorm1=L.BatchNormalization(400)(9行目)を引数に渡し、またニューラルネットワークの関数でNN.bnorm1関数(15行目)をはさみます。
バッチ再規格をする場合は、BatchNormalizationの代わりにBatchRenormalization(10行目)を指定します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import chainer.optimizers as Opt
import chainer.functions as F
import chainer.links as L
from chainer import Variable, Chain, config

# バッチ規格化(途中の結果を整理整頓しながら学習する)
C = ttrain.max() + 1
NN = Chain(l1=L.Linear(N, 400), l2=L.Linear(400, C),
bnorm1=L.BatchNormalization(400))
# bnorm1=L.BatchRenormalization(400)) #バッチの再規格化(Chainer version4以降)

def model(x):
h = NN.l1(x)
h = F.relu(h)
h = NN.bnorm1(h) # バッチの規格化(途中結果の整理)
h = NN.l2(h)
return h

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

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
# 学習用関数
def learning(model, optNN, data, T=50):
# 学習記録用エリア(返値)
train_loss = []
train_acc = []
test_loss = []
test_acc = []

for time in range(T):
# 学習
config.train = True
optNN.target.zerograds()
ytrain = model(data[0])
loss_train = F.softmax_cross_entropy(ytrain, data[2])
acc_train = F.accuracy(ytrain, data[2])
loss_train.backward()
optNN.update()

# テスト(検証)
config.train = False
ytest = model(data[1])
loss_test = F.softmax_cross_entropy(ytest, data[3])
acc_test = F.accuracy(ytest, data[3])

# 結果の記録
train_loss.append(loss_train.data)
train_acc.append(acc_train.data)
test_loss.append(loss_test.data)
test_acc.append(acc_test.data)
return train_loss, test_loss, train_acc, test_acc

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# グラフ表示関数
def show_graph(result1, result2, title, xlabel, ylabel, ymin=0.0, ymax=1.0):
# 学習記録の表示(誤差)
Tall = len(result1)
plt.figure(figsize=(8, 6))
plt.plot(range(Tall), result1, label='train')
plt.plot(range(Tall), result2, label='test')
plt.title(title)
plt.xlabel(xlabel)
plt.ylabel(ylabel)
plt.xlim([0, Tall])
plt.ylim(ymin, ymax)
plt.legend()
plt.show()

最適化手法を設定し、学習を実行したのち結果をグラフ表示します。

1
2
3
4
5
6
7
8
9
10
# 最適化手法の設定
optNN = Opt.MomentumSGD()
optNN.setup(NN)

# 学習を実行
train_loss, test_loss, train_acc, test_acc = learning(model, optNN, [xtrain, xtest, ttrain, ttest])

# 学習結果をグラフ表示
show_graph(train_loss, test_loss, 'loss function', 'step', 'lossfunction', 0.0, 4.0)
show_graph(train_acc, test_acc, 'accuracy', 'step', 'accuracy')

バッチ規格化とバッチ再規格化をそれぞれ実行した結果は下記の通りです。

バッチ規格化 バッチ再規格化
誤差 誤差
正解率 正解率

ほぼ同じ結果で明確な違いがないような気がします。。。

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

ニューラルネットワークで画像認識

ニューラルネットワークで手書き文字の認識をします。有名なMNIST(エムニスト)です。
まず必要なモジュールをインポートします。

1
2
3
4
# モジュール読み込み
import numpy as np
import matplotlib.pyplot as plt
import sklearn.datasets as ds

MNISTデータを読み込み、確認のために画像として表示してみます。

1
2
3
4
5
6
7
8
9
10
11
# MNISTデータの読み込み
MNIST = ds.load_digits()
xdata = MNIST.data.astype(np.float32)
tdata = MNIST.target.astype(np.int32)

# 配列の形を確認
D, N = xdata.shape

# 画像データの表示
plt.imshow(xdata[0,:].reshape(8, 8)) # 1列に並んだデータを8行8列に変換
plt.show()

結果

データ分割関数を定義し、実行します。今回、訓練データと学習データはちょうど半分ずつにしています。

1
2
3
4
5
6
7
8
9
10
11
# データ分割関数
def data_divide(Dtrain, D, xdata, tdata):
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]]
return xtrain, xtest, ttrain, ttest

Dtrain = D // 2
xtrain, xtest, ttrain, ttest = data_divide(Dtrain, D, xdata, tdata)

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

ニューラルネットワークを作成し、ニューラルネットワークの関数を定義します。
また誤差と正解率の遷移を記録する変数を用意します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 2層のニューラルネットワークを作成
C = tdata.max() + 1
NN = Chain(l1=L.Linear(N, 20), l2=L.Linear(20, C))

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

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

# 学習記録用エリア
train_loss = []
train_acc = []
test_loss = []
test_acc = []

最適化を行います。今回は200回学習を行います。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 最適化
T = 200
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)
test_loss.append(loss_test.data)
train_acc.append(acc_train.data)
test_acc.append(acc_test.data)

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# グラフ表示関数
def show_graph(result1, result2, title, xlabel, ylabel, ymin=0.0, ymax=1.0):
# 学習記録の表示(誤差)
Tall = len(result1)
plt.figure(figsize=(8, 6))
plt.plot(range(Tall), result1, label='train')
plt.plot(range(Tall), result2, label='test')
plt.title(title)
plt.xlabel(xlabel)
plt.ylabel(ylabel)
plt.xlim([0, Tall])
plt.ylim(ymin, ymax)
plt.legend()
plt.show()

誤差と正解率の遷移をグラフ表示します。

1
2
3
# 誤差と正解率をグラフ表示
show_graph(train_loss, test_loss, 'loss function', 'step', 'loss_function', 0.0, 4.0)
show_graph(train_acc, test_acc, 'accuracy', 'step', 'accuracy')

結果

順調に誤差が減少し、正解率が上昇していることが見てとれます。
ただ正解率が9割そこそこなのが少々不満です。
非線形関数や最適化手法を変えて改善する余地はありそうです。

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

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

いくつかある最適化方法のうち下記の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で動作確認しています。)