신경망의 성능을 향상시키는 방법에는 상당히 많은 방법이 존재합니다. 도메인이나 모델에 따라서도 다양하게 존재합니다.

하지만 공통적으로 모델을 구성하기 전에 가장 중요한 방법이 있습니다.

바로 Data augmentation 작업입니다.

데이터 증식은 flip, shear, translation, rotation 등의 방법을 제공하여 train set을 부풀리게 해주고, 우리가 구성한 모델을 더욱 견고하게 만들어주는 역할을 합니다. (모델이 한번도 보지 않은 이미지를 계속해서 보게함으로써 최종적으로 general한 모델로 만들어지니까요)

car image augmentation - 이미지 출처 캐글 코리아 3차대회

이는 Train에서의 augmentation 방법이며, test에서의 augmentation 방법이 따로 있습니다. 그리고 이것을 Test Time Augmentation(TTA)라고 부릅니다.


TTA는 증강된 이미지를 여러번 보여준 다음 각각의 단계에 대해서 prediction을 평균하고 이 결과를 최종값으로 사용하는 것입니다.

이 방법을 사용하면 가장 좋은 점은 1번만 예측하여 결과를 도출했을 시에 해당 차량의 클래스가 아니라고 추측하는 반면, TTA를 통해 도출된 최종 결과값은 올바른 차량의 클래스라고 맞춘다는 것입니다. 

이미 앙상블을 경험해 보셨다면(Xgboost + RF + adaboost etc.), 이 말이 무슨 뜻인지 이해하기 쉽습니다.

가장 쉽게 말해서 prediction ensemble이라고 생각하면 편합니다. 

keras에서 사용하기는 더욱 쉽습니다. 단 몇줄의 코드로 끝나게 됩니다. 

tta_steps = 10
predictions = []

for i in tqdm(range(tta_steps)):
	# generator 초기화
    test_generator.reset()
    
    preds = model.predict_generator(generator = test_generator, steps = len(test_set) // batch_size, verbose = 1)
    predictions.append(preds)

# 평균을 통한 final prediction
pred = np.mean(predictions, axis=0)

# argmax for submission
np.mean(np.equal(np.argmax(y_val, axis=-1), np.argmax(pred, axis=-1)))

+ tqdm은 for-loop의 상태진행바를 나타내주는 library입니다(pip install tqdm)

Reference

https://towardsdatascience.com/test-time-augmentation-tta-and-how-to-perform-it-with-keras-4ac19b67fb4d
https://www.kaggle.com/c/2019-3rd-ml-month-with-kakr

'# Machine Learning > Keras Implementation' 카테고리의 다른 글

Keras Custom Activation 사용해보기  (0) 2019.10.27
keras Custom generator - 1  (1) 2019.07.29
Keras callback함수 쓰기  (0) 2018.12.23
Keras ImageDataGenerator flow 사용  (0) 2018.12.20
CNN visualization code (3)  (0) 2018.11.26

모델이 epoch를 도는 순간에는 model.save를 사용할 순 없습니다.

이럴때 사용할 수 있게 callback이라는 기능을 제공을 해주었습니다. 

정한 epoch 또는 1 epoch이 끝날때마다 이 함수를 불러서 정해놓은 기능을 호출하는데요.

이 글에서는 모델 저장하는 법을 알려드리겠습니다.


1. from keras.callbacks import ModelCheckpoint


2.

다른 param은 Keras.io를 참고하세요. weight만 저장하는 기능도 있어 보입니다.

https://keras.io/callbacks/#modelcheckpoint


3.

이런식으로 callbacks에 세팅을 해주시면 자동으로 정해놓은 path에 저장을 해줍니다.


4.


* Flow를 사용하지 않고, fit_generator에서는 generator만 호출하면 됩니다. Flow를 쓰고싶은 경우에 이렇게 쓰면 된다는 것을 보여주는 글입니다.

 

우리가 만든 모델이 오버피팅되어 있다면, 해결방법 중 하나는 훈련셋의 데이터 양을 늘리는 것입니다. 

케라스에서는 이에 대하여 ImageDataGenerator로 간편한 data augmentation 기능을 제공해주고 있죠.

만약 우리가 디렉토리에 클래스별로 예쁘게 구분을 잘 지어놨다면 flow_from_directory 를 사용하면 되겠지만, 현실은 그렇지 않은 경우가 많은데요

이럴때는 그냥 flow 를 사용하시면 됩니다.

위의 사진처럼 간단하게 사용하시면 됩니다.

중간에 있는 datagen.fit은 featurewise기능이나 ZCA_whitening을 사용할 때 해주어야 되는 기능인데요. 이 두개의 기능을 사용하지 않는다면 굳이 쓸 필요는 없습니다.

ZCA 화이트닝은 다음과 같습니다.

 

이렇게 ImageDataGenerator 객체를 만들어둔 뒤, model.fit_generator내부에서 사용을 해주시면 됩니다.

 

Reference

 https://keras.io

 

'# Machine Learning > Keras Implementation' 카테고리의 다른 글

keras Custom generator - 1  (1) 2019.07.29
TTA(test time augmentation) with 케라스  (2) 2019.07.01
Keras callback함수 쓰기  (0) 2018.12.23
CNN visualization code (3)  (0) 2018.11.26
CNN visualization code (2)  (0) 2018.11.26

마지막으로는 원본 이미지에 hitmap을 그리는 방법입니다.

- 예제에 쓰일 모델 로드

from tensorflow.python.keras.applications.vgg16 import VGG16

model = VGG16(weights = 'imagenet')

 

- VGG16을 위한 이미지 전처리

from tensorflow.python.keras.preprocessing import image
from tensorflow.python.keras.applications.vgg16 import preprocess_input, decode_predictions
import numpy as np

img_path = './datasets/creative_commons_elephant.jpg'
img = image.load_img(img_path, target_size = (224, 224))
x = image.img_to_array(img)
x = np.expand_dims(x, axis= 0) # (1, 224, 224, 3)
x = preprocess_input(x) # 데이터 전처리  - Imagenet의 전처리는 조금 다르다.

 

이를 통해, 

decode_predictions 함수는 ImageNet 데이터셋에 대한 top 매개변수 만큼의 최상위 항목을 반환해 줍니다.

다음은 이미지에서 가장 아프리카 코끼리 같은 부위를 시각화하기 위해 Grad_CAM 처리 과정을 구현한 것입니다.

import tensorflow as tf

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())


    # 예측 벡터의 '아프리카 코끼리' 항목
    african_elephant_output = model.output[:, 386]
    
    # VGG16의 마지막 합성곱 층인 block5_conv3 층의 특성 맵
    last_conv_layer = model.get_layer('block5_conv3')

    # block5_conv3의 특성 맵 출력에 대한 '아프리카 코끼리' 클래스의 그래디언트
    grads = K.gradients(african_elephant_output, last_conv_layer.output)[0]

    # 특성 맵 채널별 그래디언트 평균 값이 담긴 (512,) 크기의 벡터
    pooled_grads = K.mean(grads, axis=(0, 1, 2))

    # 샘플 이미지가 주어졌을 때 방금 전 정의한 pooled_grads와 block5_conv3의 특성 맵 출력을 구합니다
    iterate = K.function([model.input], [pooled_grads, last_conv_layer.output[0]])

    # 두 마리 코끼리가 있는 샘플 이미지를 주입하고 두 개의 넘파이 배열을 얻습니다
    pooled_grads_value, conv_layer_output_value = iterate([x])

    # "아프리카 코끼리" 클래스에 대한 "채널의 중요도"를 특성 맵 배열의 채널에 곱합니다
    for i in range(512):
        conv_layer_output_value[:, :, i] *= pooled_grads_value[i]

    # 만들어진 특성 맵에서 채널 축을 따라 평균한 값이 클래스 활성화의 히트맵입니다
    heatmap = np.mean(conv_layer_output_value, axis=-1)

 

heatmap을 얻은 뒤, 정규화를 진행하고 

다음과 같은 사진을 얻었습니다.

OpenCv를 활용하여 이미지에 덧붙이면

import cv2

img = cv2.imread(img_path)
# heatmap을 원본 이미지 크기에 맞게 변경
heatmap = cv2.resize(heatmap, (img.shape[1], img.shape[0]))
# heatmap을 RGB 포맷으로 변환
heatmap = np.uint8(255 * heatmap)
# 히트맵으로 변환
heatmap = cv2.applyColorMap(heatmap, cv2.COLORMAP_JET)
# 0.4는 히트맵의 강도
superimposed_img = heatmap * 0.4 + img

cv2.imwrite('./datasets/elephant_cam.jpg', superimposed_img)

 

다음과 같은 히트맵을 얻을 수 있습니다.

( 결과가 다를 수 있습니다 )

 

Reference

본 코드의 내용은 '케라스 창시자에게 배우는 딥러닝' 교재의 github를 참조 하였습니다. -- 

https://github.com/fchollet/deep-learning-with-python-notebooks

두번째로는 필터가 반응하는 시각적 패턴을 그리는 것인데요.

경사 상승법을 적용하여 이미지의 필터가 무엇을 인식하고 있는지 보는 것 입니다.

 

- 필터 시각화를 위한 손실 텐서 정의

from keras.applications import VGG16
from keras import backend as K

model = VGG16(weights = 'imagenet', 
             include_top = False)

layer_name = 'block3_conv1'
filter_index = 0

layer_output = model.get_layer(layer_name).output
loss = K.mean(layer_output[:, :, :, filter_index])

# 입력에 대한 손실의 그래디언트 구하기
grads = K.gradients(loss, model.input)[0]

# 그래디언트 정규화 ( 경사 상승법 과정을 부드럽게 하기 위하여 사용 )
grads /= (K.sqrt(K.mean(K.square(grads))) + 1e-5) # 1e-5는 0 나눗셈 방지, 이를 gradient-clipping 이라고 함.

# 손실값과 그래디언트 값을 얻기 위함.
# 주어진 입력 이미지에 대해 손실 텐서와 그래디언트 텐서를 계산한다. 
iterate = K.function([model.input], [loss, grads])

import numpy as np
loss_value, grads_value = iterate([np.zeros((1, 150, 150, 3))])

# 확률적 경사 상승법을 사용한 손실 최대화하기
input_img_data = np.random.random((1, 150, 150, 3)) * 20 + 128.

step = 1.
for i in range(40):
    loss_value, grads_value = iterate([input_img_data])
    input_img_data += grads_value * step 

 

지금 계속 신경망의 필터에 경사상승법을 적용하여 어떤 패턴에 반응을 하는지 보기위한 작업을 하고 있습니다. 

다음은 텐서를 이미지 형태로 변환하기 위한 util function입니다.

def deprocess_image(x):
    x -= x.mean()
    x /= (x.std() + 1e-5)
    x *= 0.1
    
    x += 0.5
    x = np.clip(x, 0, 1)
    
    x *= 255
    x = np.clip(x, 0, 255).astype('uint8')
    return x

다음 함수는 필터 활성화를 최대화하는 패턴을 이미지 텐서로 출력합니다.

또한 위의 모든 함수를 사용하여 패턴을 출력해봅시다.

def generate_pattern(layer_name, filter_index, size = 150):
    # 주어진 층과 필터의 활성화를 최대화 하기위한 loss 정의
    layer_output = model.get_layer(layer_name).output
    loss = K.mean(layer_output[:, :, :, filter_index])
    
    grads = K.gradients(loss, model.input)[0]
    
    # grad clipping
    grads /= (K.sqrt(K.mean(K.square(grads))) + 1e-5)
    
    # 입력 이미지에 대한 손실과 그래디언트 반환
    iterate = K.function([model.input], [loss, grads])
    # 회색이미지로 시작
    input_img_data = np.random.random((1, size, size, 3)) * 20 + 128.
    
    step = 1.
    for i in range(40):
        loss_value, grads_value = iterate([input_img_data])
        input_img_data += grads_value * step
        
    img = input_img_data[0]
    return deprocess_image(img)

위 함수들을 이용하여 다른 층도 살펴봅시다.

layer_name = 'block1_conv1'
size = 64
margin = 5

# 결과를 담은 빈 검은 이미지
results = np.zeros((8 * size + 7 * margin , 8 * size + 7 * margin, 3), dtype='uint8')

for i in range(8):
    for j in range(8):
        # layer_name에 있는 i + (j*8) 번째 필터에 대한 패턴 생성
        filter_img = generate_pattern(layer_name, i + (j * 8), size = size)
        
        horizontal_start = i * size + i * margin
        horizontal_end = horizontal_start + size
        vertical_start = j * size + j * margin
        vertical_end = vertical_start + size
        results[horizontal_start:horizontal_end, vertical_start: vertical_end, :] = filter_img
        
plt.figure(figsize=(20, 20))
plt.imshow(results)

다음과 같은 패턴을 통해 여러분의 모델이 어떤 패턴을 인식하는지 알아 볼 수 있겠네요. (이미지는 일부분만 캡쳐)

 

 

Reference

본 코드의 내용은 '케라스 창시자에게 배우는 딥러닝' 교재의 github를 참조 하였습니다. -- 

https://github.com/fchollet/deep-learning-with-python-notebooks