본문 바로가기
Machine Learning/개념 정리

[파이토치] 1. 모델 선언

by W_log 2023. 12. 29.

대부분 파이토치 공부를 하다보면, 마치 수학의 집합처럼 튜토리얼 보면서 각 함수(torch.shape, torch.view)를 공부하게 되는데 한번 보면 그 당시에는 알게 되긴 해도, 막상 모델 돌리는 코드를 보면 사실 앞에서 튜토리얼로 배운게 사용하지 않는 것들도 많을뿐더러 익숙해지지 않는 것 같다. 결국 Torch를 사용하는 이유는 AI 모델을 활용하기 위한 목적이 크기 때문에 모델에서 자주 사용되는 코드들을 기반으로 파이토치 사용법을 익혀보려고 이 글을 씁니다.

 

 

전체 개요

 

 

파이토치를 활용해서 모델을 만드는 구조는 크게 위의 방식을 따른다. (물론 아직 공부하고 있는 단계의 시점 기준이지 추후에 더 많은 복잡한 모델은 다를 수 있다.)

 

코드는 크게 3가지 파트로 생각하면 된다.

 

1. 모델 : 우리가 어떤 신경망 구조로 만들지에 대해서 설계하는 방식으로 이건 설계, 창작의 영역이라고 보면 될것 같다.DNN으로 보면, 각 layer 내부 뉴런의 갯수, CNN에서는 Kernel의 갯수 등을 여기서 설정한다.

 

2. 데이터 : 크게 데이터 전처리와 데이터 로더 작업으로 나눌 수 있으며, 전처리는 우리가 학습시키려는 데이터를 모델에 들어갈 수 있게 텐서를 조절하거나 데이터를 클리닝하는 작업에 해당하며, 데이터 로더는 3번의 학습에 맞게 데이터를 배치 형태로 전달하는 역할을 한다.

*데이터 로더가 왜 필요한지? 처음 공부할 때 이게 왜 필요하지? 생각했는데 공부하면서 이렇게 이해했다. 효율적인 관점이 큰데 학습하는 과정에서 병렬 처리, 배치 처리, 셔플링 등의 작업을 진행해준다.

 

3. 학습 : 이제 1과 2에 정의된 모델과 데이터를 정의한 방식대로 학습을 시킨다. 크게 3가지의 학습 관련 함수가 있는데, training(training 측정), evaluation(validation 측정), test(최종 결과 도출) 함수를 각각 정의한다.

 

이번 포스팅에서는 모델 클래스 관련 코드를 정리해보았다.

 

Model : 신경망 구조를 정의하는 코드

 

1. class 선언

파이토치에서는 클래스 선언을 아래와 같이 정의한다.

class model_name(nn.Module):

 

model_name에는 RNN, CNN같이 각각의 사용자가 정의한 모델명이 들어가면 되고, 인자로 nn.Module을 넣어 nn.Module 클래스의 메소드를 상속받는데 이걸 이해하는데 꽤 오래 걸렸다.

 

nn.Module의 역할은?

파이토치 내부의 신경망 모듈을 다루는 요소들을 추상화(사용자가 사용하기 쉽게)해둔 거다. 우리가 일일히 하나하나 신경망을 구성하는데 필요한 요소들을 다 구현하지 않고 기본적인 메소드들을 가져와서 사용할 수 있게 만드는 것이다.

 

 

대략적으로 제공하는 기능을 적어두었다. nn.Module 클래스에서 이를 제공하지 않았다면 아래 기능들을 직접 코드로 다 구현해야한다.

 

 

1. 레이어 및 파라미터 관리

2. 자동 미분 지원(forward 함수에서 forward pass가 정의되면 backward pass는 자동으로 처리해준다.)

3. 훈련 및 평가 모드를 model.train(), model.eval()을 통해서 쉽게 전환시킬 수 있다.

4. 모델 저장 및 로딩

5. 기기 간 이동 : cpu <-> gpu간의 전환을 쉽게 지원해준다.

 

 

이 nn.Module을 통해 class를 만들때 기본적으로 두개의 메소드는 정의해주어야 한다.

1. 모델의 구조를 설계하는 __init__()

2. 실제 데이터가 신경망을 흐르게 하는 forward()

 

 

 

2. __init__() 함수 정의 : 신경망 구조를 설계하기 위한 기본 블록을 정의하는 코드

 

def __init__(self, num_classes, dropout_ratio):  #신경망 구조 설계하는 영역
    	#1. init에는 신경망 구조에서 활용할 인자(변수)들을 받는다.
        super(CNN, self).__init__(): #이렇게 쓰든 super().__init__()으로 쓰든 관계없음
        self.num_classes = num_classes #CNN에서 최종적으로 분류할 클래스 수
        self.layer = nn.Sequential(
                nn.Conv2d(in_channels = 1, out_channels = 16, kernel_size = 5),
                nn.ReLU()
                nn.Conv2d(in_channels = 16, out_channels = 32, kernel_size = 5),
                nn.ReLU()
                nn.Conv2d(in_channels = 32, out_channels = 64, kernel_size = 5),
                nn.ReLU()
                nn.MaxPool2d(kernel_size = 2)
                nn.Dropout(dropout_ratio)
            )
        self.fc = nn.Linear(64 * 3 * 3, self.num_classes)
        self.softmax = nn.LogSoftmax(dim = 1)

 

1) super(self).__init__(): super(CNN, self).__init__()으로 쓰는 경우도 있는 파이썬2 버전에서 쓰이던 방식이고 서로 다른 것을 의미하는 것은 아니다. 이걸 선언하는 이유는 아래의 문장을 참고하면 이해할 수 있다.

 

만약 자식 클래스에서 __init__ 메소드를 정의하면, 부모 클래스의 __init__ 메소드는 자동으로 호출되지 않습니다. 이 경우, 자식 클래스의 __init__ 메소드 안에서 super().__init__() 을 호출하여 부모 클래스의 생성자를 명시적으로 실행해야 합니다. 이렇게 하는 이유는 부모 클래스의 초기화 과정을 자식 클래스에서도 수행하려는 경우에 필요합니다.

 

 

2) nn.Sequential,  : nn.Modules가 있어서 헷갈리는데 사실 nn은 import torch.nn as nn을 선언해서 이 때의 nn을 의미한다. torch.nn에서는 신경망을 구성하는 Component들을 제공해줘서 위와 같이 순차적으로 나열하면 된다. Sequential을 ResNet처럼 여러개의 신경망을 하나로 뭉치고 이를 한번에 통과시키기 위해서(효율을 위해) 정의했다.

 

 

3) nn.Conv2d, fc, softmax : 위 코드에서 CNN은 classification 문제를 해결하기 위함이므로 fc, softmax를 정의해준 거라고 보면 된다. 이 역시도 nn의 메소드를 사용한다.

 

 

 

3. forward() : 기본 블록을 활용해 최종적으로 신경망을 정의하고, 데이터가 신경망을 통해 흘러서 최종적인 예측값을 내는 코드

 

def forward(self, x):
    out = self.layer(x)
    out = out.view(out[0], -1) #혹은 out.flatten(1)으로 해도 됨
    out = self.fc(out)
    pred = self.softmax(out)
    return pred

 

1) 텐서 맞추기 :  사실 위의 코드는 약간 반복처럼 느껴지지만, 핵심은 여기서는 우리가 x라는 데이터에 대해서 각 Layer를 통과하면서 텐서 차원을 맞춰주는 것이 중요하다. out = out.view(out[0], -1)과 같이 CNN에서 나온 것을 FC Layer로 전달될 수 있도록 차원을 조정해주는 것이 이 코드에서 핵심이다.

 

 

2) 블록 쌓기 : 우리가 layer로 정의된 블록을 여러개 겹쳐서 만드는 것도 forward에서 정의한다. init에서는 실제 신경망에 사용할 기본 블록을 정의한다면, 여기서는 레고를 쌓아서 최종 모형을 만들듯이 최종적인 구조를 정의해주는 것도 여기서 한다. 다만, CNN은 기본 코드라서 forward와 Init의 구조가 중복처럼 느껴진다. 

 

 

 

4. weight_initialization() : 모델이 학습가능한 parameter들을 처음 초기화시켜주는 코드

 

def weight_initialization(self):
	for m in self.modules(): 
            #nn.Conv2d, nn.ReLU, nn.Linear, nn.MaxPool2d, nn.Dropout 등과 같은 레이어나 블록들이 이에 해당합니다.
            if isinstance(m, nn.Conv2d) or isinstance(m, nn.Linear):
                nn.init.kaiming_normal_(m.weight)
                nn.init.zeros_(m.bias)

 

딥러닝이라는 것이 결국은 초기 시작점으로부터 iteration을 통해 최적해를 찾아가는 과정을 가지는데 좋은 초기점을 지정해주기 위해서 이러한 처리를 해준다. 

 

 

1) nn.init : 여기의 하위 함수들은 모두 neural network의 parameter들을 초기화하는데 사용되는 함수들이 사용된다. 여기서는 he 초기화를 사용했다.

 

 

제공되는 초기화 기법 예시는 참고로 적어둔다.

  • calculate_gain()
  • uniform()
  • normal_()
  • constant_()
  • ones_()
  • zeros_()
  • eye_()
  • dirac_()
  • xavier_uniform_()
  • xavier_normal_()
  • kaiming_uniform_()
  • kaiming_normal_()
  • trunc_normal_()
  • orthogonal_()
  • sparse_()

 

 

5.  count_parameters() : 모델이 학습해야하는 parameter의 수를 계산하는 코드

 

def count_parameters(self):
    return sum(p.numel() for p in self.parameters() if requires_grad)

 

딥러닝 모델은 연산 효율이 가장 중요하기 때문에 연산효율에 영향을 주는 요인인 파라미터 수를 계산하는 함수를 별도로 추가해보았다. 이건 선택적인것 같긴 하다.(Optional)

 

 

1) numel() : 텐서의 총 요소 수를 반환하는 함수인데 이는 곧 파라미터의 수를 가져온다. 사실 여기서 p의 단위가 궁금해서 직접 출력해봤는데 p의 shape를 찍어봤을 때 torch.size([16, 1, 5, 5]) 단위로 나오는 걸 보면 layer 단위로 parameters가 iterator를 제공하는 것으로 보인다.

 

 

2) requires_grad : 그라디언트를 계산하는 파라미터들만 세기위해 위 조건을 추가한다.

 

3) model.parameters() : nn.Module 클래스에 정의된 메소드로 모델 내 모든 파라미터를 반환하는 iterator를 제공한다.

 

 

더보기

전체 코드는 아래와 같다.

 

## 모델 코드

class CNN(nn.Module):
    def __init__(self, num_classes, dropout_ratio):  #신경망 구조 설계하는 영역
    	#1. init에는 신경망 구조에서 활용할 인자(변수)들을 받는다.
        super(CNN, self).__init__(): #이렇게 쓰든 super().__init__()으로 쓰든 관계없음
        self.num_classes = num_classes #CNN에서 최종적으로 분류할 클래스 수
        self.layer = nn.Sequential(
                nn.Conv2d(in_channels = 1, out_channels = 16, kernel_size = 5),
                nn.ReLU()
                nn.Conv2d(in_channels = 16, out_channels = 32, kernel_size = 5),
                nn.ReLU()
                nn.Conv2d(in_channels = 32, out_channels = 64, kernel_size = 5),
                nn.ReLU()
                nn.MaxPool2d(kernel_size = 2)
                nn.Dropout(dropout_ratio)
            )
        self.fc = nn.Linear(64 * 3 * 3, self.num_classes)
        self.softmax = nn.LogSoftmax(dim = 1)
        
    def forward(self, x):
    	out = self.layer(x)
        out = out.view(out[0], -1) #혹은 out.flatten(1)으로 해도 됨
        out = self.fc(out)
        pred = self.softmax(out)
        return pred
    
    
    
    def weight_initialization(self):
    	for m in self.modules(): 
            #nn.Conv2d, nn.ReLU, nn.Linear, nn.MaxPool2d, nn.Dropout 등과 같은 레이어나 블록들이 이에 해당합니다.
            if isinstance(m, nn.Conv2d) or isinstance(m, nn.Linear):
               	nn.init.kaiming_normal_(m.weight)
                nn.init.zeros_(m.bias)
    def count_parameters(self):
    	return sum(p.numel() for p in self.parameters() if requires_grad)

 

 

'Machine Learning > 개념 정리' 카테고리의 다른 글

[파이토치] 4. 여러 모듈 사용  (2) 2023.12.29
[파이토치] 2. 데이터 처리  (0) 2023.12.29
Hyperparameter Search Optimization  (1) 2023.11.30
Tree Model 정리  (0) 2023.11.29
전체 인공신경망 code 구현하기  (0) 2023.09.22