딥러닝 모델 구현에서 PyTorch
딥러닝 모델 구현에 필요한 요소들을 PyTorch를 통해 편리하게 사용할 수 있다.
이 그림을 PyTorch 클래스를 이용해서 어떻게 구현을 하는지는 다음과 같다.
Dataset, DataLoader를 이용해서 데이터를 만들게 되고, 만들어진 데이터를 모델에 넘겨주게 된다. 이 모델의 연산과정을 통해서 Output을 내게 된다. 이 Output을 이용해서 Loss를 계산하게 된다. 계산된 Loss들을 이용해서 모델의 파라미터를 업데이트하는 Optimization을 진행하게 된다.
1. 데이터
Dataset과 DataLoader를 조합하여 데이터를 손쉽게 로드하고 처리할 수 있다.
Dataset과 DataLoader를 사용하면, 데이터 집합에서 미니 배치 크기만큼의 데이터를 반환한다. 여기서 미니 배치는 전체 데이터 셋을 더 작은 부분집합으로 분할한 일부 데이터를 말한다.
이 코드에서는 미니 배치 크기를 8로 줘서 DataLoader를 통해 반환된 미니 배치 데이터를 보면 단일 데이터가 총 8개가 쌓인걸 확인할 수 있다.
Dataset
Dataset은 단일 데이터를 모델의 입력으로 사용할 수 있는 형태(tensor)로 변환하는 작업을 수행한다.
PyTorch에는 이미지 데이터를 다루는데 흔히 사용되는 ‘ImageFolder’, ‘CIFAR10’, ‘MNIST’ 과 같은 데이터 셋들에 대한 Dataset 구현체들이 이미 제공되고 있어, 편리하게 사용할 수 있다.
그러나 PyTorch에서 제공하는 구현체인 Dataset은 제한적이다. 이때, custom dataset을 구현하여 자신의 데이터를 사용한 Dataset을 만들 수 있다.
Custom Dataset
Custom dataset 구현을 위해서는 Dataset 클래스를 상속하여 custom dataset
클래스를 만들어야 한다. 이때 아래 세 개의 메서드를 꼭 작성해야 한다.
- __init__: Dataset 객체가 생성될 때 한 번만 실행된다. 주로 사용할 데이터 셋을 불러오거나 필요한 전처리를 해준다거나 필요한 변수를 선언한다.
- __getitem__: 주어진 인덱스에 해당하는 단일 데이터를 불러오고 반환한다.
- idx: 아까 위에서 보면 Dataset, DataLoader를 이용하면 미니 배치 크기의 데이터셋이 만들어진 걸 확인했는데 이 CustomDataset은 단일 데이터를 반환하기 때문에 몇 번째 단위데이터를 반환해야 하는지 DataLoader가 Dataset에게 알려주게 된다. 그걸 idx 번호로 알려주게 되고 그 idx를 받았을 때 그 idx에 해당하는 데이터를 반환하는 역할을 한다.
- __len__: CustomDataset에서 반환하는 데이터 셋의 데이터 개수를 반환한다. (e.g. 이미지 개수)
- 꼭 작성되어야 하는 이유는, DataLoader에서 len을 이용해야 동작 가능한 부분이 있기 때문에 꼭 구현이 되어있어야 한다.
Custom Dataset 구현 시 주의 사항
1. 데이터 타입
PyTorch는 데이터를 torch.tensor 객체로 다루기 때문에 데이터는 tensor로 변환되어야 한다. 따라서 __getitem__ 메서드에서 반환하는 데이터는 tensor 형태로 변경하여 반환해야 한다.
물론 꼭 tensor가 아니라 list, tuple, dictionary로 반환할 수도 있다. 이 경우, list, tuple의 원소가 tensor여야 하며, dictionary는 value가 tensor여야 한다.
2. 데이터 차원
Dataset은 DataLoader와 함께 사용된다. DataLoader에서 데이터들을 미니 배치로 묶어(stack) 주는 역할을 한다. 이때, 반환되는 모든 데이터의 차원의 크기가 모두 같아야 한다.
그 이유는 DataLoader는 Dataset에서 각 단위 데이터를 배치 사이즈만큼 가져와서 미니배치로 묶어주는 작업을 수행하는데 이때 반환되는 데이터의 크기가 모두 다르게 되면 묶어줄 수 없기 때문이다.
- e.g., 모든 이미지는 같은 높이(height), 너비(width)와 채널(channel)을 가짐.
- e.g., 모든 (텍스트를 포함한) 시퀀스는 같은 길이(max_len)를 가짐.
💡 근데 상식적으로 텍스트 입력이 모든 input이 다 같을 순 없다. 이미지 크기도 입력마다 큰 것도 있고 작은 것도 있을 텐데 이걸 어떻게 맞추냐? 이때 딥러닝 때 배운 padding 개념을 통해서 차원의 크기를 맞춘 다음에 반환하면 된다.
DataLoader
DataLoader의 역할은 데이터를 미니 배치로 묶어서 반환하는 것인데, 이는 인자로 주어진 Dataset을 이용해서 데이터 셋의 단일 데이터들을 정해진 개수만큼 모아 미니 배치(mini-batch)를 구성하는 역할을 한다.
DataLoader의 인자로 Dataset은 필수이며, 그 외의 추가 인자들이 존재한다.
- batch_size [int, default = 1]: 미니 배치의 크기를 나타낸다.
- shuffle [bool, default = False]: epoch마다 데이터의 순서가 섞이는 여부를 나타낸다.
- num_workers [int, default = 0]: 데이터 로딩에 사용하는 서브 프로세스 개수를 나타낸다. 값이 높아지면 높아질수록 속도는 빨라지지만 cpu나 메모리 사용량이 많아진다.
- drop_last [bool, default = False]: 마지막 미니 배치의 데이터 수가 미니 배치 크기보다 작은 경우, 데이터를 버릴지 말지를 나타낸다. 만약에 Dataset에서 반환되는 len값이 101이고 batch_size가 5라고 하면 5로 딱 나누어 떨어지지 않는데 남는 데이터를 버릴지 말지를 결정하는 인자다.
이렇게 데이터가 준비가 완료되면 이걸 입력으로 받아서 처리하는 모델에 대해서도 알아보자.
2. 모델
PyTorch는 이미 다양한 모델을 제공하고 있다.
Torchvision
- Torchvision 라이브러리는 이미지 분석에 특화된 다양한 모델을 제공한다.
- Torchvision 문서(https://pytorch.org/vision/stable/models.html)에서 불러올 수 있는 여러 가지 모델의 목록을 확인할 수 있다.
- ResNet, VGG, AlexNet, EfficientNet, ViT 등 널리 알려진 모델을 편리하게 사용할 수 있다.
PyTorch Hub
- PyTorch Hub는 CV, audio, generative, NLP 도메인의 모델들이 공개돼 있다.
- PyTorch Hub 홈페이지(https://pytorch.org/hub/)에서 torchvision 라이브러리와 마찬가지로 불러올 수 있는 모델의 목록을 알 수 있다.
모델 불러오기
Torchvision
torchvision.models.[model 이름]()를 활용하면 손쉽게 모델을 불러올 수 있다.
PyTorch Hub
torch.hub.load()로 모델을 불러올 수 있다.
모델마다 torch.hub.load()로 전달하는 인자가 다르기 때문에 원하는 모델을 PyTorch Hub에서 클릭해서 불러오는 코드를 확인할 수 있다.
그러나 역시 PyTorch에 공개된 모델은 제한적이다.
딥러닝 분야는 빠르게 발전하고 있으며, 새로운 모델이 지속해서 연구 및 발표되고 있다. 그러다 보니 모든 모델이 pytorch로 구현해서 제공되지는 않고 논문에 공개된 모델은 주로 GitHub 등의 코드 공유 플랫폼에만 공개되는 것이 대부분이다. 새로운 모델을 빠르게 접하고 상황에 맞게 변형해서 사용하기 위해서는, PyTorch에서 모델을 어떻게 정의하고 사용하는지 이해하는 것이 필요하다.
Custom Model
PyTorch에서 모델은 일반적으로 torch.nn.Module 클래스를 상속받아 정의한다.
Custom model을 정의하기 위해서는 아래 두 가지 메서드를 꼭 작성해야 한다.
- __init__: super().__init__()을 통해 부모 클래스 (nn.Module)를 초기화한 후, 모델의 레이어와 파라미터를 초기화한다.
- super().__init__(): 부모 클래스(nn.Module)를 초기화하여 nn.Module의 기능을 사용할 수 있다. 이를 통해, 입력값에 대한 연산을 진행하거나, 선언한 모델의 parameter에 접근할 수 있다.
- forward: 입력 데이터에 대한 연산을 정의한다. 여기서 말하는 입력 데이터는 앞에서 구현한 데이터셋과 데이터로더를 통해 들어온 데이터를 말한다. 이 부분에서 nn나 다양한 구조의 모델에 태워서 최종적인 예측값을 반환한다.
이제 데이터와 모델에 대해서 알아봤으니 이 둘을 이용해 모델을 어떻게 학습시킬 수 있는지 알아보자.
3. 역전파와 최적화
우선 PyTorch에서 사용되는 훈련 과정의 일반적인 형태를 알아보자
- optimizer.zero_grad(): 이전 gradient를 ‘0’으로 설정
- PyTorch는 기본적으로 gradient를 누적하여 사용한다. 만약 이전 gradient를 ‘0’으로 초기화하지 않는다면, gradient가 누적되어 데이터가 계속 중복된 채 학습에 사용된다. 즉, 하나의 데이터가 중복돼서 학습에 사용되다 보니 모델의 학습이 올바르지 않게 된다.
- output = model(data): 모델을 사용하여 입력값(데이터)에 대해 연산
- loss = loss_function(output, label): loss 값 계산. 여기서 label은 정답값을 의미한다.
- loss.backward(): loss에 대한 gradient 계산
- 이때, AutoGrad를 통해 자동으로 gradient 계산
- optimizer.step(): 계산된 gradient를 사용하여 각 모델의 파라미터를 업데이트
AutoGrad
loss.backward()는 AutoGrad를 기반으로 하고 있다. 여기서 AutoGrad는 tensor의 연산에 대한 미분을 자동으로 계산하기 위해 내부적으로 computational graph를 생성한다.
- Computational graph: 텐서 간의 수학적 계산을 노드(node)와 엣지(edge)의 그래프로 표현한 것이다. 그래프를 통해 연산의 흐름이 기록된다.
- 노드: 수학적 연산을 나타낸다. 예를 들어 덧셈, 곱셈, 뺄셈 등의 기본적인 연산을 나타낼 수 있다.
- 엣지: 연산의 입력값 또는 출력값을 나타낸다.
위 그래프를 풀어서 설명해 보자면 x, y 텐서가 입력으로 들어와서 더하기 연산이 수행돼서 t라는 output이 나오게 되고, t 입력을 받아서 제곱해 주고 그 결과가 z라는 텐서에 저장이 된다.라는 과정이다.
이 그래프를 통해 텐서 간의 연산의 흐름이 기록이 된다.
코드를 통해서 보면 다음과 같다. 결과를 보면 '이 두 개의 더하기를 통해서 만들어진 텐서입니다.'라는 게 보인다.
이러한 computational graph와 chain rule을 이용하여 gradient 계산을 자동으로 해준다. 이에 따라 프로그래머는 미분식을 일일이 구현하지 않아도 된다.
추론과 평가
Inference
Inference는 학습한 모델을 이용하여, 입력 데이터에 대한 예측 결과를 내놓는 과정이다.
일반적으로 inference 시에는 model.eval()과 torch.no_grad()를 함께 사용한다.
- model.eval(): 모델을 evaluation 모드로 전환한다. 이는 모델의 특정 레이어들(dropout, batchnorm는 학습에 더 용이하게 하기 위함)이 학습 과정과 추론 과정에서 다르게 작동해야 하기 때문이다.
- torch.no_grad(): AutoGrad 기능을 비활성화한다. 추론 과정에서는 gradient 계산이 필요하지 않으므로, 이를 통해 메모리 사용량을 줄이고 계산 속도를 향상시킬 수 있다.
Evaluation
Inference 과정에서 도출한 예측 결과와 실제 라벨을 비교하여 모델의 성능을 평가한다.
태스크에 맞는 metric을 선정하여 numpy array로 변환하여 외부 라이브러리를 사용하거나, PyTorch를 사용해 직접 구현하여 성능을 평가할 수 있다.
'Deep Learning' 카테고리의 다른 글
문서 요약 모델 평가지표: ROUGE 1, ROUGE 2, ROUGE-L, ROUGE-S, ROUGE-W 완벽 이해하기 (1) | 2024.11.18 |
---|---|
텍스트 데이터 전처리 방법 (토큰화, 정제, 정규화, Stemming, Lemmatization 등) (2) | 2024.11.12 |
[Pytorch] tensor shape 변경에서 -1과 1 쓰임 그리고 슬라이싱 (1) | 2024.10.23 |
손실 함수, 활성화 함수, 최적화 함수 등 순서, 쓰임, 역할 (1) | 2024.10.23 |
가중치 초기화의 중요성과 Xavier 초기화, He 초기화 소개 (1) | 2024.10.17 |