スクラッチでニューラルネットを作成すると、データの流れの確認になります。
シンプルな例で仕組みを知ると応用できるのかもね。
PyTorchチュートリアル「WHAT IS TORCH.NN REALLY?」のスクラッチでニューラルネットを構築する項目を進めていきます。今回は、PyTorchのTensorのみを利用していますので、ニューラルネットが実際にどのようにTensorを扱っているのか確認することもできたと感じます。
前回のチュートリアル記事はこちらをご参考ください。
このチュートリアルでは、ニューラルネットワークについての基本的な知識があることが前提となっています。
こんな人の役にたつかも
・機械学習プログラミングの勉強をしている人
・PyTorchのチュートリアルをやっている人
ネットワーク構造の準備プログラム
こんかいのスクラッチで作成するニューラルネットワークは最終的に、中間層のないシンプルなネットワークの構築となります。
初期化
まずはニューラルネットワークのパラメータを保持する変数を作成します。
PyTorchの提供するメソッドでランダムな値で埋められたテンソルや、ゼロで埋められたテンソルを作成することができます。(randnやzeros)
そして、テンソルをニューラルネットワークの「重みやバイアス」といったパラメータにすることができます。
Tensorのもっとも重要な特徴は、勾配が計算できるという点です。テンソルに行われた計算を保持できるので、勾配を自動的に計算することができます。(これはTensorのautogradという機能で実現しています。)
次のプログラムで、ニューラルネットワークの「重み」と「バイアス」パラメータを作成します。
import math
weights = torch.randn(784, 10) / math.sqrt(784)
weights.requires_grad_()
bias = torch.zeros(10, requires_grad=True)
ニューラルネットの「重み」パラメータのためのテンソルは、初期化のすぐ後に「required_grad_()」で計算の追跡する設定にしています。(初期化の計算をした後に追跡開始することで、初期化の計算を含めない点がポイント)
また、重みのパラメータは、「Xavier initialisation」という初期化方法を利用しています。
「バイアス」は、ゼロで初期化するとともに、「required_grad」フラグをTrueにしています。
PyTorchの自動的に勾配を計算(自動微分)してくれるおかげで、通常のPythonの関数などが利用できます。(普通に計算するだけで計算内容を保持してくれるのでPython標準の演算もOKです。)
活性化関数とモデルの定義
次に、「活性化関数」を作成します。手動で「log_softmax」を定義しています。これは、Pythonの関数として定義します。
そして、「model」では、ニューラルネットワークのモデル自体を定義しています。モデルといっても、今回は、順伝播が行われるような式を定義しているだけになります。重みと入力テンソルを掛け合わせて、バイアスを足したものを活性化関数のlog_softmaxに入れ、出力値をリターンするようなモデルです。
def log_softmax(x):
return x - x.exp().sum(-1).log().unsqueeze(-1)
def model(xb):
return log_softmax(xb @ weights + bias)
上のプログラムの「@」は内積演算(行列積)を表しています。
順伝播を行ってみる
そして、モデルには、バッチ単位でデータを渡します。(今回は64枚の画像)「xb」には64枚の画像が入ってくるということです。
実際には、xbが(64×784)の行列、そして、weightsは(784×10)の行列となります。
bs = 64 # バッチサイズ
xb = x_train[0:bs] # ミニバッチサイズ
preds = model(xb) # 順伝播で予測
preds[0], preds.shape
print(preds[0], preds.shape)
tensor([-2.1850, -3.1384, -2.6034, -2.5708, -2.8709, -1.8867, -2.6006, -1.7180,
-1.8445, -2.6079], grad_fn=<SelectBackward>) torch.Size([64, 10])
「xb」には、訓練用画像「x_train」から0〜63枚目の画像(計64枚)が格納されます。
そして、その「xb」をmodelに入れることで、modelに計算をさせ、予測値をえます。(順伝播)
この予測値には、勾配も含まれていることが確認できます。これは後の逆伝播で利用します。
損失関数の定義
それでは、損失関数として「負の対数尤度関数(NLL)」を定義します。これは、Python標準の関数で作成しています。
def nll(input, target):
return -input[range(target.shape[0]), target].mean()
loss_func = nll
負の対数尤度関数は初耳ですが、とりあえず損失を求める一つの手段という感覚で進めていきます。
それでは、損失を確認してみます。
yb = y_train[0:bs]
print(loss_func(preds, yb))
tensor(2.4548, grad_fn=<NegBackward>)
同様に、このモデルの精度を確認してみます。accuracyという関数を作成しています。
def accuracy(out, yb):
preds = torch.argmax(out, dim=1)
return (preds == yb).float().mean()
print(accuracy(preds, yb))
tensor(0.0625)
訓練ループのプログラミング
これで、訓練ループを実装できます。それぞれのループでは、次のことを行います。
・ミニバッチでデータを選択
・モデルで予測
・誤差を求める
・逆伝播をして勾配を求める
・重みを更新する
重みを更新する際には、重みの更新の計算を追跡しないようにtorch.no_grad()で囲みます。また、次のループに入る前に勾配をリセットするためにgrad_zero()を呼び出す必要があります。
学習率は「lr」、訓練回数は「epochs」で指定します。
(Python標準のデバッガも利用できるとのことです。)
from IPython.core.debugger import set_trace
lr = 0.5 # 学習率
epochs = 2 # 訓練のエポック数
print("n")
print(n)
print("bs")
print(bs)
#エポック回数分繰り返す
for epoch in range(epochs):
batch_num=0
#ミニバッチのループ
#//は切り捨て除算
for i in range((n - 1) // bs + 1):#(49999÷64)+1=782.23の切り捨て回数分のミニバッチ
batch_num = batch_num + 1
# set_trace()
start_i = i * bs
end_i = start_i + bs
xb = x_train[start_i:end_i]
yb = y_train[start_i:end_i]
pred = model(xb)
loss = loss_func(pred, yb)
loss.backward()
#勾配降下法で重みの更新(式を手動で定義)
with torch.no_grad():
weights -= weights.grad * lr
bias -= bias.grad * lr
weights.grad.zero_()
bias.grad.zero_()
print("mini_batch_num")
print(batch_num)
ミニバッチのループの計算がわかりづらかったので、ミニバッチのカウントをする「batch_num」を個人的に追加しています。それ以外はチュートリアルのプログラムと同じです。
n
50000
bs
64
mini_batch_num
782
今回は、もっともミニマムなニューラルネットワークを作成しました。これは、ロジスティック回帰であり、隠れ層のないネットワークです。
訓練後の損失と精度を確認してみます。
print(loss_func(model(xb), yb), accuracy(model(xb), yb))
tensor(0.0834, grad_fn=<NegBackward>) tensor(1.)
まだ抽象的じゃないので、一つ直すにも扱いづらいプログラム構成です。
続きの記事はこちらです。