このガイドでは、PyTorchの基本的な概念について学びます:テンソル、自動微分、ニューラルネットワークモジュール、オプティマイザ、カスタムモジュール、および動的計算グラフ。各トピックには実行可能なコードサンプルが含まれており、NumPyから始めてPyTorchの高度な機能に移行する方法を示します。
1. PyTorchの主要な特徴
- テンソル(Tensor): NumPyのndarrayと似ていますが、GPU上で動作し、計算を高速化します。
- 自動微分(Autograd): 手動でバックプロパゲーションを書くことなく、勾配を自動的に計算します。
例: 全結合ReLUネットワークを使用してランダムデータをフィッティング
2. NumPyを使ったシンプルな二層ネットワーク
ここでは、ディープラーニングフレームワークを使わずに、前進伝播、損失計算、逆伝播、およびパラメータ更新を手動で実装します。
import numpy as np
# ハイパーパラメータ
N, D_in, H, D_out = 64, 1000, 100, 10
# データ生成
x = np.random.randn(N, D_in)
y = np.random.randn(N, D_out)
w1 = np.random.randn(D_in, H)
w2 = np.random.randn(H, D_out)
learning_rate = 1e-6
for t in range(500):
# 前進伝播
h = x.dot(w1)
h_relu = np.maximum(h, 0)
y_pred = h_relu.dot(w2)
loss = np.square(y_pred - y).sum()
if t % 100 == 0:
print(t, loss)
# 逆伝播
grad_y_pred = 2.0 * (y_pred - y)
grad_w2 = h_relu.T.dot(grad_y_pred)
grad_h_relu = grad_y_pred.dot(w2.T)
grad_h = grad_h_relu.copy()
grad_h[h < 0] = 0
grad_w1 = x.T.dot(grad_h)
# パラメータ更新
w1 -= learning_rate * grad_w1
w2 -= learning_rate * grad_w2
3. PyTorchテンソルでの実装
PyTorchテンソルはNumPyと類似した構文を持ちますが、デバイスを指定してGPU上で動作させることができます。
import torch
dtype = torch.float
device = torch.device("cpu")
N, D_in, H, D_out = 64, 1000, 100, 10
x = torch.randn(N, D_in, device=device, dtype=dtype)
y = torch.randn(N, D_out, device=device, dtype=dtype)
w1 = torch.randn(D_in, H, device=device, dtype=dtype)
w2 = torch.randn(H, D_out, device=device, dtype=dtype)
learning_rate = 1e-6
for t in range(500):
h = x.mm(w1)
h_relu = h.clamp(min=0)
y_pred = h_relu.mm(w2)
loss = (y_pred - y).pow(2).sum().item()
if t % 100 == 0:
print(t, loss)
grad_y_pred = 2.0 * (y_pred - y)
grad_w2 = h_relu.t().mm(grad_y_pred)
grad_h_relu = grad_y_pred.mm(w2.t())
grad_h = grad_h_relu.clone()
grad_h[h < 0] = 0
grad_w1 = x.t().mm(grad_h)
w1 -= learning_rate * grad_w1
w2 -= learning_rate * grad_w2
4. 自動微分(Autograd)
PyTorchの自動微分機能を使って、手動で逆伝播を書かずに勾配を計算できます。
import torch
dtype = torch.float
device = torch.device("cpu")
N, D_in, H, D_out = 64, 1000, 100, 10
x = torch.randn(N, D_in, device=device, dtype=dtype)
y = torch.randn(N, D_out, device=device, dtype=dtype)
w1 = torch.randn(D_in, H, device=device, dtype=dtype, requires_grad=True)
w2 = torch.randn(H, D_out, device=device, dtype=dtype, requires_grad=True)
learning_rate = 1e-6
for t in range(500):
y_pred = x.mm(w1).clamp(min=0).mm(w2)
loss = (y_pred - y).pow(2).sum()
if t % 100 == 0:
print(t, loss.item())
loss.backward()
with torch.no_grad():
w1 -= learning_rate * w1.grad
w2 -= learning_rate * w2.grad
w1.grad.zero_()
w2.grad.zero_()
5. カスタム自動微分関数
自前のReLU関数を作成します。
import torch
class CustomReLU(torch.autograd.Function):
@staticmethod
def forward(ctx, input_tensor):
ctx.save_for_backward(input_tensor)
return input_tensor.clamp(min=0)
@staticmethod
def backward(ctx, grad_output):
input_tensor, = ctx.saved_tensors
grad_input = grad_output.clone()
grad_input[input_tensor < 0] = 0
return grad_input
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
N, D_in, H, D_out = 64, 1000, 100, 10
x = torch.randn(N, D_in, device=device)
y = torch.randn(N, D_out, device=device)
w1 = torch.randn(D_in, H, device=device, requires_grad=True)
w2 = torch.randn(H, D_out, device=device, requires_grad=True)
learning_rate = 1e-6
for t in range(500):
y_pred = CustomReLU.apply(x.mm(w1)).mm(w2)
loss = (y_pred - y).pow(2).sum()
if t % 100 == 0:
print(t, loss.item())
loss.backward()
with torch.no_grad():
w1 -= learning_rate * w1.grad
w2 -= learning_rate * w2.grad
w1.grad.zero_()
w2.grad.zero_()
6. nn.Sequentialと最適化アルゴリズムの使用
nn.Sequentialと最適化アルゴリズムを使用してモデルを簡潔に定義します。
import torch
N, D_in, H, D_out = 64, 1000, 100, 10
model = torch.nn.Sequential(
torch.nn.Linear(D_in, H),
torch.nn.ReLU(),
torch.nn.Linear(H, D_out),
)
loss_fn = torch.nn.MSELoss(reduction='sum')
optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)
for t in range(500):
y_pred = model(x)
loss = loss_fn(y_pred, y)
if t % 100 == 0:
print(t, loss.item())
optimizer.zero_grad()
loss.backward()
optimizer.step()
7. nn.Moduleのサブクラス作成
複雑なモデル構造のためにnn.Moduleを継承します。
import torch
class TwoLayerNet(torch.nn.Module):
def __init__(self, D_in, H, D_out):
super(TwoLayerNet, self).__init__()
self.fc1 = torch.nn.Linear(D_in, H)
self.fc2 = torch.nn.Linear(H, D_out)
def forward(self, x):
h_relu = self.fc1(x).clamp(min=0)
y_pred = self.fc2(h_relu)
return y_pred
N, D_in, H, D_out = 64, 1000, 100, 10
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)
model = TwoLayerNet(D_in, H, D_out)
loss_fn = torch.nn.MSELoss(reduction='sum')
optimizer = torch.optim.SGD(model.parameters(), lr=1e-4)
for t in range(500):
y_pred = model(x)
loss = loss_fn(y_pred, y)
if t % 100 == 0:
print(t, loss.item())
optimizer.zero_grad()
loss.backward()
optimizer.step()