【初心者向け】基礎&実践プログラミング

初心者がつまづきやすいところ、最短で実力が身につく方法をお伝えします。

【PyTorch】サンプル⑨ 〜 動的グラフ 〜

f:id:AIProgrammer:20200413020244p:plain

目的

  • PyTorchの特徴の一つである動的グラフに挑戦する。

前準備

PyTorchのインストールはこちらから。

初めて、Google Colaboratoryを使いたい方は、こちらをご覧ください。

コマンドラインの「>>>」の行がPythonで実行するコマンドです。 それ以外の行は、コマンドの実行結果です。

>>> print("PyTorch")    # コマンドラインに打ち込む
PyTorch    # 出力結果

関連記事

チュートリアル

サンプル

計算グラフ

計算グラフには、静的グラフ動的グラフがあります。

TensorFlowやTheanoといったフレームワークは静的グラフを用いていますが、それに対してPyTorchは動的グラフを採用しています。

静的グラフと動的グラフの説明についてはこちらをご覧ください。

簡単に言うと、静的グラフは、事前にニューラルネットワーク構造を決めてから学習・推定を実行します。この時、一度決めたネットワーク構造は学習や推定時に変更することができません。

これに対し、動的グラフは、ネットワーク構造を定義する時点でネットワーク構造を固定する必要はなく、ネットワークに入力されるデータの様子をみて、ネットワークを構造を適宜変えることができます。故に、動的な計算グラフになります。

この動的グラフにより、柔軟な学習や推測が可能となります。

パッケージのインポート

import random
import torch

モデルの定義

【PyTorch】サンプル⑧ 〜 複雑なモデルの構築方法 〜で紹介した方法でニューラルネットワークモデルを構築していきます。

これまでは、レイヤ(層)を積み重ねるようにして、ニューラルネットワークモデルを定義していましたが、今回はネットワークモデルをクラスを使って定義します。クラスについては、こちらが参考になりました。

簡単のために、線形結合のみのネットワークを構築していきます。

クラスの名前をDynamicNetとします。引数torch.nn.Moduleはお決まりです。

class DynamicNet(torch.nn.Module):

コンストラクタで2層の線形結合層をインスタンス化します。

コンストラクタとインスタンスについては、こちらが参考になりました。

    def __init__(self, D_in, H, D_out):
        super(DynamicNet, self).__init__()
        self.input_linear = torch.nn.Linear(D_in, H)
        self.middle_linear = torch.nn.Linear(H, H)
        self.output_linear = torch.nn.Linear(H, D_out)

コンストラクタで定義された線形結合層をつかってモデルの予測値y_predを計算します。

テンソルの演算式は、forwardオブジェクトで自由に定義できます。

このチュートリアルでは、動的グラフの特徴を感じてもらうために、不思議なモデルを作っていきます。

    def forward(self, x):
        h_relu = self.input_linear(x).clamp(min=0)
        for _ in range(random.randint(0, 3)):
            h_relu = self.middle_linear(h_relu).clamp(min=0)
        y_pred = self.output_linear(h_relu)
        return y_pred

ポイントは、この部分です。

        for _ in range(random.randint(0, 3)):
            h_relu = self.middle_linear(h_relu).clamp(min=0)

この部分でやっていることは、forループの回数を0〜3回をランダムに決めて、中間層の線形結合層の数を随時変更しています。

使用するデータ

バッチサイズNを64、入力の次元D_inを1000、隠れ層の次元Hを100、出力の次元D_outを10とします。

# N is batch size; D_in is input dimension;
# H is hidden dimension; D_out is output dimension.
N, D_in, H, D_out = 64, 1000, 100, 10

入力xと予測したいyを乱数で定義します。

# Create random input and output data
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)

ニューラルネットワークのモデルの構築

先程作成したニューラルネットワークモデルのクラスDynamicNetをつかってモデルmodelを定義します。

model = DynamicNet(D_in, H, D_out)

損失関数の定義

二乗誤差をこのモデルの損失関数とします。

reductionのデフォルトはmeanですので、何も指定しなければtorch.nn.MSELossは平均二乗誤差を返します。

reduction=sumとした場合は、累積二乗誤差を算出します。

criterion = torch.nn.MSELoss(reduction='sum')

最適化関数の定義

optimパッケージを使って最適化関数としてMomentum SGDを定義します。

lrは学習率です。

optimizer = torch.optim.SGD(model.parameters(), lr=1e-4, momentum=0.9)

学習回数

学習回数は500回にします。

for t in range(500):

モデルへのデータ入出力 (Forward pass)

定義したニューラルネットワークモデルmodelへデータxを入力し、予測値y_predを取得します。

    y_pred = model(x)

損失(loss)の計算

定義した損失関数Momentum SGDで予測値y_predと真値yとの間の損失を計算します。

    loss = criterion(y_pred, y)
    if t % 100 == 99:
        print(t, loss.item())

勾配の初期化

逆伝播(backward)させる前に、モデルのパラメータが持つ勾配を0(ゼロ)で初期化します。

    optimizer.zero_grad()

勾配の計算

backwardメソッドでモデルパラメータ(Weight)の勾配を算出します。

    loss.backward()

パラメータ(Weight)の更新

stepメソッドでモデルのパラメータを更新します。

    optimizer.step()

実行

以下のコードを9_dynamic_graph.pyとして保存します。

9_dynamic_graph.py

import random
import torch


class DynamicNet(torch.nn.Module):
    def __init__(self, D_in, H, D_out):
        super(DynamicNet, self).__init__()
        self.input_linear = torch.nn.Linear(D_in, H)
        self.middle_linear = torch.nn.Linear(H, H)
        self.output_linear = torch.nn.Linear(H, D_out)

    def forward(self, x):
        h_relu = self.input_linear(x).clamp(min=0)
        for _ in range(random.randint(0, 3)):
            h_relu = self.middle_linear(h_relu).clamp(min=0)
        y_pred = self.output_linear(h_relu)
        return y_pred


# N is batch size; D_in is input dimension;
# H is hidden dimension; D_out is output dimension.
N, D_in, H, D_out = 64, 1000, 100, 10

# Create random Tensors to hold inputs and outputs
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)

# Construct our model by instantiating the class defined above
model = DynamicNet(D_in, H, D_out)

# Construct our loss function and an Optimizer.
criterion = torch.nn.MSELoss(reduction='sum')
optimizer = torch.optim.SGD(model.parameters(), lr=1e-4, momentum=0.9)
for t in range(500):
    # Forward pass: Compute predicted y by passing x to the model
    y_pred = model(x)

    # Compute and print loss
    loss = criterion(y_pred, y)
    if t % 100 == 99:
        print(t, loss.item())

    # Zero gradients, perform a backward pass, and update the weights.
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

コードを保存したら、実行してみましょう。

左の数字が学習回数、右の数値がモデルの推定値と実際の答えと二乗誤差です。

$ python3 9_dynamic_graph.py 
99 3.707427978515625
199 1.2958085536956787
299 1.837145209312439
399 0.9760080575942993
499 0.43565574288368225

参考のために、静的グラフの場合の結果を載せておきます。

動的グラフの方が損失の収束が早いことがわかります。

$ python3 9_static_graph.py
99 2.5070412158966064
199 0.04455046355724335
299 0.0011508199386298656
399 3.597284376155585e-05
499 1.361027443635976e-06



頑張れ!喝!!の代わりにB!ブックマークを押していただけるとただただうれしいです(^^)! ↓