Study_Cat

꾸준히 공부하는 고양이가 될게요.

끊임없는 노력은 천재를 이긴다.

코딩/인공지능 및 데이터분석

[pytorch] CNN: 고양이, 개 분류 실습

Study_Cat 2024. 5. 5. 02:24

해당 포스팅은 간단하게 코드를 어떻게 짰는지를 위주로 작성했습니다.

사실... 정확도가 낮고.. 나중에 발전시킨걸 분석할라고 했는데.. 생각보다 잘 나와서 바로 올리게 되었습니다. 정보/인공지능 이론은 나중에 추가적으로 포스팅하겠습니다.

 

1. Dataset

kaggle에 올라온 데이터를 이용했습니다. 

 

Dogs vs. Cats | Kaggle

 

www.kaggle.com

 

class MyDataset(Dataset):
    def __init__(self, folder):
        super().__init__()
        self.url = f'./dataset/{folder}'
        self.transform = transforms.Compose([
            transforms.Resize((128,128)),
            transforms.ToTensor(),
            transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
        ])
    
        filenames = os.listdir(self.url)
        self.categorys = []
        self.Imgs = []
        self.len = len(filenames)
        for file in filenames:
            category = file.split('.')[0]
            self.Imgs.append(self.transform(Image.open(f'{self.url}/{file}')))
            if(category=='dog'):
                self.categorys.append(torch.FloatTensor([0]))
            else: self.categorys.append(torch.FloatTensor([1]))
        
    def __getitem__(self, index):
        return self.Imgs[index], self.categorys[index]
    
    def __len__(self):
        return self.len

 

1) __init__ 함수

os를 통해 해당 폴더에 들어있는 사진의 이름을 list에 담고 모든 사진을 조회하면서 labeling하고 torchvision을 이용해 사진을 전처리 하였습니다. 그리고 빠른 수렴을 위해 Normalize함수를 사용했습니다.

 

2) __getitem__   /  __len__ 함수

__getitem__ 은 해당 index에 해당되는 데이터를 return해주었고 __len__은 데이터 개수를 return했습니다.

 

 

2. Model Layer

class CNN_Model(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv_layer = nn.Sequential(
            nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3, padding=1),
            nn.ReLU(),
            nn.BatchNorm2d(32),
            nn.MaxPool2d(2),
            nn.Dropout(0.5),
            nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, padding=1),
            nn.ReLU(),
            nn.BatchNorm2d(64),
            nn.MaxPool2d(2),
            nn.Dropout(0.5),
            nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, padding=1),
            nn.ReLU(),
            nn.BatchNorm2d(128),
            nn.MaxPool2d(2),
            nn.Dropout(0.5),
            nn.Conv2d(in_channels=128, out_channels=256, kernel_size=3, padding=1),
            nn.ReLU(),
            nn.BatchNorm2d(256),
            nn.MaxPool2d(2),
            nn.Dropout(0.5),
        )
        self.fc_layer = nn.Sequential(
            nn.Flatten(),
            nn.Linear(256*8*8, 256),
            nn.ReLU(),
            nn.Dropout(0.5),
            nn.Linear(256, 32),
            nn.ReLU(),
            nn.Linear(32,1),
            nn.Sigmoid()
        )

    def forward(self, x):
        out = self.conv_layer(x)
        out = self.fc_layer(out)
        return out

 

channel 의 수는 여러 번 모델을 돌리면서 그나마 최적일 것 같은 layer로 지정했습니다.  (왜 해당 model이 정확도가 높게 나왔는진 잘 모르지만.. 다음에 이에 관해서 포스팅하겠습니다)

 

해당 Model에서 Parameter도 많고 하다보니.. 과적합을 방지하기 위해 Dropout 을 많이 사용하였고.. 활성함수는 가장 많이 사용하는 Relu를 사용했습니다. 그리고 해당 모델은 "이진분류" 로 계획했습니다. 

 

(자세한 내용은 논문을 참고해 작성해볼게욥.. 혹시나 왜 그런지 아시는 분은 댓글 부탁드립니다)

 

 

3. 학습

TrainDataset = MyDataset('train')
TestDataset = MyDataset('test')

Train_DataLoader = DataLoader(TrainDataset, 128, shuffle=True)
Test_DataLoader = DataLoader(TestDataset, 128)

model = CNN_Model()
optimizer = optim.SGD(model.parameters(), lr = 0.005)
nb_epochs = 30
history = []

for i in range(nb_epochs):
    for j, [image, label] in enumerate(Train_DataLoader):
        output = model(image)
        loss = F.binary_cross_entropy(output, label)
        
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
            # 선택.. 함수는 요런 작업 거쳐줘야함..
        prediction = output >= torch.FloatTensor([0.5]) # 예측값이 0.5를 넘으면 True
        correct_prediction = prediction.float() == label # 실제값과 같은 경우만 True
        accuracy = correct_prediction.sum().item() / len(correct_prediction) # 정확도 계산
        history.append(loss.item())
        print('Epoch {:4d}/{}, [{}번째 실행중] Cost: {:.6f} Accuracy {:2.2f}%'.format( 
            i+1, nb_epochs,j+1, loss.item(), accuracy * 100,
        ))

 

이진분류 모델을 사용했기 때문에 확률을 0.5를 기준으로 고양이 / 개를 예측하고 정확도를 추산했습니다. 

 

4. 성능 확인

model.eval() 
correct = 0
total = 0

for image, label in Test_DataLoader:
    x = image
    y_ = label

    output = model.forward(x)
    prediction = output >= torch.FloatTensor([0.5]) # 예측값이 0.5를 넘으면 True
    correct_prediction = prediction.float() == label # 실제값과 같은 경우만 True
    correct += correct_prediction.sum().item()
    total += len(label)

print('Acc : {}'.format(correct / total))

 

Model trainning 에 쓰이지 않았던 사진들을 사용해서 얼마나 잘 예측하는지 확인해 볼건데... 이 때 주의할 것은 model.eval() 을 사용한다는 점과... item() 같은 함수를 사용하는 부분.. 입니다. 원래는 output에 대한 loss를 계산하고 gradient를 계산하기 위해 torch.require_grad = True 로 되있는 상태인데... 이런 상태의 변수를 다른 곳에 함부로 쓰면.. 오류가 납니다. (예전에 이 사실을 잘 모르고 2시간을 쩔쩔 맸었죠..)

결과는 무려 88.25%... (아직 미숙하고 해서 실수로 matplotlib의 pyplot으로 history를 출력하는걸 까먹었어요 ㅠㅠ) 

일단 결과는 잘 나오네욥... 

 

 

사실 훈련시키면서 쫌 불안정하기도 했고.. 이미지 증폭을 하지 않아 해당 Model은 불안정한 것 같습니다. 그래도 Test_Data가 12000개가 넘는데 저 정도의 정확도가 나온 것도 신기하네요;;

 

(2024년 5월 5일 오후 1:42 수정)

TensorBoard 를 사용하면 학습이 잘 이뤄지고 있는지, 그리고 모델이 안정하며 정확한지 분석하기 편합니다! 다음 실습을 통해 알아보도록 하겠습니다. 


 

오늘 포스팅은 코드를 짜보는 것을 목적으로 진행해봤고.. 생각보다 정확도가 높아서 올려봤습니다. 이론적인 것을 이해하는 것도 중요하지만 초보일 땐 역시 실습이나 project를 진행하면서 재미를 붙이는 것이 중요하다고 생각합니다.

 

또한 이론적인 것을 잘 알아도 성능 좋은 Model을 잘 만드는 것은 아니기 때문에 경험을 쌓는것도 중요하다고 생각합니다. 다음 포스팅에는 인공지능과 정보학 이론에 대해 잠깐 소개하고 Model Architecture를 설계하는 방법을 논문과 함께 설명하겠습니다. 

 

고수분들은 저 모델이 안정한지... 그리고 왜 Test 결과는 저렇게 잘 나오는지 설명해주시면 감사하겠습니다..