PyTorch入門に入門してみた - その4
モデル構築
はじめに
前回のお話はこちらです。まだの方はぜひ...!こちら
日本語版チュートリアルはニューラルネットワークモデルの作り方
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