[Pytorch] Pytorch 모델링 구조

전체 프로젝트 구조


data/
experiments/
model/
    net.py
    data_loader.py
train.py
evaluate.py
search_hyperparams.py
synthesize_results.py
evaluate.py
utils.py
  • model / net.py: 뉴럴 네트워크, loss function, evaluation metrics를 지정
  • model / data_loader.py: 네트워크에 데이터를 feeding
  • train.py: 메인 training loop
  • evaluate.py: 메인 evaluation loop
  • utils.py: hyperparams / logging / storingmodel 하는 유틸리티 함수

 

Custom Model 구조


  1.  nn.Module을 상속받아 오버라이딩
  2. . __init__(): 객체가 생성될 때 자동 호출, 속성 값을 초기화
  3. . super(CustomModel, self).__init__(): 부모 클래스에서 받아오기
  4. . forward : prediction 값을 반환

예시

# module import
import torch
import torch.nn as nn


class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        ## 생략하고 super().__init__() 로만 작성해도 됨.
        ## 현재 커스텀모델에서 정의한 인자 외의 인자들이 있다면, 부모클래스인 nn.Module에서(self)에서 받아오기

        self.conv1 = nn.Conv2d(3, 16, 3, padding='same')
        self.conv2 = nn.Conv2d(16, 32, 3, padding='same')
        self.conv3 = nn.Conv2d(32, 64, 3, padding='same')
        self.conv4 = nn.Conv2d(64, 128, 3, padding='same')
        self.conv5 = nn.Conv2d(128, 256, 3, padding='same')
        
        self.pool = nn.MaxPool2d(2, 2)
        
        self.dropout = nn.Dropout(0.25)
        
        self.fc1 = nn.Linear(7*7*256, 1024)
        self.fc2 = nn.Linear(1024, 128)
        self.fc3 = nn.Linear(128, 10)
    
    def forward(self, x):
        x = torch.relu(self.conv1(x))
        x = self.pool(x)
        x = torch.relu(self.conv2(x))        
        x = self.pool(x)
        x = torch.relu(self.conv3(x))
        x = self.pool(x)
        x = torch.relu(self.conv4(x))
        x = self.pool(x)
        x = torch.relu(self.conv5(x))
        x = self.pool(x)
        x = x.view(-1, 7*7*256)
        x = self.dropout(x)
        x = torch.relu(self.fc1(x))
        x = torch.relu(self.fc2(x))
        x = self.fc3(x)
        return x


model = Net()

# Use GPU
model = model.cuda()
print(model)

 

모델 정보 확인

1. model.state_dict()

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

2. torchsummary

from torchsummary import summary
	summary(model, (input_size))

3. named_modules()

for name, layer in CustomMol.named_modules():
	print(name, layer)

 

Custom Dataset 만들기


  1. torch.utils.data.Dataset 클래스를 상속받는 자식 클래스 생성
  2. 다음 세 가지 메소드가 필요함
    - __init__(self, 인수들): 데이터셋을 처음 선언할 때 호출되는 함수 (path, transform등 인수로 받음)
    - __len__(self): 데이터 로데에서 내부적으로 사용됨
    - __getitem__(self, idx): 데이터 로더에서 내부적으로 사용됨

데이터셋 - 예시

import torch
from torch.utils.data import Dataset, DataLoader


class SimpleDataset(Dataset):
    def __init__(self, t):
        self.t = t

    def __len__(self):
        return self.t

    def __getitem__(self, idx):
        return torch.LongTensor([idx])
        
if __name__ == "__main__":
    dataset = SimpleDataset(t=5)
    print(len(dataset))
    it = iter(dataset)

    for i in range(10):
        print(i, next(it))

데이터 로더 - 예시

if __name__ == "__main__":
    dataset = SimpleDataset(t=5)
    dataloader = DataLoader(dataset=dataset,
                            batch_size=2,
                            shuffle=True,
                            drop_last=False)

    for epoch in range(2):
        print(f"epoch : {epoch} ")
        for batch in dataloader:
            print(batch)
  • 데이터 로더에는 배치 사이즈 및 셔플을 지정
  • drop_last: 배치를 돌고 남은 샘플들

커스텀 데이터셋

import glob
import torch
from torchvision import transforms
from PIL import Image
from torch.utils.data import Dataset, DataLoader


class catdogDataset(Dataset):
    def __init__(self, path, train=True, transform=None):
        self.path = path
        if train:
            self.cat_path = path + '/cat/train'
            self.dog_path = path + '/dog/train'
        else:
            self.cat_path = path + '/cat/test'
            self.dog_path = path + '/dog/test'
        
        self.cat_img_list = glob.glob(self.cat_path + '/*.png')
        self.dog_img_list = glob.glob(self.dog_path + '/*.png')

        self.transform = transform

        self.img_list = self.cat_img_list + self.dog_img_list
        self.class_list = [0] * len(self.cat_img_list) + [1] * len(self.dog_img_list) 
    
    def __len__(self):
        return len(self.img_list)
    
    def __getitem__(self, idx):
        img_path = self.img_list[idx]
        label = self.class_list[idx]
        img = Image.open(img_path)

        if self.transform is not None:
            img = self.transform(img)

        return img, label
  • __init__: path / train 여부 / transform을 입력 받음
  • __len__:  전체 이미지 개수를 return
  • __getitem__: idx번째 이미지를 PIL.Image로 열고 transform 적용
    -> torchvision.transforms은 PIL Image를 인풋으로 받기 때문에 Image.open(img_path) 사용
    -> transform에서 ToTensor() 이전에 transforms.Resize, CenterCrop, RandomHorizontalFlip 등 적용 가능
if __name__ == "__main__":
    transform = transforms.Compose(
        [
            transforms.ToTensor(),
        ]
    )

    dataset = catdogDataset(path='./cat_and_dog', train=True, transform=transform)
    dataloader = DataLoader(dataset=dataset,
                        batch_size=1,
                        shuffle=True,
                        drop_last=False)

    for epoch in range(2):
        print(f"epoch : {epoch} ")
        for batch in dataloader:
            img, label = batch
            print(img.size(), label)

 

정형데이터 예시

from torch.utils.data import Dataset
from sklearn.preprocessing import StandardScaler

class CustomDataset(Dataset):
    def __init__(self, df, target='target', normalize=True):
        super(CustomDataset, self).__init__()
        self.x = df.drop(target, 1)
        
        # 데이터 표준화
        if normalize:
            scaler = StandardScaler()
            self.x = pd.DataFrame(scaler.fit_transform(self.x))
        
        self.y = data['target']
        
        # 텐서 변환
        self.x = torch.tensor(self.x.values).float()
        self.y = torch.tensor(self.y).float()
        
    def __len__(self):
        return len(self.x)
    
    def __getitem__(self, idx):
        x = self.x[idx]
        y = self.y[idx]
        return x, y
        
dataset = CustomDataset(df, 'target', True)

 

사전 학습된 모델 사용


from torch import nn
from torchvision import models

## Custom Model 정의
class CustomModel(nn.Module):   
    def __init__(self):
        super(CustomModel, self).__init__()
        self.기존모델 = models.사전학습모델(pretrained=True)
        self.linear_layers = nn.Linear(output, num_classes) 

    def forward(self, x):
        x = self.기존모델(x)        
        return self.linear_layers(x)
## cuda 설정
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')


## accuracy 함수 정의
def binary_acc(y_pred, y_test):
    y_pred_tag = torch.round(torch.sigmoid(y_pred))

    correct_results_sum = (y_pred_tag == y_test).sum().float()
    acc = correct_results_sum/y_test.shape[0]
    acc = torch.round(acc * 100)
    
    return acc


## Model Training (Transfer Learning)
my_model = Custom_Model()
my_model = my_model.to(device)

# freeze
for param in my_model.parameters():
    param.requires_grad = False ## 파라미터 freeze
    
for param in my_model.linear_layers.parameters():
    param.requires_grad = True ## ## 마지막 Linear 층만 학습
    
    
## Loss 및 Optimizer 함수 정의
criterion = nn.BCEWithLogitsLoss()
optimizer = optim.Adam(my_model.parameters(), lr=LEARNING_RATE)


## Training !!
for epoch 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 = my_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()
        
        ## Checkpoints 및 Early Stopping 설정 추가 가능
        

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

 

훈련 루프


output_batch = model(train_batch) # 모델 output 계산
loss = loss_fn(output_batch, labels_batch)  # loss 계산

optimizer.zero_grad()  # gradients 초기화
loss.backward()        # gradients 계산

optimizer.step()       # 가중치 업데이트

 

손실 함수


# torch.nn 모듈에에서 loss function 가져오기
loss_fn = nn.CrossEntropyLoss()
loss = loss_fn(out, target)

커스텀 손실 함수

def myCrossEntropyLoss(outputs, labels):
    batch_size = outputs.size()[0]
    # 아웃풋의 배치 사이즈
    outputs = F.log_softmax(outputs, dim=1)
    # 로그 소프트맥스 계산
    outputs = outputs[range(batch_size), labels]
    return -torch.sum(outputs)/num_examples

 

Optimizer


# torch.optim
optimizer = torch.optim.SGD(model.parameters(), lr = 0.01, momentum=0.9)

optimizer = torch.optim.Adam(model.parameters(), lr = 0.0001)

 

정확도 계산


def accuracy(out, labels):
    outputs = np.argmax(out, axis=1)
    return np.sum(outputs==labels)/float(labels.size)
    
metrics = { 'accuracy': accuracy,
            ##add your own custom metrics,
          }

 

모델 저장 및 로드


state = {'epoch': epoch + 1,
        'state_dict': model.state_dict(),
        'optim_dict' : optimizer.state_dict()}
utils.save_checkpoint(state,
                      is_best=is_best,
                      # True if this is the model with best metrics
                      checkpoint=model_dir)
                      # path to folder
                      
# 모델 로드
utils.load_checkpoint(restore_path, model, optimizer)
반응형