본문 바로가기
개발/머신러닝-딥러닝

2023.06.21 딥러닝의 포켓몬 분류

by 상달군 2023. 6. 27.
728x90

1. 포켓몬스터 149종 분류하기 

 🔻데이터셋 다운로드 

# 데이터셋 다운로드
import os

os.environ['KAGGLE_USERNAME'] ='# username'
os.environ['KAGGLE_KEY'] = ' # key '

!kaggle datasets download -d thedagger/pokemon-generation-one
!kaggle datasets download -d hlrhegemony/pokemon-image-dataset

 

🔻압축해제 (리눅스 명령어)

# 받아온 데이터셋 압축해제하기 (리눅스 명령어)
!unzip -q pokemon-generation-one.zip -d ./train
!unzip -q pokemon-image-dataset.zip -d ./validation

 

🔻validation에서 train에 있는 디렉토리를 확인하여 없는 디렉토리는 제거 하기

# train : 149개, validation: 898개의 종류를 각각 가지고 있다.
# validation에서 train에 있는 디렉토리를 확인하여 없는 디렉토리는 제거 하기
import shutil

train_dir = "/content/train/dataset"
validation_dir = "/content/validation/images"

# train 디렉토리의 하위 디렉토리 목록 가져오기
train_subdirs = set(os.listdir(train_dir))

# validation 디렉토리의 하위 디렉토리 목록 가져오기
validation_subdirs = os.listdir(validation_dir)

# train에 없는 디렉토리 제거
for subdir in validation_subdirs:
    subdir_path = os.path.join(validation_dir, subdir)

    if not os.path.isdir(subdir_path) or subdir not in train_subdirs:

        # 디렉토리가 아니거나 train에 없는 디렉토리인 경우 제거
        print("Removing directory:", subdir_path)
        shutil.rmtree(subdir_path)

 

🔻 validation에 없는 2종의 포켓몬 디렉토리를 추가로 만들기

common_subdirs = train_subdirs.intersection(validation_subdirs)
print(len(common_subdirs))
print(len(train_subdirs))
print(type(train_subdirs))
print(type(common_subdirs))

for subdir in train_subdirs:
  if (subdir in common_subdirs) == False:
    print(subdir)

👀validation에 존재 하지 않는 2종의 포켓몬은 ?


🔻 사용 모듈 추가하기

# 패키지 로드(import)
import torch
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt

from torchvision import datasets, models, transforms
from torch.utils.data import DataLoader

 

🔻 GPU사용하기

device = 'cuda' if torch.cuda.is_available() else 'cpu'
# device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(device)

 

🔻transforms.Compose를 사용하여 사이즈, Affine, RandomHrizontalFlip, ToTensor 변환등등등....

data_transforms = {
    'train' : transforms.Compose([                              # Compose 아래 내용을 한꺼번에 실행시켜라
        transforms.Resize((224,224)),                           # 224,224사이즈로 변경 하기
        transforms.RandomAffine(0, shear=10, scale=(0.8, 1.2)), # 랜덤하게 10개를 뽑아서 규모는 +-20%
        transforms.RandomHorizontalFlip(),                      # 이미지 반전시킴
        transforms.ToTensor()                                   # 텐서 형태로 바꿔라
    ]),
    'validation' : transforms.Compose([
        transforms.Resize((224,224)),
        transforms.ToTensor()
    ])
}

 

🔻데이터셋, 데이터로더 (bathch size를 32) 만들기

# 데이터셋, 데이터로더 (bathch size를 32) 만들기
image_datasets = {
    'train' : datasets.ImageFolder('/content/train/dataset', data_transforms['train'], target_transform = target_transforms),
    'validation' : datasets.ImageFolder('/content/validation/images', data_transforms['validation'], target_transform = target_transforms)
}

dataloaders = {
    'train' : DataLoader(
        image_datasets['train'],
        batch_size = 32,
        shuffle = True
    ),
    'validation' : DataLoader(
        image_datasets['validation'],
        batch_size = 32,
        shuffle = False
    )
}

print(len(image_datasets['train']), len(image_datasets['validation']))

🗨 Farfetchd, MrMime 폴더가 생성만 되어있고 폴더내부에 사진이 없어 에러가 발생할것이다 ! 

두 폴더에 파오리와 마임맨 두 포켓몬의 사진을 넣어주어야 합니다. 

 

🔻 Batch 만큼 이미지를 출력 합니다. 

# 1개의 batch만큼 이미지를 출력

imgs, labels = next(iter(dataloaders['train']))
fig, axes = plt.subplots(4,8,figsize=(20,10))

for img, label, ax in zip(imgs, labels, axes.flatten()):
  ax.set_title(label.item())
  ax.imshow(img.permute(1,2,0))
  ax.axis('off')

🔻위에서 뽑아낸 이미지와 라벨일 제대로 적용되어 있는지 확인을 해봅시다. 

# 클래스 이름 확인하기
# image_datasets['train'].classes # 이름 전체뽑기
image_datasets['train'].classes[51]

 

Train의 클래스에 존재 하는 라벨로 포켓몬에 이름을 확인 하기 

51번의 포켓몬은 영어 이름 "Haunter" 로 맞게 잘 나오네요 

 

 

 

 

🔻사전 학습된 EfficientNetB4 모델 사용하기

# 사전 학습된 EfficientNetB4 모델 사용하기
model = models.efficientnet_b4(weights = 'IMAGENET1K_V1').to(device)
print(model)

🔻모델 만들기

# 모델 만들기
for param in model.parameters():
  param.requires_grad = False

model.classifier = nn.Sequential(
    nn.Linear(1792, 512),
    nn.ReLU(),
    nn.Linear(512, 149)
).to(device)

print(model)

🔻학습 시키기  ༼ つ ◕_◕ ༽つ[시간 오래 걸림]

# 학습시키기

optimizer = optim.Adam(model.classifier.parameters(), lr = 0.001)

epochs = 10

for epoch in range(epochs):
  for phase in ['train', 'validation']:
    if phase == 'train':
      model.train()
    else :
      model.eval()


    sum_losses = 0
    sum_accs = 0

    for x_batch, y_batch in dataloaders[phase]:
      x_batch = x_batch.to(device)
      y_batch = y_batch.to(device)

      y_pred = model(x_batch)

      loss = nn.CrossEntropyLoss()(y_pred, y_batch)

      if phase == 'train':
        optimizer.zero_grad()
        loss.backward() #역전파
        optimizer.step()#기울기초기화
	  
      # 배치단위 loss 저장
      sum_losses = sum_losses + loss.item()
      
      # 배치단위 정확도 저장
      y_prob = nn.Softmax(1)(y_pred) # Softmax(1): Softmax 함수가 두 번째 차원(axis)에 대해 작동함.
      
      # argmax(): 가장 큰 확률을 가지는 클래스의 인덱스를 추출
      # axis=1: 두 번째 차원에서 최댓값을 찾음. 
      y_pred_index = torch.argmax(y_prob, axis=1)
      
      acc = (y_batch == y_pred_index).float().sum() / len(y_batch) * 100 # 백분율
      # 정확도(acc): 실제레이블(y_batch)과 예측결과(y_pred_index)를 비교하여 예측된 데이터 포인트의
       비율을 계산. 레이블과 예측 결과를 비교한 결과는 Boolean형태의 텐서임.
       
      sum_accs = sum_accs + acc.item()

    avg_loss = sum_losses / len(dataloaders[phase])
    avg_acc = sum_accs / len(dataloaders[phase])


    print(f'{phase:10s}: Epoch {epoch+1:4d}/{epochs}, Loss: {avg_loss:.4f}, Accuracy: {avg_acc:.2f}%')

🔻학습된 모델 저장하기

# 학습된 모델파일 저장하기
torch.save(model.state_dict(), 'model.h5')

🔻학습된 모델 불러오기 [불러와서 다시만들기]

# 빈 efficientnet_b4()모델 생성 
model = models.efficientnet_b4().to(device)

# 모델 만들기
for param in model.parameters():
  param.requires_grad = False
  
# 모델 수정하기 
model.classifier = nn.Sequential(
    nn.Linear(1792, 512),
    nn.ReLU(),
    nn.Linear(512, 149)
).to(device)

# 빈 모델에 저장된 모델 불러오기 
model.load_state_dict(torch.load('model.h5'))

🔻이미지를 넣어 테스트 해보기 

# 테스트(validation에 있는 2종의 포켓몬을 통해 분류 테스트 확률 출력해보기 )

# 테스트 해보기
from PIL import Image

# 학습용(train)말고  validation안에 있는 이미지를 사용한다.
img1 = Image.open('/content/validation/Squirtle/0.jpg')
img2 = Image.open('/content/validation/Mew/0.jpg')

# 어떤 사진인지 확인해보기(안해도 상관은 없음)
fig, axes = plt.subplots(1,2,figsize=(12,6))

axes[0].imshow(img1)
axes[0].axis('off')

axes[1].imshow(img2)
axes[1].axis('off')
plt.show()

🔻 테스트를 위한 이미지 사이즈 맞추기 

# 테스트를 위해 사이즈 변경하기
img1_input = data_transforms['validation'](img1)
img2_input = data_transforms['validation'](img2)

print(img1_input.shape)
print(img2_input.shape)

test_batch = torch.stack([img1_input, img2_input])
test_batch = test_batch.to(device)
test_batch.shape

🔻예측

y_pred = model(test_batch)
y_pred

y_prod = nn.Softmax(1)(y_pred)
y_prod

probs, indices = torch.topk(y_prob, k=3, axis = -1)
probs = probs.cpu().data.numpy()
indices = indices.cpu().data.numpy()
print(probs)
print(indices)

fig, axes = plt.subplots(1, 2, figsize=(12, 6))
axes[0].set_title('{:.2f}% {}, {:.2f}% {}, {:.2f}% {}'.format(
    probs[0, 0] * 100, image_datasets['validation'].classes[indices[0, 0]],
    probs[0, 1] * 100, image_datasets['validation'].classes[indices[0, 1]],
    probs[0, 2] * 100, image_datasets['validation'].classes[indices[0, 2]]
))
axes[0].imshow(img1)
axes[0].axis('off')
axes[1].set_title('{:.2f}% {}, {:.2f}% {}, {:.2f}% {}'.format(
    probs[1, 0] * 100, image_datasets['validation'].classes[indices[1, 0]],
    probs[1, 1] * 100, image_datasets['validation'].classes[indices[1, 1]],
    probs[1, 2] * 100, image_datasets['validation'].classes[indices[1, 2]]
))
axes[1].imshow(img2)
axes[1].axis('off')
plt.show()


👀 살짝 과적합의 느낌이긴 합니다.....

728x90

댓글