-
[파이토치 딥러닝 프로젝트 모음집] 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()])
'얼렁뚱땅 파이토치 딥러닝 프로젝트 모음집' 카테고리의 다른 글
[파이토치 딥러닝 프로젝트] Text Classification Part 5. 국민 청원 분류하기 (0) 2022.08.24 [파이토치 딥러닝 프로젝트] Transfer Learning Part 4. 작물 잎 사진으로 질병 분류하기 (0) 2022.08.19