PyTorch 구조 - 1

c0natus·2022년 1월 31일
0

PyTorch

목록 보기
2/4

1. torch.nn.Module


  • 딥러닝 모델들은 block or layer의 박복이다.

  • 이러한 layer을 구현할 수 있도록 PyTorch에서는 torch.nn.Module을 지원한다.

  • torch.nn.Module은 딥러닝을 구성하는 layer의 base class이다.

  • 따라서 torch.nn.Module 안에는 input_feature, output_feature, forward, backward가 정의되어 있어야하고, 학습의 대상이 되는 parameter(tensor)가 정의되어 있어야한다.

1.1. torch.nn.Parameter

  • torch.nn.Module안에 parameter를 정의하기 위해 torch.nn.Parameter class가 사용된다.

  • torch.nn.Parameter는 torch.Tensor 객체의 상속 객체로, torch.nn.Moudle 내에 attribute가 될 때는 requires_grad=True로 지정되어 학습 대상이 된다.

  • 대부분의 layer에는 weights 값들이 지정되어 있어, 직접 지정할 일은 잘 없다.

1.2. 예제: nn.Module 변수 초기화 및 forward 정의

  • python super() 관련된 문법은 여기를 참고하자.
class MyClass(torch.nn.Module):
    def __init__(self, input_feature, output_feature, bias = True):
        super(MyClass, self).__init__()
        self.input_feature = input_feature
        self.output_feature = output_feature

        self.weight = torch.nn.Parameter(torch.randn(input_feature, output_feature))
        self.bias = torch.nn.Parameter(torch.randn(output_feature))

    # forward는 torch.nn.Module의 __call__에 등록되어 있다.
    def forward(self, x: Tensor):
        return x @ self.weights + self.bias

1.3. nn.Module backward 정의

  • backward는 예측값과 실제값의 차이(loss)에 대해 layer에 있는 각 parameter들의 미분을 수행 한다.

  • 그리고 parameter들의 미분값으로 parameter들을 업데이트 한다.


criterion = torch.nn.MSELoss() 
optimizer = torch.optim.SGD(model.parameters(), lr=learningRate)

for epoch in range(epochs):

    # 1. gradient buffer 초기화
    # 이전 epoch에서 사용한 gradient를 사용하지 않는다. 
    optimizer.zero_grad()

    # 2. 주어진 데이터를 model에 넣고 예측된 결과값 얻는다.
    y_hat = model(inputs)

    # 3. 예측한 결과값과 실제값으로 loss 값을 얻는다.
    # criterion은 cross entropy, RMSE 등으로 정의할 수 있다. 
    loss = criterion(y_hat, y)

    # 4. 각 parameter에 관한 gradient 값을 얻는다.
    loss.backward()

    # parameters를 한꺼번에 업데이트 한다.
    optimizer.step()
  • backward는 nn.Module에서 직접 지정이 가능하다.

  • PyTorch는 AutoGrad를 지원하기 때문에, nn.Parameter로 parameter를 선언하면 자동으로 계산해주므로 굳이 설정할 필요가 없다.

  • backward를 직접 지정하려면, nn.Module의 backward 함수와 optimizer 함수를 오버라이딩 해줘야 한다.

  • backward를 직정 지정하면, 미분식을 입력해줘야 하는 부담감이 있다.

  • backward를 쓸일은 없지만, 안에서 일어나는 순서는 파악해두자.

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

class LogisticRegression(nn.Module):
    def __init__(self, dim, lr=torch.scalar_tensor(0.01)):
        super(LogisticRegression, self).__init__()
        # intialize parameters
        self.w = ~~.to(device)
        self.b = ~~.to(device)
        self.grads = {"weight": ~~.to(device),
                      "bias": ~~.to(device)}
        self.lr = lr.to(device)

    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))

    def backward(self, x, yhat, y):
        ## compute backward
        self.grads["weight"] = (1/x.shape[1]) * torch.mm(x, (yhat - y).T)
        self.grads["bias"] = (1/x.shape[1]) * torch.sum(yhat - y)
    
    def optimize(self):
        ## optimization step
        self.w = self.w - self.lr * self.grads["weight"]
        self.b = self.b - self.lr * self.grads["bias"]

2. PyTorch Datasets and Dataloaders


  • PyTorch에서는 대용량 데이터를 잘 넣어줄 수 있는 Dataset API를 제공한다.

  • 분야마다 데이터 셋을 넣는 구조가 정해져 있다.

  • 모델에 데이터를 feed(먹이다)는 방법은 아래 사진과 같다.

자료수집 → transforms(전처리, tensor로 변환) → Dataset → DataLoader → Model

  • Dataset으로 전처리된 데이터 하나를 가져오는 방법을 정의한다.

  • DataLoader의 argument로는 Dataset과 batch_size 등이 있다

  • DataLoader는 Dataset에 정의된 방법으로 불러온 하나의 데이터를 batch_size만큼 모아 model에 feeding한다.

2.1. Dataset Class

  • 데이터 입력 형태를 정의하는 클래스로, 데이터를 입력하는 방식의 표준화한다.
class CustomDataset(Dataset):
	# 변수 초기화
    def __init__(self, text, labels):
            self.labels = labels
            self.data = text
	
    # len() 함수는 __len__ 함수를 호출한다.
    #
    # len()는 __len__과 다르게 정수인지, 
    # __len__이 존재하는 지 등을 확인한다.
    def __len__(self):
            return len(self.labels)

	# DataLoader가 iterater가 되고,
    # next()로 데이터를 뽑을 때,
    # __getitem__에 정의된 방식대로
    # 데이터를 가져온다.
    def __getitem__(self, idx):
            label = self.labels[idx]
            text = self.data[idx]
            sample = {"Text": text, "Class": label}
            return sample
  • Image, Text, Audio 등 데이터에 따라 입력 형태를 정의해야 한다. 즉, 데이터 형태에 따라 각 함수를 다르게 정의해야 한다.

  • 모든 것을 데이터 생성 시점에 처리할 필요는 없다.

  • 학습하는 시점에 Dataset에 넘겨준 transform의 정의에 따라 __getitem__ 함수 안에서 data를 tensor 등으로 바꾼다.

  • CPU에선 data를 tensor로 변환하고, GPU에서 변환된 data를 학습하는 등의 병렬 처리가 가능하기 때문에 충분한 시간이 있다.

  • 최근에는 HuggingFace 등 표준화된 라이브러리를 사용한다.

2.2. DataLoader Class

  • Data의 Batch를 생헝해주는 클래스로 학습 직전(GPU feed 직전) 데이터의 변환를 담당한다.

  • Tensor로 변환Batch 처리가 메인 업무이며, 병렬적인 데이터 전처리 코드의 고민이 필요하다.

MyDataLoader = DataLoader(MyDataset, batch_size=2, shuffle=True)
# next로 뽑는 시점에 batch_size만큼 memory(GPU)에 올라가서 처리된다.
next(iter(MyDataLoader))

DataLoader의 parameter에 대한 자세한 내용은 여기를 참고하자.

profile
Done is Better Than Perfect

0개의 댓글