본문 바로가기

Python/PyTorch

[Pytorch] 신경망 모델 구성하기

신경망은 데이터에 대한 연산을 수행하는 계층(layer)/모듈(module)로 구성되어 있다. torch.nn네임스페이스는 신경망을 구성하는데 필요한 모든 구성 요소를 제공한다. PyTorch의 모든 모듈은 nn.Module의 하위 클래스(subclass)이다. 신경망은 다른 모듈(계층; layer)로 구성된 모듈이다. 이러한 중첩된 구조는 복잡한 아키텍처를 쉽게 구축하고 관리할 수 있다.

학습을 위한 장치 얻기

가능한 경우 GPU와 같은 하드웨어 가속기에서 모델을 학습하려고 한다. torch.cuda를 사용할 수 있는지 확인하고 그렇지 않으면 CPU를 계속 사용한다.

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

클래스 정의하기

신경망 모델을 nn.Module의 하위클래스로 정의하고, __init__에서 신경망 계층들을 초기화한다. nn.Module을 상속받은 모든 클래스는 forward메소드에 입력 데이터에 대한 연산들을 구현한다.

class NeuralNetwork(nn.Module):
    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()
        )

    def forward(self, x):
        x = self.flatten(x)
        logits = self.linear_relu_stack(x)
        return logits

 

NeuralNetwork의 인스턴스(instance)를 생성하고 이를 device로 이동한 뒤, 구조(structure)를 출력한다.

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

 

Out:

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)
    (5): ReLU()
  )
)

 

모델을 사용하기 위해 입력 데이터를 전달한다. 이는 일부 백그라운드 연산들과 함께 모델의 forward를 실행한다. model.forward()를 직접 호출하지 말자. (forward가 더 직접적인 표현이라고 생각해서 사용했었는데 아프로는 사요하지 말아야겠다.)

 

모델에 입력을 호출하면 각 분류(class)에 대한 원시(raw) 예측값이 있는 10-차원 텐서가 반환된다. 원시 예측값을 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}")

모델 계층(Layer)

FashionMNIST 모델의 계층들을 살펴자. 이를 설명하기 위해, 28x28 크기의 이미지 3개로 구성된 미니배치를 가져와, 신경망을 통과할 때 어떤 일이 발생하는지 알아보자.

input_image = torch.rand(3,28,28)
print(input_image.size())
# torch.Size([3, 28, 28])

nn.Flatten

nn.Flatten계층을 초기화하여 각 28x28의 2D 이미지를 784 픽셀 값을 갖는 연속된 배열로 변환한다. (dim=0의 미니배치 차원은 유지된다.)

flatten = nn.Flatten()
flat_image = flatten(input_image)
print(flat_image.size())
# torch.Size([3, 784])

nn.Linear

선형 계층은 저장된 가중치(weight)와 편향(bias)을 사용하여 입력에 선형 변환(linear transformation)을 적용하는 모듈입이다.

layer1 = nn.Linear(in_features=28*28, out_features=20)
hidden1 = layer1(flat_image)
print(hidden1.size())
# torch.Size([3, 20])

nn.ReLU

비선형 활성화(activation)는 모델의 입력과 출력 사이에 복잡한 관계(mapping)를 만든다. 비선형 활성화는 선형 변환 후에 적용되어 비선형성(nonlinearity)을 도입하고, 신경망이 다양한 현상을 학습할 수 있도록 돕는다.

 

이 모델에서는 nn.ReLU를 선형 계층들 사이에 사용하지만, 모델을 만들 때는 비선형성을 가진 다른 활성화를 도입할 수도 있다.

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

 

Out:

Before ReLU: tensor([[ 4.1802e-01, -2.0891e-01, -4.5899e-02, -3.3012e-01, -1.1608e-01,
          3.2973e-03,  3.2860e-03, -6.3945e-02,  3.4541e-01, -1.6342e-01,
         -7.2333e-01,  1.1853e-01, -6.0752e-02,  5.8124e-02, -8.5588e-01,
         -5.5783e-01, -5.4508e-02,  1.9725e-01,  1.3158e-01,  5.1474e-01],
        [ 1.8858e-01, -8.4326e-02, -3.4691e-01, -2.7519e-01, -2.3116e-01,
          6.7568e-02,  3.7079e-01, -1.7097e-01,  3.3664e-01, -1.7874e-01,
         -7.4063e-01,  1.9791e-01,  7.7300e-04,  1.5679e-01, -9.4724e-01,
         -6.6287e-01, -2.2564e-01, -2.8688e-01, -8.5341e-03, -9.9795e-04],
        [ 1.8742e-01, -2.6774e-01, -3.3508e-01, -5.2455e-01, -2.6225e-01,
         -2.3107e-01,  1.2318e-01, -3.0981e-01,  8.8801e-02, -2.9063e-01,
         -5.9034e-01,  1.2069e-01,  1.2879e-01,  2.2934e-01, -5.0672e-01,
         -6.9769e-01,  6.9980e-02, -1.2551e-01,  1.1706e-01,  4.8915e-01]],
       grad_fn=<AddmmBackward>)


After ReLU: tensor([[0.4180, 0.0000, 0.0000, 0.0000, 0.0000, 0.0033, 0.0033, 0.0000, 0.3454,
         0.0000, 0.0000, 0.1185, 0.0000, 0.0581, 0.0000, 0.0000, 0.0000, 0.1973,
         0.1316, 0.5147],
        [0.1886, 0.0000, 0.0000, 0.0000, 0.0000, 0.0676, 0.3708, 0.0000, 0.3366,
         0.0000, 0.0000, 0.1979, 0.0008, 0.1568, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000, 0.0000],
        [0.1874, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.1232, 0.0000, 0.0888,
         0.0000, 0.0000, 0.1207, 0.1288, 0.2293, 0.0000, 0.0000, 0.0700, 0.0000,
         0.1171, 0.4891]], grad_fn=<ReluBackward0>)

nn.Sequential

nn.Sequential은 순서를 갖는 모듈의 컨테이너이다. 데이터는 정의된 것과 같은 순서로 모든 모듈들을 통해 전달된다. 순차 컨테이너(sequential container)를 사용하여 아래의 seq_modules와 같은 신경망을 빠르게 만들 수 있다.

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

신경망의 마지막 선형 계층은 nn.Softmax모듈에 전달될 ([-infty, infty] 범위의 원시 값(raw value)인) logits 를 반환한다. logits는 모델의 각 분류(class)에 대한 예측 확률을 나타내도록 [0, 1] 범위로 비례하여 조정(scale)된다. dim매개변수는 값의 합이 1이 되는 차원을 나타낸다.

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

모델 매개변수

신경망 내부의 많은 계층들은 매개변수화(parameterize)된다. 즉, 학습 중에 최적화되는 가중치와 편향과 연관지어진다. nn.Module을 상속하면 모델 객체 내부의 모든 필드들이 자동으로 추적(track)되며, 모델의 parameters()및 named_parameters()메소드로 모든 매개변수에 접근할 수 있게 된다.

 

아래 예제에서는 각 매개변수들을 순회하며(iterate), 매개변수의 크기와 값을 출력한다.

print("Model structure: ", model, "\n\n")

for name, param in model.named_parameters():
    print(f"Layer: {name} | Size: {param.size()} | Values : {param[:2]} \n")

 

Out:

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)
    (5): ReLU()
  )
)


Layer: linear_relu_stack.0.weight | Size: torch.Size([512, 784]) | Values : tensor([[-0.0071, -0.0260,  0.0311,  ...,  0.0099, -0.0002,  0.0102],
        [ 0.0024, -0.0296,  0.0093,  ..., -0.0091,  0.0080,  0.0004]],
       device='cuda:0', grad_fn=<SliceBackward>)

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

Layer: linear_relu_stack.2.weight | Size: torch.Size([512, 512]) | Values : tensor([[-0.0435, -0.0110,  0.0110,  ...,  0.0429, -0.0348, -0.0375],
        [-0.0166, -0.0264,  0.0406,  ...,  0.0188,  0.0344, -0.0395]],
       device='cuda:0', grad_fn=<SliceBackward>)

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

Layer: linear_relu_stack.4.weight | Size: torch.Size([10, 512]) | Values : tensor([[ 3.3242e-02,  9.6526e-03, -2.1534e-02,  ...,  5.6896e-03,
         -7.0137e-03,  2.6827e-02],
        [-1.0889e-02,  1.2723e-05, -3.3012e-02,  ...,  7.6711e-03,
          6.8862e-03, -8.8320e-03]], device='cuda:0', grad_fn=<SliceBackward>)

Layer: linear_relu_stack.4.bias | Size: torch.Size([10]) | Values : tensor([-0.0014, -0.0122], device='cuda:0', grad_fn=<SliceBackward>)

 

참고: https://tutorials.pytorch.kr/beginner/basics/buildmodel_tutorial.html