본문 바로가기

인공지능/딥러닝

[딥러닝DL]합성곱신경망(CNN)을 이용한 이미지 분류

~ 목차 ~

합성곱신경망(CNN) 🐸

 

합성곱신경망(CNN, Convolutional Neural Network)

이미지 분석에 주로 사용되는 대표적 계층
- 기존의 인공신경망에서의 이미지 분석시에는 높이와 너비를 곱한 1차원을 사용하였다면,
- CNN은 원형 그대로의 높이와 너비 차원을 사용함
- 전체 4차원(행, 높이, 너비, 채널)의 데이터를 사용함
- 기존 이미지 분석 시 높이와 너비를 곱하여 사용하다보면, 
  * 원형 그대로의 주변 이미지 공간 정보를 활용하지 못하는 단점이 있으며, 
  * 이러한 이유로 특징 추출을 잘 못하여, 학습이 잘 이루어지지 않는 경우가 발생함
  
- 이러한 기존 인공신경망 모델의 단점을 보완하여 만들어진 모델이 CNN
  * 원형 형태의 이미지 정보를 그대로 유지한 상태로 학습 가능하도록 만들어졌음
  * 이미지의 공간정보를 이용하여 특징을 추출함
  * 인접 이미지의 특징을 포함하여 훈련

 

 

합성곱신경망(CNN) 계층 구조 🐸

 

합성곱신경망(CNN) 계층 구조

1. 입력 계층(Input Layer)
  - 데이터 입력 계층 (기존과 동일)

2. 합성곱 계층(Convolutional Layer, CNN 계층)
  - 이미지의 특징을 추출하는 합성곱 계층이 여러층으로 구성되어 있음

3. 활성화 함수 계층(은닉계층, Activation Layer)
  - 합성곱 계층의 출력에 대한 비선형성 추가한 활성화 함수 ReLU와 같은 함수 적용

4. 풀링 계층(Pooling Layer)
  - 공간 크기를 줄이고 계산량을 감소시키기 위한 계층(중요 특징만 추출하는 계층)
  - 풀링 방법으로 최대풀링(Max Pooling), 평균풀링(Average Pooling)이 있음
  - 주로 Max Pooling 사용됨

5. 완전연결 계층(은닉계층, Dense Layer)
  - 추출된 특징을 이용하여 최종 예측을 수행하는 계층
  - 이때는 기존의 방법과 동일하게 1차원(높이 x 높이)의 전처리 계층(Plattern)을 사용하는 경우도 있음
    (인공신경망 모델과 동일한 계층 구조로 진행)

6. 출력 계층(Output Layer)
  - 최종 예측이 이루어지는 계층

  *** CNN에서 2~5번의 계층구조가 일반적으로 사용됨,
      - 나머지 계층은 기존과 동일한 계층
      - 2~5번의 CNN 계층은 여러개 추가 가능

 

 


 

라이브러리 🐸

 

import tensorflow as tf
from tensorflow import keras
from sklearn.model_selection import train_test_split

 

"""훈련계층 시각화"""
from tensorflow.keras.utils import plot_model 

 

 

패션 이미지 데이터 읽어들이기 🐸

 

"""패션 데이터셋 load"""
(train_input, train_target),(test_input, test_target) = keras.datasets.fashion_mnist.load_data()

print(train_input.shape, train_target.shape)
print(test_input.shape, test_target.shape)

(60000, 28, 28) (60000,)
(10000, 28, 28) (10000,)

 

"""3차원"""
train_input

 

 

 

 

 

 4차원 만들기 🐸

 

 

CNN에서 사용하는 차원은 4차원

 - (행, 높이, 너비, 채널)
 - 채널 = 1 또는 3 (1은 흑백, 3은 컬러(RGB)라고 보통 칭합니다.)

 

 

train_input_4d = train_input.reshape(-1, 28, 28, 1)
train_input_4d.shape

 

(60000, 28, 28, 1)

 

 

 

정규화 🐸

 

"""픽셀 데이터 정규화"""
train_scaled = train_input_4d / 255.0
train_scaled.shape, train_scaled[1][0]

 

((60000, 28, 28, 1),
 array([[0.        ],
        [0.        ],
        [0.        ],
        [0.        ],
        [0.        ],
        [0.00392157],
        [0.        ],
        [0.        ],
        [0.        ],
        [0.        ],
        [0.16078431],
        [0.7372549 ],
        [0.40392157],
        [0.21176471],
        [0.18823529],
        [0.16862745],
        [0.34117647],
        [0.65882353],
        [0.52156863],
        [0.0627451 ],
        [0.        ],
        [0.        ],
        [0.        ],
        [0.        ],
        [0.        ],
        [0.        ],
        [0.        ],
        [0.        ]]))

 

훈련 및 검증 데이터로 분리하기 →  8 : 2 🐸

 

train_scaled, val_scaled, train_target, val_target = train_test_split(train_scaled, train_target, test_size=0.2, random_state=42)

print(train_scaled.shape, train_target.shape)
print(val_scaled.shape, val_target.shape)
print(test_input.shape, test_target.shape)

 

(48000, 28, 28, 1) (48000,)
(12000, 28, 28, 1) (12000,)
(10000, 28, 28) (10000,)

 


 

 

합성곱신경망(CNN) 모델 + 계층 생성하기 🐸

 

 

CNN(입력 계층으로 추가)

- 데이터와 필터(filter)가 곱해진다고 해서 합성곱이라는 단어가 붙었음
- 이미지와 같은 2차원 데이터 분류에는 보통 2차원 합성곱(Conv2D)이 사용됨
- 입력 계층으로 사용하는 경우에는 input_shape의 입력 차원 속성을 함께 사용

<kernel_size>
- 이미지를 훑으면서 특징을 추출하는 역할을 수행
- 필터가 사용할 사이즈를 정의함
- kernel_size=3 → 필터가 사용할 사이즈 3행 3열을 의미함(3x3을 줄여서 3이라고 칭함)
- 커널 사이즈는 홀수로 정의함(3, 5가 주로 사용됨)
kernel_size=(3, 3) 또는 kernel_size=3

<filters>
- 커널사이즈의 행렬의 공간 1개에 대한 필터의 갯수 정의
- 데이터를 훑으면서 특징을 감지함
- 필터의 값이 클수록 훈련속도가 오래 걸림
- 보통 32, 64가 주로 사용됨
- CNN계층을 여러개 사용하는 경우에는 처음 CNN계층에는 작은 값부터 시작...
     16, 32, 64, 128 정도가 주로 사용됨
  

<padding>
- 경계 처리 방식을 정의함
- 입력 데이터의 주변에 추가되는 가상의 공간을 만들수 있음
- 처리 방식 : same과 valid가 있음
 * same : 패딩을 사용하여 입력과 출력의 크기를 동일하게 만들어서 훈련하고자 할 때 사용
        : 주로 권장되는 방식
 * valid : 패딩을 사용하지 않음을 의미함


<strides>
- 커널이 이미지 데이터를 훑을 때(특징 추출 시)의 스탭을 정의함
- 커널이 데이터 특징 추출 순서(행렬을 기준으로)
   왼쪽 상단에서 시작하여 오른쪽으로 이동하는 스탭 정의
   오른쪽 열을 다 훑고난 다음 아래로 이동하는 스탭 정의
strides=1 : 오른쪽으로 1씩 이동, 아래로 1씩 이동을 의미

 

 

"""모델 생성하기"""
model = keras.Sequential()
model

 

"""CNN 계층 생성하기"""
model.add(keras.layers.Conv2D(kernel_size=3,
                              filters=32,
                              activation="relu",
                              padding="same",
                              strides=1,
                              input_shape=(28, 28, 1)))

 

 

풀링레이어(Pooling Layer) 계층 추가하기

<풀링레이어(Pooling Layer)>
- CNN 계층 추가 이후에 일반적으로 사용되는 계층
- CNN 계층에서 추출된 특징들 중에 중요한 정보만을 추출하는 계층
- 머신러닝에서 주성분 분석(PCA)과 유사한 개념
- 이미지를 구성하는 픽셀들이, 주변 픽셀들끼리는 유사한 정보를 가진다는 개념에서
  중복된 값들이나 유사한 값들을 대표하는 값들, 즉 중요한 특징들만 추출하게 됩니다.
- 과적합 방지에 효율적으로 사용되며 훈련 성능에 영향을 미치지 않는 전처리 계층임

 * MaxPool2D
 - 사소한 값들은 무시하고 최대값의 특징들만 추출하는 방식
 - pool_size=2 : 2행 2열의 공간에 중요 특징들만 저장하라는 의미
 - strides=2 : CNN에서 추출한 특징값들의 행렬을 오른쪽 2칸씩, 
                    아래로 2칸씩 이동하면서 중요 특징값 추출하라는 의미
                   : 기본 디폴트값은 2 (생략가능)
 - 풀링에서 가장 대표적으로 사용되는 방식임
  

 

 

"""풀링 계층 추가하기"""
model.add(keras.layers.MaxPool2D(pool_size=2, strides=2))

 

 

"""CNN 계층 추가하기"""
model.add(keras.layers.Conv2D(kernel_size=(3, 3),
                              filters=64,
                              activation="relu",
                              padding="same",
                              strides=1))

 

 

"""풀링 계층 추가하기"""
model.add(keras.layers.MaxPool2D(pool_size=2, strides=2))

 

 

 

인공신경망 계층 추가(전처리 또는 은닉계층 + 출력계층)

 

 

"""(전처리 계층)예측을 위해 중요 특징 데이터를 1차원(높이 x 너비)로 변환하기"""
model.add(keras.layers.Flatten())

 

"""은닉계층 추가 - 활성화함수 추가됨 """
model.add(keras.layers.Dense(100, activation="relu"))

 

"""(전처리 계층)드롭아웃도 적용"""
model.add(keras.layers.Dropout(0.2))

 

"""출력계층 추가"""
model.add(keras.layers.Dense(10, activation="softmax"))

 

model.summary()

 

 

 

 

훈련계층을 이미지로 시각화 - plot_model()🐸

 

from tensorflow.keras.utils import plot_model 

plot_model(model)

 

훈련계층 시각화

 

 

 

계층모델 속성 정의하여 시각화 및 저장시키기

show_shapes : 층의 형태를 세부적으로 표현(기본값은 False)
dpi : 이미지 해상도
to_file : 저장위치(기본값은 현재 실행파일이 있는 위치와 동일한 곳에 저장됨)

 

plot_model( model, show_shapes=True, to_file="./model_img/CNN_Layer.png", dpi=300 )

 

계층 정보 시각화

 

 

 


 

 

모델 설정하기 🐸

 

"""옵티마이저 Adam"""
model.compile(optimizer="adam", loss="sparse_categorical_crossentropy", metrics=["accuracy"])

 

 

 

 


 

 

 

콜백함수 만들기(자동저장, 자동종료) 🐸

 

"""자동저장"""
checkpoint_cb = keras.callbacks.ModelCheckpoint("./model/best_CNN_Model.h5")

"""자동종료"""
early_stopping_cb = keras.callbacks.EarlyStopping(patience=2, restore_best_weights=True)

checkpoint_cb, early_stopping_cb

 

 

 

훈련 시키기 🐸

 

 

훈련 20회만 수행한 후 손실곡선과 정확도 곡선 시각화하기

 

 

""" 모델 훈련 시키기 """
history=model.fit(train_scaled, train_target, epochs=20, verbose=1,
          validation_data=(val_scaled, val_target), callbacks=[checkpoint_cb, early_stopping_cb])

 

 

 

"""시각화하기"""

import matplotlib.pyplot as plt
import seaborn as sns

"""한글처리"""
plt.rc("font", family = "Malgun Gothic")
"""마이너스 기호 처리"""
plt.rcParams["axes.unicode_minus"] = True


plt.figure(figsize=(12, 5))
plt.suptitle("합성곱신경망(CNN)을 이용한 이미지 분류", y=1.02, fontsize=16)


# 손실 시각화 - Loss
plt.subplot(1, 2, 1)  # 1행 2열 중 첫 번째 subplot
plt.title("CNN Train & Val - Loss")
plt.plot(history.epoch, history.history["loss"])
plt.plot(history.epoch, history.history["val_loss"])
plt.xlabel("epoch")
plt.ylabel("loss")
plt.legend(["Train", "Val"])
plt.grid()

# 정확도 시각화 - Accuracy
plt.subplot(1, 2, 2)  # 1행 2열 중 두 번째 subplot
plt.title("CNN Train & Val - Accuracy")
plt.plot(history.epoch, history.history["accuracy"])
plt.plot(history.epoch, history.history["val_accuracy"])
plt.xlabel("epoch")
plt.ylabel("accuracy")
plt.legend(["Train", "Val"])
plt.grid()

plt.tight_layout()  # subplot 간 간격 조절
plt.show()

 

 

 

 

 

훈련 및 검증데이터 모델 성능 검증결과 확인 🐸

 

train_eval = model.evaluate(train_scaled, train_target)
val_eval = model.evaluate(val_scaled, val_target)

train_eval, val_eval

 

1500/1500 [==============================] - 5s 3ms/step - loss: 0.1362 - accuracy: 0.9497
375/375 [==============================] - 1s 3ms/step - loss: 0.2170 - accuracy: 0.9202
 
([0.13621293008327484, 0.9497083425521851],
 [0.2169836014509201, 0.9201666712760925])

 

 

 

 

 

훈련에 사용된 데이터를 이미지로 그려보기 🐸

 

 

훈련에 사용된 데이터를 이미지로 그려보기

 - (행, 높이, 너비, 채널)
 - 이미지 데이터는 2차원으로 줄여서 그려야 합니다. reshape

 

 

val_scaled[0].shape

 

(28, 28, 1)

 

 

""" 이미지 데이터는 2차원으로 줄여서 그려야 합니다."""

val_scaled_0 = val_scaled[0].reshape(28, 28)

val_scaled_0.shape

 

(28, 28)

 

"""이미지 한개 그려보기"""
plt.imshow(val_scaled_0, cmap="gray_r")
plt.show()

 

 

"""실제 값"""
val_target[0]

8

 

 

 

예측하기 🐸

 

 

검증데이터 0번째 데이터를 이용해서 예측해보기

- 예측결과값은 모델 출력계층의 출력크기(갯수)만큼 확률값으로 반환해줍니다.
- 반환된 확률값 중 가장 큰 값의 "인덱스 번호"가 종속변수 값이 됩니다.

 

 

[내코드]

import numpy as np
preds = model.predict(val_scaled)
preds_max = np.argmax(preds[0])
preds[0], preds_max

 

(array([4.1726835e-13, 3.1553179e-16, 4.2426482e-13, 3.4465784e-13,
        1.9473605e-12, 1.8247194e-13, 4.3801055e-13, 3.0728848e-13,
        1.0000000e+00, 1.0426476e-15], dtype=float32),
 8)

 

 

[강사님 코드]

 

preds = model.predict(val_scaled[0:1])
preds

 

array([4.1726835e-13, 3.1553179e-16, 4.2426482e-13, 3.4465784e-13,
        1.9473605e-12, 1.8247194e-13, 4.3801055e-13, 3.0728848e-13,
        1.0000000e+00, 1.0426476e-15], dtype=float32)

 

plt.bar(range(10), preds[0])
plt.xticks(range(10))
plt.grid()
plt.show()

 

 

 

"""범주 명칭"""
classes = ["티셔츠", "바지", "스웨터", "드레스", "코트",
                  "샌들", "셔츠", "스니커즈", "가방", "앵클부츠"]

 

 

print(np.argmax(preds))
print(f"예측값 : {classes[np.argmax(preds)]}")
print(f"실제값 : {classes[val_target[0]]}")

8
예측값 : 가방
실제값 : 가방

 

 

테스트 데이터로 최종 예측하기 🐸

 

 

테스트 데이터로 최종 예측하기

테스트 데이터를 이용해서 예측한 후, 
- 예측 결과 중 10개만 추출하여, 
  → 실제이미지 출력, 예측값, 실제값을 각각 출력해 주세요

 

 

"""테스트 데이터 정규화하기"""
test_scaled = test_input / 255.0

 

 

"""테스트 데이터 예측하기"""

test_pred = model.predict(test_scaled)

 

"""예측 결과 10개만 추출"""
test_pred_10 = test_pred[0:10]
len(test_pred_10), test_pred_10

 

(10,
 array([[1.45199849e-07, 7.93101140e-11, 6.97332525e-10, 6.03566477e-08,
         2.40284268e-11, 9.09285404e-07, 2.43776638e-10, 3.26200316e-05,
         1.92863880e-09, 9.99966264e-01],
        [1.41747307e-06, 9.56859808e-13, 9.99956012e-01, 3.77283982e-10,
         4.20387114e-05, 3.99349743e-13, 4.66400337e-07, 7.16907228e-11,
         1.20789309e-10, 7.70235525e-12],
        [1.03377494e-10, 1.00000000e+00, 3.66464498e-10, 7.59104424e-09,
         1.28722144e-10, 8.21483554e-15, 1.40686464e-12, 1.26187137e-18,
         6.59135507e-12, 2.36610928e-17],
        [1.18863701e-08, 9.99999881e-01, 1.22996835e-09, 4.74561936e-08,
         5.74258330e-08, 1.11506312e-12, 2.09339504e-10, 2.38901887e-17,
         6.73742702e-12, 1.69071673e-14],
        [6.11671694e-02, 1.94290877e-07, 2.51566619e-03, 4.19271877e-04,
         1.53910602e-03, 9.31109412e-07, 9.34357226e-01, 4.62401104e-08,
         2.24246762e-07, 1.30901743e-08],
        [2.45150122e-10, 1.00000000e+00, 8.31993863e-10, 6.22483315e-11,
         4.20519002e-11, 1.17871167e-13, 2.83125764e-13, 2.35233752e-19,
         2.70576409e-11, 3.99504165e-18],
        [1.54505869e-05, 2.95058888e-09, 7.71999508e-02, 6.81573287e-09,
         9.22689617e-01, 1.39609557e-09, 9.50211906e-05, 6.20762400e-11,
         4.74907003e-10, 2.33394665e-10],
        [8.38937740e-06, 2.22202026e-08, 1.25062594e-04, 5.82211305e-06,
         1.28208101e-02, 1.70984862e-07, 9.87039387e-01, 3.63273500e-09,
         3.28636588e-07, 2.77321384e-08],
        [3.24827511e-06, 2.93293478e-08, 1.14028808e-07, 1.47327093e-08,
         4.34307310e-07, 9.99812901e-01, 2.21275656e-07, 1.54532128e-04,
         2.84388461e-05, 9.22231322e-08],
        [7.14366948e-08, 5.17943577e-10, 5.18394572e-09, 4.09034158e-08,
         1.67608361e-09, 5.75658952e-08, 2.03331629e-10, 9.99998331e-01,
         6.58265720e-10, 1.52245400e-06]], dtype=float32))

 

 

 

"""각 데이터별로 최대값의 인덱스만 추출하기"""
test_preds_max_idx = [np.argmax(pred) for pred in test_pred_10]
print(test_preds_max_idx)

[9, 2, 1, 1, 6, 1, 4, 6, 5, 7]

 

 

"""예측 결과 10개만 출력하기"""
for in range(0, 10, 1):
    plt.figure(figsize=(2, 2))
    """이미지로 표현하기 위해 2차원으로 변환"""
    plt.imshow(test_scaled[i].reshape(28, 28), cmap="gray_r")
    plt.show()

    """실제값과 예측결과값 비교를 위해 출력하기"""
    print(f"실제값[{test_target[i]} / {classes[test_target[i]]}]")
    print(f"예측값[{test_preds_max_idx[i]} / {classes[test_preds_max_idx[i]]}]")
    

 

 

 

출력 결과

 

728x90