신경망의 경우, 데이터에 대한 연산을 수행하는 계층(layer)/모듈(module)로 구성되어 있다.
torch.nn은 신경망을 구성하는데 필요한 요소를 제공한다. Pytorch의 모든 모듈은 nn.Module의 하위 클래스이다. 신경망은 다른 모듈로 구성된 모듈인데, 이러한 중첩된 구조는 복잡한 아키텍처를 쉽게 구축하고 관리할 수 있다.
또한, 신경망 학습을 위해 GPU or MPS같은 옵션을 설정할 수 있는데, 이를 설정하지 않으면, 기본적으로 CPU가 사용된다.
MPS의 경우는 주로 MAC OS에서 사용된다.
import os
import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
device = (
"cuda"
if torch.cuda.is_available()
else "mps"
if torch.backends.mps.is_available()
else "cpu"
)
print(f"Using {device} device")
신경망 모델을 nn.Module의 하위 클래스로 정의, __init__
에서 신경망 계층들을 초기화한다. nn.Module을 상속받은 모든 클래스는 forward 메소드에 입력 데이터에 대한 연산들을 구한다.
class NeuralNetwork(nn.Module):
def __init__(self):
super().__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),
)
def forward(self, x):
x = self.flatten(x)
logits = self.linear_relu_stack(x)
return logits
학습 진행 전, NeuralNetwork의 인스턴스를 생성하고, 이를 device(cuda, cpu, mps)로 이동한 뒤, 구조를 출력한다.
model = NeuralNetwork().to(device)
print(model)
--------------------------------------
NeuralNetwork(
(flatten): Flatten(start_dim=1, end_dim=-1)
(linear_relu_stack): Sequential(
(0): Linear(in_features=784, out_features=512, bias=True)
(1): ReLU()
(2): Linear(in_features=512, out_features=512, bias=True)
(3): ReLU()
(4): Linear(in_features=512, out_features=10, bias=True)
)
)
모델을 사용하기 위해 입력을 전달하는데, 이는 일부 백그라운드 연산들과 함께 모델의 forward를 수행한다. (model.forward()를 직접 호출하지 않는다!)
모델에 입력을 전달해 호출하면 2차원 텐서를 반환하는데, 2차원 텐서의 dim=0은 각 클래스에 대한 raw 예측값 10개가, dim=1에는 각 출력의 개별 값들이 해당한다. raw 예측값을 nn.Softmax 모듈의 인스턴스에 통과시켜 예측 확률을 얻는다.
X = torch.rand(1, 28, 28, device=device)
logits = model(X)
pred_probab = nn.Softmax(dim=1)(logits)
y_pred = pred_probab.argmax(1)
print(f"Predicted class: {y_pred}")
-----------------------------------------
Predicted class: tensor([7], device='cuda:0')
FashionMNIST 모델의 계층을 살펴본다면, 28x28 크기의 이미지 3개로 구성된 미니배치로, 신경망을 통과할 때를 확인한다.
input_image = torch.rand(3,28,28)
print(input_image.size())
----------------------------------
torch.Size([3, 28, 28])
nn.Flatten은 평탄화된 배열로 전환하는 역할을 수행하는 함수이다.
nn.Flatten을 적용해 784 픽셀을 갖는 연속된 배열로 반환한다.
flatten = nn.Flatten()
flat_image = flatten(input_image)
print(flat_image.size())
------------------------------------
torch.Size([3, 784])
nn.Linear(선형 계층)을 통해 저장된 weight, bias를 사용해 입력에 선형 변환을 적용한다.
layer1 = nn.Linear(in_features=28*28, out_features=20)
hidden1 = layer1(flat_image)
print(hidden1.size())
------------------------------------
torch.Size([3, 20])
nn.ReLU(Activation Function)을 적용해 모델의 입,출력 사이에 복잡한 관계를 만든다. 비선형 활성화의 경우, 선형 변환 후에 주로 적용되며, 비선형성을 도입하고 신경망이 다양한 현상을 학습할 수 있도록 돕는다.
실제는 ReLU, 하이퍼볼릭탄젠트, LeakyReLU 등 다양한 옵션을 사용할 수 있다.
print(f"Before ReLU: {hidden1}\n\n")
hidden1 = nn.ReLU()(hidden1)
print(f"After ReLU: {hidden1}")
----------------------------------
Before ReLU: tensor([[-0.0104, 0.0140, -0.1345, -0.2063, -0.0457, 0.1683, 0.8263, -0.0393,
0.1695, -0.1549, -0.2835, 0.5268, 0.1961, -0.1832, 0.1592, 0.1190,
-0.0760, 0.0419, 0.2624, 0.2088],
[-0.2475, -0.2524, 0.1190, -0.4282, -0.0164, 0.2265, 0.4653, -0.0714,
0.2430, 0.1137, 0.4633, 0.5006, 0.1300, 0.0761, 0.1044, 0.3631,
-0.3045, -0.2601, 0.6209, -0.1073],
[-0.2800, -0.4316, 0.0410, 0.1125, -0.0112, 0.2403, 0.3667, 0.1970,
0.2418, -0.2051, -0.0914, 0.5844, 0.1634, 0.0156, -0.2390, 0.3897,
-0.0655, -0.0901, 0.5191, -0.3114]], grad_fn=<AddmmBackward0>)
After ReLU: tensor([[0.0000, 0.0140, 0.0000, 0.0000, 0.0000, 0.1683, 0.8263, 0.0000, 0.1695,
0.0000, 0.0000, 0.5268, 0.1961, 0.0000, 0.1592, 0.1190, 0.0000, 0.0419,
0.2624, 0.2088],
[0.0000, 0.0000, 0.1190, 0.0000, 0.0000, 0.2265, 0.4653, 0.0000, 0.2430,
0.1137, 0.4633, 0.5006, 0.1300, 0.0761, 0.1044, 0.3631, 0.0000, 0.0000,
0.6209, 0.0000],
[0.0000, 0.0000, 0.0410, 0.1125, 0.0000, 0.2403, 0.3667, 0.1970, 0.2418,
0.0000, 0.0000, 0.5844, 0.1634, 0.0156, 0.0000, 0.3897, 0.0000, 0.0000,
순서를 갖는 모듈의 컨테이너를 말하고, 순서대로 수행하게끔 유도한다. Sequential에서 정의된 것과 같은 순서로 모든 모듈을 통해 전달된다. Sequential Container을 통해 신경망을 빠르게 구축할 수 있다.
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 모듈에 전달될 ([-infty, infty] 범위의 원시 값(raw value)인) logits 를 반환한다. logits는 모델의 각 클래스에 대한 예측 확률을 나타내도록 [0, 1] 범위로 비례하여 조정되고, dim
매개변수는 값의 합이 1이 되는 차원을 나타냅니다.
softmax = nn.Softmax(dim=1)
pred_probab = softmax(logits)
nn.Module을 상속하면 모델 객체 내부의 모든 필드들이 자동으로 추적되며, parameters(), named_parameters() 메소드로 모든 매개변수에 접근할 수 있게 된다.
해당 예제에서는 각 매개변수들을 순회하며 매개변수의 크기와 값을 출력한다.
print(f"Model structure: {model}\n\n")
for name, param in model.named_parameters():
print(f"Layer: {name} | Size: {param.size()} | Values : {param[:2]} \n")
--------------------------------------------
Model structure: NeuralNetwork(
(flatten): Flatten(start_dim=1, end_dim=-1)
(linear_relu_stack): Sequential(
(0): Linear(in_features=784, out_features=512, bias=True)
(1): ReLU()
(2): Linear(in_features=512, out_features=512, bias=True)
(3): ReLU()
(4): Linear(in_features=512, out_features=10, bias=True)
)
)
Layer: linear_relu_stack.0.weight | Size: torch.Size([512, 784]) | Values : tensor([[ 0.0262, 0.0165, -0.0289, ..., -0.0029, 0.0231, 0.0286],
[-0.0257, 0.0352, 0.0302, ..., 0.0138, 0.0338, -0.0297]],
device='cuda:0', grad_fn=<SliceBackward0>)
Layer: linear_relu_stack.0.bias | Size: torch.Size([512]) | Values : tensor([0.0047, 0.0353], device='cuda:0', grad_fn=<SliceBackward0>)