【PyTorchチュートリアル】WHAT IS TORCH.NN REALLY?の2、スクラッチでニューラルネットワーク

スクラッチでニューラルネットを作成すると、データの流れの確認になります。

シンプルな例で仕組みを知ると応用できるのかもね。

PyTorchチュートリアル「WHAT IS TORCH.NN REALLY?」のスクラッチでニューラルネットを構築する項目を進めていきます。今回は、PyTorchのTensorのみを利用していますので、ニューラルネットが実際にどのようにTensorを扱っているのか確認することもできたと感じます。

前回のチュートリアル記事はこちらをご参考ください。

ぱんだクリップ
【PyTorchチュートリアル】WHAT IS TORCH.NN REALLY?の1、MNISTデータをセットアップ | ぱんだクリップ PyTorchのチュートリアルをやるとすごく理解が深まります。 PyTorchにはまってますね。 PyTorchのチュートリアルを順番に進めていっています。次のチュートリアルは、「WHA...

このチュートリアルでは、ニューラルネットワークについての基本的な知識があることが前提となっています。

こんな人の役にたつかも

・機械学習プログラミングの勉強をしている人

・PyTorchのチュートリアルをやっている人

目次

ネットワーク構造の準備プログラム

こんかいのスクラッチで作成するニューラルネットワークは最終的に、中間層のないシンプルなネットワークの構築となります。

初期化

まずはニューラルネットワークのパラメータを保持する変数を作成します。

PyTorchの提供するメソッドでランダムな値で埋められたテンソルや、ゼロで埋められたテンソルを作成することができます。(randnやzeros)

そして、テンソルをニューラルネットワークの「重みやバイアス」といったパラメータにすることができます。

Tensorのもっとも重要な特徴は、勾配が計算できるという点です。テンソルに行われた計算を保持できるので、勾配を自動的に計算することができます。(これはTensorのautogradという機能で実現しています。)

ぱんだクリップ
【AIプログラミング】PyTorchのチュートリアル、自動微分についてPart2-1 | ぱんだクリップ 計算グラフを勉強してなんとなく自動微分がわかりそうな気がします。 理論的な基礎がないとチュートリアルも難しいね。 前回、計算グラフを勉強しました。計算グラフを勉強...

次のプログラムで、ニューラルネットワークの「重み」と「バイアス」パラメータを作成します。

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.)

まだ抽象的じゃないので、一つ直すにも扱いづらいプログラム構成です。

続きの記事はこちらです。

ぱんだクリップ
【PyTorchチュートリアル】WHAT IS TORCH.NN REALLY?の3、プログラムのリファクタリング1 | ぱんだクリップ 前回のスクラッチコードをPyTorchのモジュールで置き換えていきます。ボリュームがあったので、今回は半分だけやりました。 リファクタリングしてプログラムが短くわかりや...
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次