CNN 이미지 분류 모델을 이용한 논문 리뷰.1

 이번 포스트는 저번에 리뷰한 'Very Deep Convolutional Networks For Large-Scale Image Recognition' 논문을 직접 코드를 짜서 실험해 본것이다. 

들어가기 전에 정리하면 

1. 합성곱 신경망으로 3*3 필터 3개 VS 7*7 필터 1개와 3*3필터 2개 VS 5*5 필터를 비교한다.

2. 기존 논문과 다르게 매우 깊게 구현하지는 않았다. 그냥 필터의 성능 시험이다.

3. 기존 논문과 다른 점은 Building과 Glacier 이미지 두개를 분류하는 사실상 이진 분류 문제이다. 따라서 Softmax가 아닌 sigmoid 함수를 출력 함수로 사용했으며 loss function 역시 binary-crossentropy이다.

데이터 셋은 여기서 가져왔다.

https://www.kaggle.com/puneet6060/intel-image-classification

그럼 먼저 코드를 리뷰해 보도록 하겠다. 일단 사용한 라이브러리들은 다음과 같다.

import os
import numpy as np
from tqdm import tqdm
from PIL import Image
from keras.preprocessing import image
from keras.models import Sequential
from keras.layers import Conv2D, MaxPooling2D, Dense, Flatten, Dropout

기본적인 numpy, os, keras등을 임포트 한다. 그 외에 tqdm의 경우 작업 진행량을 표시하기 위해 임포트 하였다.

데이터의 경우를 보면 



학습 데이터가 각 파일별로 다음과 같이 정리가 되어있다. 이제 가장 먼저 데이터를 불러오고 각각의 전처리를 하겠다.

traindir = "data/seg_test/buildings"
dir = os.listdir(traindir)

위와 같이 데이터를 읽을 경로를 지정하고 각각에 들어있는 리스트를 읽어온다 따라서 지금 우리의 dir에는 리스트의 형태로 다음과 같은 데이터가 저장되어있다. 


현재 목표는 빌딩&빙하를 이진 분류하는 모델을 구성중이니 다음과 같이 빌딩을 포함하는 리스트 하나 빙하를 포함하는 리스트를 하나 생성해 이용했다. 
다음으로는 현재 리스트에 있는 각 파일을 읽어와 학습에 사용할 수 있는 형태로 분류하는 작업이다. 이때 우리는 케라스에서 제공하는 img_to_array를 이용하여 각 이미지를 0~255의 숫자로 바꾼다. 그럼 코드는 다음과 같아진다.

for i in tqdm(range(len(dir))):
traindir = "data/seg_test/buildings/" + dir[i]
imagee = Image.open(traindir,mode='r')
img_tensor = image.img_to_array(imagee)
img_tensor = img_tensor / 255
img_tensor = np.reshape(img_tensor,(1,150,150,3))
trainsetX = np.append(trainsetX, img_tensor, axis=0)

가장 먼저 반복문을 이용하여 각각의 이미지를 하나씩 꺼내고 꺼낸 이미지를 함수를 이용해 배열로 바꾸었다. 그리고 정규화 후 각각의 파일이 총 150X150픽셀로 구성되어 있고 RGB 3가지의 값을 가지고 있으며 4차원 데이터로 쌓아야 하기 때문에 1,150,150,3으로 reshape하였다. 그 후 학습 데이터에 저장하였다.

traindir = "data/seg_test/glacier"
dir = os.listdir(traindir)
for i in tqdm(range(len(dir))):
traindir = "data/seg_test/glacier/" + dir[i]
imagee = Image.open(traindir, mode='r')
img_tensor = image.img_to_array(imagee)
img_tensor = img_tensor / 255
if(np.size(img_tensor)!=67500):
print(dir[i])
else:
img_tensor = np.reshape(img_tensor, (1, 150, 150, 3))
trainsetX = np.append(trainsetX, img_tensor, axis=0)

동일하게 빙하 사진에 대하여도 진행했다. 다만 몇가지 코드가 다른데 이는 뒤에 설명하겠다.

라벨링 작업이다. 라벨링의 경우 크게 어렵지 않게 빌딩의 경우 0, 빙하의 경우 1로 진행했다. 
따라서 매우 간단하게 라벨링을 하였다. 
for i in range(len(dir)): 
    trainsetY.append(0)
다만 각각의 자료의 길이가 다르다 보니 각각의 자료에 맞춰서 각각 라벨링했다. 
총 길이는 약 1000개 정도의 간단한 학습이 되었다.

이제 학습 모델을 구성하였다. 학습 모델의 코드는 다음과 같다.
model = Sequential()
model.add(Conv2D(32, (3, 3), padding="same", input_shape=(150,150,3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

model.add(Conv2D(32, (3, 3), padding="same", input_shape=(150,150,3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

model.add(Conv2D(32, (3, 3), padding="same", input_shape=(150,150,3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

model.add(Flatten())
model.add(Dense(256, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])

model.fit(trainsetX, trainsetY, epochs=6, batch_size=10)
model.save('333_model.h5')
일단 들어오는 형태는 150X150X3의 형태로 들어오고 각 함수는 ReLU로 그리고 마지막에 완전 연결 계층을 구성하였다. 출력함수는 시그모이드 함수를 이용하였고 Loss function은 이진분류에 맞춰서 설정하였다.

테스트는 다음과 같은 방법으로 진행했다.
import os
import numpy as np
from tqdm import tqdm

from PIL import Image
from keras.preprocessing import image
from keras.models import load_model

model = load_model('333_model.h5')
testdir = "data/seg_train/buildings"
dir = os.listdir(testdir)

testset = np.empty(shape=(1,150,150,3))

for i in tqdm(range(len(dir))):
traindir = "data/seg_train/buildings/" + dir[i]
imagee = Image.open(traindir, mode='r')
img_tensor = image.img_to_array(imagee)
img_tensor = img_tensor / 255
if (np.size(img_tensor) != 67500):
print(dir[i])
else:
img_tensor = np.reshape(img_tensor, (1, 150, 150, 3))
testset = np.append(testset, img_tensor, axis=0)

testlabel = []

for i in range(len(dir)):
testlabel.append(0)

testset = np.array(testset)
testlabel = np.array(testlabel)

loss, acc = model.evaluate(testset, testlabel)

print(str(loss) + ':오차율 ', str(acc) + ":정확도")

위와 같이 test.py파일을 만들어 기존에 저장한 모듈을 불러와 시험을 진행하였다.
테스트셋의 데이터는 위와 동일하게 제작하였다.

기존의 문제점
1. 기존에 데이터셋을 (1, 150, 150, 3)으로 변환하는 과정에서 자꾸만 에러가 났다. 분명 150X150의 형태로 설정되어있는 상태인데 변환이 불가능하다고 에러가 났다 그래서 결국 위의 코드와 같이 조건문으로 예외를 뽑아내어서 확인 해본 결과 149X150, 110X150 같은 파일이 숨어있었다. 따라서 각각의 크기가 안 맞는 파일을 삭제하거나 조건문으로 따로 처리하는 방법으로 교체하였다.
(추가) 연구실 선배님이 추천해주셨는데 reshape 말고 resize를 이용하시라고 하셨다... resize는 알아서 이미지를 늘려서 따로 처리할 필요가 없으시다고 하셨다.

코드는 이렇게 정리가 되었다. 
모든 코드는 github에 정리되어있다.


논문 검증에 대하여는 다음 포스트에 이어서 하도록 하겠다.

댓글

이 블로그의 인기 게시물

MongoDB와 VScode 이용한 드라이빙app 개발 일지 (2022.4.10)

다양한 계층 구현을 통한 오차역전파법 구현하기(2)