ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [파이토치 딥러닝 프로젝트 모음집] Image Classification Part 4. 작물 잎 사진으로 질병 분류하기
    얼렁뚱땅 파이토치 딥러닝 프로젝트 모음집 2022. 8. 18. 21:10

    파이토치 딥러닝 프로젝트 모음집의 책의 경우 1~3은 이론에 대한 설명이 있다.

    컴팩트하게 이론이 잘 정리되어 있으니 이것을 공부하면 좋을 거 같다.

    하지만, 티스토리에서는 이론에 대한 것 보다는 코드를 올려볼까한다.

     

    코드를 작성하는 거가 딱 눈으로 보면 너무 이해도 잘 되고 쉽지만

    막상 코딩을 짜려고 하면 사소한 파라미터 하나를 까먹기도 하고, 분명 torch에 있는 건데.. 어떻게 부르더라? 와 같이 다 안다고 생각하지만 모르는 부분이 꽤 있다고 생각한다.

     

    그래서 이 책을 하면서, 다양한 데이터 형태와 다양한 Task에 대해서 어떻게 접근해야 할 지를 보고자 한다

    그리고 여기에 없는 정형 데이터를 이용한 Regression 등과 같은 것도

    천천히 여기다가 정리를 해보고자 한다.

    https://www.coupang.com/vp/products/5936870581?itemId=10567231587&vendorItemId=77848585109&src=1042503&spec=10304982&addtag=400&ctag=5936870581&lptag=10304982I10567231587&itime=20220818200356&pageType=PRODUCT&pageValue=5936870581&wPcid=16294632729038521084456&wRef=&wTime=20220818200356&redirect=landing&gclid=Cj0KCQjwxveXBhDDARIsAI0Q0x0R6jqwcis_xX1AN6r3prw-65I8FfTAOZeBzNglwc0p-adSfRF_G_saAjE2EALw_wcB&campaignid=12207438463&adgroupid=115720946583&isAddedCart=

     

    이것은 책의 공식 Github이다.

    여기서 데이터 셋을 다운받아서 쉽게 따라할 수 있으니 참고하면 좋을 거 같다.

    그럼 코드도 있고 데이터도 있는데 굳이 책을 왜 봐? 라고 생각할 수도 있지만, 책에 한 줄 한 줄 코드에 대한 설명이 있기 때문에 나는 책을 보는 것을 추천한다.

     

    https://github.com/deep-learning-with-projects/deep-learning-with-projects

     

    GitHub - deep-learning-with-projects/deep-learning-with-projects

    Contribute to deep-learning-with-projects/deep-learning-with-projects development by creating an account on GitHub.

    github.com

     

    Train/Val/Test 데이터 분류

    - os.listdir(!!)이 의미하는 것은 !! 경로에 있는 데이터들의 이름을 가져오는 것을 의미한다.

    - os.path.join(a,b)는 a 경로와 b 경로를 합치는 것이다 a/b와 같이

    - 공식 github에는 mkdir로 폴더를 만들지만, 나는 makedirs로 만들었다. 그렇게 한 이유는 다른 건 없고, exist_ok를 써주기 위해서다. 가끔 오타나서 중간까지만 실행되고 그럴 때가 있는데 이미 앞의 경우 폴더를 만들어 버렸으면, 에러가 뜬다. 그것을 방지해주는 것이다.

    import os
    import shutil
    
    dataroot = 'dataset'
    classes_list = os.listdir(dataroot)
    
    base_dir = 'splitted'
    os.makedirs(base_dir, exist_ok=True)
    
    train_dir = os.path.join(base_dir, 'train')
    os.makedirs(train_dir, exist_ok=True)
    val_dir = os.path.join(base_dir, 'val')
    os.makedirs(val_dir, exist_ok=True)
    test_dir = os.path.join(base_dir, 'test')
    os.makedirs(test_dir, exist_ok=True)
    
    for class_ in classes_list :
        os.makedirs(os.path.join(train_dir, class_))
        os.makedirs(os.path.join(val_dir, class_))
        os.makedirs(os.path.join(test_dir, class_))
        
    import math
    
    for class_ in classes_list :
        path = os.path.join(dataroot, class_)
        fnames = os.listdir(path)
        
        train_size = math.floor(len(fnames)*0.6)
        validation_size = math.floor(len(fnames)*0.2)
        test_size = math.floor(len(fnames)*0.2)
    
        train_fnames = fnames[:train_size]
        print('Train size : ' + str(len(train_fnames)) )
    
        for fname in train_fnames :
            src = os.path.join(path, fname)
            dst = os.path.join(os.path.join(train_dir,class_), fname)
            shutil.copyfile(src,dst)
    
        validation_fnames = fnames[train_size:(validation_size + train_size)]
        print('Validation size : ' + str(len(validation_fnames)) )
    
        for fname in validation_fnames :
            scr = os.path.join(path,fname)
            dst = os.path.join(os.path.join(val_dir, class_), fname)
            shutil.copyfile(src, dst)
    
        test_fnames = fnames[(train_size + validation_size):(validation_size + train_size + test_size)]
        print('Test size : ' + str(len(test_fnames)) )
    
        for fname in test_fnames :
            scr = os.path.join(path,fname)
            dst = os.path.join(os.path.join(test_dir, class_), fname)
            shutil.copyfile(src, dst)

     

    Torch 설정, Transform, Dataloader를 이용한 데이터 전처리

    - Torchvision.datasets에 있는 ImageFolder의 경우 아래와 같이 폴더명이 Label이고 그 안에 Label에 해당하는 이미지가 잘 정리되어 있을 때 사용하면 좋다. 

    - 저절로 Apple_Apple_scab은 0 과 같이 labeling이 된다

    - 근데, 0번이 뭐고 1번이 어떤 데이터일까 궁금할 수도 있는데 그것을 확인하고 싶으면 (아래)코드를 사용하면 된다

    train_dataset.class_to_idx

    - 쉽게 보자면 ImageFolder의 경우 Transform을 사용하여 이미지 데이터를 전처리해주고, 그 다음에 Labeling을 진행해준 다고 생각하면 될 거 같다

    - 밑의 Dataloader의 경우 딥러닝 모델에 들어갈 수 있는 자세를 취해준다고 보면 된다 그러니까 Batch size대로 잘라주고 그런다

    import torch
    
    USE_CUDA = torch.cuda.is_available()
    DIVECE = torch.device('cuda' if USE_CUDA else 'cpu')
    
    BATCH_SIZE = 256
    EPOCH = 30
    
    import torchvision.transforms as transforms
    from torchvision.datasets import ImageFolder
    
    transform_base = transforms.Compose(
        [transforms.Resize((64,64)),
        transforms.ToTensor()]
    )
    
    train_dataset = ImageFolder(root = 'splitted/train', transform = transform_base)
    val_dataset = ImageFolder(root = 'splitted/val', transform = transform_base)
    
    from torch.utils.data import DataLoader
    
    train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=4)
    val_loader = torch.utils.data.DataLoader(val_dataset,batch_size=BATCH_SIZE, shuffle=True, num_workers=4)

     

    모델 정의하기

    - 정말 자랑은 아니지만 올만에 했더니 기억이 새록새록 한 게 재밌었다 >_<

    import torch.nn as nn
    import torch.nn.functional as F
    import torch.optim as optim
    
    class Net(nn.Module) :
        def __init__(self):
            super(Net, self).__init__()
            self.conv1 = nn.Conv2d(3,32,3, padding = 1)
            self.pool = nn.MaxPool2d(2,2)
            self.conv2 = nn.Conv2d(32,64,3, padding = 1)
            self.conv3 = nn.Conv2d(64,64,3, padding = 1)
            self.fc1 = nn.Linear(4096, 512)
            self.fc2 = nn.Linear(512,33)
        
        def forward(self,x) :
            x = self.conv1(x)
            x = F.relu(x)
            x = self.pool(x)
            x = F.dropout(x, p=0.25, training=self.training)
    
            x = self.conv2(x)
            x = F.relu(x)
            x = self.pool(x)
            x = F.dropout(x, p=0.25, training=self.training)
    
            x = self.conv3(x)
            x = F.relu(x)
            x = self.pool(x)
            x = F.dropout(x, p=0.25, training=self.training)
    
            x = x.view(-1,4096)
            x = self.fc1(x)
            x = F.relu(x)
            x = F.dropout(x, p=0.5, training=self.training)
            x = self.fc2(x)
    
            return F.log_softmax(x, dim=1)

     

    모델 객체 할당, Optimizer 정의

    model_base = Net().to(DIVECE)
    optimizer = optim.Adam(model_base.parameters(), lr = 0.001)

     

    Train / evaluate 함수 정의

    - CE에는 Softmax가 CE 안에 포함되어 있어서 Softmax를 굳이 쓰지 않아도 괜찮음.
    - 하지만 BCE에서는 CE만 구현되어 있기 때문에 Activation Function이 반드시 필요함
    - 만약에 BCE에서 Sigmoid를 사용하고 CE 하고 싶으면, BCEWithLogitsLoss를 사용하면 자체에 Sigmoid까지 포함하고 있음
    - print(output.size()) # 256(bs) x 33 (batch, class)
    - print(target.size()) # 256 (batch, )
    - BCELoss에서는 output : (batch,)  // target : (batch,) 

     

    - print(output.max(1, keepdim=True))
    - [값이 가장 큰 것 max값 한 개 뽑아(Softmax 통과 후 확률값), indices = 가장 큰 값의 인덱스 반환)
    - output.max(1, keepdim=True)[1] - > 확률값이 가장 큰 것의 인덱스를 의미함
    - target.view_as(pred)가 의미하는 것은 target을 pred랑 같은 shape로 만들어라 라는 의미
    - 그리고 pred.eq(~)는 pred와 ~가 같으면 1 아니면 0으로 반환함
    - 그래서, sum() 함수가 있기 때문에 같은 애들의 개수를 셈

    def train(model, train_loader, optimizer) :
        model.train()
        for batch_idx, (data, target) in enumerate(train_loader) :
            # print(target)
            data, target = data.to(DIVECE), target.to(DIVECE)
            optimizer.zero_grad()
            output = model(data)
            loss =F.cross_entropy(output, target)
            loss.backward()
            optimizer.step()
    
    def evaluate(model, test_loader) :
        model.eval()
        test_loss = 0
        correct = 0
    
        with torch.no_grad() :
            for data, target in test_loader :
                data, target = data.to(DIVECE), target.to(DIVECE)
                output = model(data)
                test_loss += F.cross_entropy(output, target, reduction='sum').item()
    
                pred = output.max(1, keepdim=True)[1]
                correct += pred.eq(target.view_as(pred)).sum().item()
    
        test_loss /= len(test_loader.dataset)
        test_accuracy = 100.0 * correct / len(test_loader.dataset)
        return test_loss, test_accuracy

     

    Train_baseline 정의

    - 위에 Train은 진짜 딱 모델을 학습시키는 것이고 Train_baseline은 epoch에 따라 몇번을 돌게 할건지, val 데이터를 이용해서 evaluate해보고 제일 좋은 성능의 모델을 저장하기 등 Traning 과정 속에서 필요한 다양한 것을 수행한다.

     

    import time
    import copy
    
    def train_baseline(model, train_loader, val_loader, optimizer, num_epochs = 30) :
        best_acc = 0.0
        best_model_wts = copy.deepcopy(model.state_dict())
    
        for epoch in range(1,num_epochs + 1) :
            since = time.time()
            train(model, train_loader, optimizer)
            train_loss, train_acc = evaluate(model, train_loader)
            val_loss, val_acc = evaluate(model, val_loader)
    
            if val_acc > best_acc :
                bast_acc = val_acc
                best_model_wts = copy.deepcopy(model.state_dict())
    
            time_elapsed = time.time() - since
            print('------epoch {}------'.format(epoch))
            print('train_loss : {:4f}, train_accuracy : {:.2f}%'.format(train_loss, train_acc))
            print('val_loss : {:4f}, val_accuracy : {:.2f}%'.format(val_loss, val_acc))
        
        model.load_state_dict(best_model_wts)
        return model

     

    학습 진행하기

    - train_baseline 함수에서 마지막으로 val 데이터셋을 이용해 테스트 했을 때 가장 좋은 성능을 가진 모델을 반환하게 했기 때문에 해당 모델을 저장한다.

    - 아래 사진과 같이 프린트된다. 

    base = train_baseline(model_base, train_loader, val_loader, optimizer, EPOCH)
    torch.save(base,'baseline.pt')

     

     

    Test 데이터를 이용해서 평가, Inference

    from PIL import Image
    
    test_dataset = ImageFolder(root = 'splitted/test', transform = transform_base)
    test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=4)
    
    test_loss, test_accuracy = evaluate(base, test_loader)
    print('test loss : ' + str(test_loss))
    print('test accuracy : ' + str(test_accuracy))
    
    image = Image.open('splitted/test/Grape___Esca_(Black_Measles)/image (80).JPG')
    display(image)
    print('real class : Grape___Esca_(Black_Measles)')
    
    image = transform_base(image)
    image = image.reshape(1,3,64,64)
    image = image.cuda()
    
    pred_prob = model_base(image)
    pred = pred_prob.max(1, keepdim=True)[1]
    
    cls_dict = train_dataset.class_to_idx
    reverse_cls = {v:k for k,v in cls_dict.items()}
    print('pred clss : ' + reverse_cls[pred.item()])

     

    댓글

Designed by Tistory.