부스트캠프 AI Tech 4기

PyTorch 모델 불러오기

쉬엄쉬엄블로그 2023. 5. 19. 11:36
728x90

이 색깔은 주석이라 무시하셔도 됩니다.

학습 결과를 공유하고 싶다면?

  • 학습 결과를 저장할 필요가 있다.

model.save()

  • 학습의 결과를 저장하기 위한 함수
  • 모델 형태(architecture)와 파라미터를 저장
    • 파라미터만 저장하면 적은 용량으로 저장 가능
  • 모델 학습 중간 과정의 저장을 통해 최선의 결과모델을 선택
  • 만들어진 모델을 외부 연구자와 공유하여 학습 재연성 향상
import torch
import torch.nn as nn
import torch.optim as optim
import os

class TheModelClass(nn.Module):
    def __init__(self):
        super(TheModelClass, self).__init__()
        self.layer1 = nn.Sequential(
            nn.Conv2d(3, 16, kernel_size=3, stride=2, padding=0),
            nn.BatchNorm2d(16),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2))

        self.layer2 = nn.Sequential(
            nn.Conv2d(16, 32, kernel_size=3, stride=2, padding=0),
            nn.BatchNorm2d(32),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2))

        self.layer3 = nn.Sequential(
            nn.Conv2d(32, 64, kernel_size=3, stride=2, padding=0),
            nn.BatchNorm2d(64),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2))

        self.drop_out = nn.Dropout()
        self.fc1 = nn.Linear(3 * 3 * 64, 1000)
        self.fc2 = nn.Linear(1000, 1)

    def forward(self, x):
        out = self.layer1(x)
        out = self.layer2(out)
        out = self.layer3(out)

        out = out.view(out.size(0), -1)
        out = self.drop_out(out)
        out = self.fc1(out)
        out = self.fc2(out)
        return out

# Initialize model
model = TheModelClass().cuda()

# Initialize optimizer
optimizer = optim.SGD(model.parameters(), lr=0.1, momentum=0.9)

# Print model's state_dict
print("Model's state_dict:")
for param_tensor in model.state_dict():
    print(param_tensor, "\t", model.state_dict()[param_tensor].size())

'''
Model's state_dict:
layer1.0.weight      torch.Size([16, 3, 3, 3])
layer1.0.bias      torch.Size([16])
layer1.1.weight      torch.Size([16])
layer1.1.bias      torch.Size([16])
layer1.1.running_mean      torch.Size([16])
layer1.1.running_var      torch.Size([16])
layer1.1.num_batches_tracked      torch.Size([])
layer2.0.weight      torch.Size([32, 16, 3, 3])
layer2.0.bias      torch.Size([32])
layer2.1.weight      torch.Size([32])
layer2.1.bias      torch.Size([32])
layer2.1.running_mean      torch.Size([32])
layer2.1.running_var      torch.Size([32])
layer2.1.num_batches_tracked      torch.Size([])
layer3.0.weight      torch.Size([64, 32, 3, 3])
layer3.0.bias      torch.Size([64])
layer3.1.weight      torch.Size([64])
layer3.1.bias      torch.Size([64])
layer3.1.running_mean      torch.Size([64])
layer3.1.running_var      torch.Size([64])
layer3.1.num_batches_tracked      torch.Size([])
fc1.weight      torch.Size([1000, 576])
fc1.bias      torch.Size([1000])
fc2.weight      torch.Size([1, 1000])
fc2.bias      torch.Size([1])
'''
from torchsummary import summary
summary(model, (3, 224, 224))

'''
----------------------------------------------------------------
        Layer (type)               Output Shape         Param #
================================================================
            Conv2d-1         [-1, 16, 111, 111]             448
       BatchNorm2d-2         [-1, 16, 111, 111]              32
              ReLU-3         [-1, 16, 111, 111]               0
         MaxPool2d-4           [-1, 16, 55, 55]               0
            Conv2d-5           [-1, 32, 27, 27]           4,640
       BatchNorm2d-6           [-1, 32, 27, 27]              64
              ReLU-7           [-1, 32, 27, 27]               0
         MaxPool2d-8           [-1, 32, 13, 13]               0
            Conv2d-9             [-1, 64, 6, 6]          18,496
      BatchNorm2d-10             [-1, 64, 6, 6]             128
             ReLU-11             [-1, 64, 6, 6]               0
        MaxPool2d-12             [-1, 64, 3, 3]               0
          Dropout-13                  [-1, 576]               0
           Linear-14                 [-1, 1000]         577,000
           Linear-15                    [-1, 1]           1,001
================================================================
Total params: 601,809
Trainable params: 601,809
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.57
Forward/backward pass size (MB): 5.53
Params size (MB): 2.30
Estimated Total Size (MB): 8.40
----------------------------------------------------------------
'''
MODEL_PATH ="saved"
if not os.path.exists(MODEL_PATH):
    os.makedirs(MODEL_PATH)
# state_dict() : 모델의 파라미터를 의미
torch.save(model.state_dict(), os.path.join(MODEL_PATH, "model.pt"))

# 모델의 파라미터를 저장
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

new_model = TheModelClass() # 불러올 때 동일한 모델이어야 함
# 파라미터 불러오기(같은 모델의 형태에서만 가능)
new_model.load_state_dict(torch.load(os.path.join(MODEL_PATH, "model.pt")))

# 모델의 architecture와 함께 저장
torch.save(model, os.path.join(MODEL_PATH, "model_pickle.pt"))  # 모델 구조 자체를 pickle 형태로 저장할 수도 있음
# 모델의 architecture와 함께 불러오기
model = torch.load(os.path.join(MODEL_PATH, "model_pickle.pt"))
  • 보통 파라미터만 저장하고 불러오는 방식을 더 많이 사용
  • 모델 구조까지 저장하고 불러오는 방식은 코드까지(?) 공유가 되어야 해서 번거로움

checkpoints

  • 학습의 중간 결과를 저장하여 최선의 결과를 선택
  • ealrystopping 기법 사용시 이전 학습의 결과물을 저장
  • loss와 metric 값을 지속적으로 확인 저장
  • 일반적으로 epoch, loss, metric을 함께 저장하여 확인
  • colab에서 지속적인 학습을 위해 필요
for e in range(1, EPOCHS+1):
    epoch_loss = 0
    epoch_acc = 0
    for X_batch, y_batch in dataloader:
        X_batch, y_batch = X_batch.to(device), y_batch.to(device).type(torch.cuda.FloatTensor)

        optimizer.zero_grad()        
        y_pred = model(X_batch)

        loss = criterion(y_pred, y_batch.unsqueeze(1))
        acc = binary_acc(y_pred, y_batch.unsqueeze(1))

        loss.backward()
        optimizer.step()

        epoch_loss += loss.item()
        epoch_acc += acc.item()


    torch.save({
        'epoch': e,    # 모델의 정보를 epoch과 함께 저장
        'model_state_dict': model.state_dict(),
        'optimizer_state_dict': optimizer.state_dict(),
        'loss': epoch_loss,
        }, f"saved/checkpoint_model_{e}_{epoch_loss/len(dataloader)}_{epoch_acc/len(dataloader)}.pt")


    print(f'Epoch {e+0:03}: | Loss: {epoch_loss/len(dataloader):.5f} | Acc: {epoch_acc/len(dataloader):.3f}')

# 사용 예시
checkpoint = torch.load(PATH)
model.load_state_dict(checkpoint['model_state_dict'])
optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
epoch = checkpoint['epoch']
loss = checkpoint['loss']

Transfer learning

  • 다른 데이터셋으로 만든 모델을 현재 데이터에 적용
  • 일반적으로 대용량 데이터셋으로 만들어진 모델의 성능 ↑
  • 현재의 DL에서는 가장 일반적인 학습 기법
  • backbone architecture가 잘 학습된 모델에서 일부분만 변경하여 학습을 수행함
  • CV는 TorchVision, NLP는 HuggingFace가 사실상 표준

Freezing

  • pretrained model을 활용시 모델의 일부분을 frozen 시키고 학습
from torch import nn
from torchvision import models

class MyNewNet(nn.Module):   
    def __init__(self):
        super(MyNewNet, self).__init__()
        self.vgg19 = models.vgg19(pretrained=True)
        self.linear_layers = nn.Linear(1000, 1)    # 모델의 마지막에 linear layer 추가


    # Defining the forward pass    
    def forward(self, x):
        x = self.vgg19(x)        
        return self.linear_layers(x)

my_model = MyNewNet()
my_model = my_model.to(device)

for param in my_model.parameters():    # 마지막에 추가했던 linear layer를 제외한 나머지 layer들은 gradient 계산 안하도록 설정(frozen)
    param.requires_grad = False
for param in my_model.linear_layers.parameters():    # 마지막에 추가한 linear layer는 gradient 계산하도록 설정
    param.requires_grad = True
  • 모델 저장할 때 pth 확장자명은 안쓰는 것을 추천
    • pth가 파이썬에서 별도의 확장자로 사용하는 확장자명임