티스토리 뷰

728x90
반응형

목차

     

    지난 글에선 다소 뜬금없이 파이토치에 대해 알아보았다.

     

    [PyTorch]갑자기 써보는, PyTorch에 대하여

     

    [PyTorch]갑자기 써보는, PyTorch에 대하여

    목차 PyTorch는 딥러닝에 사용되는 오픈소스 라이브러리이다. 이름처럼 파이썬과 궁합이 좋으며, Numpy 등을 텐서로 가져올 수 있고 GPU 가속 기능(CUDA)을 지원한다. 이 글에선 파이토치를 학습하기

    gnidinger.tistory.com

     

    이번 글에선 그에 이어서, 실제 파이썬 환경에서 파이토치를 설정하는 방법과

     

    가장 간단하고 널리 알려진 MNIST 실습을 구현하며 각 코드가 어떤 것을 의미하는지 자세히 파볼 생각이다.

     

    아주아주 너무너무 간단한 튜토리얼이기 때문에 조금이라도 아는 분은 읽을 필요가 없다.

     

    그럼 시작!

     

    Configuration

     

    당연히 파이썬은 설치되어 있다고 가정하고 진행하겠다.

     

    추가로, 나는 NVIDIA GPU가 없는 맥미니에서 테스트를 구성하기 때문에

     

    CUDA를 지원하지 않는 버전으로 실습하겠다.

     

    먼저 터미널에 아래와 같은 명령을 입력한다.

    pip install torch torchvision torchaudio -f https://download.pytorch.org/whl/cpu/torch_stable.html

    그다음?

     

    그다음은 없다. 이거 하나로 오늘 실습에 필요한 모든 라이브러리가 설치된다.

     

    Implementation

     

    시작하기 전에 먼저, 이 글에서 완성할 코드는 아래와 같이 생겼다.

    import torch
    import torch.nn as nn
    import torch.optim as optim
    import torchvision.datasets as datasets
    import torchvision.transforms as transforms
    from torch.utils.data import DataLoader
    
    train_data = datasets.MNIST(root="data", train=True, download=True, transform=transforms.ToTensor())
    test_data = datasets.MNIST(root="data", train=False, transform=transforms.ToTensor())
    
    train_loader = DataLoader(train_data, batch_size=64, shuffle=True)
    test_loader = DataLoader(test_data, batch_size=64, shuffle=False)
    
    
    class MLP(nn.Module):
        def __init__(self):
            super(MLP, self).__init__()
            self.layers = nn.Sequential(
                nn.Linear(28 * 28, 64), nn.ReLU(), nn.Linear(64, 64), nn.ReLU(), nn.Linear(64, 10)
            )
    
        def forward(self, x):
            x = x.view(x.size(0), -1)
            return self.layers(x)
    
    
    model = MLP()
    
    criterion = nn.CrossEntropyLoss()
    optimizer = optim.Adam(model.parameters(), lr=0.001)
    
    for epoch in range(5):
        for batch_idx, (data, target) in enumerate(train_loader):
            optimizer.zero_grad()
            output = model(data)
            loss = criterion(output, target)
            loss.backward()
            optimizer.step()
        print(f"Epoch {epoch}: Loss = {loss.item()}")
    
    correct = 0
    total = 0
    
    with torch.no_grad():
        for data, target in test_loader:
            output = model(data)
            _, predicted = torch.max(output.data, 1)
            total += target.size(0)
            correct += (predicted == target).sum().item()
    
    print(f"Accuracy: {100 * correct / total}%")

    50줄 남짓의 굉장히 단순한 코드이며, 그 결과는 대략 아래와 같은 모습을 가진다.

    Epoch 0: Loss = 0.19801555573940277
    Epoch 1: Loss = 0.1288914531469345
    Epoch 2: Loss = 0.22620297968387604
    Epoch 3: Loss = 0.13473093509674072
    Epoch 4: Loss = 0.0723961591720581
    Accuracy: 97.21%

    그럼 계속해서 코드를 위에서부터 차례차례 뜯어보자.

     

    Library

     

    import torch
    import torch.nn as nn
    import torch.optim as optim
    import torchvision.datasets as datasets
    import torchvision.transforms as transforms
    from torch.utils.data import DataLoader

    먼저 실습에 필요한 라이브러리를 임포트 한다.

     

    • torch: PyTorch의 기본 라이브러리
    • torch.nn: 신경망을 구성하기 위한 다양한 데이터 구조나 레이어 등이 정의되어 있는 라이브러리
    • torch.optim: 다양한 최적화 알고리즘이 정의되어 있는 라이브러리(예: SGD, Adam).
    • torchvision.datasets: 유명한 데이터셋을 쉽게 불러올 수 있는 라이브러리 (예: MNIST).
    • torchvision.transforms: 이미지 전처리를 위한 도구.
    • DataLoader: 데이터셋을 쉽게 사용할 수 있게 해주는 유틸리티 클래스.

     

    Data Loading

     

    train_data = datasets.MNIST(root="data", train=True, download=True, transform=transforms.ToTensor())
    test_data = datasets.MNIST(root="data", train=False, transform=transforms.ToTensor())

    여기선 지난 글에 소개한 MNIST 훈련 데이터와 테스트 데이터를 다운로드하고, 픽셀 값을 텐서화한다.

     

    여기서 텐서화라는 것은 별게 아니고, 이미지 데이터의 픽셀 값을 지난 글에 적었던 텐서 형태로 반환해

     

    파이토치에서 사용할 수 있도록(파이토치에 입력할 수 있도록) 만든다는 뜻이다.

     

    좀 더 구체적으론 MNIST 데이터셋은 28x28의 흑백 이미지이며, 각 픽셀의 값은 0~255의 정수인데,

     

    transforms.ToTensor()는 이 픽셀 값을 [0,1] 범위의 부동소수점으로 스케일링해 파이토치 텐서로 변형한다.

     

    train_loader = DataLoader(train_data, batch_size=64, shuffle=True)
    test_loader = DataLoader(test_data, batch_size=64, shuffle=False)

    데이터를 미니배치로 관리하며, 훈련 데이터를 무작위로 섞는다.

     

    미니배치라는 것은 말 그대로 작은 묶음을 가리키며, 큰 데이터셋을 잘게 쪼개 나누어 처리함으로써

     

    메모리와 계산을 효율적으로 쓸 수 있게 된다.

     

    DataLoader 클래스를 사용하면 이런 미니배치를 쉽게 만들어주며, 위의 설정에 따르면

     

    64개의 샘플을 하나의 미니배치로 묶어주는 역할을 한다.

     

    Model

     

    class MLP(nn.Module):
        def __init__(self):
            super(MLP, self).__init__()
            self.layers = nn.Sequential(
                nn.Linear(28 * 28, 64), nn.ReLU(), nn.Linear(64, 64), nn.ReLU(), nn.Linear(64, 10)
            )

    다층 퍼셉트론(Multi-Layer Perceptron, MLP)을 정의한다.

     

    단어가 어려워 보이지만 지난 글에서 보았던 레이어 구조로 구성된 인공신경망의 한 형태라고 보면 된다.

     

    기본적으로 입력층, 출력층, 그리고 하나 이상의 은닉층으로 이루어지며,

     

    각 노드 사이에는 가중치가 있어 학습 과정에서 가중치가 조정되는 방식이다.

     

    추가로 MLP의 학습방식은 주로 지도 학습 방식이라고 한다.

     

    주어진 입력에 대한 예측 출력과 실제 출력 사이의 차이를 계산하는 손실 함수(loss function)를 사용해 모델을 훈련시키고,

     

    경사 하강법(gradient descent) 알고리즘 사용해 손실을 최소화하는 방향으로 가중치를 조절한다.

     

    Loss Function

     

    손실 함수는 모델의 예측과 실제 값 간의 차이를 수치화하는 함수이다.

     

    모델의 학습 목적은 이 손실함수의 값을 최소화하는 것이라고 할 수 있다.

     

    손실 함수의 종류는 여러 가지가 있으며, 문제의 종류에 따라 적합한 함수를 선택해 사용해야 한다.

     

    몇 가지 예를 들어보자.

     

    • 평균 제곱 오차(Mean Squared Error, MSE)

    주로 회귀 문제에서 사용되며, 예측 값과 실제 값의 차이를 제곱한 뒤 평균을 구한다.

     

    $$MSE = \frac{1}{n}\sum_{i = 1}^n(y_i - \hat{y_i})^2$$

     

    • 교차 엔트로피 손실 함수(Cross-Entropy Loss Function)

    주로 분류 문제에서 사용되며, 실제 값과 예측 값의 확률 분포 차이를 측정한다.

     

    $$Cross Entropy = -\sum_{i = 1}^ny_i\log(\hat{y_i})$$

     

    Gradient Descent

     

    경사 하강법은 손실함수의 값을 최소화하기 위한 최적화 알고리즘이다.

     

    손실 함수를 $J$라고 했을 때, 다음과 같은 수식을 반복 적용해 그 최솟값을 찾는다.

     

    $$\theta = \theta - \alpha\triangledown J(\theta)$$

     

    • θ: 모델의 파라미터 (가중치와 편향)
    • α: 학습률, 얼마나 큰 단계로 이동할지 결정
    • ∇J(θ): 손실 함수의 그래디언트 (미분값)

    수식과 설명이 복잡해 보이지만, 쉽게 말해 함수(오차)가 줄어드는 방향으로 파라미터를 갱신하는 것이다.

     

    또한 경사 하강법에는 다음과 같은 종류가 있다.

    • Batch Gradient Descent:
      전체 데이터를 사용하여 그래디언트를 계산한다. 계산이 느리지만 안정적이다.
    • Stochastic Gradient Descent (SGD)
      하나의 데이터 포인트를 무작위로 선택하여 그래디언트를 계산한다. 속도는 빠르지만 불안정하다.
    • Mini-batch Gradient Descent
      n개의 랜덤한 샘플을 사용하여 그래디언트를 계산한다. Batch와 SGD의 중간 형태.

    마지막으로 인공 신경망에서는 역전파(Backpropagation) 알고리즘을 사용해 출력층에서 입력층으로

     

    오차를 전파시키며 그래디언트를 효율적으로 계산한다.

     

        def forward(self, x):
            x = x.view(x.size(0), -1)
            return self.layers(x)

    모델 호출 시 자동으로 실행되는 forward 메서드를 정의한다. 이는 신경망의 순전파를 구현하며,

     

    입력이 주어졌을 때 어떤 연산을 거쳐 출력을 생성하는지 정의하는 함수이다.

     

    예제를 조금 더 보면, view함수를 이용해 28x28 크기의 2차원 이미지를 길이 784인 일차원 텐서로 평탄화한다.

     

    이어서 return 부분에선 신경망의 레이어를 통과시킨다. self.layers는 nn.Sequantial로 정의된 레이어의 묶음이며,

     

    이 레이어를 순서대로 통과한 결과가 반영된다.

     

    이 레이어에는 선형변환과 ReLU 활성화 함수 등이 포함된다.

     

    Loss Function, Gradient Descent

     

    model = MLP()
    
    criterion = nn.CrossEntropyLoss()
    optimizer = optim.Adam(model.parameters(), lr=0.001)

    모델에 방금 만든 클래스를 해당한 뒤 손실함수화 최적화 알고리즘을 선택한다.

     

    손실 함수는 위에서 본 교차 엔트로피 손실 함수를, 최적화 알고리즘에는 경사하강법의 일종인 Adam을 사용한다.

     

    Training Loop

     

    for epoch in range(5):
        for batch_idx, (data, target) in enumerate(train_loader):
            optimizer.zero_grad()
            output = model(data)
            loss = criterion(output, target)
            loss.backward()
            optimizer.step()
        print(f"Epoch {epoch}: Loss = {loss.item()}")

    최대 5번의 에포크를 수행하며 훈련을 하는 루프이다.

        for batch_idx, (data, target) in enumerate(train_loader):

    먼저 위에서 생성한 훈련 데이터와 결과 데이터를 미니배치 단위로 로드한다.

            optimizer.zero_grad()

    각 비니배치마다 이전에 계산된 그래디언트를 초기화한다.

            output = model(data)

    입력 데이터를 모델에 넣어 출력값을 얻는다. 이를 순전파(forward pass)라고 한다.

            loss = criterion(output, target)

    모델의 출력과 실제 값을 비교해 손실을 계산한다. 여기선 교차손실법을 이용한다.

            loss.backward()

    손실값에 대한 그래디언트를 입력층으로 역전파한다.

            optimizer.step()

    계산된 그래디언트를 사용해 모델 파라미터를 업데이트한다. 여기선 Adam 알고리즘을 사용하고 있다.

        print(f"Epoch {epoch}: Loss = {loss.item()}")

    각 에포크가 끝날 때마다 마지막 미니배치에서의 손실값을 콘솔에 출력한다.

     

    이는 훈련 과정을 모니터링하기 위한 것이다.

     

    Evaluation

     

    correct = 0
    total = 0
    
    with torch.no_grad():
        for data, target in test_loader:
            output = model(data)
            _, predicted = torch.max(output.data, 1)
            total += target.size(0)
            correct += (predicted == target).sum().item()
    
    print(f"Accuracy: {100 * correct / total}%")

    훈련이 종료되면 테스트 데이터를 이용해 모델을 평가한다.

    correct = 0
    total = 0

    먼저 예측이 맞는 횟수와 테스트 데이터 개수를 저장하는 변수를 초기화한다.

    with torch.no_grad():

    모델 평가에는 그래디언트 계산이 불필요하기 때문에 명시적으로 기능을 꺼준다.

     

    이는 리소스 사용을 효율적으로 할 수 있게 도움을 준다.

        for data, target in test_loader:

    데이터 로더에서 테스트 데이터와 실제 값을 미니매치 단위로 가져온다.

            output = model(data)

    모델에 데이터를 넣어 출력을 얻는다. 이는 위에서도 봤듯이 순전파 과정이다.

            _, predicted = torch.max(output.data, 1)

    모델의 출력은 10개의 클래스에 대한 확률 값으로 나타난다. torch.max는 그중 가장 높은 값을 갖는

     

    클래스의 인덱스를 반환한다. 추가로 '_'는 해당 확률 값을 저장하지만 여기서는 사용되지 않는다.

            total += target.size(0)

    테스트 데이터의 개수를 누적한다.

            correct += (predicted == target).sum().item()

    모델의 예측과 실제 값이 일치하는 경우의 수를 누적한다.

    print(f"Accuracy: {100 * correct / total}%")

    전체 테스트 데이터에 대한 정확도를 출력한다.

     

    Result

     

    Epoch 0: Loss = 0.19801555573940277
    Epoch 1: Loss = 0.1288914531469345
    Epoch 2: Loss = 0.22620297968387604
    Epoch 3: Loss = 0.13473093509674072
    Epoch 4: Loss = 0.0723961591720581
    Accuracy: 97.21%

    이렇게 구성하고 코드를 실행하면 위와 같은 결괏값을 얻을 수 있다.

     

    각 에포크당 손실값과 최종 정확도를 출력하는 것을 확인할 수 있다.

     

    구현할 때는 그냥 따라서 구현했는데, 이렇게 뜯어놓고 보니

     

    의외로 별 거 아니고(?) 꽤 재밌다는 생각이 들었다.

     

    틈이 나는 대로 이렇게 테스트하며 도메인에 익숙해질 계획이다.

     

    우선 오늘은 끝!

    반응형

    'Python > PyTorch' 카테고리의 다른 글

    [PyTorch]갑자기 써보는, PyTorch에 대하여  (1) 2023.08.26
    댓글
    공지사항
    최근에 올라온 글
    최근에 달린 댓글
    Total
    Today
    Yesterday
    링크
    «   2024/06   »
    1
    2 3 4 5 6 7 8
    9 10 11 12 13 14 15
    16 17 18 19 20 21 22
    23 24 25 26 27 28 29
    30
    글 보관함