社会人研究者が色々頑張るブログ

pythonで画像処理やパターン認識をやっていきます

pytorchのお勉強(2):誤差逆伝播とパラメタの更新

はじめに

pytorchのお勉強の続きです。
今回は定義したNetworkにデータを与えてネットワークを学習する所を学んでいきます。

今回もこのチュートリアルサイトを参考にしています。

Neural Networkの学習

Neural Network(NN)は入力値を与えると、畳み込みや全結合などの様々なパラメタを持つ演算を経て、出力値を得ます。
入力に対してそれに対応する出力を返す関数の様な性質を持っているのですが、その関数の振る舞いはNNが内部に持つパラメータによって規定されます。

f:id:nsr_9:20210825114042p:plain

すなわち、NNのパラメータをうまい具合に設定する事ができれば、あらゆる振る舞いをする凄い便利な関数を得ることができます。 f:id:nsr_9:20210825114252p:plain

一般的なNNは数万から多いもので数億個のパラメータがあり、専門家を持ってしても人力で設定する事はできません。

そこで、入力データ(関数の入力)と教師データ(関数の出力)のペアデータを大量にNNに与えて、その関数の対応関係を再現する様に回帰学習を行います。
回帰学習を行う為には、入力データに対する現在のパラメータにおける関数の出力値を算出した後、目的の出力値に近づく様にパラメータをちょこっと変える作業を繰り返し行います。

このパラメータをちょこっと動かす作業に勾配情報を用います。
パラメータの微分(関数の傾き)を求めて、その勾配方向にパラメタを動かすことで、目的の出力に近づくようなパラメータを求められます。

最急降下法 - Wikipedia

勾配降下法を自分で実装しようとすると結構大変なのですが、pytorchには微分とパックプロパゲーションを簡単に行ってくれる機能があるので、学習を行うだけなら非常に簡単に実装する事ができます。
昔、C++で多層パーセプトロンの実装を行ってたのですが非常に苦労した記憶があります。微分計算やバックプロパゲーションが正しく実装できてるか、ひーひー言いながら何度も何度も近似計算で検証していました。

pytorchによる実装

pytorchで以下を実装していきます

① Networkに入力を与え出力を得る
② 出力と教師データとの誤差を計算
バックプロパゲーションを行いパラメタを更新
④ 近似した事の確認

Networkに入力を与え出力を得る

定義したNetworkモデルはtorch.tensorを入力できます。
torch.tensorは直接宣言したり、numpy.arrayから生成する事ができます。

# ゼロ埋めされたtorch tensorの宣言
a = torch.zeros([3, 3], dtype=torch.int32)

# 1埋めされたtorch tensorの宣言
b = torch.ones([3, 3], dtype=torch.int32)

# 乱数で初期化されたtorch tensorの宣言
c = torch.randn([3, 3])

# numpy arrayからtorch tensorの宣言
_d = np.zeros([3, 3])
d = torch.tensor(_d)

前回作成したNetworkは(バッチサイズ、チャンネル、Height、Width)の4次元Tensorを入力として受け付けます。
4次元Tensorを生成して、出力outを得るコードは以下の様になります。

B = 1
C = 1
H = 32
W = 32

x = torch.randn([B, C, H, W])

out = net(x)
print(out)
tensor([[ 0.0878,  0.0604, -0.0544,  0.0344,  0.0332,  0.0372,  0.1227,  0.0589,
         -0.0451, -0.1109]], grad_fn=<AddmmBackward>)

出力と教師データとの誤差を計算

まず教師データを作成します。
networkの出力値の次元は(バッチサイズ、10)なので、それに合わせて作成します。

target = torch.zeros([1, 10]) # (バッチサイズ, 10)
target[0, 3] = 1 #なんとなく4番目の値のみ1を取るone-hotベクトルを作成

誤差はLoss関数で計算するのですが、今回は標準に実装されているMean Squared Error(二乗誤差)を用います。

mse = nn.MSELoss 
loss = mse(output, target)
print(loss)

出力は次のようになりました。

tensor(0.0984, grad_fn=<MseLossBackward>)

バックプロパゲーションを行いパラメタを更新

出力と教師データの誤差を算出できたので、この誤差を用いてバックプロパゲーションをやっていきます。
バックプロパゲーションはloss.backword()を呼び出すことで簡単に実行することができます。

net.zero_grad() # バックプロパゲーションを行う前に勾配情報を初期化する
loss.backward()

バックプロパゲーションをする前に、net.zero_grad()を呼び出して勾配を初期化する必要があります。
バッチ学習のようにある程度勾配情報を保持してからドスンと更新する際に勾配情報を保持する機能があるのですが、適宜初期化しないと勾配情報がどんどん累積してしまいます。

勾配情報を使ってパラメータを更新する際は、Optimizerと呼ばれる最適化処理を行います。
参考にしているチュートリアル資料ではOptimizerとしてSGDを用いているのですが、なんとなくAdamを使いたくなったのでそっちを使ってみます。

import torch.optim as optim
mse = nn.MSELoss 
opt = optim.Adam(net.parameters()) # Optimizerの宣言
opt.zero_grad() # 勾配情報の初期化
output = net(input) 
loss = mse(output, target)
loss.backward()  
opt.step()     #パラメータの更新

近似した事の確認

学習前後のLoss値を出力すると次のようになりました。

# Loss値の取得 numpyにするためにはdetachして自動微分の追跡を外す必要がある
loss = criterion(output, target).detach().numpy()
array(0.09820779, dtype=float32)
array(0.06995001, dtype=float32)

0.098から0.069に下がっていますね。
これを何度も何度もいろんなデータでパラメータを更新することで、目的の関数(パラメタ)を得る事ができます。

まとめ

今回はpytorchでネットワークの学習する方法を勉強しました。
pytorchは他のDNNライブラリと比較して、直感的に記述ができるなぁと感じました。
次回は実際に画像データを用いて学習を行ってみたいと思います。