머신러닝&딥러닝/자연어처리

Transformer(1) - Scaled Dot-Product Attention

Like_Me 2019. 12. 24. 00:15
반응형

 Transformer를 이해하기 위해서는 우선 Self attention에 대한 이해가 필요하다. 셀프 어텐션은 문장에서 각 단어끼리 얼마나 관계가 있는지를 계산해서 반영하는 방법이다. 즉, 셀프 어텐션으로 문장 안에서 단어들 간의 관계를 파악할 수 있는 것이다.

예를 들어, '나는 자연어 처리를 즐겨한다.'라는 문장에서 '자연어'라는 단어에 대해 관계를 측정한다. 이때, 밑의 표처럼 나왔다고 하면 각 단어에 대한 어텐션 스코어가 나온다. 어텐션 스코어는 각 단어 간의 관계를 측정한 값이고 어텐션 스코어 값을 하나의 테이블로 만든 것을 어텐션 맵이라고 부른다.

나는     자연어 자연어     자연어 처리를    자연어 즐겨한다    자연어
Dot product Dot product
Dot product
Dot product
0.3 0.3 0.25 0.15

실제 문장이 모델에 적용될 때는 Embedding 된 벡터 형태로 입력이 될 것이다. 트랜스포머 모델에서는 각 단어 벡터간의 관계를 나타내는 어텐션 스코어를 구할 때, 단어 벡터끼리 내적 연산을 함으로써 구한다. (위의 예시는 트랜스포머를 가정해서 내적 연산을 해줬으며, 맨해튼 거리나 Dense 층을 거쳐 나온 값을 활용하는 방법도 있다.)

 

그 후, Softmax를 통과시켜 다른 단어와의 연관도 값을 확률로 나타낸다.  마지막으로 어텐션 스코어와 각 단어 벡터를 곱한 후 모두 더해주면(가중치를 각 벡터에 곱해주고 더해주는 가중합 과정) 단어에 대한 콘텍스트 벡터가 나오게 된다.

 

위의 예시를 다시 가져와 생각해보면, 이 경우에는 '자연어'에 대해 나온 어텐션 스코어를 다시 각 단어 벡터와 곱해주고, 모두 더해주면 '자연어'에 대한 컨텍스트 벡터가 나오는 것이다. 이 과정을 다른 단어들에 대해서도 수행한다.

 

이런 Self Attention은 같은 문장 내 모든 단어 쌍 사이의 의미적, 문법적 관계를 포착해내는 중요한 역할을 하게된다!


 

 이제, Scaled Dot-Product Attention을 이해해보자. 스케일 내적 어텐션의 구조는 아래의 그림과 같다. 전체적인 개념은 앞에서 본 셀프 어텐션과 거의 동일하다.

 

스케일 내적 어텐션 구조

 위의 그림처럼 query, key, value가 입력값으로 들어간다. query는 위의 예시에서 '자연어' 같은 다른 단어와의 관계를 알아보려는 특정 단어가 되며 다른 단어들이 key, value가 되는 것이다. 이 경우, key=value라고 볼 수 있다. 

 

1. query, key, value는 모두 문장인데 각 단어가 벡터로 되어있고, 이것들이 모여서 결국 행렬로 돼 있는 구조다. 그래서, Query와 Key를 행렬 계산을 먼저 해주는데, Key 행렬은 Query행렬의 Transpose 된 형태로 만들어 행렬곱을 해주면 결국 내적 연산과 같은 과정으로 각 단어에 대한 어텐션 스코어를 구함을 알 수 있다. 내적 연산을 한 값은 -1~1의 값을 가지게 될 텐데, 어떤 쿼리와 키가 중요한 역할을 하고 있다면 어텐션 블록은 이들 사이의 내적 값을 키우는 방식으로 학습을 할 것이다. 내적 값이 커지면 해당 쿼리와 키가 벡터 공간상 가까이 있을 가능성이 높아진다.

 

 

2. 행렬 연산(내적 연산)을 한 후, 벡터의 차원이 커지면 학습이 잘 안될 수 있기에 스케일링을 해준다.

 

3. 다음으로는 mask를 해주는데, 그 이유는 전체 문장을 한 번에 행렬 형태로 입력해주는 구조이므로 자신보다 뒤에 있는 단어를 참고해서 단어를 예측하는 상황이 생길 수 있기 때문이다. 따라서, 자신보다 뒤에 있는 단어를 참고하지 않게 해줘야 한다. 그래서, Query와 Transpose 된 Key의 행렬곱으로 생성된 어텐션 맵이 4*4로 생성되었을 때 Query 단어 뒤에 나오는 Key 단어들에 대해서 마스킹하면 아래의 표와 같이 된다.

Query/Key 나는 자연어 처리를 즐겨한다
나는 23 X X X
자연어 16 27 X X
처리를 14 20 23 X
즐겨한다 12 19 20 23

4. mask까지 해주면 softmax로 확률 값으로 변환시켜주고 Value와 행렬곱을 해줘서 각 콘텍스트 벡터를 구해준다.

(앞의 self attention과 동일한 과정)

 

 

 ※ 위의 내용을 코드로 구현해보면 다음과 같다.

def scaled_dot_product_attention(query, key, value, masked=False):
    key_dim_size = float(key.get_shape().as_list()[-1])
    key = tf.transpose(key, perm=[0, 2, 1])
    
    # normalize를 위해 차원수로 나눠준다.
    outputs = tf.matmul(query, key)/tf.sqrt(key_dim_size)
    
    # attention score가 있는 attention map을 구한다.
    if masked:
        # 마스킹할 영역 준비
        diag_vals = tf.ones_like(outputs[0, :, :])
        # 행렬을 하삼각행렬로 만들기. 상삼각영역은 패딩 처리한다.
        tril = tf.linalg.LinearOperatorLowerTriangular(diag_vals).to_dense()
        # 배치크기만큼 확장.
        masks = tf.tile(tf.expand_dims(tril, 0), [tf.shape(outputs)[0], 1, 1])
        # padding 영역에 할당할 아주 작은 음수 값을 행렬로 만들어둠.
        paddings = tf.ones_like(masks)*(-2**30)
        # mask가 0이면 paddings의 값을 사용하고 아닐경우는 outputs를 사용함.
        outputs = tf.where(tf.equal(masks, 0), paddings, outputs)
    # softmax로 마스킹영역이 걸러짐.
    attention_map = tf.nn.softmax(outputs)
    return tf.matmul(attention_map, value)  # 컨텍스트 벡터를 리턴해준다.

 * 위에서 마스킹을 하는 방법은 mask 영역에 매우 작은 숫자를 할당해줘서 softmax를 거치며 0에 가까운 값을 갖도록 해주는 것이다.

 

 총과정에 대해 수식으로 정리해보면 $ Attention(Q,K,V) = C^{T}V =Softmax(\frac{QK^{T}} {\sqrt{d_{k}}})V$ 으로 표현할 수 있다.

여기까지 스케일 내적 어텐션을 완성했다.

반응형