【フレームワークを使用せず】ゼロから作る物体検出AIモデル
【No.12】ニューラルネットワークの実装(3層のNN構築-学習と推論)
1. 本記事について
本記事は、これまでの内容(ニューラルネットワークの理解その1〜4)を踏まえ、ニューラルネットワークをpythonプログラムに落とし込んでいきます。
本記事はこれまで実装してきた処理を使用して、簡単なニューラルネットワーク(NN)を実装していきます。
(前回は、3層のNN構築-実装について解説しました。)
ニューラルネットワークの実装(入力層・Affine前半〜3層のNN構築-実装)を見てから、本記事を見ることをお勧めします。
注意事項として、本シリーズではpythonの基礎的な解説、numpyの基礎的な解説は割愛させていただきます。特に、numpy配列やshape等頻出なので、その辺の知識が不十分の場合は、そちらの習得を先に行うことをお勧めします。
本シリーズを進めていくにあたり、参考にさせていただく書籍があります。オライリー・ジャパンから出版されている「ゼロから作るDeep Learning ーPythonで学ぶディープラーニングの理論と実装」です。
本記事は、上記書籍を参考にさせていただいております。
2. NNの学習と推論
前回迄実装してきた各処理を使って、簡単なニューラルネットワーク(以下NN)を構築し、実際に学習と推論を行ってみます。
これから行うことは、①NNの概要を決定(前々回)、②NNをpythonで構築(前回)、③構築したNNで学習と推論
です。今回は③を行います。
では、実際にNNを学習・それを元に推論していきます。
NNの学習
まず、前回実装したNNを再度以下に示します。これを使っていきます。
import numpy as np
from collections import OrderedDict
class NeuralNetwork1:
def __init__(self):
# 重み・バイアスの初期化
weight_init_std = 0.01
self.params = {}
self.grads = {}
self.params['W1'] = weight_init_std * np.random.randn(784, 500)
self.params['b1'] = np.zeros((1, 500))
self.params['W2'] = weight_init_std * np.random.randn(500, 100)
self.params['b2'] = np.zeros((1, 100))
self.params['W3'] = weight_init_std * np.random.randn(100, 3)
self.params['b3'] = np.zeros((1, 3))
# 中間層の構築
self.layers = OrderedDict()
self.layers['Affine1'] = Affine(self.params['W1'], self.params['b1'])
self.layers['LeakyReLU1'] = LeakyReLU(0.1)
self.layers['Affine2'] = Affine(self.params['W2'], self.params['b2'])
self.layers['LeakyReLU2'] = LeakyReLU(0.1)
self.layers['Affine3'] = Affine(self.params['W3'], self.params['b3'])
self.loss = loss()# ロス関数
self.update = Momentum()# 更新関数
def predict(self, x):
for layer in self.layers.values():
x = layer.forward(x)
return x
def loss_func(self, x, t):
return self.loss.forward(x, t)
def gradient(self):
dout = self.loss.backward()
layers = list(self.layers.values())
layers.reverse()
for layer in layers:
dout = layer.backward(dout)
self.grads['W1'] = self.layers['Affine1'].dW
self.grads['b1'] = self.layers['Affine1'].db
self.grads['W2'] = self.layers['Affine2'].dW
self.grads['b2'] = self.layers['Affine2'].db
self.grads['W3'] = self.layers['Affine3'].dW
self.grads['b3'] = self.layers['Affine3'].db
return self.grads
def update_func(self):
self.update.update(self.params, self.grads)
今回の趣旨は、「0, 1, 2とそれぞれ書かれた画像データをNNに渡し、画像認識させる」こととします。なので、まず0、1、2の画像データと教師データを収集することが必要です。
今回はお馴染みMNISTを使用していきます(MNISTの詳しい解説は省略させていただきます)。
0、1、2だけ使用するので、それらの画像データと、対になる教師データを抽出します。
import numpy as np
import matplotlib.pyplot as plt
from collections import OrderedDict
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
#訓練データ
train_dataset = torchvision.datasets.MNIST(root='./data',
train=True,
transform=transforms.ToTensor(),
download=True# 初回
#download=False# 2回目以降
)
#検証データ
test_dataset = torchvision.datasets.MNIST(root='./data',
train=False,
transform=transforms.ToTensor(),
download=True# 初回
#download=False# 2回目以降
)
train_box = []
for i, data in enumerate(train_dataset):
if data[1] in (0, 1, 2):
t1 = data[0].numpy().copy().reshape(-1, 784)
t2 = np.eye(3)[data[1]]
train_box.append((t1, t2))
if len(train_box) == 5000:
break
test_box = []
for i, data in enumerate(test_dataset):
if data[1] in (0, 1, 2):
t1 = data[0].numpy().copy().reshape(-1, 784)
t2 = np.eye(3)[data[1]]
test_box.append((t1, t2))
if len(test_box) == 20:
break
各dataset取得時、初回はdownload=True、2回目以降はdownload=Falseにしています。
訓練データとは学習に使用するデータのことで、検証データとは文字通り学習後の精度検証用に使用するデータのことです。
今回訓練データを5000、検証データを20にしました。
本来は訓練データ:検証データ = 8:2 とか 9:1 が望ましいですが、今回ガチの精度検証はせず、軽く目検のみなので、上記の比率でいきます。
[0, 1, 2]のデータをそれぞれtrain_boxとtest_boxに格納しました。これらから1枚ずつ取り出して学習及び推論を実施していきます。
教師データは、画像データに対応する数字、つまり単なる数値なのですが、この数値をone-hotベクトルにします。
one-hotベクトルとは、例えば数値の範囲が0~2の場合、「0」を[1, 0, 0]と表します。「1」は[0, 1, 0]、「2」は[0, 0, 1]と変換します。
なぜこの様な変換をするかというと、今回のNNの出力ノードは3つで、各ノードの値は、学習前はもちろんそれぞれ適当な数値が出力されるはずです。教師データをono-hotベクトルにすることで、学習が進むにつれ出力がそれぞれ1か0に収束するようになります。
「0」画像の教師データは[1, 0, 0]、「1」画像の教師データは[0, 1, 0]、「2」画像の教師データは[0, 0, 1]となります。
一応画像と教師データをみてみます。
for i, data in enumerate(train_box):
x = data[0]
t = data[1]
print(t)
plt.imshow(x.reshape(28, 28), cmap='gray')
plt.show()
if i == 0:
break
[1. 0. 0.]
これで入力する画像データ、教師データの準備ができました。ここから実際にNNに入力し、学習を実施してみたいと思います。
import numpy as np
import matplotlib.pyplot as plt
from collections import OrderedDict
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
# NN
class NeuralNetwork1:
def __init__(self):
# 重み・バイアスの初期化
weight_init_std = 0.01
self.params = {}
self.grads = {}
self.params['W1'] = weight_init_std * np.random.randn(784, 500)
self.params['b1'] = np.zeros((1, 500))
self.params['W2'] = weight_init_std * np.random.randn(500, 100)
self.params['b2'] = np.zeros((1, 100))
self.params['W3'] = weight_init_std * np.random.randn(100, 3)
self.params['b3'] = np.zeros((1, 3))
# 中間層の構築
self.layers = OrderedDict()
self.layers['Affine1'] = Affine(self.params['W1'], self.params['b1'])
self.layers['LeakyReLU1'] = LeakyReLU(0.1)
self.layers['Affine2'] = Affine(self.params['W2'], self.params['b2'])
self.layers['LeakyReLU2'] = LeakyReLU(0.1)
self.layers['Affine3'] = Affine(self.params['W3'], self.params['b3'])
self.loss = loss()# ロス関数
self.update = Momentum()# 更新関数
def predict(self, x):
for layer in self.layers.values():
x = layer.forward(x)
return x
def loss_func(self, x, t):
return self.loss.forward(x, t)
def gradient(self):
dout = self.loss.backward()
layers = list(self.layers.values())
layers.reverse()
for layer in layers:
dout = layer.backward(dout)
self.grads['W1'] = self.layers['Affine1'].dW
self.grads['b1'] = self.layers['Affine1'].db
self.grads['W2'] = self.layers['Affine2'].dW
self.grads['b2'] = self.layers['Affine2'].db
self.grads['W3'] = self.layers['Affine3'].dW
self.grads['b3'] = self.layers['Affine3'].db
return self.grads
def update_func(self):
self.update.update(self.params, self.grads)
# 訓練データと検証データを取得
train_dataset = torchvision.datasets.MNIST(root='./data',
train=True,
transform=transforms.ToTensor(),
#download=True
download=False
)
test_dataset = torchvision.datasets.MNIST(root='./data',
train=False,
transform=transforms.ToTensor(),
#download=True
download=False
)
# 0, 1, 2のみを抽出。及び教師データをone-hotベクトル化
train_box = []
for i, data in enumerate(train_dataset):
if data[1] in (0, 1, 2):
t1 = data[0].numpy().copy().reshape(-1, 784)
t2 = np.eye(3)[data[1]]
train_box.append((t1, t2))
if len(train_box) == 5000:
break
test_box = []
for i, data in enumerate(test_dataset):
if data[1] in (0, 1, 2):
t1 = data[0].numpy().copy().reshape(-1, 784)
t2 = np.eye(3)[data[1]]
test_box.append((t1, t2))
if len(test_box) == 20:
break
nn = NeuralNetwork1()# ネットワークをインスタンス化
# 学習
for i, data in enumerate(train_box):
x = data[0]
t = data[1]
if i % 100 == 0:
print(f'train {i+1}-{i+100}...')
y = nn.predict(x)# 順伝播
loss_calc = nn.loss_func(y, t)# ロス
grads = nn.gradient()# 逆伝播→勾配計算
nn.update_func()# 重みの更新
if i % 200 == 0:
print(f'--loss: {loss_calc}')
train 1-100...
--loss: 0.501434176031198
train 101-200...
train 201-300...
--loss: 0.0782794788204203
train 301-400...
train 401-500...
省略
train 4501-4600...
train 4601-4700...
--loss: 0.13184783039830777
train 4701-4800...
train 4801-4900...
--loss: 0.004502928226713012
train 4901-5000...
学習が完了しました。
余談ですが、おそらく300回目のロス計算で値がかなり小さくなっているみたいなので、このNNは学習データ5000個もいらないかもしれません。その辺グラフ化して、ロスが底をうっているか確認する必要があります(今回はしていませんが)。
NNの推論
学習したNNがどの程度のものか、実際に推論して確かめます。検証方法は目検です(精度検証についても後日記事にしたいと思っています)。
# 上記の学習の続きから書く
# 推論
for i, data in enumerate(test_box):
x = data[0]
t = data[1]
y = nn.predict(x)# 順伝播
predict = np.argmax(y)
print(f'predict: {predict} <-- {y}')
plt.imshow(x.reshape(28, 28), cmap='gray')
plt.show()
predict: 2 <-- [[0.36059703 0.13137184 0.50844576]]
predict: 1 <-- [[-0.02663263 0.99466058 0.03140036]]
predict: 0 <-- [[ 0.79300986 0.23215765 -0.02393032]]
predict: 1 <-- [[-0.06686145 1.08179798 -0.01505955]]
predict: 0 <-- [[0.74730933 0.1652882 0.08997015]]
predict: 0 <-- [[0.78678812 0.19816317 0.01809926]]
predict: 1 <-- [[-0.12694668 1.08833617 0.0394031 ]]
predict: 0 <-- [[ 0.82427907 0.21126809 -0.03306473]]
predict: 0 <-- [[0.77275124 0.20889788 0.01981551]]
predict: 1 <-- [[-0.08340048 1.00862371 0.07528398]]
predict: 1 <-- [[-0.08169642 1.01635994 0.06542205]]
predict: 2 <-- [[0.03304641 0.11196785 0.85466351]]
predict: 1 <-- [[-0.11789704 1.06673833 0.05232005]]
predict: 2 <-- [[0.07267232 0.02074368 0.90492775]]
predict: 1 <-- [[-0.08315619 1.06286295 0.02070193]]
predict: 1 <-- [[-0.03343533 0.96542191 0.06749356]]
predict: 2 <-- [[0.06231169 0.31565589 0.62024599]]
predict: 1 <-- [[-0.13759874 0.87797909 0.2593149 ]]
predict: 2 <-- [[0.03652807 0.12435752 0.84026237]]
predict: 0 <-- [[0.74064184 0.23156223 0.02766383]]
矢印の右側が実際の推論値(出力値)で、矢印の左側が最も高い推論値のインデックス、つまり予測値を示しています。
しっかり予測できていることが分かります。
この様に、タスクによっては、3層で重みの数もさほど多くないNNでも十分精度を出すことができます。
今回でNN構築が完了しました。本シリーズでは、これからNNの精度を上げるための手法などを解説、その後、画像データに特化したNN(CNN)の解説・実装を行っていきたいと思います。引き続きよろしくお願い致します。
3. まとめ
今回はニューラルネットワークの実装(3層のNN構築-学習と推論)について解説しました。
次回も是非みてみてください!
4. 参考文献
斎藤康毅 著 「ゼロから作るDeep Learning ーPythonで学ぶディープラーニングの理論と実装」
Link
Search