CNN 이미지 데이터 전처리 및 변환 기초 지식 [메모리와 하드 용량이 큰 경우 빠른 연산], Cat and dog dataset

    CNN 하면서 이미지 데이터 전처리는 아주 중요한 것이다.  본인이 수집하거나 관찰 한 사진이 많고,  데이터  라벨 작업을 하였다면,  아래와 같은  딥러닝 할 수 있는 데이터 셋을 만들어야 한다. 

즉 feature (image) 값과  label값(cat & dog)을   numpy array값을 변환 하여야 한다.  데이터 전처리 하는데,  아래의 2가지 방법이 있다.  이번 설명은 첫번째 설명 방법이다.   이런 방법으로 하려면,  RAM 메모리와 HDD 용량이 커야 한다.   

  • 이미지 데이터 원본 그대로 학습
  • Data generator 로 학습
  즉,  개별 설비를 AI로  자동 제어 하고자 하는 시스템을 구현하는 경우 첫번째 방법으로 한다. 

Image processing

참고로 훈련할 파일은 아래에 있다. 

이미지 전처리 기초 지식

   데이터 디렉토리위치 및 파일이름 리스트 작업

      데이터 디렉토리 위치를 알려면, 아래와 같이  os  모듈을 가져와서,  위치를 안 다음에, 그림 파일에 있는 위치로 바꾸어야 한다. 

# numpy를 가져오는 것은 기본이다.
import numpy as np  


# os  모듈을 로딩 하고 디렉토리 확인 
import os
print(os.getcwd())

# 디렉토리 위치 바꾸기
data_dir = os.chdir("/home/아이디/zz.capston/image")



 glob 모듈은 파일이름을 리스트 형태로 변환 하는데 사용되며 위와 같이 디렉토리 위치를 이미지 화일 있는 곳으로 바꾸어야 한다. 

import glob  # 
from glob import glob

# 파일이름을 리스트 형태로 변환 하는데 사용
data_list = glob('*.jpg')
data_list[1:5]
['cat.1484.jpg', 'cat.688.jpg', 'cat.550.jpg', 'dog.1795.jpg']

# 리스트의 갯수
len(data_list)
4000


텍스트 토큰화

  리스트에 있는 텍스트 값을 나누는 것을 텍스트 토큰화 작업이라고 한다.  즉 텍스트 값을 나누어야 라벨을  정리 하여 하는데,  Target 값을 "cat", "dog"로 나누는 것이다.  즉 2가지 분류로 나누어 지니,  2진 분류 함수를 사용하는 sigmod 함수를 사용할 것이다. 

# 케라스의  text_to_word_sequence 모듈을 로딩한다.
from tensorflow.keras.preprocessing.text import text_to_word_sequence
text_to_word_sequence(data_list[0])

라벨변환

  'cat'과 'dog'과 같은 문자열은 딥러닝에서 인식 하지 못 하므로,  1,0으로 인코딩 하여 라벨을 변환 하여야 한다.  아래는 100개의 샘플 데이터를 가지고, Token 화 작업을 한 다음에 라벨 변환 하는 작업 이다. 


# label 100개를 하였다고 가정하자 
label = []
for n, path in enumerate(data_list[:100]):
    token = text_to_word_sequence(data_list[n])
    label.append(token[0])

위와 같이  loop문으로 토큰화 작업을 완료 하였으면,  아래와 같이 인코딩 작업을 한다. 

# label 인코드를 불러온다
from sklearn.preprocessing import LabelEncoder
items = label
encoder = LabelEncoder()
encoder.fit(items)
label = encoder.transform(items)

# lebel 디코딩 확인한다
encoder.inverse_transform(label)
array(['dog', 'cat', 'cat', 'cat', 'dog', 'dog', 'dog', 'dog', 'dog',
       'cat', 'cat', 'dog', 'dog', 'dog', 'dog', 'dog', 'dog', 'dog',
       'cat', 'dog', 'dog', 'cat', 'dog', 'dog', 'dog', 'cat', 'cat',
       'dog', 'cat', 'dog', 'cat', 'dog', 'dog', 'cat', 'dog', 'cat',
       'dog', 'dog', 'dog', 'cat', 'dog', 'cat', 'dog', 'cat', 'cat',
       'cat', 'dog', 'dog', 'cat', 'cat', 'dog', 'dog', 'dog', 'cat',
       'cat', 'cat', 'dog', 'cat', 'cat', 'cat', 'dog', 'dog', 'dog',
       'cat', 'cat', 'dog', 'dog', 'dog', 'dog', 'cat', 'dog', 'cat',
       'cat', 'cat', 'dog', 'dog', 'dog', 'dog', 'cat', 'cat', 'dog',
       'dog', 'cat', 'cat', 'dog', 'cat', 'cat', 'dog', 'dog', 'dog',
       'cat', 'dog', 'dog', 'dog', 'cat', 'dog', 'dog', 'cat', 'dog',
       'cat'], dtype='<U3')


이미지 변환

  이미지 변환은 이미지를 받아서, 딥러닝에 연산 할 수 있게 array 값으로 바꾸는 것이다. 이것은 open cv라는 파이썬 모듈이 있다.  이것을 이용을 한다. 

   open cv는 이미지 데이터를 0 ~ 255 까지의 데이터 셋으로 변환 하는 데 사용이 된다.  cv2.imread 함수에 화일리스트를 정하면,  0 ~ 255 까지의 데이터가 되며  RGB 이므로 3개의 체널이 된다.  그리고 딥러닝 연산을 하려면 0 ~ 1 까지 데이터 스케일 작업을 해야 한다.  다른 스케일 작업에 비해 이미지 스케일 작업은 생각 보다 쉽다.   255를 나누면 된다. 

# cv2 모듈을 불러 온다. 
import cv2
from matplotlib import pyplot as plt

# 100번째 있는 사진의 샘플을 본다. 
img = cv2.imread(data_list[100])
plt.imshow(img)
plt.show()

dog image set



# 이미지를 데이터 셋으로 변환 하고,  이것을 255로 나누어 0 ~ 1로 스케일 한다.
image = cv2.imread(data_list[0])/255
image.shape


샘플의 있는 사진이 제 각각 이므로,  사진의 길이와 높이를 통일화 하여,  초기화 한다. 

# 데이터 초기화 
data_height = 150
data_width = 150
channel_n = 3


초기화가 완료 되었으면, 아래와 같이 이미지 데이터 셋을 만들수 있다.

# label 100개를 하였다고 가정하자 
images = np.zeros((100, data_height, data_width, channel_n)) 
for n, path in enumerate(data_list[:100]):
    image = cv2.imread(data_list[n])
    image = cv2.resize(image, (data_height, data_width))
    images[n, :, :, :] =image

image.shape
(100, 150, 150, 3)



예제 ) 이미지 전처리 함수화


아래 예신에 대한 환경은 아래와 같다.
CUDA 11.1
cudnn 8.0.5
tensorflow 2.4

  위의  기반 지식이 있다면,  아래 예제와 같이  함수화 하여,  간단하게 구현할 수 있다. 

import tensorflow as tf
from tensorflow.keras import datasets, layers, models
import cv2


# image initialization
data_height = 150
data_width = 150
channel_n = 3
batch_size = len(data_list)

# data processing 함수 
from tensorflow.keras.preprocessing.text import text_to_word_sequence

def make_file(data_height, data_width, channel_n, batch_size):
    label = []
    images = np.zeros((batch_size, data_height, data_width, channel_n))
    for n, path in enumerate(data_list[:batch_size]):

    # lable 
        token = text_to_word_sequence(data_list[n])
        label.append(token[0])
        
    # image transform
        image = cv2.imread(data_list[n])
        image = cv2.resize(image, (data_height, data_width))/255
        images[n, :, :, :] =image
    
    label = np.array(label)
        
    return (label, images)

# 함수를 이용하여 라벨과 이미지 데이터셋을 가져오기 
(label, images) = make_file(data_height, data_width, channel_n, batch_size)

# 이미지 데이터 구조와 라벨 구조를 보기
print(images.shape, label.shape)
(4000, 150, 150, 3) (4000,)

위의 데이터 구조를  부연 설명 하면,  이미지 데이터 셋은 4000장의 사진과 높이, 길이 각각 150 픽셀의 3채널이 들어 있고,   라벨은 4000개 가 있다. 

위에서 설명한 바와 같이 라벨을 인코딩 해야 한다. 
# data label encoder
from sklearn.preprocessing import LabelEncoder
items = label
encoder = LabelEncoder()
encoder.fit(items)
label = encoder.transform(items)


그리고 사이킨런에 있는 train_test_split 을 import 하여,  학습 데이터와 테스트 데이터를 7:3으로 나눈다.  데이터 셋을 나눌 때도,  이 값은 메모리에 올라가서 메모리를 많이 차지 한다.  하지만 메모리 용량이 충분하다면, 이런 방식 대로 하는 것이 매우 좋다.  연산 속도가 매우 빨라 진다.   



# data split train set, test set
from sklearn.model_selection import train_test_split
x = images 
y = label

X_train, X_test, y_train, y_test = train_test_split(x, 
                                                    y, 
                                                    test_size=0.3, 
                                                    random_state=1004)

# 데이터 셋이  나누어진 것에 대한 구조보기
print(X_train.shape, X_test.shape)
(2800, 150, 150, 3) (1200, 150, 150, 3)

CNN 네트워크로 모델을 만들고 모델을 돌린다.  지금은 데이터 전처리가 목적이므로 아래에 대한 상세한 설명은 CNN 시리즈를 설명 할 때 한다. 

  # CNN model network
model = models.Sequential() model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(data_height, data_width, channel_n))) model.add(layers.MaxPooling2D((2, 2))) model.add(layers.Conv2D(64, (3, 3), activation='relu')) model.add(layers.MaxPooling2D((2, 2))) model.add(layers.Conv2D(128, (3, 3), activation='relu')) model.add(layers.Flatten()) model.add(layers.Dense(64, activation='relu')) model.add(layers.Dense(1, activation='sigmoid'))

model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d (Conv2D)              (None, 148, 148, 32)      896       
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 74, 74, 32)        0         
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 72, 72, 64)        18496     
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 36, 36, 64)        0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 34, 34, 128)       73856     
_________________________________________________________________
flatten (Flatten)            (None, 147968)            0         
_________________________________________________________________
dense (Dense)                (None, 64)                9470016   
_________________________________________________________________
dense_1 (Dense)              (None, 1)                 65        
=================================================================
Total params: 9,563,329
Trainable params: 9,563,329
Non-trainable params: 0
_________________________________________________________________

model.compile(optimizer='adam',
              loss='binary_crossentropy',
              metrics=['accuracy'])

history = model.fit(X_train, y_train, validation_data=(X_test,y_test),
                                                       batch_size=128,
                                                       epochs=5)
Epoch 1/5
22/22 [==============================] - 39s 1s/step - loss: 1.1010 - accuracy: 0.5156 - val_loss: 0.7177 - val_accuracy: 0.4892
Epoch 2/5
22/22 [==============================] - 1s 35ms/step - loss: 0.6912 - accuracy: 0.5344 - val_loss: 0.6836 - val_accuracy: 0.5567
Epoch 3/5
22/22 [==============================] - 1s 35ms/step - loss: 0.6757 - accuracy: 0.6034 - val_loss: 0.6702 - val_accuracy: 0.5617
Epoch 4/5
22/22 [==============================] - 1s 35ms/step - loss: 0.6423 - accuracy: 0.6382 - val_loss: 0.6058 - val_accuracy: 0.6717
Epoch 5/5
22/22 [==============================] - 1s 34ms/step - loss: 0.5782 - accuracy: 0.7012 - val_loss: 0.6060 - val_accuracy: 0.6667


# model chart
plt.figure(figsize=(12,4))
plt.subplot(1, 2, 1)
plt.plot(history.history['accuracy'])
plt.plot(history.history['val_accuracy'])
plt.title('model accuracy')
plt.xlabel('epoch')
plt.ylabel('accuracy')
plt.legend(['train', 'validation'])

plt.subplot(1, 2, 2)
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.title('model loss')
plt.xlabel('epoch')
plt.ylabel('loss')
plt.legend(['train', 'validation'])

학습곡선

위의 부연 설명을 하고자 하면, 모델은 CNN network를 사용했고, 2진 분류법이므로 activation을 sigmod 함수를 사용 했다. loss 함수는 크로스 엔트로피를 사용했고, 매트릭은 accuracy를 사용하였다. 위와 같이 학습 곡선을 그렸는데, 에포크를 적게 그려서 정확도가 높아질 가능 성이 보인다. 약 20 에포크 이상 돌리면, 최적점에 도달 할 수 있을 거 같다.

  하지만,  계속 돌리다보면,  위 모델을 학습셋과, 검증셋과의 차이로 인해 over-fitting이 발생 한다.
조금 더 튜닝 할 것이 남아 있다. 

댓글 없음:

댓글 쓰기

css cheat sheet 클래스 선택자, margin(마진), display , center 조정 간단한 구성 요소

 앞에서는 html의 간단한 sheet를 소개 하였습니다.   html은  주로 골격을 나타나는 것이라, 디자인을 하는데는 css로 하여야 합니다.  아래 코드와 같이 css 관련 하여 매우 간단하게 코딩 하겠습니다.  body 부분의 css 코딩  ...