Machine Learning/[Kaggle Course] ML (+ 딥러닝, 컴퓨터비전)

The Sliding Window

WakaraNai 2021. 4. 10. 07:29
728x90
반응형

하나의 이미지로부터 특징을 추출하는 3가지 연산에 대해서 배워보았습니다.

  1. filter with a convolution layer
  2. detect with ReLU activation
  3. condense with a maximum pooling layer

convolution과 pooling 연산은 같은 특징을 공유합니다. 둘 다 sliding window 위에서 수행하기 때문입니다.

convolution에서는 이 window는 커널의 차원인 kernel_size 매개변수에 의해 결정됩니다.

pooling에서는 pool_size에 의해 pooling window가 결정됩니다.

 

 

convolution과 pooling 레이어 모두에게 영향을 주는 두 개의 매개변수가 있습니다.

window의 strides 매개변수는 각 단계마다 window가 얼만큼 움직여야 하는지 결정합니다. 이미지의 가장자리에 사용하는 padding 매개변수는 입력값의 가장자리에 위치한 픽셀들을 어떻게 다룰지 설명하는 곳입니다.

두 매개변수를 이용해 두 개의 레이어를 정의해봅시다

 

from tensorflow import keras
from tensorflow.keras import layers

model = keras.Sequential([
    layers.Conv2D(filters=64,
                  kernel_size=3,
                  strides=1,
                  padding='same',
                  activation='relu'),
    layers.MaxPool2D(pool_size=2,
                     strides=1,
                     padding='same')
    # More layers follow
])

 

Stride

window가 각 스텝마다 움직인 거리를 stride라고 부릅니다.

왼쪽에서 오른쪽으로 움직일 때의 거리와, 위에서 아래로 움직일 때의 거리를 지정합니다.

오른쪽의 그림은 strides=(2, 2)를 나타냈으며, 단계마다 2 픽셀 씩 움직입니다.

 

분류에 사용하기 좋은 특징을 원하기 때문에 convolutional layer는 대부분 strides=(1,1)로 설정합니다. 

stride가 증가하면, 의미있는 정보를 놓칠 수 있습니다.

그러나 Maximum pooling layers는 항상 1보다 큰 strides를 사용합니다. 예로 (2,2) (3,3)이죠. window보다 크게 사용하진 않습니다.

 

왼쪽에서 오른쪽으로, 위에서 아래로 움직이는 간격이 서로 같은 경우, 숫자를 하나만 적어도 됩니다.

strides=(2,2) 는 strides=2라고 적어도 됩니다.

 

Padding

sliding window 계산 중에는 입력값의 가장자리에서는 어떻게 해야할지 질문이 생깁니다.

입력 이미지 내부에 완전히 머무르는 것은 입력의 다른 모든 픽셀에서처럼 window가 이러한 경계에 놓인 픽셀들 위에 정사각형으로 배치되지 않음을 의미합니다.

모든 픽셀을 동일한 방식으로 다룰 수 없기에 문제가 발생합니다.

 

convolution이 경계값에서 취하는 방식을 padding 매개변수로 정합니다.

tensorflow에서 2가지 방안이 있습니다. padding='same'과 padding='valid'입니다.

이 둘은 trade-off 관계입니다. 한 쪽이 좋아지면 다른 쪽은 나빠질 수밖에 없는 관계죠.

 

padding='valid'으로 설정하면, convolution window는 무조건 입력값 이미지 속에서만 머무르려 합니다.

단점은 픽셀이 손실되어 출력이 작아지고 커널이 클수록 더 작아진다는 것입니다.

이는 특히 입력 크기가 작은 경우 네트워크에 포함 가능한 레이어의 수를 제한합니다.

대안으로 padding='same'을 사용하는 것입니다. 경계값 주위에 0을 놓는 것이죠(pad)

그럼 출력 크기는 입력 크기와 동일해질만큼 충분히 0을 놓습니다.

단점은 경계에 위치한 픽셀의 정보가 새로 놓은 0값과 섞이는 효과를 낳습니다.

오른쪽 그림은 이 방식에 대한 설명 그림입니다.

 

VGG모델은모든 convolution에 same padding 방식을 씁니다.

대부분의 현대 convnets은 same 외에 또다른 매개변수와 함께 조합하여 사용합니다.

 

 

Example

sliding window의 효과에 대해 이해하기 위해, 저해상도 이미지에서 특징을 추출하는 과정을 관찰해봅시다.

그럼 각각의 픽셀에 대해 어떻게 변하는지 확인이 가능하빈다.

 

일단 이미지와 커널을 생성해봅시다.

import tensorflow as tf
import matplotlib.pyplot as plt

plt.rc('figure', autolayout=True)
plt.rc('axes', labelweight='bold', labelsize='large',
       titleweight='bold', titlesize=18, titlepad=10)
plt.rc('image', cmap='magma')

image = circle([64, 64], val=1.0, r_shrink=3)
image = tf.reshape(image, [*image.shape, 1])
# Bottom sobel
kernel = tf.constant(
    [[-1, -2, -1],
     [0, 0, 0],
     [1, 2, 1]],
)

show_kernel(kernel)

 

 

VGG는 매우 간단합니다. stride가 1인 convolution과, stride가 2이며 window가 2x2인 maximum pooling을 사용합니다.

여기에 visiontools utility script 속 함수를 사용하여 각 단계를 관찰해보겠습니다.

show_extraction(
	image, kernel,

    # Window parameters
    conv_stride=1,
    pool_size=2,
    pool_stride=2,

    subplot_shape=(1, 4),
    figsize=(14, 6),
)

 

입력(input)과 나머지 3개의 이미지가 서로 비슷한 것으로 보아 잘 작동됩니다. 

커널은 수평선을 감지하기 위해 고안되었기에 출력된 feature map에서 입력 사진의 수평 부분이 가장 많이 활성화 된것을 볼 수 있습니다.

 

 

만약 convolution의 stride를 3으로 바꾸면 어떻게 될까요?

show_extraction(
    image, kernel,

    # Window parameters
    conv_stride=3,
    pool_size=2,
    pool_stride=2,

    subplot_shape=(1, 4),
    figsize=(14, 6),    
)

이렇게 하니 feature extraction의 품질이 떨어졌습니다.

입력 사진은 1픽셀 단위로 그림을 그려 원을 완성했습니다. 이는 매우 정교하고 세부적인 그림이죠.

stride가 3인 convolution은 이를 감지하기에 역부족이었기에 좋은 feature map을 생성할 수 없습니다.

 

때때로 모델은 초기 레이어 속에서 더 큰 stride를 가진 convolution을 사용합니다.

이는 일반적으로 더 큰 커널과 결합됩니다.

예를 들어, 첫번째 레이어에서 2 stride를 가진 RestNet50 모델은 7x7 커널을 사용합니다.

이는 입력에서 너무 많은 정보를 희생하지 않고 대규모의 feature를 생산하는 속도를 가속화해줍니다.

 

 


Exercise

feature extractoin을 위해 convnet이 사용하는 연산을 둘러보고

어떻게 convnet이 대규모의 시각적 특징을 layer를 쌓는 과정(stacking layer)를 통하여 찾아내는지 배울 겁니다.

마지막으로 convolution으로 일차원 데이터인 time series를 어떻게 다루는지도 볼 것입니다.

 

# Setup feedback system
from learntools.core import binder
binder.bind(globals())
from learntools.computer_vision.ex4 import *

import tensorflow as tf
import matplotlib.pyplot as plt
import learntools.computer_vision.visiontools as visiontools


plt.rc('figure', autolayout=True)
plt.rc('axes', labelweight='bold', labelsize='large',
       titleweight='bold', titlesize=18, titlepad=10)
plt.rc('image', cmap='magma')

 

 

(Optional) Experimenting with Feature Extraction

sliding window의 계산 과정을 둘러보고 여기에 사용되는 매개변수가 feature extraction에 어떤 영향을 주는 볼 것입니다. 옳고 그른 정답이 존재하지 않으니 그저 실험해보세요.

 

이미지와 커널을 불러옵시다

from learntools.computer_vision.visiontools import edge, blur, bottom_sobel, emboss, sharpen, circle

image_dir = '../input/computer-vision-resources/'
circle_64 = tf.expand_dims(circle([64, 64], val=1.0, r_shrink=4), axis=-1)
kaggle_k = visiontools.read_image(image_dir + str('k.jpg'), channels=1)
car = visiontools.read_image(image_dir + str('car_illus.jpg'), channels=1)
car = tf.image.resize(car, size=[200, 200])
images = [(circle_64, "circle_64"), (kaggle_k, "kaggle_k"), (car, "car")]

plt.figure(figsize=(14, 4))
for i, (img, title) in enumerate(images):
    plt.subplot(1, len(images), i+1)
    plt.imshow(tf.squeeze(img))
    plt.axis('off')
    plt.title(title)
plt.show();

kernels = [(edge, "edge"), (blur, "blur"), (bottom_sobel, "bottom_sobel"),
           (emboss, "emboss"), (sharpen, "sharpen")]
plt.figure(figsize=(14, 4))
for i, (krn, title) in enumerate(kernels):
    plt.subplot(1, len(kernels), i+1)
    visiontools.show_kernel(krn, digits=2, text_size=20)
    plt.title(title)
plt.show()

 

conv_stride와 pool_stride를 늘릴 수록 출력 이미지의 해상도가 점점 떨어지는 걸 확인해보세요.

커널도 바꿔보구요.

# YOUR CODE HERE: choose an image
image = kaggle_k

# YOUR CODE HERE: choose a kernel
kernel = bottom_sobel

visiontools.show_extraction(
    image, kernel,

    # YOUR CODE HERE: set parameters
    conv_stride=1,
    conv_padding='valid',
    pool_size=2,
    pool_stride=2,
    pool_padding='same',
    
    subplot_shape=(1, 4),
    figsize=(14, 6),
)

 

kernel == bottom_sobel
kernel == edge
kernel == sharpen

 

 

The Receptive Field

몇 개의 신경의 연결을 추적하다보면 입력 이미지에 도달합니다.

신경이 연결된 모든 입력값의 픽셀들은 신경의 receptive filed(수용필드)입니다.

receptive field는 신경이 정보를 받는 입력 이미지의 부분을 알려줍니다.

 

아시다시피 첫번째 레이어가 3x3 커널을 가진 convolution이라면 그 레이어 속 각각의 신경은 픽셀들의 3x3 크기의 patch로부터 입력값을 받습니다. (물론 경계값에 놓이면 3x3 크기는 안되겠지요)

 

만약 또다른 3x3 커널을 가진 convoltional layer를 추가했다면 어떻게 될까요?

다음 사진을 고려해보세요.

 

 

맨 위쪽 신경과의 연결부터 추적해봅시다. 보시면 그 신경은 입력값(bottom layer) 속 5x5 크기의 patch에 연결되어있네요.  중간 레이어 속 3x3 patch 안에 있는 각 뉴런은 3x3 크기의 입력 patch와 연결되었습니다.

하지만 이들은 5x5 입력 패치에도 겹쳐있죠.

그러므로 꼭대기에 있는 뉴런은 5x5 receptive field를 가지고 있습니다.

 

 

1. Growing the Receptive Field

만약 (3,3) 커널을 가진 세번째 convolutional layer를 추가한다면,  그 레이어의 신경은 어떤 receptive field를 가질까요?

 

힌트를 주자면, 두번째 레이어를 쌓으면 각 경계면에서 하나의 신경만큼 receptive field가 확장되어 각 차원에 대해 3+1+1= 5가 됩니다.

그럼 하나의 신경이 또 확장되면 어떤 결과를 얻게 될까요?

 

세번째 레이어는 7x7 receptive field를 가지게 됩니다.

 

 

 

그럼 왜 레이어를 쌓아두는 걸까요? 

(3,3) 커널은 27개의 매개변수를 (7,7) 커널의 49개의 매개변수를 가집니다. 그러나 둘 다 같은 수의 receptive field를 생성하죠.

stacking-layer의 속임수는 바로 convnet이 매개변수의 수를 많이 늘리지 않고도 큰 receptive field를 생성할 수 있다는 것입니다. 다음 레슨에 이에 대한 설명이 있습니다.

 

 

(Optional) One-Dimensional Convolution

Convoutional network는 1차원인 time-series, 2차원인 이미지, 3차원인 동영상에서도 유용합니다.

 

지금까진 convnet에서 이미지에서 특징을 추출하는 방법을 봤습니다. 이번엔 time-series와 동영상에서 해보려 합니다.

 

time-series로는 Google Trends를 사용합시다.

2015년 1월 25일부터 2020년 1월 25일 사이 머신러닝 검색 용어의 주간 인기를 측정하려 합니다.

import pandas as pd

# Load the time series as a Pandas dataframe
machinelearning = pd.read_csv(
    '../input/computer-vision-resources/machinelearning.csv',
    parse_dates=['Week'],
    index_col='Week',
)

machinelearning.plot();

 

 

2차원 이미지에서는 커널도 2차원 배열이지만 현재는 1차원을 다루기에 커널도 1차원으로 사용해봅시다.

detrend = tf.constant([-1, 1], dtype=tf.float32)

average = tf.constant([0.2, 0.2, 0.2, 0.2, 0.2], dtype=tf.float32)

spencer = tf.constant([-3, -6, -5, 3, 21, 46, 67, 74, 67, 46, 32, 3, -5, -6, -3], dtype=tf.float32) / 320

 

time-seires에 대한 convolution은 하나의 이미지에 대한 convolution과 비슷한 결과를 도출합니다.

차이점이라면, sliding window는 왼쪽에서 오른쪽이라는 하나의 방향만 가진다는 점이 이미지와 다릅니다.

이전과 같은 점은 특징들은 커널 속 숫자의 패턴에 의해 좌우된다는 점입니다.

 

위의 3개의 커널이 각각 어떤 특징을 추출하는 테스트 해보세요.

# UNCOMMENT ONE
#kernel = detrend
#kernel = average
kernel = spencer

# Reformat for TensorFlow
ts_data = machinelearning.to_numpy()
ts_data = tf.expand_dims(ts_data, axis=0)
ts_data = tf.cast(ts_data, dtype=tf.float32)
kern = tf.reshape(kernel, shape=(*kernel.shape, 1, 1))

ts_filter = tf.nn.conv1d(
    input=ts_data,
    filters=kern,
    stride=1,
    padding='VALID',
)

# Format as Pandas Series
machinelearning_filtered = pd.Series(tf.squeeze(ts_filter).numpy())

machinelearning_filtered.plot();
detrend

 

detrend 커널은 series의 변화를 감지하지만 

average와 spencer 커널은 모드 진동수가 낮은 지점을 필터링하여 부드럽게 표현되었습니다.

 

검색 용어의 향후 인기 예측에 관심있다면 convnet으로 time-series를 훈련해보세요.

이러한 series 속에서 어떤 특징이 학습과 예측에 도움되는지 공부해봐요.

728x90
반응형