🔍

PyTorch入門に入門してみた - その4

2023/08/20に公開

モデル構築

はじめに

前回のお話はこちらです。まだの方はぜひ...!
https://zenn.dev/morixx/articles/2874bd8fc54416
日本語版チュートリアルはこちら

ニューラルネットワークモデルの作り方

PyTorchでは、NNモデルを用意に作成できるよう、すべてのモジュールがnn.Moduleを継承しており、torch.nnで用意されている関数やクラスは全て独自のNNを構築するために必要な要素を網羅しています。

今回利用するモジュールのimport

import os
import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets, transforms

GPUデバイスの確認

前回も出現しましたが、NNモデルでの計算を高速に行いたいため、GPU(CUDA)を使用します。以下のコードでGPUを使えるかどうかみておきましょう(使えるなら容赦無く使います)

device = 'cuda' if torch.cuda.is_available() else 'cpu'
print('Using {} device'.format(device))

NNモデルのクラス定義

nn.Moduleを継承し、独自のネットワークを構築し、その後ネットワークレイヤを初期化メソッドで初期化します。とりあえず、クラスの初期化メソッドでネットワークを定義します。そのほかのメソッドとして順伝播のメソッドを持たせます

原著では、

class NeuralNetwork(nn.Module):の詳説

1. class NeuralNetwork(nn.Module):
これはクラス定義の際にnn.Moduleを継承してNeuralNetworkを作成するコードですね。

2. 初期化メソッド定義

def __init__(self):
	super(NeuralNetwork, self).__init__()
        self.flatten = nn.Flatten()
        self.linear_relu_stack = nn.Sequential(
            nn.Linear(28*28, 512),
            nn.ReLU(),
            nn.Linear(512, 512),
            nn.ReLU(),
            nn.Linear(512, 10),
            nn.ReLU()
        )

super(NeuralNetwork, self).init()
これはModelクラスの親クラス(この場合はnn.Module)の__init__メソッドを呼び出します。これにより、nn.Moduleクラスで定義されている初期化のプロセスが実行され、モデルが正しく動作するための内部的な設定が行われます。

self.flatten = nn.Flatten()
これは、2次元以上のテンソルを1次元テンソルにするクラスのインスタンスを定義しています。

self.linear_relu_stack = nn.Sequential(...
このコード以下では、linear_relu_stackというモデルが構築されています。nn.sequential内で複数モジュールをまとめて格納することができます。

次に構築したネットワークをdevice=cuda上に移動させてGPUで処理できるようにします。

model = NeuralNetwork().to(device)
print(model)

今回のモデルでは、入力データを与えると、各クラスの生の予測値を含む10次元のテンソルを返します。
nn.Softmaxモジュールにこの出力結果を与えることで、入力データが各クラスに属する確率の予測値を求めることができます。

X = torch.rand(1, 28, 28, device=device)
logits = model(X)
# print(X)
print(logits)
pred_probab = nn.Softmax(dim=1)(logits)
print(pred_probab)
y_pred = pred_probab.argmax(1)
print(y_pred)
"""
tensor([[0.0000, 0.0000, 0.0306, 0.0000, 0.0000, 0.0301, 0.0063, 0.0269, 0.0000,
         0.0000]], device='cuda:0', grad_fn=<ReluBackward0>)
tensor([[0.0991, 0.0991, 0.1021, 0.0991, 0.0991, 0.1021, 0.0997, 0.1018, 0.0991,
         0.0991]], device='cuda:0', grad_fn=<SoftmaxBackward0>)
tensor([2], device='cuda:0')
"""

モデルレイヤー

バッチサイズ3の画像を仮に定義しましょう。

input_image = torch.rand(3, 28, 28)
print(input_image.size())

"""
torch.Size([3, 28, 28])
"""

nn.Flatten()

nn.Flatten()レイヤーで2次元の画像を1次元Tensorに変換します。0次元目(今回は3)は、サンプル番号を示す次元で、この次元はnn.Flattenを通しても変化しません。

flatten = nn.Flatten()
flat_image = flatten(input_image)
print(flat_image.size())

"""
torch.Size([3, 784])
"""

nn.Linear()

nn.Linear()は、線形変換のモジュールで、引数として、inputのサイズとoutputのサイズを渡します。

layer1 = nn.Linear(in_features=28*28, out_features=20)
hidden1 = layer1(flat_image)
print(hidden1.size())

nn.ReLU()

これは非線形の活性化関数であるReLUを用いて非線形性を与えます。

print(f"Before ReLU: {hidden1}\n \n")
hidden1 = nn.ReLU()(hidden1)
print(f"After ReLU: {hidden1}")

"""
Before ReLU: tensor([[ 0.1669, -0.2034,  0.0643, -0.0435, -0.0955,  0.1845, -0.3133, -0.0645,
         -0.3299,  0.4222, -1.0488,  0.3029,  0.4046, -0.3916,  0.2352, -0.3289,
         -0.3688,  0.6674, -0.0969, -0.3645],
        [ 0.0417,  0.1629,  0.2122,  0.0110, -0.1328,  0.1351, -0.7230,  0.3554,
         -0.3740,  0.3045, -0.6062, -0.0778,  0.4882, -0.1422,  0.1498, -0.2785,
         -0.0507,  0.5722,  0.1246,  0.1802],
        [ 0.0109,  0.1882,  0.2240, -0.0627, -0.5239,  0.5350, -0.6056,  0.2645,
         -0.6044,  0.0847, -0.7515,  0.2747,  0.3523, -0.3462,  0.0146, -0.3359,
         -0.0645,  0.6193, -0.2379,  0.5842]], grad_fn=<AddmmBackward>)


After ReLU: tensor([[0.1669, 0.0000, 0.0643, 0.0000, 0.0000, 0.1845, 0.0000, 0.0000, 0.0000,
         0.4222, 0.0000, 0.3029, 0.4046, 0.0000, 0.2352, 0.0000, 0.0000, 0.6674,
         0.0000, 0.0000],
        [0.0417, 0.1629, 0.2122, 0.0110, 0.0000, 0.1351, 0.0000, 0.3554, 0.0000,
         0.3045, 0.0000, 0.0000, 0.4882, 0.0000, 0.1498, 0.0000, 0.0000, 0.5722,
         0.1246, 0.1802],
        [0.0109, 0.1882, 0.2240, 0.0000, 0.0000, 0.5350, 0.0000, 0.2645, 0.0000,
         0.0847, 0.0000, 0.2747, 0.3523, 0.0000, 0.0146, 0.0000, 0.0000, 0.6193,
         0.0000, 0.5842]], grad_fn=<ReluBackward0>)
"""

nn.Sequential()

nn.Sequential()は、各レイヤーのモジュールを格納する箱のようなものです。入力データはnn.Sequential()の中に定義された順番に伝わっていきます。

seq_modules = nn.Sequential(
    flatten,
    layer1,
    nn.ReLU(),
    nn.Linear(20, 10)
)
input_image = torch.rand(3,28,28)
logits = seq_modules(input_image)

nn.Softmax()

先ほど定義されたモデルの出力は10次元のTensorが出力されますが数値の範囲は制限されていません。これをsoftmax関数を通して各クラスである確率に変化させます。dimパラメータは次元を示しており、dim=1の次元で和を求めると確率の総和なので1になります。

softmax = nn.Softmax(dim=1)
pred_probab = softmax(logits)

モデルパラメータ

nnの種々のモジュールにはパラメータが多数存在しています。重みやバイアスなどが訓練時に最適化されます。モデルのパラメータ(各レイヤー)を確認できます。

for name, param in model.named_parameters():
    print(f"Layer: {name} | Size: {param.size()} | Values : {param[:2]} \n")
"""
Layer: linear_relu_stack.0.weight | Size: torch.Size([512, 784]) | Values : tensor([[-0.0271,  0.0032,  0.0215,  ..., -0.0280,  0.0147, -0.0189],
        [-0.0309, -0.0222, -0.0338,  ...,  0.0123, -0.0311,  0.0323]],
       device='cuda:0', grad_fn=<SliceBackward0>) 

Layer: linear_relu_stack.0.bias | Size: torch.Size([512]) | Values : tensor([ 0.0176, -0.0162], device='cuda:0', grad_fn=<SliceBackward0>) 

Layer: linear_relu_stack.2.weight | Size: torch.Size([512, 512]) | Values : tensor([[-2.7173e-02, -6.3195e-03, -4.0269e-02,  ..., -1.2803e-02,
          1.4853e-02, -4.1242e-02],
        [ 1.9707e-02,  4.3284e-02, -2.4815e-02,  ...,  1.4360e-02,
         -9.9756e-05,  3.7441e-02]], device='cuda:0', grad_fn=<SliceBackward0>) 

Layer: linear_relu_stack.2.bias | Size: torch.Size([512]) | Values : tensor([0.0381, 0.0106], device='cuda:0', grad_fn=<SliceBackward0>) 

Layer: linear_relu_stack.4.weight | Size: torch.Size([10, 512]) | Values : tensor([[-0.0095,  0.0074,  0.0002,  ..., -0.0429,  0.0091, -0.0152],
        [ 0.0073, -0.0229, -0.0144,  ..., -0.0043,  0.0246,  0.0165]],
       device='cuda:0', grad_fn=<SliceBackward0>) 

Layer: linear_relu_stack.4.bias | Size: torch.Size([10]) | Values : tensor([-0.0266, -0.0212], device='cuda:0', grad_fn=<SliceBackward0>) 
"""

今回はここで終わりです。とりあえずSequentialで各レイヤーを定義してモデルをすぐ実装できるのが便利だなと思いました。

Discussion