우선 간단한 이미지분류기를 하기 위해 앞서 포스팅한 전이학습에 대한 이해가 있어야 한다. 

링크를 통해 먼저 학습하고 다음 글을 이해하자.

https://undeadkwandoll.tistory.com/22

 

Transfer Learning(전이 학습)

"Transfer learning(TL) is a research problem in machine learning(ML) that focuses on storing knowledge gained while solving one problem and applying it to a different but related problem" [Definitio..

undeadkwandoll.tistory.com

 

마동석, 이병헌, 김종국 분류기를 만들어보자.

 

import matplotlib
import matplotlib.pyplot as plt
import matplotlib.font_manager as fm

fontpath='/usr/share/fonts/truetype/nanum/NanumBarunGothic.ttf'
font=fm.FontProperties(fname=fontpath, size=10)
plt.rc('font',family='NanumBarunGothic')
matplotlib.font_manager._rebuild()

import os
os.environ['KMP_DUPLICATE_LIB_OK']='True'

matplotlib는 누구나 알듯 그래픽 출력 라이브러리.

한국어 출력을 위한 font 설정.

!git clone https://github.com/ndb796/bing_image_downloader

fatal: destination path 'bing_image_downloader' already exists and is not an empty directory.

위 github 사이트에서 필요한 이미지 다운로더 라이브러리 다운.

 

import shutil
from bing_image_downloader.bing_image_downloader import downloader

그 후, 라이브러리 import.

 

 

directory_list=['C:/data/train/',
               'C:/data/test/',]

#초기 데릭토리 만들기
for directory in directory_list:
    if not os.path.isdir(directory):
        os.makedirs(directory)
        
#수집한 이미지를 학습 데이터와 평가 데이터로 구분하는 함수
def dataset_split(query,train_cnt):
    for directory in directory_list:
        if not os.path.isdir(directory+'/'+query):
            os.makedirs(directory+'/'+query)
    cnt=0
    for file_name in os.listdir(query):
        if cnt<train_cnt:
            print(f'[Train Dataset]{file_name}')
            shutil.move(query+'/'+file_name,'C:/data/train/'+query+'/'+file_name)
        else:
            print(f'[Test Dataset]{file_name}')
            shutil.move(query+'/'+file_name,'C:/data/test/'+query+'/'+file_name)
        cnt+=1
    shutil.rmtree(query)
            

 

 

 

초기 본인이 원하는 디렉토리를 지정하여, test,train data 디렉토리를 지정해준다. 그 후 우리가 받아야 할 이미지 파일의 저장경로를 지정해준다.

 

query='마동석'
downloader.download(query,limit=40,output_dir='./',adult_filter_off=True,force_replace=False,timeout=60)
dataset_split(query,30)

query = '김종국'
downloader.download(query, limit=40,  output_dir='./', adult_filter_off=True, force_replace=False, timeout=60)
dataset_split(query, 30)

query = '이병헌'
downloader.download(query, limit=40,  output_dir='./', adult_filter_off=True, force_replace=False, timeout=60)
dataset_split(query, 30)

 

위 코드를 통해, 마동석, 김종국, 이병헌에 대한 이미지 파일을 다운로드 한다.

 

import torch
import torch.nn as nn
import torch.optim as optim

import torchvision
from torchvision import datasets, models,transforms
import numpy as np
import time

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

 

GPU 사용을 최소화하기 위해 , 다음과같이 torch를 import 하여 모델을 학습시키고, Flask Server를 통해 이미지를 주고받는 식으로 사용하는 방법이다.

 

# 데이터셋을 불러올 때 사용할 변형(transformation) 객체 정의
transforms_train = transforms.Compose([
    transforms.Resize((224, 224)),
    transforms.RandomHorizontalFlip(), # 데이터 증진(augmentation)
    transforms.ToTensor(),
    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) # 정규화(normalization)
])

transforms_test = transforms.Compose([
    transforms.Resize((224, 224)),
    transforms.ToTensor(),
    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])

data_dir = 'C:/data'
train_datasets = datasets.ImageFolder(os.path.join(data_dir, 'train'), transforms_train)
test_datasets = datasets.ImageFolder(os.path.join(data_dir, 'test'), transforms_test)

train_dataloader = torch.utils.data.DataLoader(train_datasets, batch_size=4, shuffle=True, num_workers=4)
test_dataloader = torch.utils.data.DataLoader(test_datasets, batch_size=4, shuffle=True, num_workers=4)

print('학습 데이터셋 크기:', len(train_datasets))
print('테스트 데이터셋 크기:', len(test_datasets))

class_names = train_datasets.classes
print('클래스:', class_names)


학습 데이터셋 크기: 90
테스트 데이터셋 크기: 32
클래스: ['김종국', '마동석', '이병헌']

train data와 test data에 대해 transformation을 시킨 후 , 정규화 하여 pytorch tensor에 맞게 변형시켜준다.

 

이후, 확인해 보기 위한 코드를 진행한다.

def imshow(input, title):
    # torch.Tensor를 numpy 객체로 변환
    input = input.numpy().transpose((1, 2, 0))
    # 이미지 정규화 해제하기
    mean = np.array([0.485, 0.456, 0.406])
    std = np.array([0.229, 0.224, 0.225])
    input = std * input + mean
    input = np.clip(input, 0, 1)
    # 이미지 출력
    plt.imshow(input)
    plt.title(title)
    plt.show()


# 학습 데이터를 배치 단위로 불러오기
iterator = iter(train_dataloader)

# 현재 배치를 이용해 격자 형태의 이미지를 만들어 시각화
inputs, classes = next(iterator)
out = torchvision.utils.make_grid(inputs)
imshow(out, title=[class_names[x] for x in classes])

model = models.resnet34(pretrained=True)
num_features = model.fc.in_features
# 전이 학습(transfer learning): 모델의 출력 뉴런 수를 3개로 교체하여 마지막 레이어 다시 학습
model.fc = nn.Linear(num_features, 3)
model = model.to(device)

criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9)
num_epochs = 50
model.train()
start_time = time.time()

# 전체 반복(epoch) 수 만큼 반복하며
for epoch in range(num_epochs):
    running_loss = 0.
    running_corrects = 0

    # 배치 단위로 학습 데이터 불러오기
    for inputs, labels in train_dataloader:
        inputs = inputs.to(device)
        labels = labels.to(device)

        # 모델에 입력(forward)하고 결과 계산
        optimizer.zero_grad()
        outputs = model(inputs)
        _, preds = torch.max(outputs, 1)
        loss = criterion(outputs, labels)

        # 역전파를 통해 기울기(gradient) 계산 및 학습 진행
        loss.backward()
        optimizer.step()

        running_loss += loss.item() * inputs.size(0)
        running_corrects += torch.sum(preds == labels.data)

    epoch_loss = running_loss / len(train_datasets)
    epoch_acc = running_corrects / len(train_datasets) * 100.

    # 학습 과정 중에 결과 출력
    print('#{} Loss: {:.4f} Acc: {:.4f}% Time: {:.4f}s'.format(epoch, epoch_loss, epoch_acc, time.time() - start_time))
#0 Loss: 1.0096 Acc: 51.1111% Time: 25.6392s
#1 Loss: 0.4748 Acc: 80.0000% Time: 54.3030s
#2 Loss: 0.5483 Acc: 80.0000% Time: 84.5246s
#3 Loss: 0.5021 Acc: 81.1111% Time: 118.2342s
#4 Loss: 0.2609 Acc: 90.0000% Time: 150.8176s
#5 Loss: 0.1877 Acc: 91.1111% Time: 184.1703s
#6 Loss: 0.1991 Acc: 92.2222% Time: 214.8244s
#7 Loss: 0.1990 Acc: 95.5556% Time: 245.0987s
#8 Loss: 0.3073 Acc: 84.4444% Time: 275.1495s
#9 Loss: 0.1584 Acc: 93.3333% Time: 304.9051s
#10 Loss: 0.2887 Acc: 94.4444% Time: 335.1631s
#11 Loss: 0.2231 Acc: 92.2222% Time: 365.2182s
#12 Loss: 0.2435 Acc: 90.0000% Time: 397.8394s
#13 Loss: 0.2753 Acc: 87.7778% Time: 428.1891s
#14 Loss: 0.4128 Acc: 93.3333% Time: 458.8629s
#15 Loss: 0.4522 Acc: 86.6667% Time: 488.5716s
#16 Loss: 0.0865 Acc: 97.7778% Time: 518.9268s
#17 Loss: 0.2886 Acc: 92.2222% Time: 550.2028s
#18 Loss: 0.5119 Acc: 77.7778% Time: 580.1998s
#19 Loss: 0.4541 Acc: 88.8889% Time: 612.7564s
#20 Loss: 0.0438 Acc: 98.8889% Time: 643.0622s
#21 Loss: 0.1214 Acc: 96.6667% Time: 673.6289s
#22 Loss: 0.0683 Acc: 97.7778% Time: 703.9003s
#23 Loss: 0.0541 Acc: 98.8889% Time: 734.5212s
#24 Loss: 0.1325 Acc: 95.5556% Time: 764.8524s
#25 Loss: 0.4077 Acc: 90.0000% Time: 795.4360s
#26 Loss: 0.0673 Acc: 98.8889% Time: 826.0624s
#27 Loss: 0.0974 Acc: 95.5556% Time: 857.2442s
#28 Loss: 0.2435 Acc: 92.2222% Time: 887.8970s
#29 Loss: 0.1925 Acc: 95.5556% Time: 919.2276s
#30 Loss: 0.2115 Acc: 95.5556% Time: 950.5770s
#31 Loss: 0.0892 Acc: 96.6667% Time: 980.9778s
#32 Loss: 0.0289 Acc: 98.8889% Time: 1011.7911s
#33 Loss: 0.0399 Acc: 98.8889% Time: 1042.0252s
#34 Loss: 0.0207 Acc: 100.0000% Time: 1072.0983s
#35 Loss: 0.0575 Acc: 96.6667% Time: 1102.7425s
#36 Loss: 0.1135 Acc: 96.6667% Time: 1133.0551s
#37 Loss: 0.1599 Acc: 96.6667% Time: 1162.9883s
#38 Loss: 0.1392 Acc: 95.5556% Time: 1193.0512s
#39 Loss: 0.4575 Acc: 88.8889% Time: 1223.0743s
#40 Loss: 0.1242 Acc: 95.5556% Time: 1253.0653s
#41 Loss: 0.2291 Acc: 93.3333% Time: 1283.8887s
#42 Loss: 0.2359 Acc: 92.2222% Time: 1314.8435s
#43 Loss: 0.0434 Acc: 97.7778% Time: 1345.1724s
#44 Loss: 0.1595 Acc: 96.6667% Time: 1375.3669s
#45 Loss: 0.0501 Acc: 98.8889% Time: 1406.0365s
#46 Loss: 0.0097 Acc: 100.0000% Time: 1437.1793s
#47 Loss: 0.0804 Acc: 98.8889% Time: 1467.5719s
#48 Loss: 0.0749 Acc: 97.7778% Time: 1498.2033s
#49 Loss: 0.0100 Acc: 100.0000% Time: 1528.7953s
model.eval()
start_time = time.time()

with torch.no_grad():
    running_loss = 0.
    running_corrects = 0

    for inputs, labels in test_dataloader:
        inputs = inputs.to(device)
        labels = labels.to(device)

        outputs = model(inputs)
        _, preds = torch.max(outputs, 1)
        loss = criterion(outputs, labels)

        running_loss += loss.item() * inputs.size(0)
        running_corrects += torch.sum(preds == labels.data)

        # 한 배치의 첫 번째 이미지에 대하여 결과 시각화
        print(f'[예측 결과: {class_names[preds[0]]}] (실제 정답: {class_names[labels.data[0]]})')
        imshow(inputs.cpu().data[0], title='예측 결과: ' + class_names[preds[0]])

    epoch_loss = running_loss / len(test_datasets)
    epoch_acc = running_corrects / len(test_datasets) * 100.
    print('[Test Phase] Loss: {:.4f} Acc: {:.4f}% Time: {:.4f}s'.format(epoch_loss, epoch_acc, time.time() - start_time))

[예측 결과: 이병헌] (실제 정답: 이병헌)

[예측 결과: 김종국] (실제 정답: 김종국)

[예측 결과: 마동석] (실제 정답: 마동석)

[예측 결과: 이병헌] (실제 정답: 이병헌)

 

 

 

 

[출처]www.youtube.com/watch?v=Lu93Ah2h9XA

 

+ Recent posts