상세 컨텐츠

본문 제목

딥러닝으로 꽃 분류하기

딥러닝 기초 이론/딥러닝 파이썬 실습

by nosungmin 2023. 2. 10. 17:17

본문

 

import tensorflow as tf
from tensorflow import keras 
import numpy as np

import matplotlib.pyplot as plt
import os
import PIL
import pathlib
data_dir = tf.keras.utils.get_file('flower_photos', origin=dataset_url, untar=True)#origin=dataset_url: 상용할 url, untar=True:압축을 풀어라
data_dir = pathlib.Path(data_dir)
roses = list(data_dir.glob('roses/*'))
PIL.Image.open(str(roses[2]))
tulips = list(data_dir.glob('tulips/*'))
PIL.Image.open(str(tulips[2]))

batch_size = 32
img_height = 180
img_width = 180

num_classes = 5
epochs = 15
train_ds = tf.keras.preprocessing.image_dataset_from_directory(
    data_dir,
    validation_split=0.2,
    subset='training',
    seed=123,
    image_size=(img_height, img_width),
    batch_size=batch_size
)
 

이 코드는 학습 데이터셋(training dataset)을 만드는 코드입니다.

tf.keras.preprocessing.image_dataset_from_directory() 함수는 이미지 데이터 디렉토리에서 이미지 데이터를 불러와 텐서플로우의 이미지 데이터셋 객체로 만듭니다.

data_dir 매개변수는 이미지 데이터 디렉토리의 경로입니다.

validation_split 매개변수는 검증 데이터셋의 비율을 의미합니다. 예를 들어, validation_split이 0.2일 경우, 전체 이미지 데이터셋 중 20%의 데이터가 검증 데이터셋으로 사용됩니다.

subset 매개변수는 생성할 데이터셋의 종류를 의미합니다. 예를 들어, subset이 'training'일 경우, 학습 데이터셋만 생성됩니다.

seed 매개변수는 랜덤한 순서를 정하는 난수 초기값입니다.

image_size 매개변수는 이미지의 높이와 너비를 의미합니다. 예를 들어, image_size가 (180, 180)일 경우, 이미지의 높이와 너비가 모두 180픽셀로 만듭니다.

batch_size는 위에서 설정해둔 batch_size로 설정합니다.

 

val_ds = tf.keras.preprocessing.image_dataset_from_directory(
    data_dir,
    validation_split=0.2,
    subset='validation',
    seed=123,
    image_size=(img_height, img_width),
    batch_size=batch_size
)
 
이 코드는 검증 데이터셋을 만드는 코드입니다.
위의 학습 데이터 셋 만드는 과정과 동일합니다.

 

 

plt.figure(figsize=(10,10))
for images, labels in train_ds.take(1):
    for i in range(9):
        ax = plt.subplot(33, i+1)
        plt.imshow(images[i].numpy().astype('uint8'))
        plt.title(class_names[labels[i]])
        plt.axis('off')
 

이 코드는 훈련 데이터 세트에서 9개의 이미지를 추출하여 시각화하는 코드입니다.

  1. plt.figure 명령은 그래프 크기를 10x10으로 설정합니다.
  2. 반복문 for images, labels in train_ds.take(1)은 훈련 데이터 세트에서 한 번에 1 배치 (32개)의 이미지와 레이블을 추출합니다.
  3. 내부 반복문 for i in range(9)는 9개의 이미지를 순회합니다.
  4. plt.subplot 명령은 3x3 격자 구조에서 i+1번째 위치에 그래프를 그리기 위해 사용됩니다.
  5. plt.imshow 명령은 이미지를 시각화합니다. 이미지의 타입을 'uint8'으로 변환하여 표시합니다.
  6. plt.title 명령은 각 이미지에 대한 클래스 이름을 타이틀로 설정합니다.
  7. plt.axis('off') 명령은 그래프의 축을 숨깁니다.
AUTOTUNE = tf.data.experimental.AUTOTUNE

train_ds = train_ds.cache().shuffle(1000).prefetch(buffer_size=AUTOTUNE)
val_ds = val_ds.cache().prefetch(buffer_size=AUTOTUNE)

이 코드는 TensorFlow의 tf.data API를 사용하여 데이터셋(train_ds와 val_ds)을 전처리하는 과정을 나타냅니다.

  • train_ds = train_ds.cache(): 이 명령은 훈련 데이터셋을 캐시하는 것을 나타냅니다. 캐시는 한 번 로드된 데이터를 메모리에 저장하여 다음에 필요할 때마다 빠르게 접근할 수 있도록 합니다.
  • train_ds = train_ds.shuffle(1000): 이 명령은 훈련 데이터셋을 1000개씩 섞는 것을 나타냅니다. 이는 각 epoch의 훈련 과정에서 다른 데이터를 보여주기 위해 사용됩니다.
  • train_ds = train_ds.prefetch(buffer_size=AUTOTUNE): 이 명령은 훈련 데이터셋의 미리 적재를 위해 TensorFlow가 사용할 수 있는 버퍼 크기를 지정하는 것을 나타냅니다.
  • val_ds = val_ds.cache().prefetch(buffer_size=AUTOTUNE): 이 명령은 검증 데이터셋을 캐시하고 미리 적재를 위해 TensorFlow가 사용할 수 있는 버퍼 크기를 지정하는 것을 나타냅니다.
normalization_layer = keras.layers.experimental.preprocessing.Rescaling(1./255)

이 코드는 TensorFlow의 Keras API를 사용하여 이미지 정규화 층을 생성하는 것입니다. Rescaling 레이어는 입력 이미지의 각 픽셀 값을 255로 나누어 정규화합니다. 이렇게 정규화된 이미지는 모델에서 더 쉽게 학습할 수 있도록 만듭니다.

 

normalized_ds = train_ds.map(lambda x, y: (normalization_layer(x), y))
 
이 코드는 train_ds 데이터셋에서 각 이미지와 레이블을 가져와서, normalization_layer을 통해 정규화 적용한 후, 정규화된 이미지와 레이블을 쌍으로 가지는 새로운 데이터셋인 normalized_ds를 생성하는 것입니다. map 메소드는 train_ds 데이터셋의 각 원소에 대해 제공된 함수(여기서는 lambda 함수)를 적용하여 새로운 데이터셋을 생성합니다.
 
image_batch, labels_batch = next(iter(normalized_ds))
 

이 코드는 normalized_ds에서 하나의 배치(batch)의 이미지와 레이블(labels)를 가져오는 코드입니다.

next(iter(normalized_ds))는 normalized_ds의 다음 원소를 가져오는 것으로, 하나의 배치씩 데이터셋을 순회하면서 모델의 훈련 등을 수행하기 위해서 사용될 수 있습니다.

해당 코드를 통해 확인할 수 있는 것이 있거나, 전처리된 이미지 배치 데이터의 크기, 레이블 데이터의 크기 등을 확인하기 위해서 사용될 수도 있습니다.

 
first_image = image_batch[0]
# Notice the pixels values are now in `[0,1]`.
print(np.min(first_image), np.max(first_image))
 
model = keras.Sequential([
  keras.layers.experimental.preprocessing.Rescaling(1./255, input_shape=(img_height, img_width, 3)),
  keras.layers.Conv2D(163, padding='same', activation='relu'),
  keras.layers.MaxPooling2D(),
  keras.layers.Conv2D(323, padding='same', activation='relu'),
  keras.layers.MaxPooling2D(),
  keras.layers.Conv2D(643, padding='same', activation='relu'),
  keras.layers.MaxPooling2D(),
  keras.layers.Flatten(),
  keras.layers.Dense(128, activation='relu'),
  keras.layers.Dense(num_classes)
])

이 코드는 Keras의 Sequential 모델을 생성하는 코드입니다. Sequential 모델은 여러 개의 층(layer)을 순차적으로 쌓아서 구성하는 모델입니다.

첫 번째 층으로는 Rescaling layer가 사용되어 있으며, 이미지의 각 픽셀 값을 255로 나누어 0과 1 사이의 값으로 변환합니다. 그 다음, 3x3 크기의 컨볼루션 층(Conv2D)이 여러 개 쌓여있으며, 이미지의 특징을 추출합니다. 컨볼루션 층 뒤에는 Max pooling 층이 여러 개 쌓여있어 이미지의 차원을 줄여줍니다.

그 다음에 Flatten 층으로 이미지를 1차원 텐서로 변환한 뒤, 완전 연결 층(Dense)이 2개 쌓여있습니다. 최종적으로는 num_classes 개의 노드를 가진 Dense 층이 있어 분류할 클래스 개수만큼의 출력을 생성합니다.

 

Conv2D는 2차원 합성곱(convolution) 층입니다. 이 층은 이미지에서 여러 개의 필터를 적용하여 공간적 특징을 추출하는데 사용됩니다. 합성곱 연산을 수행하는 동안, 각 필터는 입력 이미지의 작은 영역에서 슬라이딩(sliding)하여 특징을 검출합니다. 검출된 특징은 각 필터의 출력 값으로 변환되어 매핑됩니다.

이 코드에서 필터/커널의 수를 증가시키는 이유는 이미지의 특징을 더욱 잘 감지할 수 있도록 하기 위해서입니다.

각 Conv2D 층에서 증가하는 필터/커널의 수는 고차원의 특징을 감지할 수 있는 능력을 증가시킵니다. 첫 번째 Conv2D 층에서는 16개의 필터/커널을 가지고 있으며, 두 번째 Conv2D 층에서는 32개의 필터/커널을 가지고 있습니다. 이 특징은 신경망의 깊이가 깊어질수록 더욱 복잡하고 정교한 특징을 감지할 수 있도록 합니다.

MaxPooling2D 층은 이미지의 크기를 줄이면서 고차원의 특징을 유지하는데 도움을 줍니다. 이 층을 통과하면서 이미지의 크기가 절반으로 줄어들지만 고차원의 특징은 유지되므로 신경망의 성능을 개선할 수 있습니다.

 

model.compile(optimizer='adam',
              loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
              metrics=['accuracy'])
 
history = model.fit(train_ds,
                    validation_data=val_ds,
                    epochs=epochs)
def plot_graphs(historymetric):
  plt.plot(history.history[metric])
  plt.plot(history.history['val_'+metric], '')
  plt.xlabel("Epochs")
  plt.ylabel(metric)
  plt.legend([metric, 'val_'+metric])
  plt.show()
 
plot_graphs(history, 'accuracy')
 
plot_graphs(history, 'loss')

 

 

정확도와 손실률을 시각화 해 본 결과 오버피팅이 일어났습니다.

이 오버피팅 현상을 가능한 줄여보도록 하겠습니다.

 

data_augmentation = keras.Sequential(
  [
    keras.layers.experimental.preprocessing.RandomFlip("horizontal"
                                                 input_shape=(img_height, 
                                                              img_width,
                                                              3)),
    keras.layers.experimental.preprocessing.RandomRotation(0.1),
    keras.layers.experimental.preprocessing.RandomZoom(0.1),
  ]
)

이 코드는 Keras 라이브러리를 사용하여 데이터 증강(data augmentation) 신경망을 생성하는 코드입니다.

"Sequential" 객체를 생성하여 순차적으로 적용할 레이어를 나열합니다.

첫 번째 레이어는 "RandomFlip"이며, 입력 이미지를 수평으로 랜덤하게 뒤집는 기능을 가집니다. "input_shape" 파라미터는 이미지의 높이와 너비, 채널 수(3: RGB)를 지정합니다.

두 번째 레이어는 "RandomRotation"으로, 이미지를 랜덤하게 회전시킵니다. "0.1" 파라미터는 회전 각도의 범위를 지정합니다.

세 번째 레이어는 "RandomZoom"으로, 이미지를 랜덤하게 확대/축소합니다. "0.1" 파라미터는 확대/축소 비율의 범위를 지정합니다.

컴퓨터는 같은 사진이라도 꺽고 확대하는 것만으로 같은 사진을 다르게 인식하여 학습 데이터의 량을 증가시킬 수 있습니다.

plt.figure(figsize=(1010))
for images, _ in train_ds.take(1):
  for i in range(9):
    augmented_images = data_augmentation(images)
    ax = plt.subplot(33, i + 1)
    plt.imshow(augmented_images[0].numpy().astype("uint8"))
    plt.axis("off")
 

이렇게 같은 사진을 뒤집고 확대함으로서 학습 데이터의 증가를 시켰습니다.

 

model = keras.Sequential([
  data_augmentation,
  keras.layers.experimental.preprocessing.Rescaling(1./255, input_shape=(img_height, img_width, 3)),
  keras.layers.Conv2D(163, padding='same', activation='relu'),
  keras.layers.MaxPooling2D(),
  keras.layers.Conv2D(323, padding='same', activation='relu'),
  keras.layers.MaxPooling2D(),
  keras.layers.Conv2D(643, padding='same', activation='relu'),
  keras.layers.MaxPooling2D(),
  #새로 추가된 정규화 방법인 드롭아웃 추가
  keras.layers.Dropout(0.2),#20퍼센트의 신경망을 제거
  keras.layers.Flatten(),
  keras.layers.Dense(128, activation='relu'),
  keras.layers.Dense(num_classes)
])
 
이제 위의 신경망을 다시 만들어보겠습니다.
또한 드롭아웃 기법을 사용하여 오버피팅을 줄여보도록 하겠습니다.
 
model.compile(optimizer='adam',
              loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
              metrics=['accuracy'])
 
history = model.fit(train_ds,
                    validation_data=val_ds,
                    epochs=epochs)
plot_graphs(history, 'accuracy')
plot_graphs(history, 'loss')

드롭아웃 기법과 학습 데이터의 량을 증가시킨 결과 처음의 결과보다 오버피팅 현상이 현격히 줄어드는 것을 확인했습니다.

관련글 더보기