NLP(Natural Language Processing)

NLP란 자연어 처리를 의미하며, 이는 인간의 언어를 컴퓨터에게 이해시키기 위한 기술이라고 생각하면 된다. 어떻게 인간의 언어를 컴퓨터에게 이해시킬 수가 있을까? 우선 언어를 이해시키기 위해서는 언어를 구성하면서 의미가 있는 가장 작은 단위인 '단어'에 대해 이해해야 한다. 어떻게 하면 컴퓨터가 '단어의 의미'를 이해할 수 있을까? 이 방법을 알기 위해서 우선 우리는 자연어 처리에 대한 역사에 대해 알 필요가 있다.

 

 

 

시소러스

'단어의 의미'를 나타내는 방법으로 가장 쉽지만 가장 비효율적인 것은 국어사전과 같이 '사람이 직접 단어의 의미를 정의하는 방식'이다. 시소러스는 정확히는 유의어 사전으로, '뜻이 같은 단어(동의어)'나 '뜻이 비슷한 단어(유의어)'가 한 그룹으로 분류되어 있다. 또한 자연어 처리에 이용되는 시소러스는 단어 사이의 '상위와 하위' 혹은 '전체와 부분' 등, 세세한 관계까지 정의해둔 경우도 있다. 이처럼 모든 단어에 대한 유의어 집합을 만든 후, 단어간의 연결을 정의하면 이러한 네트워크를 통해 컴퓨터는 단어 사이의 관계를 이해할 수 있다.

 

 

WordNet은 자연어 처리 분야에서 가장 유명한 시소러스로, 프린스턴 대학교에서 1985년부터 구축하기 시작하여 현재까지 많은 연구와 다양한 자연어 처리 애플리케이션에서 활용되고 있다. WordNet은 앞서 설명했던 단어 네트워크를 이용할 수 있으며 단어 간의 유사도를 구할 수 있다.

 

 

시소러스의 단점

하지만 시소러스의 문제점은 치명적인 단점이 존재한다.

우선적으로 시대 변화에 대응하기 어렵다는 것이다. 이에 대해 간단히 설명하자면 최근에 새롭게 생긴 단어인 내로남불, 창렬, 혜자와 같은 단어에 대해서 사람이 직접 입력하기 전까지 이해하지 못한다는 것이다.

 

두번째로는 사람을 쓰는 비용이 크다는 것이다. 위의 '시대 변화에 대응하기 어렵다'는 단점에 대한 해결책으로 사람이 일일이 새롭게 생겨난 단어에 대해 입력해줘야하는데, 그러기엔 너무나 단순 노동 뿐만 아니라 인건비가 많이 든다.

 

세번째로는 단어의 미묘한 차이를 표현할 수 없다. 이에 대한 예시로는 우리나라의 대표적인 만능 문자 '헐'이 있겠다. 사람들은 단순히 문자로 되어있는 글에서도 '헐'의 의미를 유추하고 파악할 수 있으나 시소러스로는 '헐'의 미묘한 차이를 이해할 수 없다.

 

 

 

 


 

 

 

 

통계 기반 기법

위의 시소러스의 단점들을 해결하고자 새롭게 만들어 낸 방법이 통계 기반 기법이다. 통계 기반 기법은 특정 단어를 주목했을 때, 그 주변에 어떤 단어가 몇 번이나 등장하는지를 세어 집계하는 방법이다. 이에 대한 이해를 하기 위해서는 앞으로 내가 기술할 내용들의 흐름을 따라가야 한다. 우리는 통계 기반 기법에서 '말뭉치(corpus)'라는 것을 사용하게 된다. 말뭉치란 자연어 처리 연구나 애플리케이션을 염두에 두고 수집된 대량의 텍스트 데이터를 의미한다. 말뭉치에 담긴 문장들을 보면 문장을 쓰는 방법, 단어 선택 방법, 단어의 의미 등 자연어에 대한 사람의 '지식'이 담겨져 있다. 통계 기반 기법은 결론적으로 사람의 지식으로 가득한 말뭉치에서 자동으로, 그리고 효율적으로 핵심을 추출하고자 하는 기법이다.

 

말뭉치를 처리하고자 하는 코드적인 부분인 여기를 참고하길 바란다. 간략하게 말하자면 자연어는 순서와 흐름이 존재하므로 ('나는 너를 싫어한다.' 와 '너는 나를 싫어한다.' 의 의미가 다른 이유는 나와 너의 위치의 차이이며, 이는 자연어에서 중요한 특징이다.) 이 순서를 기억하기 위해서 문장을 구성하는 단어들을 순서(단어ID)와 단어에 대한 정보를 각각 저장하는(단어ID->단어, 단어->단어ID) 딕셔너리로 만들어 저장한다.

 

여기서 잠깐 생각해보면 아무리 말뭉치고 뭐고 생각을 해도 컴퓨터가 자연어를 이해한다는게 이해되지 않지 않는가? (뭔 말장난 같은 문장이여) 컴퓨터는 숫자, 근본적으로는 0과 1을 이해하는 숫자충(충실할 충)이고 자연어는 지극히 언어적이다. 이런 완전히 반대되는 녀석을 어떻게 숫자충인 컴퓨터가 이해할까? 그러기 위해서는 당연하게도 컴퓨터가 이해할 수 있도록 자연어를 처리해야한다. 그 말은 즉, 단어를 숫자로 바꿔야한다는 의미이다. (정말 충격적이지 않은가?! 말이 쉽지 이걸 그래서 어떻게 하는데) 따라서 우리는 단어의 의미를 정확하게 파악할 수 있도록 단어를 벡터로 표현해야 하고 이를 '단어의 분산 표현'이라고 한다.

 

 

분포 가설

단어를 벡터로 표현하기 위해서는 어떻게 해야할까? 이를 위해 우리는 이 사실을 알아야 한다. 단어의 의미는 주변 단어에 의해 형성이 된다는 것이다. 이를 분포 가설이라고 하며, 이 분포 가설이 의미하는 바는 매우 간단하다. 단어란, 그 자체에는 의미가 없고, 그 단어가 사용된 맥락(context)이 의미를 형성한다는 것이다. 이를 쉽게 이해하려면 간단히 '헐'을 떠올리면 된다. (이하 설명은 생략하겠다. 한국인이라면 충분히 이해할 수 있다.)

 

윈도우 크기(window size) 설명

 

위의 이미지에서 설명하는 내용은 window size에 대한 내용이다. window size란 '맥락의 크기'를 의미하며 '맥락'이란 특정 단어를 중심에 둔 주변 단어들을 의미한다. 따라서 위의 경우에서 windwo size가 1인 경우는 say와 and가 goodbye의 맥락이다. window size가 2인 경우는 you,say,and,i가 goodbye의 맥락이 된다. 이러한 맥락을 벡터로 나타내는 것을 우리는 '동시발생 행렬'이라고 한다.

 

 

동시발생 행렬

이에 대한 설명은 아래의 이미지를 통해 직관적으로 이해하고 넘어가겠다.

 

 

 

그림 2-7은 모든 단어에 대해 동시발생하는 단어를 표로 정리한 것이다. 이 표의 각 행은 해당 단어를 표현한 벡터가 되며, 이 표가 행렬의 형태를 띤다는 뜻에서 '동시발생 행렬'이라고 한다.

 

 

벡터 간 유사도

단어 벡터의 유사도를 나타낼 때는 코사인 유사도를 자주 이용한다. 코사인 유사도의 식은 아래와 같고, 코사인 유사도를는직관적으로 '두 벡터가 가리키는 방향이 얼마나 비슷한가'를 의미한다. 두 벡터의 방향이 완전히 일치한다면 1, 완전히 반대라면 -1이 된다.

 

 

놀랍게도 이 코사인 유사도를 사용하면 유의어 및 반의어에 대해 컴퓨터가 이해한다. 이렇게 단어에 대한 네트워크를 이해하기 위해서는 큰 말뭉치가 필요하다. (역시 현대사회에선 다다익선)

 

 

PMI(상호정보량)

동시발생 행렬의 원소는 두 단어가 동시에 발생한 횟수를 나타낸다. 하지만 이것의 치명적인 단점이 있는데, 한국말에서는 조사, 영어에서는 관사가 이 단점의 원흉이다. 영어 문법을 보면 수량을 알기 위해서나 단어를 특정하기 위해 사용되는 a,an,the가 실제 명사(car) 다음에 오는 동사(drive)보다 동시발생 행렬의 개념('등장 횟수가 높을수록 관련성이 강함')에 의해 훨씬 관련성이 높게 나온다는 것이다. 결국 우리는 앞서 설명한 동시발생 행렬로 유의미한 단어의 관계성을 파악할 수 없다. 이를 해결하기 위한것이 PMI이다.

 

PMI란 점별 상호정보량이라는 척도로, 확률의 개념이 포함되어 단어의 연관성을 나타내는 행렬이다.

 

 

P(x)는 x가 일어날 확률을, P(y)는 y가 일어날 확률을, P(x,y)는 x와 y가 동시에 일어날 확률을 의미한다. 위의 식을 보면 PMI는 결론적으로 값이 높을수록 관련성이 높음을 의미한다. 이 식을 동시발생 행렬을 사용하는 식으로 바꾸면 다음과 같다.

 

 

 

C는 동시발생 행렬이고 N은 말뭉치의 단어 수이며, C(x,y)는 단어 x,y가 동시에 발생하는 횟수, C(x)와 C(y)는 각각 단어 x와 y의 등장 횟수이다. PMI("the","car")의 수치와 PMI("car","drive")의 수치는 다음과 같이 연산되어 나타난다.

 

 

앞서 설명했던 동시발생 행렬의 단점을 완벽하게 보완한 모습을 볼 수 있다. 이렇게 계산이 될 수 있었던 것은 PMI는 단어가 단독으로 출현하는 횟수에 대해서도 고려되었기 때문이다. 관사인 a,an,the는 car 뿐만 아니라 다양한 단어와 함께 나오고, 많이 나온다. 그 모든 횟수를 고려하면 사실 the는 car와 그렇게 크게 관련성이 없다는 것을 알 수 있다.

 

PMI도 단점이 존재하는데, 바로 두 단어의 동시발생 횟수가 0이면 -inf가 된다는 점이다. 이 문제를 피하기 위해 실제 구현시에는 양의 상호정보량(PPMI)를 사용하며 식은 다음과 같다. 별 내용은 없다. PMI가 음수일 때 0으로 취급한다는 의미이다.

 

 

이 PPMI에 대해서도 또 문제점이 존재한다.(완벽이란 없어...^^) 말뭉치의 어휘 수가 증가함에 따라 각 단어 벡터의 차원 수도 증가한다는 문제이다. 즉, 말뭉치의 어휘 수가 10만개라면 벡터의 차원 수도 10만이 된다는 것이다. 또한 심지어 이 벡터의 대부분의 원소는 0이다. 결론적으로 벡터가 차원이 큰것 치고 실속이 없다는 얘기다. 이렇게 실속없고 등치만 큰 벡터는 노이즈에 약하며 견고하지 못하다. 이에 대한 문제를 해결하기 위해 우리는 차원 감소를 수행한다.

 

차원 감소는 말 그대로 벡터의 차원을 '중요한 정보'는 최대한 유지하며 줄이는 방법이다. 이 방법에 대해서는 특이값분해(SVD) 등 다양한 방법이 있다. 이에 대한 자세한 내용은 여기를 참고하길 바란다. 또한 내가 앞에서 설명했던 차원 감소 방법 중 하나인 주성분 분석(PCA)와 특이값분해(SVD)에 대한 차이점 및 설명에 대해서는 여기를 참고하길 바란다.

 

 

 


 

 

 

 

결론적으로 통계 기반 기법은 말뭉치로부터 단어의 동시발생 행렬을 만들고, PPMI 행렬로 변환한 후, 안전성을 높이기 위해 SVD를 이용해 차원을 감소시켜 각 단어의 분산 표현을 만들어 낸다.

 

놀랍게도(ㅠㅠ) 아직 단어의 분산 표현을 만드는 방법을 다 설명하지 않았다.

단어의 분산 표현을 만드는 방법인 또 하나의 방법인 '추론 기반 기법'에 대해서는 다음에 설명하도록 하겠다.

 

 

 

 

 

Pooling 계층에서의 im2col

 

입력 데이터에 풀링 적용 영역을 전개 (2x2 풀링)

 

풀링 계층도 앞서 설명한 합성곱 계층과 마찬가지로 im2col을 사용해 입력 데이터를 전개한다.

이번에는 코드와 함께 설명하도록 하겠다.

 

우선, pooling 계층을 구현하기 위해서는 im2col 함수가 필요하다.

import numpy as np

def im2col(input_data, filter_h, filter_w, stride=1, pad=0):
    """
    다수의 이미지를 입력받아 2차원 배열로 변환한다(평탄화).

    Parameters
    ----------
    input_data : 4차원 배열 형태의 입력 데이터(이미지 수, 채널 수, 높이, 너비)
    filter_h : 필터의 높이
    filter_w : 필터의 너비
    stride : 스트라이드
    pad : 패딩

    Returns
    -------
    col : 2차원 배열
    """
    N, C, H, W = input_data.shape
    out_h = (H + 2 * pad - filter_h) // stride + 1
    out_w = (W + 2 * pad - filter_w) // stride + 1

    img = np.pad(input_data, [(0, 0), (0, 0), (pad, pad), (pad, pad)], 'constant')
    col = np.zeros((N, C, filter_h, filter_w, out_h, out_w))

    for y in range(filter_h):
        y_max = y + stride * out_h
        for x in range(filter_w):
            x_max = x + stride * out_w
            col[:, :, y, x, :, :] = img[:, :, y:y_max:stride, x:x_max:stride]

    col = col.transpose(0, 4, 5, 1, 2, 3).reshape(N * out_h * out_w, -1)
    return col

 

 

이후 풀링 계층의 진행 순서는 다음과 같다.

(선택사항). 입력 데이터가 만약 4차원이 아니라면 im2col의 입력값으로 바꾸기 위해서 4차원으로 변경한다.

1. im2col로 4차원 데이터를 행렬로 (2차원) 변경한다.

2. np.max를 사용하여 각 행에서의 최대값을 취한다.

3. 최대값만 취한 데이터를 다시 입력 데이터의 shape으로 변경한다.

 

 


 

 

 

그럼 다음의 그림과 같은 예제를 코드로 진행하도록 하겠다.

 

2x2 크기의 max-pooling 적용 예제

 

1. im2col로 4차원 데이터를 행렬로 (2차원) 변경한다.

# 예제 데이터 생성
x = np.array([[[[1,2,3,0],[0,1,2,4],[1,0,4,2],[3,2,0,1]],
              [[3,0,6,5],[4,2,4,3],[3,0,1,0],[2,3,3,1]],
              [[4,2,1,2],[0,1,0,4],[3,0,6,2],[4,2,4,5]]]])

x.ndim # 4차원
x.shape # (1,3,4,4)

# 1번 : im2col로 4차원 데이터를 행렬로 (2차원) 변경한다.
step_1 = im2col(x,2,2,2,0) # 입력 데이터(x)를 필터크기(2x2)와 stride(2), padding(0)으로 평탄화함
step_1

'''
array([[1., 2., 0., 1., 3., 0., 4., 2., 4., 2., 0., 1.],
       [3., 0., 2., 4., 6., 5., 4., 3., 1., 2., 0., 4.],
       [1., 0., 3., 2., 3., 0., 2., 3., 3., 0., 4., 2.],
       [4., 2., 0., 1., 1., 0., 3., 1., 6., 2., 4., 5.]])
'''

step_1.shape # (4,12)

# 1번_2 : 평탄화 된(im2col 사용) 행렬을 그림과 같은 2차원으로 변경한다.
step1 = step_1.reshape(-1,4)

'''
array([[1., 2., 0., 1.],
       [3., 0., 4., 2.],
       [4., 2., 0., 1.],
       [3., 0., 2., 4.],
       [6., 5., 4., 3.],
       [1., 2., 0., 4.],
       [1., 0., 3., 2.],
       [3., 0., 2., 3.],
       [3., 0., 4., 2.],
       [4., 2., 0., 1.],
       [1., 0., 3., 1.],
       [6., 2., 4., 5.]])
'''

step1.shape # (12,4)

 

그림의 전개에 해당하는 부분으로 보고 싶으면 이렇게 묶어 봐야함

im2col 함수를 사용한 변수의 결과는 위의 이미지로, 그림의 전개에 해당하는 부분처럼 이해하고 싶으면 사각형으로 봐야 한다. 실제 코드로 구현할 때는 채널1, 채널2, 채널3에 해당하는 수들을 전개하는 것이 아니라 채널1과 채널2, 채널3에 같은 위치(행)에 존재하는 것들을 일렬로 전개한다. 이때문에 2번째로 reshape을 해 준 array는 그림에서의 전개 후 이미지와는 약간 다르다.

 

 

2. np.max를 사용하여 각 행에서의 최대값을 취한다.

# 2. np.max를 사용하여 각 행에서의 최대값을 취한다.
step2 = np.max(step1,axis=1)
step2 # array([2., 4., 4., 4., 6., 4., 3., 3., 4., 4., 3., 6.])

 

여기서 np.max(,axis=1)을 사용하는 이유는 여기를 참고하길 바란다.

step2의 결과물도 그림의 최댓값 이후의 전개물과 동일하게 이해하려면 다음의 그림을 참고하면 된다.

 

주황색 : 채널1 / 초록색 : 채널2 / 파란색 : 채널3

 

 

3. 최대값만 취한 데이터를 다시 입력 데이터의 shape으로 변경한다.

# 3. 최대값만 취한 데이터를 다시 입력 데이터의 shape으로 변경한다.
step3 = step2.reshape(1,2,2,-1) # step2의 결과를 배치사이즈(1), 출력데이터크기(2x2), 채널(-1)로 reshape한다.
step3.transpose(0,3,1,2) #(1,2,2,3) : step3의 결과(N,H,W,C)를 (N,C,H,W)로 순서를 바꿔준다.

'''
array([[[[2., 4.],
         [3., 4.]],

        [[4., 6.],
         [3., 3.]],

        [[4., 4.],
         [4., 6.]]]])
'''

 

그럼 그림과 일치하게, 우리가 원하는 풀링 계층의 출력 데이터를 만들 수 있다.

위의 흐름을 따라서 Pooling class를 만들면 다음과 같다.

 

class Pooling :
    def __init__(self,pool_h,pool_w,stride=1,pad=0) :
        self.pool_h = pool_h
        self.pool_w = pool_w
        self.stride = stride
        self.pad = pad
        
    def forward(self,x) :
        N, C, H, W = x.shape
        out_h = int(1+(H - self.pool_h)/self.stride) # 공식 사용, padding은 Pooling에서 사용하지 않으므로 애초에 수식에서 뺌
        out_w = int(1+(W - self.pool_w)/self.stride) # 공식 사용, padding은 Pooling에서 사용하지 않으므로 애초에 수식에서 뺌
        
        col = im2col(x,self.pool_h,self.pool_w,self.stride,self.pad) # im2col로 평탄화해줌
        col = col.reshape(-1,self.pool_h*self.pool_w) # np.max를 수행하기 위해 reshape 해줌
        out = np.max(col,axis=1) # np.max 수행
        out = out.reshape(N,out_h,out_w,C).transpose(0,3,1,2) # (N,H,W,C)의 모양을 (N,C,H,W)로 바꿔줌
        return out
    
pool = Pooling(2,2,2) # pooling size(2x2), stride(2)의 풀링 계층 구현

pool.forward(x)

 

 

 

 

 

 


 

 

 

 

 

 

 

im2col을 사용하는 이유는 행렬 연산에 최적화 된 컴퓨터가 연산을 더욱 빠르게 하도록,

또 딥러닝에 많이 사용되는 GPU의 병렬 연산을 효율적으로 사용하기 위해서였다.

 

 

그런데 im2col과 완전연결신경망인 MLNN(Multi-Layer Neural Network)에서의 flatten은 정확히 무슨 차이가 있는걸까?

MLNN은 가중치를 갱신하면서 최적의 가중치를 찾아가며 신경망을 학습하고 이 때 입력 데이터의 모든 데이터가 사용된다. 따라서 초반에 2차원, 혹은 3차원의 입력 데이터를 flatten함으로써 모든 데이터가 신경망 전체를 아우르는 가중치의 학습 및 갱신에 사용된다고 볼 수 있다.

하지만 im2col은 결국 Conv층과 Pooling층의 연산 속도를 빠르게 하기 위해 사용하는 함수이고, 실질적으로 im2col을 사용한 후의 데이터는 Conv층의 합성곱을 수행하고 Pooling층의 기능을 한다. 또한 Conv층과 Pooling층은 각각의 filter를 갖고 있기 때문에 입력 데이터는 각각의 filter에 대해서만 영향을 받는다. 따라서 im2col과 flatten은 데이터를 압축시킨다는 역할은 같으나, im2col은 각 계층의 연산을 빠르게 해준다는 장점 이외의 데이터 변형, 또는 연산 변형은 전혀 없다. (정확하다고 볼 수는 없겠지만 느낌적으로 이 둘의 차이를 이해하길 바란다. 개인적인 해석으로 틀렸다면 꼭 알려주시길 바랍니다 크흡,,)

 

 

결론적으로 CNN은 MLNN과 달리 데이터를 flatten시키지 않아 데이터 형상을 유지하면서 Conv계층, Pooling계층의 연산(내부적으로는 im2col을 사용하여 연산속도를 빠르게 함)을 사용하며 이미지 자체를 학습할 수 있다.

 

 

 

 

 

im2col (Image to Column)

CNN은 3차원의 데이터 (주로 이미지)를 학습시키는데 특화되어있는 신경망이다.

차원이 큰 데이터에 대해서도 정보를 잃지 않기 위해 합성곱을 사용하기 때문에 for문을 여러개를 사용해야 한다.

 

import numpy as np

x=np.array([[[1,2,3,0],
                [0,1,2,3],  # --> red 행렬
                [3,0,1,2],
                [2,3,0,1]],
              [[2,3,4,1],
               [1,2,3,4],  # --> green 행렬
               [4,1,2,3],
              [3,4,1,2]],
             [[3,4,5,2],  # --> blue 행렬
              [2,3,4,5],
              [5,2,3,4],
              [4,5,2,3]]])

print (x.ndim)  # 3
print (x.shape)  # (3,4,4)  (차원,행,열)

f=np.array([[[2,0,1],
               [0,1,2],
               [1,0,2]],
               [[3,1,2],
                [1,2,3],
                [2,1,3]],
                [[4,2,3],
                [2,3,4],
                [3,2,4]]])

print ( f.ndim )  #  3
print ( f.shape ) #  ( 3, 3, 3 )

output = np.zeros([x.shape[1]-f.shape[1]+1,x.shape[2]-f.shape[2]+1])

fh,fw = f.shape[1], f.shape[2]

for i in range(output.shape[0]):
    for j in range(output.shape[1]):
        for k in range(x.shape[0]) :
            output[i,j]+=np.sum(x[k][i:fw+i,j:fh+j]*f[k])
            
print(output)

 

위의 함수를 보다시피, for문을 여러번 사용해야 합성곱 연산을 할 수 있다. 하지만 이런 식이라면 연산 속도가 매우 떨어질 것이다.

수만건의 데이터를 처리해야하는 CNN은 이러한 연산속도 저하 문제를 어떻게 해결할까?

 

그에 대한 해결이 im2col 함수이다.

 

3차원 데이터를 im2col 함수에 넣은 후의 출력 데이터

위의 이미지는 im2col의 동작을 단적으로 보여주고 있다. im2col은 쉽게 말해서 다차원의 데이터를 행렬로 변환하여 행렬 연산을 하도록 해주는 함수를 말한다.

다차원 데이터의 합성곱(convolution)은 im2col을 통해 행렬로 변환된 데이터의 내적과 같다.

 

 

 

합성곱 계층(Convolution Layer)에서의 im2col

여기 (7,7,3) 인 입력데이터와 (5,5,3)인 필터가 있다고 하자. C는 RGB로 3이다.

 

 

우리는 원래대로라면 이 3차원 입력데이터와 필터에 대해 합성곱을 진행해야 한다. 하지만 im2col을 사용하면 행렬의 내적으로 합성곱과 같은 결과를 낼 수 있다.

어떻게 3차원 데이터를 2차원 행렬로 변환시킬 수 있을까?

 

7x7xC 입력 데이터는 필터에 의해 9개로 나눠진다.

 

위의 그림이 우리가 기본적으로 알고 있던 합성곱에 대한 개념이다. 입력 데이터(7,7,3)는 필터 사이즈(5,5,3)만큼 가로로 3번, 세로로 3번 총 9개(3x3)로 나뉘어 필터와 합성곱 연산을 한다. 아래는 9개로 나뉜 데이터의 사이즈(filter 사이즈와 같음)를 함께 보여주는 그림이다.

 

9개로 나뉜 데이터의 size는 (5,5,3)으로 filter size와 같다.

 

filter size만큼 나뉘어진 원본 데이터를 행렬로 변환하면 다음과 같다.

 

 

 

입력 데이터는 필터에 의해 총 9개의 데이터로 쪼개졌고, 그 데이터 하나 하나를 행렬로 만들어 준다. 쪼개진 데이터는 (5,5,3)의 크기를 갖고 이것을 행렬로 만들면 세로는 1(데이터 1개라는 의미), 가로는 5x5xC의 크기로, 행렬 1개(1,5x5xC)로 3차원의 데이터를 나타낼 수 있다.

 

입력데이터를 im2col로 변환한 최종 결과 행렬

 

이러한 과정을 쪼개진 데이터 개수인 9만큼을 반복해서 나타낸 행렬은 입력 데이터에 대한 정보를 모두 갖게 된다.

이 과정을 filter에 대해서도 진행한다.

 

filter에 대해서 im2col을 진행한 결과 행렬

 

우리는 (5,5,C)의 모양을 가진 필터 하나를 사용하기 때문에 im2col을 진행하면 행렬 1개가 나온다.

 

 

3차원의 데이터를 합성곱하는 결과는 각각의 데이터를 im2col 함수를 사용해 모든 데이터를 잃지 않으면서 2차원 행렬로 변환시켜 행렬연산을 진행하는 것과 같은 결과를 도출한다. 컴퓨터는 행렬 연산에 매우 특화되어 있기 때문에 im2col을 사용하여 행렬 연산을 진행하면 비록 연산해야하는 데이터 수는 늘어났지만(원래는 입력데이터가 1개 뿐이었지만 입력데이터 1개를 9개로 나눴기 때문에 연산해야하는 데이터 수는 결과적으로 늘었다.) 연산 속도는 빠르다. 하지만 연산해야하는 데이터 수가 늘어났기 때문에 연산 메모리가 증가한다는 단점도 존재한다.

 

im2col로 변환한 데이터와 필터의 연산 과정

 

이렇게 행렬로 변환한 입력 데이터와 필터의 행렬 연산 이후, 우리는 출력된 데이터(행렬)를 다시 원래의 데이터(3차원)로 변환해주는 작업을 한다.

이러한 흐름이 합성곱 계층의 구현 흐름이다.

 

그리고 더 정확하게 말하자면 데이터들은 정확히는 3차원이 아니라 4차원이다. 여기를 보면 알다시피 입력 데이터는 (N,C,H,W)로 구성되어있으며 N은 데이터의 갯수를 의미하는 배치 사이즈를 뜻한다. 따라서 정확히는 입력 데이터와 filter 데이터, 출력 데이터 모두 4차원이지만 이해를 돕기위해 입력 데이터 1개와 필터 1개를 예시로 하여 3차원으로 설명하였으니 오해가 없길 바란다.

 

 

 

 

 

출처 : https://ettrends.etri.re.kr/ettrends/159/0905002137/0905002137.html

 

딥러닝이란 층을 깊게 한 심층 신경망을 의미한다. 신경망의 층이 깊어지면 깊어질수록 너비가 늘어난다. 이에 대한 자세한 설명은 여기를 참고하길 바란다. 층이 깊어질수록 너비가 늘어나는 이유는 합성곱 이후의 너비는 필터 개수와 연관이 있기 때문이다. 내가 지정한 필터 수가 늘어나면 늘어날수록 합성곱 계층의 너비 또한 늘어난다.

 

이러한 딥러닝 신경망의 정확도를 높이기 위해서는 앙상블 학습, 학습률 감소(Learning Rate 감소), 데이터 확장(Data Augmentation) 등을 하는데 특히 이 중에 데이터 확장은 훈련 이미지를 인위적으로 확장하는 방법이다. (필자도 예전에 케글 당뇨성 망막증 대회를 참여했을 때 이 방법을 사용하여 데이터 개수를 늘렸었다.) 이 방법은 데이터가 몇 개 없을 때 효과적인 수단으로 훈련 데이터를 회전, 이동, 확대, 축소, 밝기 변형, 좌우 반전(flip)을 하는 등의 미세한 변화로 데이터의 개수를 늘리는 방법이다.

 

 

 

 

신경망을 깊게 하는 이유

신경망이 깊을수록 처음에는 단순한 특징에만 반응하다 점점 더 복잡하고 추상화된 정보의 특징을 파악하며 결론적으로는 사물의 의미를 이해하게 된다는 것을 이전에 말했었다. 직관적으로 이해가 가면서도 이해가 되지 않는다. 지금부터는 이 부분에 대해서 조금 더 자세하게 설명하도록 하겠다.

 

왼쪽 : 5x5 합성곱 연산 / 오른쪽 : 3x3 합성곱 계층 2회 반복

 

오른쪽의 3x3 연산을 2회 수행한 것은 왼쪽의 5x5 합성곱 연산을 한 것과 동일한 결과를 낼 수 있다. 그런데 왜 3x3을 굳이 2번이나 반복할까? 그냥 5x5 합성곱 연산을 한번만 하는게 훨씬 이득이 아닐까?

 

5x5 합성곱 연산은 매개변수(Filter)가 25(5x5)개이지만, 3x3 합성곱 연산 2회 반복은 매개변수가 18(2x3x3)개이다. 이러한 매개변수 개수 차이는 층이 깊어지면 깊어질수록 더 커지게 된다. 결국 신경망이 깊어질수록 매개변수가 줄어들어, 층이 깊지 않은 경우보다 적은 매개변수로 같은 수준의 표현력을 달성할 수 있다는 것이다.

 

매개변수가 줄어들 때의 장점은 무엇일까? 첫번째로 넓은 수용 영역(Receptive Field)를 소화할 수 있다는 장점이 생기는데, 쉽게 말하면 필터가 작을수록 국소부위에 대한 특징을 잡아낼 수 있어 더 많은 디테일을 찾아낼 수 있다는 것이다. 두번째로는 층마다 활성화 함수를 사용하면서 활성화 함수가 신경망의 비선형을 유지시키며 신경망의 표현력을 개선할 수 있다는 것이다. 또 층이 깊어질수록 사물을 이해하기 때문에 비교적 적은 학습 데이터로도 좋은 성능의 신경망을 만들 수 있어 학습의 효율성 또한 좋아진다.

 

 

 

 

전이 학습 (Transfer Learning)

전이학습은 이미지넷(100만장이 넘는 이미지를 담고 있는 데이터셋으로 각 이미지에 레이블이 붙어 있음)이 제공하는 거대한 데이터셋으로 학습한 가중치의 일부를 능력이 유사하거나 새로운 분야의 신경망에 복사한 후, 그 상태로 재학습을 수행하는 것을 의미한다. 전이 학습은 학습 데이터가 부족한 프로젝트를 진행하는 경우 매우 큰 도움이 된다.

 

예를 들자면 내가 동양인과 서양인을 구분하는 신경망을 구현하고자 할 때, 여자와 남자를 구분하는 신경망에서 이미지의 특징을 추출하는 부분에 대한 파라미터들을 가져와 나머지를 내 데이터로 학습시켜 완성도를 높이는 것이 전이 학습이다.

 

전이 학습은 데이터의 수가 적을 때도(물론 많을 때도) 매우 효과적이며 학습 속도 또한 빠르고 전이 학습 없이 학습하는 것보다 훨씬 높은 정확도를 제공한다는 장점이 있다. 전이 학습에 대한 코드적인 자세한 예제는 여기여기를 참고하길 바란다.

 

 

 

 

강화 학습 (Reinforcement Learning)

 

 

위의 그림처럼 에이전트가 더 좋은 보상을 받기 위해 스스로 학습하는 것을 강화 학습이라고 한다. 강화 학습은 기존의 지도 학습과는 다르다. 지도 학습은 정답과 예측 값에 대한 오차를 통해(가르침) 모델을 완성하는 반면, 강화 학습은 에이전트라는 것이 환경에 맞게 행동을 선택하고, 그 행동에 의해서 환경이 변하면서 그에 맞는 보상을 얻으며 모델을 완성해간다. 강화 학습의 보상은 명확한 것이 아니라 상황에 따라 달라지기 때문에 게임 점수나 게임 종료와 같은 명확한 지표로부터 역산하여 예상 보상을 정해야 한다. 강화학습에 대한 코드적인 예제는 여기여기를 참고하길 바란다.

 

 

 

 

 

MLNN(Multi-Layer Neural Network) : Affine(완전 연결) 계층으로 이뤄진 네트워크

여태 설명했던 신경망은 인접하는 모든 계층의 뉴런과 결합되어 있었다.

이를 완전연결(Fully-Connected)계층(Dense Layer라고도 함)이라고 하며,

완전히 연결된 계층을 Affine 계층이라는 이름으로 구현했다.

 

Affine 계층으로 이뤄진 신경망

 

 

 

CNN (Convolutional Neural Network)

CNN은 여태 배웠던 신경망이 Affine-ReLU 연결을 한 것과는 달리 Conv-ReLU-Pooling의 흐름으로 신경망이 구성된다.

 

CNN으로 이뤄진 신경망 (합성곱 계층, 풀링 계층 추가)

 

그런데 왜 CNN을 사용하는걸까?

 

 

 

Affine 계층의 문제점

Affine 계층은 학습을 진행할 때 데이터의 형상을 무시한다. 이미지 데이터는 가로, 세로, 색(RGB) 총 3차원의 데이터인데, Affine 계층으로 학습 시 이 3차원의 데이터를 1차원으로 평평하게 평탄화(Flatten)해야 학습을 할 수 있다. 이렇게 차원을 줄이다보면 유의미한 데이터를 정확하게 살릴 수 없다.

 

MLNN의 문제점

 

또한 위의 MLNN의 문제점 이미지를 살펴보면 글자의 형상을 고려하지 않고 학습을하기 때문에 데이터가 살짝만 모양이 바뀌어도 새롭게 학습을 시켜야 한다는 문제점이 있다. 따라서 데이터의 모든 정보를 최대한 손실 없이 학습 시키면서 데이터의 모양 변화에 큰 영향을 받지 않는 새로운 방법이 필요했고, 그에 대한 해답이 CNN이었다.

 

 

 

 

 


 

 

 

 

Conv 계층

Conv(합성곱) 계층의 입출력 데이터를 CNN에서는 특징 맵(Feature Map)이라고도 해서 Conv 계층의 입력 데이터를 입력 특징 맵(Input Feature Map), 출력 데이터를 출력 특징 맵(Output Feature Map)이라고 한다.

 

Conv 계층은 이름과 일치하게 합성곱 연산을 처리하며, 이는 이미지 처리에서 말하는 필터(커널(Kernel)이라고 주로 표현) 연산에 해당한다. 합성곱 연산은 Kernel의 Window를 일정 간격으로 이동하며 입력 데이터에 적용한다. 이러한 과정은 기존의 Affine 계층으로만 이루어진 신경망과 달리 데이터의 형상을 잃지 않으면서 학습을 진행할 수 있도록(이미지의 특징을 파악할 수 있도록) 도와준다. CNN도 기존 신경망과 동일하게 가중치와 편향이 존재하는데 필터를 구성하는 매개변수들이 가중치에 해당하고 편향은 항상 1x1로 하나만 존재한다.

 

합성곱 연산

 

 

 

 

필터 (Filter)

필터란 필터에 해당하는 특징이 데이터에 있는지를 검출해주는 함수로, 아래의 그림에서는 곡선이 필터이다.

이 필터를 기존의 데이터와 합성곱 연산을 진행하면 필터에 해당하는 특성을 갖고 있으면 결과 값으로 큰 값이 나오고, 특성을 가지지 않으면 결과값이 0에 가깝게 나오면서 입력데이터가 필터에 해당하는 특징을 갖고 있는지, 아닌지에 대한 여부를 알 수 있다.

 

필터(Filter)에 대한 이해를 돕기 위한 이미지

 

 

 

 

패딩 (Padding)

합성곱 연산을 하기 이전, 입력데이터 주변을 특정값으로 채워 늘리는 것을 패딩(Padding)이라고 한다. 패딩(Padding)은 주로 출력 데이터의 크기를 조정할 목적으로 사용한다. 합성곱 연산을 반복하다보면 어느 시점부터는 출력 데이터의 크기가 1이 되어 더이상 합성곱 연산을 진행할 수 없게 된다. (위의 이미지로만 봐도 4x4 크기의 입력 데이터가 합성곱 연산을 진행하면서 2x2의 데이터로 출력되는 것을 확인할 수 있다.) 따라서 패딩(Padding)을 사용하여 입력데이터의 외각의 크기를 늘려 출력 데이터의 크기가 줄어드는 것을 방지한다.

 

Padding 사용 시 합성곱 연산 : 출력 데이터의 크기가 입력 데이터의 크기와 동일

 

 

 

 

스트라이드 (Stride)

Stride는 입력데이터에 필터를 적용할 때 이동할 간격을 조절하는, 즉 필터가 이동할 간격을 뜻한다. Stride의 값을 키우면 키울수록 출력 데이터의 크기는 작아지며 Padding은 크게하면 할수록 출력 크기가 커진다.

 

Padding, Stride를 적용한 합성곱 연산

 

이렇게 Padding, Stride를 사용하면 출력 크기가 어떻게 되는지에 대한 수식은 다음과 같으며, 출력 크기에 해당하는 OH와 OW는 정수로 나타내져야 한다.

 

더보기

 

위의 수식을 이용한 예제로 한번 생각해보길 바란다.

 

 

 

 

 

3차원 데이터의 합성곱 연산

이미지 데이터는 가로, 세로, 색(RGB) 총 3차원 데이터라고 언급했다. 하지만 위에서 봤던 Conv 계층의 연산은 2차원 데이터로 진행했다. 그렇다면 이미지 데이터와 같은, 혹은 그 이상의 차원의 데이터같은 경우는 어떻게 모든 차원을 살리며 합성곱 연산을 진행할 수 있을까?

 

3차원 데이터 합성곱 연산의 예

 

이에 대한 해답은 위의 그림을 참고하면 된다. 입력 데이터의 3개의 데이터는 색(RGB) 채널 방향으로 특징 맵이 늘어났다. 즉, 각각의 색에 대한 정보를 담은 데이터로 늘어났다고 보면 된다. 그리고 이것을 각의 필터로 합성곱 연산을 진행하여 출력 데이터로 반환하면 된다. 그러면 출력데이터는 2차원의 형태지만 기존 3차원의 데이터를 모두 갖고 있는 데이터로 출력된다. 이러한 3차원 합성곱 연산에서 주의할 점은 입력 데이터의 채널 수와 필터의 채널 수가 같아야 한다는 사실이다. 예를 들어 RGB 3개의 채널을 갖고 있다면 필터의 채널 수도 3개여야하고, RB 2개의 채널만 갖고 있다면 필터의 채널 수도 2개여야 한다는 뜻이다.

 

 

 

 

 

합성곱 연산의 배치 처리

합성곱 연산 시 배치 처리된 데이터를 학습시키는 방법은 기존의 배치와 크게 다르지 않다. 우리가 CNN을 사용하는 이유는 주로 이미지 데이터와 같이 다차원의 데이터를 최대한 기존 데이터의 특징을 모두 학습시키기 위함인데, 배치를 사용하면 3차원이었던 데이터가 4차원으로 늘어나면 된다. 즉, 맨 앞에 배치 사이즈(N)를 추가하여 데이터를 학습시키면 된다.

 

합성곱 연산 처리

 

위의 이미지는 CNN의 연산 처리 과정으로 지금부터 하나하나 뜯어 설명해보도록 하겠다. 입력 데이터는 배치된 데이터로 N개의 데이터로 구성되어져있어 채널, 가로, 세로로 구성되어져 있는 N개의 데이터 묶음(배치)이다. 그렇다면 필터의 FN은 정확히 무엇을 의미하는걸까? 필터의 FN은 입력 데이터의 배치 사이즈와는 정확히 다르다. 이해를 돕기위해 다시 한번 맨 위의 필터(Filter) 설명에 사용했던 쥐의 이미지를 가져 오겠다.

 

1개의 필터에 대한 합성곱 연산

 

이 그림에 대한 필터는 곡선으로, 오직 1개 뿐이다. 그러면 출력 데이터는 특징 1개에 대한 정보만을 갖고 있는 데이터로 출력된다. 그렇다면 필터의 개수를 FN개로 늘려보면 어떨까?

 

FN개의 필터들

 

FN개의 필터들로 입력 데이터를 학습시키면 각각의 특징에 대한 출력 데이터를 FN개 얻을 수 있다. 이것들의 묶음을 블록 단위로 하여 다음 계층에 넘기는 것이 필터 (FN, C, FH, FW)의 의미이다. 입력 데이터와 FN개의 필터들과의 합성곱 연산으로 출력된 데이터 (FN, OH, OW)는 필터 개수만큼의 편향 (FN, 1, 1) 과의 연산을 통해 (FN, OH, OW)의 데이터로 출력될 수 있다. 여기서 초기 입력 데이터의 C(채널)가 출력 데이터들에 없는 이유는 합성곱을 진행하면서 C에 대한 데이터가 OH, OW에 모두 스며들었기 때문이다.

 

 

 

 

 

 


 

 

 

 

 

풀링(Pooling) 계층

풀링은 세로, 가로 방향의 공간을 줄이는 연산이다.

풀링의 종류로는 크게 max pooling과 average pooling이 있는데,

max pooling은 이미지를 선명하게 만드는 역할을, average pooling은 이미지를 부드럽게 만드는 역할을 한다.

 

Max-pooling을 사용하는 경우

 

Pooling은 주로 Conv 계층에서 출력데이터의 크기는 입력 데이터의 크기 그대로 유지하고, 풀링 계층에서 크기를 조절한다.

그렇다면 풀링 계층의 특징은 무엇일까?

 

  • 학습해야 할 매개변수가 없다.
  • 채널 수가 변하지 않는다.
  • 입력의 변화에 영향을 적게 받는다.

 

여기서 맨 마지막 입력의 변화에 영향을 적게 받는 이유는 아래의 이미지를 참고하면 이해하기 쉽다.

 

 

출력 데이터가 오른쪽으로 하나씩 밀려도 풀링 결과는 달라지지 않는다.

 

또한 Pooling은 parameter를 줄이기 때문에 신경망의 표현력이 줄어들어 Overfitting을 억제하고 연산을 비교적 간단하게 만들어준다.

 

 

 


 

 

 

CNN 구현 흐름 시각화 / 원하는 만큼 Conv, Pooling 계층 진행 후 완전연결계층 진행

 

 

 

 

 

 

 

 


 

 

 

 

 

 

 

층 깊이에 따른 특징 추출 변화

 

딥러닝의 흥미로운 점은 합성곱 계층이 여러 개 쌓이면서 층이 깊어질수록 더 복잡하고 추상화된 정보가 추출된다는 것이다. 위의 이미지를 보면 Conv1층은 단순하게 Edge에만 반응하지만 Conv3층은 텍스쳐에 반응하고 Conv5는 사물의 일부에 반응하도록 변화된다. 즉, 층이 깊어지면서 뉴런이 반응하는 대상이 단순한 모양에서 고급 정보로 변화해간다. 따라서 합성곱 계층이 깊어지면 깊어질수록 신경망은 사물의 의미를 이해하게 된다.

 

 

 

+ Recent posts