TIL_220916 Transformer

Alice1304·2022년 9월 16일

AIB SUMMARY

목록 보기
1/12

핵심단어

Transformer, BERT, GPT

warm-up

Attention is All You Need - 이 문구 때문에 뉴진스 노래가 계속 머리에 맴돌았다
링크텍스트

간략정리

Transformer는 RNN에 비해 학습 속도도 빠르고, 성능도 더 좋다(RNN사용안함)
트랜스포머는 병렬로 데이터를 처리한다
인코더디코더의 컨셉만 가져가고 RNN을 제거하여 학습시간을 단축했다

학습목표

  1. 코드를 이해하지 못하더라도 개념은 이해하고 넘어가기
  2. 왜 Attention is All You Need 인지 이해하기

1. 트랜스 포머란?

  • Transformerattention 매커니즘을 극대화한 기계 번역을 위한 새로운 모델
  • 성능이 좋아서 최근 자연어 처리 모델(SOTA)의 기본 아이디어가 모두 트랜스포머 기반
  • 자연어 처리가 아닌 다른 문제도 잘 풀고 있음!(부럽당..)
  • RNN기반 모델이 가진 구조적 단점은 단어가 순차적으로 들어온다는 점인데
  • 처리해야 하는 시퀀스가 길수록 연산 시간이 길어진다
  • 항상 우리는 올바른 답을 찾기 때문에 이런 단점을 해결 하기 위해 등장한 모델!
  • 모든 토큰을 동시에 입력받아 병렬처리 하기 때문에 GPU 연산에 최적화 되어있음!

트랜스포머의 구조를 단순하게 시각화한 그림은 아래 참고
트랜스포머는 인코더 블록과 디코더 블록이 6개씩 모여있는 구조이다.
왼쪽은 인코더 블럭, 오른쪽은 디코더 블럭

내부 구조를 살펴보면

그럼 다시 이걸 하나하나 뜯어보자

Positional Encoding (위치 인코딩)

??포지셔널 인코딩 왜 필요한가??

트랜스포머에서는 병렬화를 위해 모든 단어 벡터를 동시에 입력 받는데
컴퓨터는 바-보라서 어떤 단어가 어디에 위치한지 모름!
그래서 컴퓨터에게 단어의 위치정보를 알려주기 위해 위치정보를 담은 벡터를 제공해줘야 함
이런 단어의 상대적인 위치 정보(위치는 아무래도 상대적이지..)를 담은 백터를 만드는 과정을
Positional Encoding (위치 인코딩) 이라고 한다.

  • 아래는 코드- 다 숙지는 안되지만 일단 한 번 훑고 지나가기!
def get_angles(pos, i, d_model):
    """
    sin, cos 안에 들어갈 수치를 구하는 함수입니다.
    """
    angle_rates = 1 / np.power(10000, (2 * (i//2)) / np.float32(d_model))
    return pos * angle_rates
def positional_encoding(position, d_model):
    """
    위치 인코딩(Positional Encoding)을 구하는 함수입니다.
    """
    angle_rads = get_angles(np.arange(position)[:, np.newaxis],
                          np.arange(d_model)[np.newaxis, :],
                          d_model)

    # apply sin to even indices in the array; 2i
    angle_rads[:, 0::2] = np.sin(angle_rads[:, 0::2])

    # apply cos to odd indices in the array; 2i+1
    angle_rads[:, 1::2] = np.cos(angle_rads[:, 1::2])

    pos_encoding = angle_rads[np.newaxis, ...]

    return tf.cast(pos_encoding, dtype=tf.float32)

Self-Attention (★★★★★)

셀프어텐션은 세가지 가중치 벡터를 대상으로 어텐션 적용

The animal didn't cross the street because it was too tired
위 문장을 제대로 번역하라면 'it'과 같은 지시 대명사가 어떤 대상을 가르키는 지 파악해야 하는데
트랜스포머에서는 번역하려는 문장 내부 요소의 관계를 파악하기 위해
문장 자신에 대한 어텐션 매커니즘 적용하고, 이를 Self-Attention 이라고 한다!

  • 어텐션을 이해하기 위해서 Attention을 다룰 때 등장했던 검색 시스템 아이디어를 보자!

  • 검색시스템은 3단계를 거쳐 작동하는데

  1. 찾고자 하는 정보에 대한 검색어 입력(Query)
  2. 검색어와 가장 비슷한 키워드 찾기 (Key)
  3. 해당 키워드(Key)와 연결된 페이지 (Value) 보여주기
  • 각각의 벡터는
    • 쿼리(q) : 분석하고 자하는 단어에 대한 가중치 백터
    • 키(k) : 각 단어가 쿼리에 해당하는 단어와 얼마나 연관있는지 비교하기 위한 가중치 백터
    • 벨류(v) : 각 단어의 의미를 살려주기 위한 가중치 백터

Self-Attention은 위의 세 가지 백터를 대상으로 어텐션을 적용하며
그 방식은 기존 Attention 매커니즘과 거의 동일

  1. 먼저, 특정 단어의 쿼리(q) 벡터와 모든 단어의 키(k) 벡터를 내적, 내적을 통해 나오는 값이 Attention 스코어(Score)

  2. 트랜스포머에서는 이 가중치를 q,k,v 벡터 차원 dkd_k 의 제곱근인 dk\sqrt{d_k} 로 나누어줍니다.
    계산값을 안정적으로 만들어주기 위한 계산 보정. (바로 소프트 맥스를 먹이는 게 아님)

  3. 다음으로 Softmax를 취해줍니다. 이를 통해 쿼리에 해당하는 단어와 문장 내 다른 단어가 가지는 관계의 비율을 구할 수 있습니다.

  4. 마지막으로 밸류(v) 각 단어의 벡터를 곱해준 후 모두 더하면 Self-Attention 과정이 마무리됩니다.

정리하자면
인풋- 임베딩 - 쿼리/키 백터내적 - 스코어 출력 - 가중치를 q,k,v의 벡터차원의 제곱근으로 나누기 - 소프트 맥스 - 밸류x소프트맥스 - sum !

너무 어려워 너무어려워!!!

아래는 일단 코드. 마찬가지로 이해는 반밖에 몬했다. (ㅜ_ㅜ)

def scaled_dot_product_attention(q, k, v, mask):
    """
    Attention 가중치를 구하는 함수입니다.
    q, k, v 의 leading dimension은 동일해야 합니다.
    k, v의 penultimate dimension이 동일해야 합니다, i.e.: seq_len_k = seq_len_v.

    Mask는 타입(padding or look ahead)에 따라 다른 차원을 가질 수 있습니다.
    덧셈시에는 브로드캐스팅 될 수 있어야합니다.
    
    Args:
        q: query shape == (..., seq_len_q, depth)
        k: key shape == (..., seq_len_k, depth)
        v: value shape == (..., seq_len_v, depth_v)
        mask: Float tensor with shape broadcastable 
            to (..., seq_len_q, seq_len_k). Defaults to None.
        
    Returns:
        output, attention_weights
    """

    matmul_qk = tf.matmul(q, k, transpose_b=True)  # (..., seq_len_q, seq_len_k)
    
    # matmul_qk(쿼리와 키의 내적)을 dk의 제곱근으로 scaling 합니다.
    dk = tf.cast(tf.shape(k)[-1], tf.float32)
    scaled_attention_logits = matmul_qk / tf.math.sqrt(dk)

    # 마스킹을 진행합니다.
    if mask is not None:
        scaled_attention_logits += (mask * -1e9)  

    # 소프트맥스(softmax) 함수를 통해서 attention weight 를 구해봅시다.
    attention_weights = tf.nn.softmax(scaled_attention_logits, axis=-1)  # (..., seq_len_q, seq_len_k)

    output = tf.matmul(attention_weights, v)  # (..., seq_len_q, depth_v)

    return output, attention_weights

그리고 위와 같은 형태가 여러개 모여있는... Multi-head Attention으로 넘어가기..

Multi-Head Attention

Multi-Head Attention 을 적용하면 여러 개의 Attention 메커니즘을 동시에 병렬적으로 실행
각 헤드마다 다른 Attention 결과를 내어주기 때문에 앙상블과 유사한 효과를 얻을 수 있고,
병렬화 효과를 극대화 할 수 있음

예시를 그림으로 보자면

이렇게 각각의 z0 ~ z7까지 (예시가 8인 경우!) 행렬을 출력하고, 이후에 이어 붙이기.
그리고 또 다른 파라미터 행렬인 w0와의 내적을 통해 최종 결과 출력

최종적으로 생성된 행렬z는 입력 행렬x와 동일한 형태를 가지고 있음

Layer Normalization & Skip Connection (ADD & Norm)

트랜스포머의 sub-layer에서 출력된 벡터는 Layer normalization과 Skip connection을 거치게 됨
Layer normalization의 효과는 Batch normalization과 유사 학습이 훨씬 빠르고 잘 되도록 함
Skip connection(혹은 Residual connection)은 역전파 과정에서 정보가 소실되지 않도록 함

Feed Forward Neural Network

ADD&Norm 를 거친 이후 FFNN(Feed forward neural network) 로 들어감
은닉층의 차원이 늘어났다가 다시 원래 차원으로 줄어드는 단순한 2층 신경망
활성화 함수(Activation function)으로 ReLU를 사용함

def point_wise_feed_forward_network(d_model, dff):
    """
    FFNN을 구현한 코드입니다.

    Args:
        d_model : 모델의 차원입니다.
        dff : 은닉층의 차원 수입니다. 논문에서는 2048을 사용하였습니다.
    """
    return tf.keras.Sequential([
        tf.keras.layers.Dense(dff, activation='relu'),  # (batch_size, seq_len, dff)
        tf.keras.layers.Dense(d_model)  # (batch_size, seq_len, d_model)
    ])

Masked Self-Attention

Masked Self-Attention은 디코더 블록에서 사용하기 위해서 마스킹 과정이 포함된 Self-Attention

언어 모델에서 디코더가 단어를 생성할 때에는 Auto-Regressive(왼쪽 단어를 보고 오른쪽 단어를 반복하여 예측)하게 진행됨.
RNN을 사용한 번역 모델에서도 생성하려는 단어 이후의 왼쪽에 있는 단어 정보만을 고려하여 단어를 생성함.

이러한 방법은 트랜스포머에서도 유지해주게 되는데
단어가 순차적으로 입력되는 RNN과 달리 트랜스포머에서는 타깃 문장 역시 한 번에 입력되기 때문에
해당 위치 타깃 단어 뒤에 위치한 단어는 Self-Attention에 영향을 주지 않도록 마스킹(Masking)을 해주게 됨!

  • SELF-ATTENTION을 할 때, SOFT-MAX를 취해주는 과정이 있는데그 그 전에 가려 주려는 과정에 아주 작은 값(-무한대)을 더해준다.
  • 마스킹하는 과정이 들어간다는 것을 제외하고는 multi-head attention과 동일하다는 점. Cheating을 막기 위해 마스킹이 들어간다는 점 기억하기

Encoder-Decoder Attention

디코더에서 Masked Self-Attention 층을 지난 벡터는 Encoder-Decoder Attention 층으로 들어가는데 좋은 번역을 위해서는 번역할 문장과 번역된 문장 간의 관계가 중요하다.(당연)
번역할 문장과 번역되는 문장의 정보 관계를 엮어주는 부분이 바로 이 부분.

이 층에서는 디코더 블록의 Masked Self-Attention으로부터 출력된 벡터를 쿼리(Q) 벡터로 사용합니다.
키(K)와 밸류(V) 벡터는 최상위(=6번째) 인코더 블록에서 사용했던 값을 그대로 가져와서 사용하는데 Encoder-Decoder Attention 층의 계산 과정은 Self-Attention 했던 것과 동일하다.

Linear & Softmax Layer

디코더의 최상층을 통과한 벡터들은 Linear 층을 지난 후 Softmax를 통해 예측할 단어의 확률을 구하게 된다.

참고 REFERENCE

https://gall.dcinside.com/mgallery/board/view/?id=thesingularity&no=57914
-- 동기분이 공유해준 글인데 훑어보니깐 대강~ 이해에 도움이 된다.


학습후기

지난 주 학습이 촘촘하지 않다보니깐 파란 하늘에 구름이 떠다니는 것처럼 부분,,부분 이해가 되는데 결국 뭐든 반복학습이 답이다! 개념은 와닿지만 코드가 안 와닿는 부분은 코드를 계속 반복하면 될일 같고, 딥러닝 자체 개념이 부족한 부분은 주말에 복습으로 극복하기로!
아쟈쟈 화이팅!

profile
기록기록

0개의 댓글