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

1_プログラミング

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

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

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

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

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

タイトルとURLをコピーしました