네이버 부스트캠프/LEVEL-1

[부스트캠프][WK03 / Day12] PyTorch 구조 학습하기

1. 강의 내용

AutoGrad & Optimizer (최성철 교수님)

1) torch.nn.Module

  • 딥러닝을 구성하는 Layer의 base class
  • Input, Output, Forward, Backward(weights를 AutoGrad) 정의
  • 학습의 대상이 되는 parameter(tensor) 정의

2) nn.Parameter

  • Tensor 객체의 상속 객체
  • nn.Module 내에 attribute가 될 떄는 required_grad=True로 지정되어 학습 대상이 되는 Tensor
  • 대부분의 layer에는 weights 값들이 지정되어 있기 때문에 우리가 직접 지정할 일은 잘 없음
# y = xw+b, in_features * out_features 만큼의 parameter(weights) 값들을 지정해줘야함
class MyLiner(nn.Module):
    def __init__(self, in_features, out_features, bias=True):
        super().__init__()
        self.in_features = in_features
        self.out_features = out_features

        self.weights = nn.Parameter(
                torch.randn(in_features, out_features))

        self.bias = nn.Parameter(torch.randn(out_features))

    def forward(self, x : Tensor):
        return x @ self.weights + self.bias

3) Backward

  • Layer에 있는 Parameter들의 미분을 수행
  • Forward의 결과값 (model의 output=예측치)과 실제값간의 차이(loss)에 대해 미분을 수행
  • 해당 값으로 Parameter 업데이트
for epoch in range(epochs):
    # gradient값이 계속 업데이트되는데, 이전의 값이 지금의 학습에 영향을 주지 않게 하기 위해 초기화
    optimizer.zero_grad()

    outputs = model(inputs)

    loss = criterion(outputs, labels)
    print(loss)

    loss.backward() # loss에 대해 우리가 구하고자하는 모든 weight값을 구해주는 것

    optimizer.step() # weight 업데이트, step을 하면 w = new_w(X) => w += new_w

실제 backward는 Module 단계에서 직접 지정할 필요는 없지만 가능하며 직접 지정하려면 Module에서 backward와 optimize를 오버라이딩 해줘야 합니다. 하지만 이 경우 사용자가 직접 미분 수식을 써야하는 부담이 있기 때문에 쓸일은 없으나 순서는 이해하는 것이 좋습니다.

class LR(nn.Module):
    def __init__(self, dim, lr=torch.scalar_tensor(0.01)):
        super(LR, self).__init__()

        self.w = torch.zeros(dim, 1, dtype=torch.float).to(device)
        self.b = torch.scalar_tensor(0).to(device)
        self.grads = {"dw": torch.zeros(dim, 1, dtype=torch.float).to(device),
                      "db": torch.scalar_tensor(0).to(device)}
        self.lr = lr.to(device)

Forward

    def forward(self, x):
        ## compute forward
        z = torch.mm(self.w.T, x) + self.b
        a = self.sigmoid(z)
        return a

    def sigmoid(self, z):
        return 1/(1 + torch.exp(-z))

Backward

    def backward(self, x, yhat, y):
        ## compute backward
        self.grads["dw"] = (1/x.shape[1]) * torch.mm(x, (yhat - y).T)
        self.grads["db"] = (1/x.shape[1]) * torch.sum(yhat - y)

Optimize

    def optimize(self):
        ## optimization step
        self.w = self.w - self.lr * self.grads["dw"]
        self.b = self.b - self.lr * self.grads["db"]

Dataset & Dataloader (최성철 교수님)

Dataset과 Dataloader가 진행되는 순서를 보면 아래와 같습니다.

Dataset을 보면 getitem이 있는데 map_style이라고 하며하나의 데이터를 불러올 때 어떻게 반환을 해 주는지 정의해주는 것입니다.

transforms는 데이터를 전처리, augmentation등을 할때 해주는 과정이며 주로 하는 역할은 앞에서 전처리한 데이터를 Tensor로 바꿔주는 것입니다.

Dataloader는 Dataset을 통해 데이터를 어떻게 처리할지 정의했으면 이것들을 묶어 모델에 feeding해주는 역할을 합니다.

1) Dataset 클래스

  • 데이터 입력 형태를 정의하는 클래스
  • 데이터를 입력하는 방식의 표준화
  • Image, Text, Audio 등에 따른 다른 입력정의
import torch
from torch.utils.data import Dataset, DataLoader

class CustomDataset(Dataset):
    # 초기 데이터 생성 방법 지정
    def __init__(self, text, labels):
            self.labels = labels
            self.data = text

    # 데이터 전체 길이
    def __len__(self):
            return len(self.labels)

    # index 값을 주었을때 반환되는 데이터의 형태(X, y)
    def __getitem__(self, idx):
            label = self.labels[idx]
            text = self.data[idx]
            sample = {"Text": text, "Class": label}
            return sample

# Dataset 생성
text = ['Happy', 'Amazing', 'Sad', 'Unhapy', 'Glum']
labels = ['Positive', 'Positive', 'Negative', 'Negative', 'Negative']
MyDataset = CustomDataset(text, labels)

생성시 유의점

  • 데이터 형태(특징)에 따라 각 함수를 다르게 정의함
  • 모든 것을 데이터 생성 시점에 처리할 필요는 없으며 image의 Tensor 변화는 학습에 필요한 시점에 변환(transform 함수 사용)
  • 데이터 셋에 대한 표준화된 처리방법 제공 필요(다른 연구자를 위해)
  • 최근에는 HuggingFace등 표준화된 라이브러리 제공

2) DataLoader 클래스

  • Data의 Batch를 생성해주는 클래스
  • 학습직전(GPU feed 전) 데이터의 변환을 책임
  • Tensor로 변환 + Batch 처리가 메인 업무
  • 병렬적인 데이터 전처리 코드의 고민 필요
MyDataLoader = DataLoader(MyDataset, batch_size=2, shuffle=True)
# batch_size=2: 위의 데이터에서 한번에 2개씩 추출
# shuffle=True: 2개를 추출시 순서와 상관없이 랜덤으로 2개 뽑아줌

next(iter(MyDataLoader)) # DataLoader는 기본적으로 iterable한 객체로 호출하는 시점에 메모리에 올라갑니다.
# {'Text': ['Glum', 'Sad'], 'Class': ['Negative', 'Negative']}

MyDataLoader = DataLoader(MyDataset, batch_size=2, shuffle=True)
for dataset in MyDataLoader:
    print(dataset)
# {'Text': ['Glum', 'Unhapy'], 'Class': ['Negative', 'Negative']}
# {'Text': ['Sad', 'Amazing'], 'Class': ['Negative', 'Positive']}
# {'Text': ['Happy'], 'Class': ['Positive']}


DataLoader(dataset, batch_size=1, shuffle=False, sampler=None,
batch_sampler=None, num_workers=0, collate_fn=None,
pin_memory=False, drop_last=False, timeout=0,
worker_init_fn=None, *, prefetch_factor=2,
persistent_workers=False)
# sampler: 데이터를 어떻게 뽑을지 인덱스를 정해줌
# collate_fn: variable length의 가변자가 있어 padding을 해줄 때 각각의 데이터에 동일하게 적용해줘야할 때 사용(sequence형 데이터 처리시 자주 사용)

3) 실습

  • 데이터 다운로드 부터 loader까지 직접 구현해보기
  • NotMNIST 데이터의 다운로드 자동화 도전

2. 과제 수행 과정 / 결과물 정리

  • Custom Model 개발하기 과제의 필수과제 부분 마무리.
  • Optional 부분은 시간이 부족해 끝내지 못했으며 제출전까지 마무리 예정입니다.
  • Custom Dataset 과제의 Dataset 구현까지 마무리.

3. 피어세션 정리

https://hackmd.io/__XG77A-T9GQHQrYXjxsZg


4. 학습 회고

필수과제 부분은 모두 해결했지만 PyTorch 라이브러리의 Documentation을 활용한 구현 과정에서 많은 문서와 코드를 더 자세히 봐야할 것 같습니다.