본문 바로가기

CS/AI

[ML] AutoGrad, Optimizer, 데이터셋과 데이터 로더

반응형

벌써 7월이 찾아와서 많이 덥네요.

 

요즘 자격증 공부도 하고 백준 풀면서 네부캠 준비도 하는데 이게 참 힘든 일이네요.

어떤 일 하나에 집중해서 잘하는 것도 능력입니다...

이것저것 다 해내고 싶은 욕심도 중요하지만 무엇이든지 마무리까지 잘 달리는 능력이 더 중요한 것 같습니다.

 

아래 모든 내용은 부스트캠프 AI Tech 프리코스에서 가져왔습니다.


모델들은 결국엔 여러 층의 연속일 뿐입니다.

그 층을 잘 찾아서 쌓는 것이 중요한 것.

 

이런 Layer들을 정의할 수 있는 방법으로 torch.nn.Module이 있습니다.

 

Input, Output, Forward, Backward 등을 정의할 수 있고

학습에 사용되는 파라미터를 결정해줄 수 있습니다.

 

Forward는 그냥 FNN 식 같은 것일 거고, Backward 부분이 미분을 통해서 파라미터 값을 학습시킵니다.

이 미분을 AutoGrad을 통해 처리해주는 것이 파이토치의 CNN의 장점입니다.

 

# PyTorch의 Parameter를 사용하여, linear layer 구현
class MyLinear(nn.Module):
  def __init__(self, in_features, out_features, bias=True):
    super().__init__()
    self.in_features = in_features
    self.out_features = out_features

    # (in_features, out_features) shape을 갖는 random tensor를 학습 가능한 Parameter 객체로 변환
    self.weights = nn.Parameter(
            torch.randn(in_features, out_features))

    # (out_features) shape을 갖는 random tensor를 학습 가능한 Parameter 객체로 변환
    self.bias = nn.Parameter(torch.randn(out_features))

  def forward(self, x : Tensor):
    # input tensor를 선형 변환한 후, bias term을 더하는 연산 정의
    return x @ self.weights + self.bias

 

nn.Parameter은 이렇게 input feature과 output feature을 지정하고 계산하는 데에 쓰입니다.

 


 

for epoch in range(epochs):
  # input과 label을 PyTorch Variable 객체로 변환
  if torch.cuda.is_available():
    inputs = Variable(torch.from_numpy(x_train).cuda())
    labels = Variable(torch.from_numpy(y_train).cuda())
  else:
    inputs = Variable(torch.from_numpy(x_train))
    labels = Variable(torch.from_numpy(y_train))

  # 이전 기록된 gradient를 0으로 초기화
  optimizer.zero_grad()

  # model을 통해 input forward propagation 진행
  outputs = model(inputs)

  # loss 값 계산
  loss = criterion(outputs, labels)
  print(loss)
  # 모든 파라미터에 대해 gradient 계산
  loss.backward()

  # 파라미터 업데이트
  optimizer.step()

  print('epoch {}, loss {}'.format(epoch, loss.item()))

 

이 코드는 앞뒤는 자르고 볼게요. 각 epoch마다 optimizer에서 zero_grad()를 통해 전 단계의 정보는 지우고요.

들어온 값을 처리합니다.

 

y hat에서 y를 빼서 오차를 구하고, 오차에 대하여 편미분을 진행합니다. 이건 backward() 를 통해 행해집니다.

 

위 코드는 데이터 로더 없이 모든 그래디언트를 사용하게 됩니다.

SGD를 사용하면 또 다를 수도 있습니다.

 

# flatten을 수행하기 위한 class 정의
class ReshapeTransform:
  def __init__(self, new_size):
    self.new_size = new_size

  def __call__(self, img):
    result = torch.reshape(img, self.new_size)
    return result

# 이미지 변환을 위한 transforms 정의. image Resize, Center Crop, tensor로 변경, flatten
data_transforms = {
    'train': transforms.Compose([
        transforms.Resize(224),
        transforms.CenterCrop(224),
        transforms.ToTensor(),
        ReshapeTransform((-1,))
    ]),
    'val': transforms.Compose([
        transforms.Resize(224),
        transforms.CenterCrop(224),
        transforms.ToTensor(),
        ReshapeTransform((-1,))
    ]),
}

# image dataset 생성 개미: 클래스 0 / 벌: 클래스 1
image_datasets = {x: datasets.ImageFolder(os.path.join(data_dir, x),
                                          data_transforms[x])
                  for x in ['train', 'val']}

 

데이터 증강 코드도 한 번 보겠습니다.

이미지를 가져와서 변형해주고, 데이터셋에 추가해주고, 데이터 로더(위 코드엔 없음)도 설정해줍니다.


데이터셋 API도 알아봅시다.

 

모으고, 전처리하고, 트랜스폼한 데이터를 모아 데이터셋 삼을 수 있습니다.

 

예를 들어 happy라는 텍스트가 있고 여기에 Positive라는 레이블링을 하겠습니다.

from torch.utils.data import Dataset, DataLoader

# 파이토치의 Dataset 클래스를 상속받아 CustomDataset 클래스를 정의합니다.
class CustomDataset(Dataset):

    # 초기화 함수: 객체 생성 시 호출되며, 데이터와 레이블을 인자로 받아 저장합니다.
    def __init__(self, text, labels):

        # 레이블 정보를 저장합니다.
        self.labels = labels

        # 텍스트 데이터를 저장합니다.
        self.data = text

    # 데이터셋의 총 길이(데이터 개수)를 반환하는 함수입니다.
    def __len__(self):
        return len(self.labels)

    # 인덱스를 받아 해당 인덱스의 데이터와 레이블을 반환하는 함수입니다.
    def __getitem__(self, idx):

        # 해당 인덱스의 레이블을 가져옵니다.
        label = self.labels[idx]

        # 해당 인덱스의 텍스트 데이터를 가져옵니다.
        text = self.data[idx]

        # 텍스트와 레이블을 딕셔너리 형태로 묶어 반환합니다.
        sample = {"Text": text, "Class": label}

        return sample

 

그러면 위 코드와 같은 방식으로 데이터를 받아, 데이터셋으로 만들어줄 수 있습니다.

 

이건 그냥 단순한 텍스트여서 별 거 없는데, 이미지나 음성 데이터라면 텐서로 변환하고 데이터셋으로 처리해야 할 수 있습니다.

 

이런 표준화를 잘 해두어야 남들이 사용할 때 쉽게 사용할 수가 있습니다.

늘 잘 정리하는 것이 협업의 지름길!

 

이렇게 데이터셋을 만들었으면 DataLoader를 통해 Batch를 생성하고 데이터를 변환합니다.

# 배치 크기가 2인 DataLoader 객체를 생성하고 데이터를 섞어서 로드합니다.
MyDataLoader = DataLoader(MyDataset, batch_size=2, shuffle=True)

# DataLoader에서 첫 번째 배치의 데이터를 가져와 출력합니다.
next(iter(MyDataLoader))

 

MyDataLoader을 Iterable한 제네레이터로 생성해주고, 배치 크기가 2이기 때문에 매 호출마다 2쌍의 데이터가 불러와집니다.

셔플 옵션 때문에 순서는 랜덤으로 나오겠네요.

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)

 

이런 옵션들이 있는데 collate_fn은 { Data : Label } 쌍이 데이터는 데이터끼리, 레이블은 레이블끼리 묶어주는 기능이라고 합니다.

그 외에도 여러 옵션이 있으나 이건 알아두면 좋은 거 정도?

 

데이터를 로드해와서 트랜스포머로 224 사이즈로 자른다든가 뒤집는다든가 해서 다양한 데이터를 만들고 텐서로 바꿔주면

데이터를 올바르게 사용하는 게 되겠습니다!