아이티윌_데이터 분석 55기/강의내용_딥러닝

#3 3일차_ANN(다중 분류), 이미지 분석(PCA+KNN, ANN, CNN)

ecosso 2026. 7. 27. 16:22

01 ANN - 다중 분류

02 PCA + KNN 모델을 이용한 이미지 분석

03 ANN 모델을 사용한 이미지 분석

04 CNN 모델을 사용한 이미지 분석


01  ann - 다중 분류

1) 데이터 로딩

from sklearn.datasets import load_iris
iris = load_iris()
X, y = iris.data, iris.target
X.shape
y.shape

X.shape
Out[4]: (150, 4)

y.shape
Out[5]: (150,)


# target 변수 원핫 인코딩 (3개 클래스)
import pandas as pd
y3 = pd.get_dummies(y, prefix='Y').astype('int')

pd.get_dummies(y, prefix='Y').astype('int')
Out[9]: 
     Y_0  Y_1  Y_2
0      1    0    0
1      1    0    0
2      1    0    0
3      1    0    0
4      1    0    0
..   ...  ...  ...
145    0    0    1
146    0    0    1
147    0    0    1
148    0    0    1
149    0    0    1

[150 rows x 3 columns]

2) 스케일링

from sklearn.preprocessing import StandardScaler, MinMaxScaler
m_sc = MinMaxScaler()
X_sc = m_sc.fit_transform(X)

 

3) 데이터 분리

from sklearn.model_selection import train_test_split
train_x, test_x, train_y, test_y = train_test_split(X, y, random_state=0, stratify=y)

 

4) 모델링

# 1) seed 고정
import numpy as np
import tensorflow as tf
seed = 0
np.random.seed(seed)
tf.random.set_seed(seed)

# 2) 모델 정의
from tensorflow.keras.layers import Input
from keras import Sequential
from keras.layers import Dense

model = Sequential()
model.add(Input(shape=(train_x.shape[1], )))  
model.add(Dense(2, activation='relu'))
model.add(Dense(1, activation='sigmoid'))                       

# 3) 오차함수, 최적화 결정
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])

# 4) early stopping rule
from tensorflow.keras.callbacks import EarlyStopping
es = EarlyStopping(monitor='val_loss', patience=10, restore_best_weights = True)         
                                                                                                   
# 5) 학습
hist = model.fit(train_x, train_y, validation_split=0.25,   
                 batch_size = 10, epochs=50000, callbacks=[es])          

Epoch 1/50000
C:\Users\itwill\anaconda3\Lib\site-packages\keras\src\losses\losses.py:34: SyntaxWarning: In loss categorical_crossentropy, expected y_pred.shape to be (batch_size, num_classes) with num_classes > 1. Received: y_pred.shape=(None, 1). Consider using 'binary_crossentropy' if you only have 2 classes.
  return self.fn(y_true, y_pred, **self._fn_kwargs)
9/9 ━━━━━━━━━━━━━━━━━━━━ 1s 23ms/step - accuracy: 0.3571 - loss: 0.0000e+00 - val_accuracy: 0.2500 - val_loss: 0.0000e+00
Epoch 2/50000
9/9 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.3571 - loss: 0.0000e+00 - val_accuracy: 0.2500 - val_loss: 0.0000e+00
Epoch 3/50000
9/9 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.3571 - loss: 0.0000e+00 - val_accuracy: 0.2500 - val_loss: 0.0000e+00
Epoch 4/50000
9/9 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.3571 - loss: 0.0000e+00 - val_accuracy: 0.2500 - val_loss: 0.0000e+00
Epoch 5/50000
9/9 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.3571 - loss: 0.0000e+00 - val_accuracy: 0.2500 - val_loss: 0.0000e+00
Epoch 6/50000
9/9 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.3571 - loss: 0.0000e+00 - val_accuracy: 0.2500 - val_loss: 0.0000e+00
Epoch 7/50000
9/9 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.3571 - loss: 0.0000e+00 - val_accuracy: 0.2500 - val_loss: 0.0000e+00
Epoch 8/50000
9/9 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.3571 - loss: 0.0000e+00 - val_accuracy: 0.2500 - val_loss: 0.0000e+00
Epoch 9/50000
9/9 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.3571 - loss: 0.0000e+00 - val_accuracy: 0.2500 - val_loss: 0.0000e+00
Epoch 10/50000
9/9 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.3571 - loss: 0.0000e+00 - val_accuracy: 0.2500 - val_loss: 0.0000e+00
Epoch 11/50000
9/9 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - accuracy: 0.3571 - loss: 0.0000e+00 - val_accuracy: 0.2500 - val_loss: 0.0000e+00

# 1. 데이터 로딩
from sklearn.datasets import load_iris
iris = load_iris()
X, y = iris.data, iris.target
X.shape
y.shape

# target 변수 원핫 인코딩(3개 클래스)
import pandas as pd
y3 = pd.get_dummies(y, prefix='Y').astype('int')

# 2. 스케일링
from sklearn.preprocessing import StandardScaler, MinMaxScaler
m_sc = MinMaxScaler()
X_sc = m_sc.fit_transform(X)

# 3. 데이터 분리
from sklearn.model_selection import train_test_split
train_x, test_x, train_y, test_y, train_y3, test_y3 = train_test_split(X, y, y3, random_state=0, stratify=y)

# 4. 모델링
# 1) seed 고정
import numpy as np
import tensorflow as tf
seed = 0
np.random.seed(seed)
tf.random.set_seed(seed)

# 2) 모델 정의
from tensorflow.keras.layers import Input
from keras import Sequential
from keras.layers import Dense

model = Sequential()
model.add(Input(shape=(train_x.shape[1], )))  
model.add(Dense(2, activation='relu'))
model.add(Dense(3, activation='softmax'))             # 다중분류일때 출력층 활성함수는 sigmoid 또는 softmax 적합          

# 3) 오차함수, 최적화 결정
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])

# 4) early stopping rule
from tensorflow.keras.callbacks import EarlyStopping
es = EarlyStopping(monitor='val_loss', patience=10, restore_best_weights = True)         
                                                                                                   
# 5) 학습
hist = model.fit(train_x, train_y3, validation_split=0.25,   
                 batch_size = 10, epochs=50000, callbacks=[es])

 

 


02 PCA + KNN 모델을 이용한 이미지 분석

 이미지는 비정형데이터이므로, 우선 분석을 위해서는 정형데이터처럼 컴퓨터가 인식가능한 형태로 변형되어야 한다.

 이미지 데이터는 픽셀로 나누어져 있으며 각 픽셀은 RGB 코드에 의해 0~255의 값을 각각 가지게 된다.

 예를 들어 4 X 5의 해상도를 가지는 파일의 경우, 20개의 픽셀을 가지게 된다.

 즉, 20개의 열이 생성된다.

 

 PCA 또는 KNN과 같은 머신러닝 모델은 일반적으로 2차원 표 형태의 입력을 사용하기 때문에 위와 같은 과정을 통해 이미지 한 장의 높이 X 너비 배열을 하나의 긴 벡터로 펼치는 작업이 필요하다. 이를 flatten이라고 한다.

 즉, 이미지 한 장이 1차원 벡터로 펼쳐지며 전체 이미지 데이터셋이 2차원 표가 된다.

 

 이미지를 flatten하는 과정에서 픽셀 하나하나가 변수가 되므로 해상도에 따라 변수의 수가 매우 많아지게 되는데, 이 과정에서 계산량 증가, 거리 계산의 의미가 약해지는 차원의 저주 등 여러가지 문제가 발생하게 된다.

 따라서 PCA를 통해 데이터의 분산을 최대한 많이 설명할 수 있는 새로운 축인 주성분을 만들게 된다.

 이는 여러 픽셀값을 선형적으로 조합하여 이미지 사이의 주요 변화 패턴을 나타내는 축을 찾는 과정이다.

 => PCA는 가장 큰 분산을 설명하는 주성분을 만들어줌으로써, 특정 픽셀의 변동이 심한 사진의 유사성을 판별/분류할 수 있게 도와준다.

 

 PCA로 차원을 축소한 이후에는 각 이미지가 몇 개의 주성분 점수로 표현되며, KNN을 통해 새로운 이미지와 기존 학습 이미지 사이에서 가까운 학습 이미지들을 찾아 주변 이웃이 가장 많이 속한 클래스로 분류한다.

 

 그러나 차원 압축 / 분류 등을 통해 원래 이미지가 가지고 있던 픽셀들의 신호가 약해지는 경향을 보완하기 위하여 이 neural network를 통한 학습/분류가 이후에 발전하게 된다.


# 1) 데이터 로딩 (RGB로 변환된 데이터)
from tensorflow.keras.datasets import mnist
(train_x, train_y), (test_x, test_y) = mnist.load_data() # mnist는 train / test가 사전에 나누어져 있다.

(train_x, train_y), (test_x, test_y) = mnist.load_data()
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz
11490434/11490434 ━━━━━━━━━━━━━━━━━━━━ 1s 0us/step

 

train_x.shape

# 층 6만개, 28 X 28의 해상도

train_x.shape
Out[132]: (60000, 28, 28)

 

train_y

train_y
Out[134]: array([5, 0, 4, ..., 5, 6, 8], shape=(60000,), dtype=uint8)

 

import numpy as np
np.unique(train_y)

# 숫자 손글씨이기 때문에 0~9의 값을 가진다.

np.unique(train_y)
Out[136]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=uint8)

 

# 데이터 확인

import matplotlib.pyplot as plt

 

첫 번째 사진의 값을 확인해본다.

28 X 28 의 해상도를 가지고 있다.

train_x[0].shape

train_x[0].shape
Out[138]: (28, 28)

 

이미 RGB 변환이 완료되었기에 2차원의 형태로 출력된다.

train_x[0]

train_x[0]
Out[137]: 
array([[  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   3,
         18,  18,  18, 126, 136, 175,  26, 166, 255, 247, 127,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,  30,  36,  94, 154, 170,
        253, 253, 253, 253, 253, 225, 172, 253, 242, 195,  64,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,  49, 238, 253, 253, 253, 253,
        253, 253, 253, 253, 251,  93,  82,  82,  56,  39,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,  18, 219, 253, 253, 253, 253,
        253, 198, 182, 247, 241,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,  80, 156, 107, 253, 253,
        205,  11,   0,  43, 154,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,  14,   1, 154, 253,
         90,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, 139, 253,
        190,   2,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,  11, 190,
        253,  70,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,  35,
        241, 225, 160, 108,   1,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
         81, 240, 253, 253, 119,  25,   0,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,  45, 186, 253, 253, 150,  27,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0,  16,  93, 252, 253, 187,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0,   0,   0, 249, 253, 249,  64,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,  46, 130, 183, 253, 253, 207,   2,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,  39,
        148, 229, 253, 253, 253, 250, 182,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,  24, 114, 221,
        253, 253, 253, 253, 201,  78,   0,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,  23,  66, 213, 253, 253,
        253, 253, 198,  81,   2,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,  18, 171, 219, 253, 253, 253, 253,
        195,  80,   9,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,  55, 172, 226, 253, 253, 253, 253, 244, 133,
         11,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0, 136, 253, 253, 253, 212, 135, 132,  16,   0,
          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0]], dtype=uint8)

 

plt.imshow(train_x[0])

원본 데이터를 확인할 수 있다.

 

# 위 이미지의 실제 정답 찾기

train_y[0]

train_y[0]
Out[142]: np.uint8(5)

 

2) 데이터 변환

train_x.reshape(60000, -1) # 28 * 28 로 펼쳐짐 (flatten)

train_x.reshape(60000, -1)
Out[143]: 
array([[0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       ...,
       [0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0]], shape=(60000, 784), dtype=uint8)

 

train_x.reshape(60000, -1) / 255 # 0~255 => 0~1의 범위로 변환

MinMaxScaling과 같은 scaling 효과를 낸다.

 

train_x_sc = train_x.reshape(60000, -1) / 255
test_x_sc = test_x.reshape(10000, -1) / 255 # test는 1만건

 

3) 모델링

# (1) knn
from sklearn.neighbors import KNeighborsClassifier
m_knn = KNeighborsClassifier()
m_knn.fit(train_x_sc, train_y)
m_knn.score(train_x_sc, train_y)
m_knn.score(test_x_sc, test_y)

m_knn.score(train_x_sc, train_y)
Out[151]: 0.9819166666666667

m_knn.score(test_x_sc, test_y)
Out[152]: 0.9688

 

# (2) pca + knn
from sklearn.neighbors import KNeighborsClassifier
from sklearn.decomposition import PCA
from sklearn.pipeline import make_pipeline

# 스케일링은 아까 수행했기 때문에 생략한다.
pipe = make_pipeline(PCA(100), KNeighborsClassifier(5))
pipe.fit(train_x_sc, train_y)
pipe.score(train_x_sc, train_y)
pipe.score(test_x_sc, test_y)

pipe.score(train_x_sc, train_y)
Out[159]: 0.9840333333333333

pipe.score(test_x_sc, test_y)
Out[160]: 0.9727

knn만 사용하는 것보다 pca + knn 을 같이 수행한 경우 score가 조금 더 향상되었다.

 

# 4) 예측 실패한 데이터 확인
pre_te = pipe.predict(test_x_sc)

pre_te
Out[162]: array([7, 2, 1, ..., 4, 5, 6], shape=(10000,), dtype=uint8)


pre_te != test_y

pre_te != test_y
Out[163]: array([False, False, False, ..., False, False, False], shape=(10000,))

 

test_fail = test_x[pre_te != test_y, :, :]

 

# 10,000개 중 273개를 예측 실패하였음을 확인할 수 있다.

test_x[pre_te != test_y, :, :]
Out[165]: 
array([[[0, 0, 0, ..., 0, 0, 0],
        [0, 0, 0, ..., 0, 0, 0],
        [0, 0, 0, ..., 0, 0, 0],
        ...,
        [0, 0, 0, ..., 0, 0, 0],
        [0, 0, 0, ..., 0, 0, 0],
        [0, 0, 0, ..., 0, 0, 0]],

       [[0, 0, 0, ..., 0, 0, 0],
        [0, 0, 0, ..., 0, 0, 0],
        [0, 0, 0, ..., 0, 0, 0],
        ...,
        [0, 0, 0, ..., 0, 0, 0],
        [0, 0, 0, ..., 0, 0, 0],
        [0, 0, 0, ..., 0, 0, 0]],

       [[0, 0, 0, ..., 0, 0, 0],
        [0, 0, 0, ..., 0, 0, 0],
        [0, 0, 0, ..., 0, 0, 0],
        ...,
        [0, 0, 0, ..., 0, 0, 0],
        [0, 0, 0, ..., 0, 0, 0],
        [0, 0, 0, ..., 0, 0, 0]],

       ...,

       [[0, 0, 0, ..., 0, 0, 0],
        [0, 0, 0, ..., 0, 0, 0],
        [0, 0, 0, ..., 0, 0, 0],
        ...,
        [0, 0, 0, ..., 0, 0, 0],
        [0, 0, 0, ..., 0, 0, 0],
        [0, 0, 0, ..., 0, 0, 0]],

       [[0, 0, 0, ..., 0, 0, 0],
        [0, 0, 0, ..., 0, 0, 0],
        [0, 0, 0, ..., 0, 0, 0],
        ...,
        [0, 0, 0, ..., 0, 0, 0],
        [0, 0, 0, ..., 0, 0, 0],
        [0, 0, 0, ..., 0, 0, 0]],

       [[0, 0, 0, ..., 0, 0, 0],
        [0, 0, 0, ..., 0, 0, 0],
        [0, 0, 0, ..., 0, 0, 0],
        ...,
        [0, 0, 0, ..., 0, 0, 0],
        [0, 0, 0, ..., 0, 0, 0],
        [0, 0, 0, ..., 0, 0, 0]]], shape=(273, 28, 28), dtype=uint8)

 

y 역시 틀린 값만 추출해준다.

y_true_fail = test_y[pre_te != test_y]
y_pre_fail = pre_te[pre_te != test_y]

 

plt.imshow(test_fail[0])

 

y_true_fail[0]
y_pre_fail[0]

y_true_fail[0]
Out[171]: np.uint8(4)

y_pre_fail[0]
Out[172]: np.uint8(9)

 

실제값은 4, 예측값은 7로 예측실패하였음을 알 수 있다.

 

plt.imshow(test_fail[1])


y_true_fail[1]
y_pre_fail[1]

y_true_fail[1]
Out[174]: np.uint8(4)

y_pre_fail[1]
Out[175]: np.uint8(0)

 

# 여러 그래프를 동시에 시각화
fig, ax = plt.subplots(2,5)

# i는 y축 좌표
for i in range(0,10) :
    if i <= 4 :
        ax[0,i].imshow(test_fail[i])
        ax[0,i].set_title('true : %s, pre : %s' %(y_true_fail[i], y_pre_fail[i]))
    else : 
        ax[1,i-5].imshow(test_fail[i])
        ax[1,i-5].set_title('true : %s, pre : %s' %(y_true_fail[i], y_pre_fail[i]))

 


03 ANN 모델을 사용한 이미지 분석

 

# 1. 데이터 로딩(RGB로 변환된 데이터)
from tensorflow.keras.datasets import mnist
import numpy as np

(train_x, train_y), (test_x, test_y) = mnist.load_data()

# 2. 데이터 변환
train_x_sc = train_x.reshape(60000, -1) / 255          # 0~255 -> 0~1 범위로 변환(minmax 스케일링 효과)
test_x_sc  = test_x.reshape(10000, -1) / 255           # 0~255 -> 0~1 범위로 변환(minmax 스케일링 효과)

# 원핫 인코딩
import pandas as pd
train_y10 = pd.get_dummies(train_y, prefix='Y').astype(int).values
test_y10  = pd.get_dummies(test_y, prefix='Y').astype(int).values

# 3. 모델링
# 1) seed 고정
import numpy as np
import tensorflow as tf
seed = 0
np.random.seed(seed)
tf.random.set_seed(seed)

# 2) 모델 정의
from tensorflow.keras.layers import Input
from keras import Sequential
from keras.layers import Dense

model = Sequential()
model.add(Input(shape=(train_x_sc.shape[1], )))  
model.add(Dense(392, activation='relu'))
model.add(Dense(196, activation='relu'))
model.add(Dense(98, activation='relu'))
model.add(Dense(10, activation='softmax'))          

# 3) 오차함수, 최적화 결정
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])

# 4) early stopping rule
from tensorflow.keras.callbacks import EarlyStopping
es = EarlyStopping(monitor='val_loss', patience=10, restore_best_weights = True)         

# 5) 학습
# * 각 에포크마다 반복 수 = 60000 * 0.75 / 10 = 4500
hist = model.fit(train_x_sc, train_y10, validation_split=0.25,   
                 batch_size = 10, epochs=50000, callbacks=[es])          


# 6) 평가
model.evaluate(train_x_sc, train_y10)[1]     # 0.9829
model.evaluate(test_x_sc, test_y10)[1]       # 0.9763

acc = hist.history['accuracy']
plt.plot(range(1, len(acc)+1), hist.history['accuracy'], label = 'train')
plt.plot(range(1, len(acc)+1), hist.history['val_accuracy'], label = 'val')
plt.legend()
plt.xticks(range(1, len(acc)+1))


# 4. 예측 실패한 데이터 시각화
pre_te = model.predict(test_x_sc).argmax(axis=1)

test_fail = test_x[ pre_te != test_y, :, :]
y_true_fail = test_y[ pre_te != test_y]
y_pre_fail = pre_te[ pre_te != test_y]

# 시각화
fig, ax = plt.subplots(5,5)
for i in range(0,25) :                  # i는 y축 좌표
    row = i // 5                        # 5로 나눈 몫
    col = i % 5                         # 5로 나눈 나머지
    ax[row, col].imshow(test_fail[i])
    ax[row, col].set_title('true : %s, pre : %s' % (y_true_fail[i], y_pre_fail[i]))
    ax[row, col].axis('off')


04 CNN 모델을 사용한 이미지 분석

 컨볼루션 신경망 (CNN)은 입력된 이미지에서 다시 한 번 특징을 추출하기 위해 마스크 (필터, 윈도 또는 커널)을 도입하는 기법이다. (마스크가 가중치이다.)

 즉, 하나의 레이어가 필터를 입혀서 기존 이미지를 요약하는 역할을 하게 된다.

 

 마스크의 크기 및 개수는 개인이 설정하는 하이퍼파라미터로, 마스크의 크기 자체는 모델을 설계하는 사람이 선택한다.

 마스크의 크기가 작아질수록 처리 시간이 오래 걸린다.

 

 각 마스크가 공간의 패턴에 집중하며 돌아다니면서 가중치를 각 픽셀에 곱하고, 그러한 과정을 반복해서 새롭게 만들어진 층을 컨볼루션 (합성곱)이라고 부른다.

 

 이미지 내에서도 불필요한 특징이 있을 수 있다. 

 이미지 데이터셋 자체가 원체 크기 때문에 불필요한 특징을 제하고 의미있는 신호만 남기는 것을 풀링이라고 한다.

 이 때 풀링을 어떻게 지정하냐에 따라 달라지는데, 평균으로 요약해 도출하면 average pooling, 최소로 요약해 도출하면 min pooling, 최대로 요약해 도출하면 max pooling이라고 한다.

 (일반적으로 신호가 강할수록 의미가 있기 때문에 max pooling을 사용한다.)

 이 과정을 통해 불필요한 정보를 간추리게 된다.

 

1) 데이터 로딩(RGB로 변환된 데이터)
from tensorflow.keras.datasets import mnist
import numpy as np

(train_x, train_y), (test_x, test_y) = mnist.load_data()

 

# 데이터 확인
train_x.shape

train_x[0].shape
plt.imshow(train_x[0])

 

2) 변환

# 1) 설명변수 (스케일링, 차원 변경)

** Conv2D 층은 한 데이터의 입력을 (높이, 너비, 채널) 형태로 요구

(채널 : 흑백의 이미지인지 (1) 컬러의 이미지인지 (3)에 대한 차이)

 

** NN 모델은 스케일링에 매우 민감 -> 사전 스케일링이 필수적

 

train_x_sc = train_x / 255
test_x_sc = test_x / 255

train_x_sc = train_x_sc.reshape((60000, 28, 28, 1))
test_x_sc = test_x_sc.reshape((10000, 28, 28, 1))

 

# 2) 종속변수

train_y10 = pd.get_dummies(train_y).astype(int).values
test_y10 = pd.get_dummies(test_y).astype(int).values

 

# ** 참고 : 원핫 인코딩

# 1) pandas

pd.get_dummies(train_y).astype(int).values

 

# 2) keras

from tensorflow.keras.utils import to_categorical
to_categorical(train_y)

 

3) 모델링

# 1) seed 고정
import numpy as np
import tensorflow as tf
seed = 0
np.random.seed(seed)
tf.random.set_seed(seed)

# 2) 모델 정의
from tensorflow.keras.layers import Input
from keras import Sequential
from keras.layers import Dense, Conv2D, Dropout, Flatten, MaxPooling2D, Input

model = Sequential()
model.add(Input(shape=(28, 28, 1)))   # 한 데이터 기준 사이즈 전달
model.add(Conv2D(32, kernel_size = (2,2), activation='relu')) # 2*2 크기의 마스크를 32개 씌움
model.add(Conv2D(32, kernel_size = (2,2), activation='relu'))
model.add(MaxPooling2D(2,2))
model.add(Flatten())
model.add(Dense(128, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(10, activation='softmax'))

# 3) compile
model.compile('adam', loss='categorical_crossentropy', metrics=['accuracy'])

# 4) early stopping rule
from tensorflow.keras.callbacks import EarlyStopping
es = EarlyStopping(monitor='val_loss', patience=10, restore_best_weights = True)         
                                                                                                   
# 5) 학습
# * 각 에포크마다 반복 수 = 60000 * 0.75 / 10 = 4500
hist = model.fit(train_x_sc, train_y10, validation_split=0.25,   
                 batch_size = 10, epochs=50000, callbacks=[es])  

 

# 6) 평가
model.evaluate(train_x_sc, train_y10)[1]     # 0.9939
model.evaluate(test_x_sc, test_y10)[1]       # 0.9869

model.evaluate(train_x_sc, train_y10)[1]     # 0.9829
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 4s 2ms/step - accuracy: 0.9939 - loss: 0.0221        
Out[269]: 0.9938666820526123

model.evaluate(test_x_sc, test_y10)[1]       # 0.9763
313/313 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - accuracy: 0.9869 - loss: 0.0499     
Out[270]: 0.9868999719619751


acc = hist.history['accuracy']
plt.plot(range(1, len(acc)+1), hist.history['accuracy'], label = 'train')
plt.plot(range(1, len(acc)+1), hist.history['val_accuracy'], label = 'val')
plt.legend()
plt.xticks(range(1, len(acc)+1))

 

4) 예측 실패한 데이터 시각화
pre_te = model.predict(test_x_sc).argmax(axis=1)

test_fail = test_x[ pre_te != test_y, :, :]
y_true_fail = test_y[ pre_te != test_y]
y_pre_fail = pre_te[ pre_te != test_y]

# 시각화
fig, ax = plt.subplots(5,5)
for i in range(0,25) :                  # i는 y축 좌표
    row = i // 5                        # 5로 나눈 몫
    col = i % 5                         # 5로 나눈 나머지
    ax[row, col].imshow(test_fail[i])
    ax[row, col].set_title('true : %s, pre : %s' % (y_true_fail[i], y_pre_fail[i]))
    ax[row, col].axis('off')