Artificial Intelligence/Deep Learning

다양한 신경망 - 이미지 처리

DaTALK 2021. 12. 8.

1. 서론

* 이미지 처리 기술 예시

: 얼굴 인식 카메라, 화질 개선(Super Resolution), 이미지 자동 태깅 (사진 안의 물체를 분류해서 tagging)

 

* 이미지 처리를 위해 우리는 '데이터 전처리' -> '딥러닝 모델'의 과정을 거친다.

 

2. 이미지 데이터 전처리

* 컴퓨터에게 이미지는 각 pixel값을 가진 숫자 배열로 인식

(작은 단위의 정사각형으로 이루어짐 - pixel 안의 색상으로 숫자 표현

 

* 배열로 이루어짐 - 색상 숫자

 

* 모두 같은 크기를 갖는 이미지로 통일

- 가로, 세로 픽셀 사이즈를 표현하는 해상도 통일
(예를 들어 HD는 1280x720으로 1280 가로 pixel & 720 세로 pixel 수만큼 표현됨: 이런 해상도 방식을 모두 통일)

- 색을 표현하는 방식 통일 (RGB, HSV, Gray-scale, Binary, ....)

 

ex)
- MNIST 데이터: 사람의 손글씨를 이미지로 표현한 데이터

- 전처리 점검 1) 위 MNIST 데이터에서 보면 총 9개 데이터가 모두 28x28의 해상도로 표현됨

- 전처리 점검 2) 색상 스케일은 Gray-scale로 표현됨 (Gray-scale은 검정색, 흰색, 회색으로 표현하는 방식)

- 즉, 위 9개의 데이터는 모두 동일한 해상도 & 색상 스케일로 표현됨

 

- 위 원본 이미지(맨 오른쪽)의 경우 250 x 300으로 해상도가 맞지 않음 (가로와 세로가 동일한 해상도여야 함)
- 해상도를 28x28로 동일하게 표현

- Gray-scale 방식을 이용해서 색 표현을 변환한다

- 결과, MNIST 데이터와 비슷하게 표현 가능: 전처리 완료

 

3. [1] 실습 - MNIST 분류 CNN 모델: 데이터 전처리

** 손으로 쓴 0 ~9 까지의 글자들이 있고, 이 데이터를 이용해서 신경망을 학습시킨다. 학습된 결과가 손글씨를 인식할 수 있는 지 검증하자.

* MNIST 구성 - 색상은 Gray-scale & 크기는 28x28 & 각 사진별 label들이 정해져 있음

 

MNIST 데이터

 

 

[1] CNN을 위한 데이터 전처리

* CNN 모델은 채널(RGB 혹은 흑백 - 색상 고려)까지 고려한 3차원 데이터를 입력으로 받기에 채널 차원을 추가해 데이터의 모양(shape)을 바꿔야 함

* 즉, [데이터 수, 가로 길이, 세로 길이] -> (변환) [데이터 수, 가로 길이, 세로 길이, 채널 수]
(shape() 결과)

 

{1} import & MNIST dataset 불러오기 및 가공

* 내장함수 load_data()를 통해 test set & train set으로 나누어 불러온다.

* 불러온 train set & test set은 모두 tensor 형태로 저장되어 있다.

* 불러온 각각의 set을 train set의 경우 5000개, test set의 경우 10000개만 slicing한다.

 

import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt

mnist = tf.keras.datasets.mnist

(train_images, train_labels), (test_images, test_labels) = mnist.load_data()

train_images, train_labels = train_images[:5000], train_labels[:5000]
test_images, test_labels = test_images[:1000], test_labels[:1000]

 

* train_images와 test_images의 shape() 결과 각각 (5000, 28, 28) & (1000, 28, 28)이 나온다.

* train_labels & test_labels는 각각 레이블용 설명으로 달린 MNIST 데이터

 

{2} sample data 출력하기

1. 한 개의 MNIST 데이터 출력

 

plt.figure(figsize=(10, 10))
plt.imshow(train_images[0], cmap=plt.cm.binary)
plt.colorbar()
plt.title("Training Data Sample")
plt.savefig("sample1.png")

 

- 상단 코드 설명 - 

* imshow) train_images의 첫번째 예시 출력 (train_images[0]) & cmap 값으로 Gray-scale 방식 사용

(Gray-scale 방식은 우측의 색상 바의 색상 구성이 흰색 ~ 검은색 사이의 하단 색으로 구성 - 즉, 해당 구성된 색상으로만 픽셀에 표현된다는 뜻)

* colorbar() 우측의 색상 바 출력

* title 넣고 savefig 출력하여 사용자 지정 이름으로 저장

 

MNIST 예시 출력 결과

 

2. 9개의 샘플 데이터 출력

* class_names 따로 리스트 형태로 뽑아서 출력 결과에 label 값까지 표현

 

class_names = ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine']
for i in range(9):
    plt.subplot(3,3,i+1)
    plt.xticks([])
    plt.yticks([])
    plt.grid(False)
    plt.imshow(train_images[i], cmap=plt.cm.binary)
    plt.xlabel(class_names[train_labels[i]])

 

{2} CNN 모델의 input 형태로 변환하기 

* tf.expand_dims(data, axis) 함수 활용

= tensor 배열 데이터에서 해당 축 index에 차원 하나를 추가하는 함수

- axis가 0이라면 data[0], 즉 첫 번째 데이터에 차원 하나가 추가되고 그 뒤의 데이터가 밀림

- axis가 -1일 경우 맨 마지막에 차원 하나가 추가되는 형태

* (하단 실습 한정) CNN 모델의 입력으로 사용할 수 있도록 (샘플개수, 가로픽셀, 세로픽셀, 1) 형태로 변환하기 (채널값 = 1)

- 해당 실습의 경우 맨 마지막 인자로 channel 1값이 추가되므로 axis값으로는 -1을 입력받음

 

tf.expand_dims(data, axis)

train_images = # axis -1
test_images = # axis -1

 

- train_images_shape의 결과는 (5000, 28, 28, 1)

- test_images_shape의 결과는 (1000, 28, 28, 1)

 

4. 이미지 처리 - 딥러닝 모델

[1] 기존 다층 퍼셉트론 기반 신경망(MLP)의 이미지 처리 방식

 * 예를 들어 6x6 형태의 이미지를 MLP 신경망 모델의 input으로 집어넣을 경우 2차원이기에 이를 1차원 형태로 총 36가지의 데이터 배열 형태여야 하기에 극도로 많은 수의 파라미터가 필요하다

* 또한 2차원을 1차원으로 변환하였기에 2차원 형태의 기존 이미지 의미를 잃게 된다

* 추가로 이미지에 조금의 변화가 생기면 데이터의 관점에서는 해당 이미지의 의미를 쉽게 파악하기 어렵게 된다

 

-> 해결 방법은 '합성곱 신경망'

 

[2] 합성곱 신경망(Convolution Neural Network; CNN)

* 작은 필터를 순환시키는 방식

* 이미지의 패턴이 아닌 특징을 중점으로 인식

CNN 예) 귀, 코, 수염, 꼬리의 특징 위주로 판별

{1} 합성곱 신경망의 구조

* 입력 이미지의 특징을 추출 & 분류하는 과정으로 동작

- CNN(Convolution Layer + Pooling Layer) + FC(Fully-Connected Layer)
- Fully-Connected: 각 layer마다 node들이 서로 서로 full로 연결되어 있다는 뜻 (=Dense Layer)
(받은 layer들은 CNN을 통해 받은 각 특징별 layer 집합)

- Convolution Layer & Pooling Layer가 특징을 추출

(즉 CNN이 위의 예에서 고양이의 귀, 코, 수염, 꼬리라는 특징을 추출해 냄) 

- 그 이후 FC가 분류 과정을 수행

(분류 딥러닝 모델 포스팅은 아래 링크 참조 - FC의 역할)

 

{2} Convolutional Layer

 

* Convolution Layer - 이미지에서 어떠한 특징이 있는 지를 구하는 과정

- 필터가 이미지를 이동하며 새로운 이미지(피쳐맵)를 생성

- 구별해 낼 특징을 가진 필터를 입력 이미지 내에서 한 칸 씩 이동하며 해당 이미지가 있거나 비슷하면 큰 값으로 계산

- 그 결과 원하는 특징이 이미지에서 존재하는 지, 존재한다면 어디에 위치해 있는 지 확인 가능

 

* 특징별 수만가지의 필터 생성 가능 - 이에 따른 각각의 피쳐맵(feature map) 생성 가능

 

* Padding - 원본 이미지의 상하좌우에 한 줄씩 추가

- feature map의 경우 필터가 이동한 결과 원본 입력 이미지에 비해 한 칸씩 크기가 작게 나오기에, 그 크기를 맞추기 위해 패딩 사용

- 즉, 원본 이미지의 상하좌우에 0을 각각 추가

 

* Striding - 필터를 이동시키는 거리(stride) 설정

 

- 이렇게 padding & striding을 통해 feature map을 다양한 방식으로 만들 수 있음

 

{3} Pooling Layer

* 이미지 왜곡의 영향(노이즈)를 축소하는 과정 - feature map 축소

 

 

* 예를 들어 귀 특징 필터를 적용해서 feature map을 생성하였을 때, 원하는 귀가 있는 쪽의 데이터만 중요할 뿐, 그 외에 의미 없는 0의 숫자들은 필요 없음 - 이 때 pooling layer를 통해 필요부분만 남겨 강조하는 방식 (정보 압축)

* pooling layer를 통해 알고자 하는 필요 부분은 해당 특징의 유무 & 해당 특징이 존재한다면 그의 위치 정보

 

* Average pooling보다는 사실상 Max pooling을 주로 많이 사용

 

{4} Fully-Connected Layer

* 추출된 특징을 사용하여 이미지를 분류

* pooling layer의 결과로 생성된 각 특징 필터의 결괏값이 1차원 형태의 배열로 각 필터들이 쭉 나열되어 생성됨 (flatten layer 활용하면)

- 생성된 해당 1차원 형태의 배열을 통해 분류가 가능

 

* Softmax 활성화 함수

- 단순이 한 개의 label이 아닌 지, 맞는 지의 경우 앞서 언급된 계단함수를 사용하면 되지만,

- 여러 개의 label을 분류하고 싶다면 softmax 함수를 사용

- 즉, 마지막 FC layer의 unit 개수는 예측해야 하는 범주 label의 개수에 해당

 

 

- softmax함수의 출력값은 (위 a ~f) 각각 각 label이 나올 확률이 됨

- 즉 a~f는 모두 확률값이며 (상단 예), a+b+c+d+e+f = 1, a,b,c,d,e,f >=0이 됨

- 즉, 결과물인 여러 확률을 봤을 때 가장 큰 값을 가진 확률에 해당하는 label이 우리가 원하는 이미지의 label이라고 결론을 내릴 수 있다.

 

{5} 정리

* 합성곱을 통해 특징 추출 - 풀링을 통해 사이즈 조절 & 노이즈 처리 - FC layer의 활성함수를 통해 분류가 가능

 

* Convolution Layer는 특징을 찾아내고

* Pooling Layer는 처리할 맵(이미지) 크기를 줄여준다. 이를 N번 반복

- 합성곱 & 풀링 과정은 N번 반복

- 즉, 예를 들어 고양이의 '귀' 특징 필터를 만들어 내었지만, 귀의 더 자세한 여러 특징 필터를 생성하여 더 자세하게 특징을 분리해 낸다

- 필터의 개수가 많아져도 풀링의 과정을 통해 도중도중 지속적으로 사이즈를 축소시키기에 용량 문제는 해당 없음

 

* 반복할 때마다 줄어든 영역에서의 특징을 찾게 되고, 영역의 크기를 작아졌기 때문에 빠른 학습이 가능해진다.

 

 

* Object detection & segmentation

- 그림 안의 물체를 판단하고 어떤 종류인지 판단하는 기술

* Super Resolution (SR)

- 해상도가 낮은 이미지를 높이는 기술 역시 CNN 기반의 딥러닝으로 실현 가능

 

5. [2] 실습 - MNIST 분류 CNN 모델: 모델 구현

* 위 [1] 실습에 이어서 진행 (이미지 데이터 전처리 완료 가정)

 

* 모델 설정

- keras를 활용한다

- 분류 모델에 맞게 마지막 layer의 node 수는 10개 (MNIST데이터 숫자 총 0 ~9 까지 구성) & activation 함수는 'softmax'로 설정

 

[1] CNN - Convolution layer 만들기

: 입력 이미지의 특징, 즉 처리할 feature map을 추출하는 layer

- filters: 필터(커널) 개수

- kernel_size: 필터(커널)의 크기

- activation: 활성화 함수 종류

- padding: 이미지가 필터를 거칠 때 그 크기가 줄어드는 것을 방지하기 위해서 가장자리에 0의 값을 가지는 픽셀을 넣을 지 말 지를 결정하는 변수 ('SAME'은 넣는다는 뜻 / 'VALID'는 넣지 않는다는 뜻)

 

tf.keras.layers.Conv2D(filters, kernel_size, activation, padding)

 

[2] CNN - Pooling layer 만들기 - Maxpool layer

: 처리할 feature map의 크기를 줄여주는 layer

- padding: 'SAME' (적용) / 'VALID' (미적용)

 

tf.keras.layers.MaxPool2D(padding)

 

[3] Flatten layer 만들기

: convolution layer 또는 pooling layer의 결과는 N차원의 tensor 형태이다. 따라서 이 결과를 1차원의 형태로 평평하게 만들어 준다.

- 앞선 모든 convolution & pooling layer를 거친 다음 마지막에 삽입

 

tf.keras.layers.Flatten()

 

[4] Dense layer 만들기

: 분류 과정 수행

- node: 노드(뉴런) 개수

- activation: 활성화 함수 종류

 

tf.keras.layers.Dense(node, activation)

 

[5] 실습 코드

{1} 모델 설정

- 첫 번째 layer는 32개의 filter, 크기는 (3,3) 적용 & 활성화 함수는 ReLU 적용

(첫 번째 layer에는 앞선 실습에서 이미지 데이터 전처리한 결과 형태 (28,28,1)을 input_shape 인자로 꼭 집어넣는다.) (가로픽셀, 세로픽셀, 채널값)

- 총 3개의 layer (각 layer 당 padding 적용)

- flatten layer 적용) 만들어진 특징들을 1차원 형태의 vector로 변환

- Dense layer 적용) 총 두 종류 relu & softmax 각각 설정

 

model = tf.keras.Sequential([
    tf.keras.layers.Conv2D(filters = 32, kernel_size = (3,3), activation = 'relu', padding = 'SAME', input_shape = (28,28,1)),
    tf.keras.layers.MaxPool2D(padding = 'SAME'),
    tf.keras.layers.Conv2D(filters = 32, kernel_size = (3,3), activation = 'relu', padding = 'SAME'),
    tf.keras.layers.MaxPool2D(padding = 'SAME'),
    tf.keras.layers.Conv2D(filters = 32, kernel_size = (3,3), activation = 'relu', padding = 'SAME'),
    tf.keras.layers.MaxPool2D(padding = 'SAME'),
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(64, activation = 'relu'),
    # tf.keras.layers.Dense(, activation = '') - 10 nodes with 'softmax' activation function
])

 

{2} 모델의 학습방법 설정 &  학습

* 해당 실습 모델은 분류를 통한 학습을 수행하므로 분류 관련 CNN 모델 학습방법을 설정한다

* 분류 학습에서는 loss 함수로 sparse_categorical_crossentropy 주로 사용

* adam optimizer를 사용하며 매 epoch마다 accuracy 정확도를 평가하는 모델을 설정한다.

 

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

 

* keras fit 함수에서 batch_size 인자까지 epochs와 함께 동시에 설정 가능

 

history = model.fit(train_images, train_labels, epochs = 20, batch_size = 512)

 

{3} 모델 학습 결과 시각화

* Visualize 함수 생성 

 

def Visulaize(histories, key='loss'):
    for name, history in histories:
        plt.plot(history.epoch, history.history[key], 
             label=name.title()+' Train')

    plt.xlabel('Epochs')
    plt.ylabel(key.replace('_',' ').title())
    plt.legend()
    plt.xlim([0,max(history.epoch)])    
    plt.savefig("plot.png")

 

* 학습 결과 시각화 - Visualize 함수 사용

 

Visulaize([('CNN', history)], 'loss')

 

* 시각화 결과 (plot.png) 및 정확도 결과

- loss & accuracy의 수치 결과 CNN 모델이 잘 작동되었음이 확인 가능하다

 

 

- epoch가 진행되면서 loss 값이 감소 - 모델이 잘 작동되었음을 확인 가능

 

{4} 모델 구조 review

 

print(model.summary())

 

 

* conv2d: shape (None, 28, 28, 32)

- input shape으로 넣은 28x28 (가로픽셀x세로픽셀) + 32개의 filter 개수

 

* max_pooling2d: shape (None, 14, 14, 32)

- pooling의 결과 size가 각각 반으로 줄어듦

 

* conv + max_pooling을 총 두 번 더 반복 (two layers 추가)

- max_pooling2d_2까지의 결과 (None, 4, 4, 32)

 

* flatten: shape (None, 512)

- pooling까지 모두 마친 결과 가로 pixel 4 x 세로 pixel 4 x 필터 개수 32 = 512

 

* Dense

- 총 두 개의 Dense layer를 통해 각각 64개의 node & 10개의 node 생성

 

* 모델 내에는 총 52,298개의 파라미터

 

6. [3] 실습 - MNIST 분류 CNN 모델: 모델 평가 및 예측

[1] 모델 평가

* evaluate 함수 사용

* 학습된 모델을 바탕으로 입력한 feature data X & label Y의 lossr값과 metrics값을 출력한다 - 평가이기에 당연히 test data 입력

 

model.evaluate(X, Y)

 

[2] 모델 예측

* predict_classes 함수 사용

- 해당되는 class의 softmax 값들 중 제일 큰 확률을 갖는 값을 label로 선정하여 뽑아 list 형태로 정렬되어 출력
(X데이터의 예측 label 출력)

- 즉, softmax의 값들에서 제일 큰 값을 골라 label로 선정하는 모든 과정을 predict_classes 함수가 맡는다

 

model.predict_classes(X)

 

[3] 실습 코드

{1} 모델 평가 및 예측

* 복잡성을 줄이기 위해 위 모델의 epochs 값을 10으로 줄이고, batch_size를 128로 줄임

 

loss, test_acc = model.evaluate(test_images, test_labels, verbose = 0)

predictions = model.predict_classes(test_images)

 

- 예측 결과를 predictions에 저장

 

{2} 결과 출력 (+ layer별 인식 class 형태 출력)

 

* 예측 결과를 출력한다 (test loss & test accuracy 및 예측한 test data가 어디 class에 속하는 지 출력)

 

print('\nTest Loss : {:.4f} | Test Accuracy : {}'.format(loss, test_acc))
print('예측한 Test Data 클래스 : ',predictions[:10])

 

 

* 평가용 데이터에 대한 각 layer 결과를 시각화한다.

 

def Plotter(test_images, model):

    img_tensor = test_images[0]
    img_tensor = np.expand_dims(img_tensor, axis=0) 
    
    layer_outputs = [layer.output for layer in model.layers[:6]]
    activation_model = models.Model(inputs=model.input, outputs=layer_outputs)

    activations = activation_model.predict(img_tensor)
    
    layer_names = []
    for layer in model.layers[:6]:
        layer_names.append(layer.name)
    
    images_per_row = 16

    for layer_name, layer_activation in zip(layer_names, activations):
        n_features = layer_activation.shape[-1]
    
        size = layer_activation.shape[1]
    
        n_cols = n_features // images_per_row
        display_grid = np.zeros((size * n_cols, images_per_row * size))
    
        for col in range(n_cols):
            for row in range(images_per_row):
                channel_image = layer_activation[0, :, :, col * images_per_row + row]
            
                channel_image -= channel_image.mean() 
                channel_image /= channel_image.std()
                channel_image *= 64
                channel_image += 128
                channel_image = np.clip(channel_image, 0, 255.).astype('uint8')
            
                display_grid[col * size : (col+1) * size, row * size : (row+1) * size] = channel_image
            
        scale = 1. / size
        print('레이어 이름: ', layer_name)
        plt.figure(figsize=(scale * display_grid.shape[1], scale * display_grid.shape[0]))
        plt.grid(False)
        plt.imshow(display_grid, aspect='auto', cmap='viridis')
        plt.savefig("plot.png")
        elice_utils.send_image("plot.png")
        
    plt.show()

 

Plotter(test_images, model)

 

* layer별 출력 그림 결과)

 

- 첫 layer conv2d 결과

 

 

- 마지막 layer max_pooling2d_2 결과

 

 

- 해석: 첫 층에서 마지막 층으로 가면서 단순한 데이터로 정리가 됨. 마지막 층에서는 찾고자 하는 feature가 있는 지의 여부 + 있다면 어느 위치에 있는 지 위치 정보들이 담겨 있음. 여러 layer를 거치기에 pixel size가 많이 줄어든 것을 확인할 수 있다.

 

 

 

 

 

- 출처 - 2021 NIPA/AI 기본/응용 교육과정

'Artificial Intelligence > Deep Learning' 카테고리의 다른 글

다양한 신경망 - 자연어 처리  (0) 2021.12.11
텐서플로우(TensorFlow) (+Keras)  (0) 2021.12.07
퍼셉트론 (perceptron)  (0) 2021.12.06

댓글