[Imple] 캐릭터 분류를 위한 컴퓨터비전 신경망 예제(Neural network for image classification)
심슨 애니메이션에 나오는 캐릭터 분류를 위한 신경망 예제를 진행해본다.
캐릭터 이미지는 케글에서 구할 수 있다.
https://www.kaggle.com/datasets/ravinash218/homerbart1
Homer-Bart-1
www.kaggle.com
먼저 필요한 라이브러리를 임포트
import cv2
import numpy as np
import os
import zipfile
from google.colab.patches import cv2_imshow
import tensorflow as tf
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt #그래프
다음은 전체 이미지에서 픽셀을 추출한다.
구글 드라이브에 마운트해 데이터를 가져온다.
#구글 드라이브 연결
from google.colab import drive
drive.mount('/content/drive')
path = '/content/drive/MyDrive/데이터파일.zip'
zip_object = zipfile.ZipFile(file = path, mode = 'r') #read모드
zip_object.extractall('./')
zip_object.close()
directory = '/content/homer_bart_1'
files = [os.path.join(directory, f) for f in sorted(os.listdir(directory))]
print(files)
신경망 학습을 위해서는 입력층에서 같은 형태의 이미지들이 필요하다.
따라서 신경망의 입력층으로 보내기 위해 모든 이미지의 형태를 맞춰줘야한다.
height, width = 128, 128 #더 크게 해도되지만 신경망 학습이 느려질 수 있음
images = []
classes = []
for image_path in files:
#이미지 파일이 아닌경우 에러가 발생할 수 있으므로 try-catch 문으로 감싼다
try:
image = cv2.imread(image_path)
(H, W) = image.shape[:2] #image.shape는 (heicht, width, channel)로 되어있음, 그중 높이 너비까지만 사용
except:
continue
image = cv2.resize(image, (width, height))
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
cv2_imshow(image)
#이미지를 벡터로 변환
image = image.ravel()
print(image.shape)
images.append(image)
#이미지 이름 추출
image_name = os.path.basename(os.path.normpath(image_path))
if image_name.startswith('b'): #bart 이미지면 클래스 0
class_name = 0
else: #homer 이미지면 클래스 1
class_name = 1
classes.append(class_name)
print(class_name)
앞서 만든 images와 classes 의 타입을 확인해보면 list 임을 볼 수 있는데
신경망의 입력으로 이용하기 위해선 이 변수들을 Numpy 형식으로 변환해야한다.
X = np.asarray(images)
y = np.asarray(classes)
X.shape를 확인해보면 (267, 16384)이고 y.shape를 확인해보면 (269,) 임을 확인할 수 있다.
(두 변수 shape 픽셀이 269로 같음을 확인, 두개 꼭 같아야함)
X[0]
X[0]를 확인해보면 array([255,255,255, ... ,255,255,255], dtype=uint8) 임을 확인할 수 있는데 이는 이미지인 행렬형태가 아닌 벡터 형태이기 때문에 해당 데이터들을 다시 행렬로 변환해줘야한다.
#X[0].reshape(width, height).shape 하면 (128,128) 임을 확인할 수 있음
#시각화
cv2_imshow(X[0].reshape(width, height))
이렇게 이미지 데이터를 완성해주면 데이터 정규화를 진행한다.
(0과1사이의 값으로 정규화)
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
X = scaler.fit_transform(X)
#최대값,최소값 확인
X[0].max(), X[0].min()
다음으로 학습셋과 검증셋을 만든다.
from sklearn.model_selection import train_test_split
#데이터셋의 20%를 신경망 검증에 사용, 80%를 가중치 학습에 사용
#random_state 인스턴스를 같게 유지해주는 변수
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 1)
다음으로 신경망을 구현한다.
# 뉴런 수 변화 : 16384 -> 8193 -> 8193 -> 1
# 텐서플로에서 신경망 선언하는 클래스
network1 = tf.keras.models.Sequential()
# 16384를 첫번째 은닉층 입력 뉴런 수로 선언
network1.add(tf.keras.layers.Dense(input_shape=(16384,), units=8193, activation='relu'))
# 두번째 뉴런 수 = 첫번째 뉴런 수(16384) + 클래스수(2) / 2 = 8193
# 활성화함수 relu
network1.add(tf.keras.layers.Dense(units=8193, activation = 'relu'))
# 이진분류 문제이므로 시그모이드 함수 사용ㅎ
network1.add(tf.keras.layers.Dense(units = 1, activation = 'sigmoid'))
정의한 신경망 구조를 확인한다.
network1.summary()
확인이 맞으면 신경망을 컴파일 한다.
# Adam 옵티마이저사용, 이진분류문제이므로 손실함수는 binary_crosentropy 사용
network1.compile(optimizer='Adam', loss='binary_crossentropy', metrics = ['accuracy'])
다음으로 학습을 진행한다.
history = network1.fit(X_train, y_train, epochs=50)
학습된 신경망의 오류와 정확도를 확인해본다.
history.history.keys()
해당 소스를 실행하면 loss(오류)와 accuracy(정확도)가 리턴된다.
해당 값들의 그래프를 그려보면
plt.plot(history.history['loss']);
위의 그래프를 보면 처음엔 오류 값이 아주 높지만, 몇 차례 뒤에는 급격히 낮아짐을 볼 수 있고 4~5회 이후에는 오류에 변화가 거의 없음을 볼 수 있다. 따라서 유의미한 10번 내로 에포크를 실행하는게 좋을 듯 하다.
plt.plot(history.history['accuracy']);
학습이 완료되면 알고리즘의 성능 평가를 위해 신경망을 검증셋으로 평가해야한다.
predictions = network1.predict(X_test)
predictions #0과1사이의 확률 값으로 나타남
#0(False = Bart) , 1(True = Homer)로 변환
predictions = (predictions > 0.5)
predictions
위의 predictions 값들을 y_test 와 비교해본다.
from sklearn.metrics import accuracy_score
accuracy_score(y_test, predictions)
검증 데이터셋에서 72% 정도의 성능을 기록했다.
오차행렬로 비교해보기
from sklearn.metrics import confusion_matrix
cm = confusion_matrix(y_test, predictions)
cm
sns.heatmap(cm, annot=True);
오차행렬을 이용한 히트맵 결과에서 21개는 정확히 Bart 이미지로 분류 되었지만, 7개는 Bart 임에도 Homer 로 잘 못 분류되었음을 볼 수 있다.Homer는 18개가 정확히 분류되었고, 8개가 잘 못 분류되었음을 볼 수 있다.
마지막으로 recall, f1스코어를 살펴본다.
from sklearn.metrics import classification_report
print(classification_report(y_test, predictions))
0에 대해 support가 28임은 28개의 Bart 이미지가 있음을 나타내고, 1에 대해 supprt 26은 26개의 Homer 이미지가 있음을 나타낸다.
클래스 0(Bart 이미지)에서 0.75 recall 은 본 신경망이 Bart 이미지에서 75%정도로 이미지를 제대로 분류할 수 있음을 의미한다.
그리고 precision 0.72는 해당 Bart 이미지를 식별할 때 72% 경우 정확했음을 의미한다.
마찬가지로 클래스 1(Homer 이미지)에서 0.69 recall 은 본 신경망이 Homer 이미지에서 69%정도로 이미지를 제대로 분류할 수 있음을 의미하고, precision 0.72는 Homer 이미지를 식별할 때 72% 경우로 정확했음을 의미한다.
여기까지 모델학습이 완성되었다면 해당 모델을 저장하고 로드해서 다음에 사용하도록 한다.
모델 저장.
model_json = network1.to_json()
#write 파일
with open('network1.json', 'w') as json_file:
json_file.write(model_json)
from keras.models import save_model
#weights1.hdf5 : 텐서플로를 이용해 가중치를 저장할 때 사용하는 파일
network1_saved = save_model(network1, 'weights1.hdf5')
모델 로드.
with open('network1.json') as json_file:
json_saved_model = json_file.read()
json_saved_model
network1_loaded = tf.keras.models.model_from_json(json_saved_model)
network1_loaded.load_weights('/content/weights1.hdf5')
network1_loaded.compile(loss = 'binary_crossentropy', optimizer = 'Adam', metrics = ['accuracy'])
모델 확인.
network1_loaded.summary()
이제 위 모델을 통해 단일 이미지를 분류해본다.
검증 데이터셋에서 테스트 이미지를 가져와서 확인해본다.
X_test[0].shape
(16384,) 로 벡터는 맞지만 이미지 행렬의 형식이 아니기 때문에 형식을 바꿔줘야한다.
또한 0~1 사이로 정규화 처리 된 픽셀 데이터를 원래의 형식으로 다시 변환해야한다.
test_image = X_test[0]
#반정규화 0~255 사이로 변환
test_image = scaler.inverse_transform(test_image.reshape(1, -1))
테스트 이미지 데이터를 시각화해 확인해본다
cv2_imshow(test_image.reshape(width, height))
예측을 위해 모델 predict을 진행한다.
network1_loaded.predict(test_image)[0][0]
#다수의 인스턴스가 있는 행렬형식이기 때문에 결과값만 추출하려면 [0][0]으로 추출
0에 가까우면 Bart 1에 가까우면 Homer 로 출력하도록 한다.
if network1_loaded.predict(test_image)[0][0] < 0.5:
print('Bart')
else:
print('Homer')
.