Deep learning

PyTorch를 사용한 신경망 구축

taeeyeong 2024. 5. 4. 18:34

신경망은 데이터에 대한 연산을 수행하는 레이어 또는 모듈로 구성됩니다. PyTorch의 `torch.nn` 네임스페이스는 자신만의 신경망을 구축하는 데 필요한 모든 기본 요소를 제공합니다. PyTorch의 모든 모듈은 `nn.Module`을 상속받으며, 신경망 자체도 다른 모듈(레이어)로 구성된 모듈입니다. 이러한 중첩 구조는 복잡한 아키텍처를 쉽게 구축하고 관리할 수 있게 해줍니다.

 


하드웨어 가속기를 사용한 훈련 설정
모델을 훈련할 때 가능하다면 GPU 또는 MPS(애플의 Metal Performance Shaders)와 같은 하드웨어 가속기를 사용하면 좋습니다. 이를 위해 PyTorch에서는 `torch.cuda`와 `torch.backends.mps`의 사용 가능 여부를 확인하여 적절한 디바이스를 선택할 수 있습니다. 사용 가능한 가속기가 없을 경우 CPU를 사용합니다.

import torch

device = (
    "cuda" if torch.cuda.is_available() else
    "mps" if torch.backends.mps.is_available() else
    "cpu"
)
print(f"Using {device} device")



이 코드는 사용 가능한 디바이스를 확인하고, 해당 디바이스를 사용 설정하는 과정을 보여줍니다. 이렇게 디바이스를 설정함으로써, 모델의 훈련 속도를 향상시킬 수 있습니다.

FashionMNIST 데이터셋을 사용한 신경망 구축
FashionMNIST 데이터셋은 Zalando의 패션 아이템 이미지로 구성된 데이터셋으로, 28x28 픽셀의 흑백 이미지들로 이루어져 있습니다. 이 데이터셋을 사용하여 이미지 분류 모델을 훈련하고 테스트할 수 있습니다. 다음 단계에서는 이 데이터셋에 적합한 신경망 아키텍처를 구축하고, Data Loader를 설정하여 훈련 과정을 진행하겠습니다.

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

# 데이터 변환 설정
transform = transforms.Compose([
    transforms.ToTensor(),  # 이미지를 PyTorch 텐서로 변환
    transforms.Normalize((0.5,), (0.5,))  # 정규화: 평균 0.5, 표준편차 0.5
])

# FashionMNIST 데이터셋 로드
train_dataset = datasets.FashionMNIST(root='./data', train=True, download=True, transform=transform)
test_dataset = datasets.FashionMNIST(root='./data', train=False, download=True, transform=transform)

# DataLoader 생성
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False)



위 코드를 통해 데이터셋을 로드하고 데이터 로더를 설정하는 과정을 설명하였습니다. 실제로 신경망을 설계하고, 이를 통해 이미지 분류 태스크를 수행하는 방법을 다뤄보겠습니다.

 

정의된 클래스를 이용한 신경망 구축
신경망을 정의하기 위해 `nn.Module`을 상속받는 클래스를 생성하고, `__init__` 메서드에서 신경망 레이어들을 초기화합니다. 모든 `nn.Module` 서브클래스는 입력 데이터에 대한 연산을 `forward` 메서드에서 구현합니다.

신경망 클래스 정의
아래의 예시는 FashionMNIST 데이터셋을 위한 신경망 구조를 정의하는 방법을 보여줍니다. 이 구조는 이미지를 평탄화하는 레이어와 세 개의 선형 레이어를 포함한 `nn.Sequential` 컨테이너를 사용합니다. 각 선형 레이어 사이에는 활성화 함수로 ReLU가 사용됩니다.

import torch
from torch import nn

class NeuralNetwork(nn.Module):
    def __init__(self):
        super().__init__()
        self.flatten = nn.Flatten()
        self.linear_relu_stack = nn.Sequential(
            nn.Linear(28*28, 512),  # 첫 번째 선형 레이어: 입력 크기는 784, 출력 크기는 512
            nn.ReLU(),              # ReLU 활성화 함수
            nn.Linear(512, 512),    # 두 번째 선형 레이어: 입력 및 출력 크기 모두 512
            nn.ReLU(),              # ReLU 활성화 함수
            nn.Linear(512, 10),     # 세 번째 선형 레이어: 입력 크기는 512, 출력 크기는 10 (클래스 수)
        )

    def forward(self, x):
        x = self.flatten(x)             # 입력 이미지를 평탄화
        logits = self.linear_relu_stack(x)  # 순차적 레이어를 통과
        return logits



모델 인스턴스 생성 및 디바이스 할당
신경망 인스턴스를 생성하고, 사용 가능한 디바이스(GPU, MPS, 또는 CPU)로 모델을 이동한 후, 모델의 구조를 출력합니다.

device = torch.device("cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu")
model = NeuralNetwork().to(device)
print(model)


이 코드는 모델이 올바르게 디바이스에 할당되었는지 확인하고, 모델의 구조를 확인할 수 있게 해줍니다. 이러한 단계는 모델의 초기 구성을 검토하고, 훈련 전에 모든 것이 제대로 설정되었는지 확인하는 데 유용합니다.

이처럼 클래스를 정의하여 신경망을 구축하는 접근 방식은 코드의 가독성과 모듈성을 높이며, 다양한 데이터셋에 적용할 수 있도록 유연성을 제공합니다.

 

nn.Flatten 설명
`nn.Flatten` 레이어는 2D 이미지를 1D 배열로 변환하는데 사용됩니다. 이 레이어는 각 28x28 이미지를 784 픽셀 값의 연속 배열로 변환하는 역할을 합니다. 여기서 중요한 점은 미니배치 차원(dim=0)이 유지된다는 것입니다. 즉, 배치에 있는 각 이미지는 평탄화된 후에도 독립적인 항목으로 관리됩니다.

nn.Flatten 사용 예시
다음은 `nn.Flatten`을 사용하여 2D 이미지를 1D 텐서로 변환하는 과정을 보여주는 예시 코드입니다.

import torch
from torch import nn

# 예제 입력 이미지 생성 (배치 크기 = 10, 채널 = 1, 높이 = 28, 너비 = 28)
input_image = torch.rand(10, 1, 28, 28)

# Flatten 레이어 초기화
flatten = nn.Flatten()

# 이미지 평탄화
flat_image = flatten(input_image)

# 평탄화된 이미지의 크기 출력
print(flat_image.size())  # 결과는 [10, 784]


이 코드에서 `input_image`는 10개의 28x28 크기의 이미지를 포함하는 텐서입니다. `nn.Flatten()`을 적용하면, 이 텐서는 `[10, 784]` 크기의 2D 텐서로 변환됩니다. 여기서 첫 번째 차원은 미니배치를 나타내며, 두 번째 차원은 평탄화된 픽셀 값입니다.

`nn.Flatten`의 역할
`nn.Flatten`은 신경망에서 데이터를 처리하기 전에 데이터의 형태를 적절히 조정할 필요가 있을 때 유용하게 사용됩니다. 특히, 컨볼루션 레이어를 거친 후의 특성 맵을 완전 연결 레이어(fully connected layer)에 입력하기 위해 평탄화하는 경우에 필수적입니다. 이를 통해 신경망의 다음 단계에서 데이터를 더 쉽게 처리할 수 있습니다.

 

 nn.Linear 레이어 설명
`nn.Linear`는 PyTorch에서 가장 기본적인 레이어 중 하나로, 입력 데이터에 선형 변환을 적용하는 모듈입니다. 이 레이어는 내부에 저장된 가중치와 편향을 사용하여 입력 데이터에 대해 선형 변환을 수행합니다. 선형 레이어는 신경망에서 데이터의 차원을 변환하고, 특성을 결합하여 새로운 표현을 학습하는 데 사용됩니다.

nn.Linear 사용 예시
다음은 `nn.Linear` 레이어를 사용하여 입력 이미지를 변환하는 과정을 보여주는 예시 코드입니다.

import torch
from torch import nn

# 입력 이미지 (평탄화된 이미지, 크기: [배치 크기, 784])
input_image = torch.rand(10, 784)  # 배치 크기를 10으로 가정

# 선형 레이어 초기화 (입력 특성 784개, 출력 특성 20개)
layer1 = nn.Linear(in_features=28*28, out_features=20)

# 선형 변환 적용
hidden1 = layer1(input_image)

# 변환된 특성의 크기 출력
print(hidden1.size())  # 결과는 [10, 20]


이 코드에서 `nn.Linear` 레이어는 입력 차원이 784(28x28 이미지 평탄화)이고, 출력 차원이 20인 선형 변환을 정의합니다. `layer1`을 통과한 후, 결과 텐서 `hidden1`은 `[10, 20]`의 크기를 갖습니다. 여기서 첫 번째 차원은 미니배치를 나타내고, 두 번째 차원은 변환된 특성 차원입니다.

`nn.Linear`의 역할
`nn.Linear` 레이어는 신경망의 다양한 계층에서 기본적인 특성 추출과 변환을 담당합니다. 이 레이어를 통해 네트워크는 비선형 문제를 해결하기 위해 필요한 복잡한 함수를 근사하는 능력을 키울 수 있습니다. 또한, 여러 `nn.Linear` 레이어를 적층함으로써 더 깊은 신경망을 구성할 수 있으며, 각 레이어는 다양한 수준의 특성을 학습할 수 있습니다. 이는 딥러닝에서 중요한 개념으로, 데이터로부터 더 복잡하고 추상적인 패턴을 추출할 수 있게 해 줍니다.

 

nn.ReLU 설명
`nn.ReLU`는 신경망에서 널리 사용되는 비선형 활성화 함수 중 하나입니다. ReLU(Rectified Linear Unit)는 입력값이 0보다 크면 그 값을 그대로 출력하고, 0 이하면 0을 출력합니다. 이 간단한 함수는 신경망이 복잡한 입력과 출력 간의 매핑을 생성하는 데 중요한 역할을 합니다. 선형 변환 후에 ReLU와 같은 비선형 활성화 함수를 적용함으로써, 신경망은 다양한 현상을 학습할 수 있게 됩니다.

nn.ReLU 사용 예시
아래의 코드는 선형 레이어를 통과한 후 ReLU 활성화 함수를 적용하는 과정을 보여줍니다.

import torch
from torch import nn

# 선형 레이어의 출력 가정 (배치 크기 = 10, 특성 = 20)
hidden1 = torch.tensor([[0.5, -0.2, 0.0], [-0.3, 0.8, -0.1]], dtype=torch.float32)

print(f"Before ReLU: {hidden1}\n\n")

# ReLU 활성화 적용
hidden1 = nn.ReLU()(hidden1)

print(f"After ReLU: {hidden1}")


이 코드에서 `hidden1`에는 선형 레이어를 통과한 예제 텐서가 들어 있습니다. `nn.ReLU`를 적용한 후, 음수 값은 모두 0으로 변환되며, 양수 값은 그대로 유지됩니다. 결과적으로 비선형성이 도입되어 신경망이 더 복잡한 함수를 학습할 수 있는 기능이 강화됩니다.

 

출력결과 예시

Before ReLU: tensor([[ 0.5000, -0.2000,  0.0000],
                     [-0.3000,  0.8000, -0.1000]])

After ReLU: tensor([[0.5000, 0.0000, 0.0000],
                    [0.0000, 0.8000, 0.0000]])

 


ReLU의 역할과 중요성
ReLU는 학습 과정을 개선하고, 더 빠른 수렴을 도울 수 있는 비교적 간단하면서도 효율적인 함수입니다. 그 결과, ReLU는 깊은 신경망에서 특히 자주 사용됩니다. 또한, 그라디언트 소실 문제(vanishing gradient problem)를 완화하는 데 도움을 줍니다. 이는 ReLU가 음수 입력에 대해 0을 출력하기 때문에, 양수 입력에 대한 그라디언트가 변화하지 않고 네트워크를 통해 자유롭게 흐를 수 있기 때문입니다. 다른 활성화 함수로는 Sigmoid나 Tanh가 있으며, 이들도 비슷한 역할을 하지만 다른 특성과 장단점을 가지고 있습니다.

 

PyTorch의 nn.Sequential과 nn.Softmax 사용하기

 nn.Sequential
`nn.Sequential`은 모듈의 순서가 지정된 컨테이너로, 정의된 순서대로 데이터가 모든 모듈을 통과합니다. 이 컨테이너를 사용하여 빠르게 네트워크를 구성할 수 있으며, 간단한 순차적 네트워크를 손쉽게 구축할 수 있습니다.

import torch
from torch import nn

# 모듈 초기화
flatten = nn.Flatten()
layer1 = nn.Linear(28*28, 20)

# 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
신경망의 마지막 선형 레이어는 로짓을 반환합니다. 로짓은 [-∞, ∞] 범위의 원시 값으로, `nn.Softmax` 모듈을 통해 [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")

 

이 코드는 모델의 각 레이어 및 해당 매개변수의 크기와 값을 출력하여, 모델 구성과 각 레이어가 어떻게 구성되어 있는지 파악하는 데 도움을 줍니다. 이러한 정보는 모델의 성능을 이해하고, 필요에 따라 튜닝하는 데 유용합니다.

 

지금까지 PyTorch를 활용한 신경망 모델 구축의 기본적인 구조를 살펴보았습니다.

모든 내용은 PyTorch 공식문서를 참고하였습니다.