머신러닝

CatDog

content0474 2024. 10. 25. 10:11

지난주말부터 잡고있었던 cat vs dog  구분하는 CNN 신경망

모델은 잘 안되었지만 그래도 새로 배운것들이 몇 개 있어서 포스팅하려고 한다.

ResNet을 사용하면 더 개선될 여지가 있다고 하니 다음번에 시도해보는 것으로..

 

전체코드

import kagglehub

# Download latest version
path = kagglehub.dataset_download("karakaggle/kaggle-cat-vs-dog-dataset")

print("Path to dataset files:", path)

 

import torch
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
import torch.nn as nn
import torch.optim as optim

# 1. 이미지 변환(transform) 설정 (이미지 크기 조정 및 정규화)
transform = transforms.Compose([
    transforms.ToTensor(), 
    transforms.Resize((256, 256)),
    transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225)),
])

# 2. 학습용 데이터셋 불러오기
train_dataset = datasets.ImageFolder(
    root='C:/Users/hispa/.cache/kagglehub/datasets/karakaggle/kaggle-cat-vs-dog-dataset/versions/1',  
    transform=transform
)

# 3. 검증용 데이터셋 불러오기
validation_dataset = datasets.ImageFolder(
    root='C:/Users/hispa/.cache/kagglehub/datasets/karakaggle/kaggle-cat-vs-dog-dataset/versions/1',  
    transform=transform
)

# 4. DataLoader로 배치 단위로 불러오기
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
validation_loader = DataLoader(validation_dataset, batch_size=32, shuffle=False)

 

class SimpleCNN(nn.Module):
    def __init__(self):
        super(SimpleCNN, 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.adaptive_pool = nn.AdaptiveAvgPool2d((16, 16))  # 출력 크기를 16x16으로 축소
        self.fc1 = nn.Linear(64 * 16 * 16, 512)  # 64채널 * 16x16 크기
        self.dropout = nn.Dropout(0.5)
        self.fc2 = nn.Linear(512, 2)

    def forward(self, x):
        x = self.pool(torch.relu(self.conv1(x)))
        x = self.pool(torch.relu(self.conv2(x)))
        x = self.adaptive_pool(x)  # 크기를 16x16으로 줄이기
        x = x.view(x.size(0), -1)
        x = torch.relu(self.fc1(x))
        x = self.dropout(x)
        x = self.fc2(x)
        return x

 

# 모델 초기화
model = SimpleCNN()

# 손실 함수와 최적화 알고리즘 정의
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9, weight_decay=0.001)

# 모델 학습
max_batches=32

for epoch in range(5):
    running_loss = 0.0
    for i, data in enumerate(train_loader, 0):
        if i>=max_batches:
            break
        inputs, labels = data

        # 기울기 초기화
        optimizer.zero_grad()

        # 순전파 + 역전파 + 최적화
        outputs = model(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()

        # 손실 출력 (매 배치마다 출력)
        print(f'[Epoch {epoch + 1}, Batch {i + 1}] loss: {loss.item():.3f}')


print('Finished Training')

 

correct = 0
total = 0
max_batches=10
with torch.no_grad():
    for i, data in enumerate(validation_loader,0):
        if i>=max_batches:
            break
        images, labels = data
        outputs = model(images)
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()

print(f'Accuracy of the network on the 10000 test images: {100 * correct / total:.2f}%')

 

from PIL import Image

# 이미지 불러올 준비
preprocess = transforms.Compose([
    transforms.Resize((256, 256)),  # 이미지 크기를 모델에 맞게 조정 
    transforms.ToTensor(),          # 이미지를 Tensor로 변환
    transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225))  # 정규화
])

image_path = " "  
img = Image.open(image_path)

input_tensor = preprocess(img)

input_tensor = input_tensor.unsqueeze(0)

 

#결과 보여주기

model.eval()
with torch.no_grad():  
    output = model(input_tensor)

_, predicted = torch.max(output, 1)

if predicted.item() == 0:  
    print("고양이로 분류되었습니다!")
else:
    print("고양이가 아닌 것으로 분류되었습니다.")

 

 

새로 배운것

 

transforms.Resize((256, 256))

더보기

데이터셋의 사진크기를 일괄로 맞춰주기 위해 사용했다. 이 코드가 없으면 나중에 사이즈 문제로 에러가 난다.

 

transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225))

더보기

괄호안의 숫자는 각각 R,G,B의 평균과 표준편차를 규정하고 있으며, 이 값은 cat vs dog일 때 자주 사용하는 정규화로 알려져있다. 참고로 앞부분 (0.485, 0.456, 0.406) 이 평균, 뒷부분 (0.229, 0.224, 0.225) 이 표준편차이다.


참고로, 과적합방지 기법 중 하나인 데이터 증강도 여기서 이루어진다.

더보기

transforms.RandomHorizontalFlip(),

transforms.RandomRotation(30),

transforms.RandomResizedCrop(32, scale=(0.8, 1.0)),

transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.2)

이렇게 이미지를 뒤집고 돌리고 자르고 색깔변화시켜서 다양한 이미지를 학습하게 한다.


 

새로 배운것

 

self.adaptive_pool = nn.AdaptiveAvgPool2d((16, 16))

x = self.adaptive_pool(x)

더보기

기존에는 선형모델에 전달할 때 크기가 얼마나 될 지 직접 계산해서 숫자를 넣었다.

그런데 이미지 크기가 바뀌거나 풀링을 빼거나 추가하면 선형모델에 전달할 크기도 바뀌므로 다시 계산해야 해서 귀찮았다.

그런데 AdaptiveAvgPool2d((숫자,숫자)) 를 쓰면 이미지를 자동으로 괄호안의 크기로 바꾸어준다.

 

self.dropout=nn.Dropout(0.5)

더보기

드롭아웃은 과적합 방지 기법 중 하나이다. 학습할 때는 일정비율의 뉴런을 무작위로 꺼버려서 모델이 특정 뉴런에 과하게 의존하지 않게 한다. 뒤의 숫자 0.5는 50%의 뉴런을 비활성화한다는 의미로, 대부분 0.5를 많이 쓰는것 같다.


새로 배운것

weight_decay=0.001

L2정규화라는건데, 이것도 과적합을 방지하는 방법 중 하나이다. 구체적인 수학적 내용은 너무 복잡해서 가중치 값을 제한하는 방법이라는 것만 이해했다.

 

max_batches=32

for i, data in enumerate(train_loader, 0):
        if i>=max_batches:
            break

이건 배웠다기보다는 응용인데, 데이터셋 크기가 커 학습에 시간이 오래 걸리니까 배치개수를 제한한 것이다.

예를들어 내 배치크기가 32인데 사진이 32000개 있으면 배치개수는 32000/32=1000개가 나와서 1에포크동안 1000개의 배치가 모델로 전달된다. 그러면 너무 오래 걸리니까 1000개의 배치가 있더라도 32개 배치까지만 하고 다음으로 넘어가라는 뜻이다.

 

최종: [Epoch 5, Batch 32] loss: 0.649

결과

난리가남

 

모델의 정확도를 테스트용 데이터셋을 이용해 평가했다. 처참하다

여기도 마찬가지로 데이터셋이 크니까 10개까지만 평가에 쓰도록 제한했다.

 

여기가 좀 만들기 복잡했는데, 내가 제공한 사진을 모델이 옳게 평가할 수 있을지 알아보는 코드이다.

더보기

input_tensor = preprocess(img)

이미지를 그냥 막 넣을수는 없고, 데이터셋을 전처리했던 것처럼 제공할 이미지도 전처리해야한다.

그렇게 전처리하는 부분이 바로 

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

 

또 괄호안의 img에 바로 이미지 디렉토리를 넣으면 안되고 open을 통해 이미지를 파이썬의 객체로 만들어야 한다.

이미지 디렉토리는 단순 문자열일 뿐이라서 그걸로는 아무것도 못한다.

마지막으로 

input_tensor = input_tensor.unsqueeze(0)

첫번째차원(0)에 배치크기를 추가해준다. 단일이미지이므로 배치크기=1이 된다.

 

if predicted.item() == 0:  
    print("고양이로 분류되었습니다!")

더보기

이 부분을 보면, 최종예측=0이면 고양이, 1이면 강아지로 구분함을 알 수 있다.

그렇다면 실제로 모델이 cat과 dog을 어떻게 분류하는지 알 수 있는 방법은?

이 코드를 쓰면 된다. 


참고로 제일 처음에는 데이터의 폴더구조의 문제로 저 코드를 입력하면 결과는 {'kagglecatsanddogs_3367a': 0} 

그러니까 kagglecatsanddogs_3367a 폴더 안에 cat과 dog 폴더가 있고, 모델은 kagglecatsanddoggs 폴더 안의 모든 사진(=cat폴더, dog폴더의 모든 사진)을 다 고양이로 학습한건데, 나는 그 사실을 모르고 있었다.

 

그래서 학습속도는 엄청 빠르고 손실은 빠르게 0이 되고 accuracy는 100인데

당연히 모델은 내가 제공한 모든 사진을 다 고양이로 분류해버린다. 

 

이 상황을 나는 과적합이 심한 것으로만 보고 자꾸만 과적합 방지 코드를 추가했던 것이다.

다음부터는 폴더구조를 잘 확인하도록 하자...

 

결과는?


학습률을 만지거나 옵티마이저를 바꿔보거나 뭘 이것저것 해도 모든것을 고양이로 분류하거나 고양이가 아닌것으로 분류해서 차라리 max_batches를 없애버리고 시간이 걸리더라도 다 학습하게 했다.


다시.. 결과는?

뭘 해도 고양이가 아닌 것으로 분류한다..

 

이럴수가.

 

 

idh

 

 

mycat

 

이 외에도 몇 가지 사진을 넣었는데, 다 고양이가 아니라고 한다.

다음번에 더 좋은 모델에 넣어서 다시 학습시켜 보는 것으로..

 

 

what's next?

더보기

도전과제2

or

skin canccer MNIST

 

'머신러닝' 카테고리의 다른 글

skin cancer HAM10000  (0) 2024.10.29
wordcloud  (2) 2024.10.28
리뷰 분석  (3) 2024.10.24
RNN의 이해-2  (0) 2024.10.23
딥러닝 모델들  (0) 2024.10.22