본문 바로가기
Machine Learning/유튜브, 책, 아티클 정리

만들면서 배우는 생성AI 9장 : 트랜스포머 모델

by W_log 2023. 12. 12.

오늘날 모든 GPT와 모든 생성AI 모델의 근간이라고 볼 수 있는 트랜스포머 모델에 대해 배우는 장입니다. 사실 내부적으로 뜯어보면 굉장히 이해하는데 오래걸리는 모델인데, 이번 기회에 찬찬히 뜯어보려고 합니다.

 

 

Transformer 모델

 

1. Attention Mechanism

 

보통 완성되지 않은 문장에서 다음 단어를 예측하는데 있어서 인간은 문장 속 여러 단어들을 통해서 힌트를 얻습니다. 하지만 개별 단어들이 각기 동일한 중요도를 가질까요? 아닙니다. 

 

어느 벌판에서 회색 코끼리가 ㅇㅇㅇㅇ. 라는 문장에서 회색이라는 단어는 사실 다음 동사를 예측하는데 전혀 기여를 하지 않습니다. 

즉, 다음 단어를 예측하는데 있어서 중요한 단어가 따로 있다는 것입니다. 그렇다면 모델도 같은 메커니즘으로 다음 단어를 예측하게 하면 좋지 않을까가 어텐션 메커니즘의 핵심입니다.

 

관련없는 단어들 속에서 유용한 정보가 가려지는 일을 막고, 유용한 정보를 뽑도록 훈련시키는 신경망을 추가한 것입니다. 이전의 RNN에서는 이런 역할을 hidden vector에서 했는데 이미 순환이 진행되면서 너무 많은 단어가 포함되어버려 개별 단어를 예측하는데 도움이 안되는 정보가 많이 포함되어 있습니다. 

 

그래서 우선 핵심 아이디어는 여러 문장에서 개별 단어별로 영향을 끼치는 주요 단어가 어떤 것이 있는지를 신경망을 통해 학습시키는 것입니다.

 

 

위의 그림처럼 홈런이라는 단어를 예측하는데 있어서 "A", "baseball", "player", "hit", "a" 라는 단어가 주는 영향도는 모두 다를 것이기 때문에 단순히 문장 자체를 학습하는 것 외에도 각 단어별 중요도를 함께 학습시킨다고 보면 됩니다. 

 

이게 encoder와 decoder일 때 좀 다른데 encoder는 문장 전체에서 각 단어별로 다 중요도를 학습시키는 개념이고, decoder는 다음 단어를 예측하는 형태(번역 모델에서는 다음 번역어로 뭐가 나올지를 예측)로 학습이 들어간다.

 

2. Query, Key, Value 

 

위에서, 우리는 각 문장에서 단어별로 어떤 단어와 가장 연관이 깊은지를 학습하는 것이 Attention이라고 했습니다. 이걸 기계에게 어떻게 학습시킬지를 실제로 구현한 방법론이 Query, Key, Value입니다.

 

책에서 설명이 잘 나와있는데 정리해보면 이와 같다.

 

우리는 "어떤 단어 x"에 대해서, "문장 속 다른 단어들과의 중요도"를 학습시켜서 "task에 도움이 되는 정보"를 제공하는 것이 목표입니다.

 

그에 따라 어떤 단어를 특정해야하기 때문에 이 x를 Key라고 부릅니다. 다음 단어 예측하려면 현재 단어가 query라고 볼 수 있습니다.

두번째로 문장 속 다른 단어들과의 중요도를 학습해야합니다. query와 각 단어간의 유사도, 관계를 의미하는 벡터가 key입니다. 그리고 각 key에 해당하는 단어들이 실제로 가지고 있는 의미, 단어 그 자체를 내포하는 벡터를 value라고 합니다.

 

헷갈렸던 점
여기서 key, value가 나오는데, 어차피 Key나 value나 둘다 단어의 임베딩 벡터에서 추출하는데 왜 중요도를 바로 하나의 벡터로 표현하지 않고, 키와 밸류라는 헷갈리는 개념을 나눠서 넣었나 이해하는데 좀 시간이 걸렸습니다.

이해한 바로는 각자 기계에게 학습시키고자 했던 의미가 달라서이지 않나입니다. 우리가 똑같은 단어를 쓰더라도 그 단어 자체가 주는 의미는 단어가 위치한 문장에 따라 다른 의미를 가지게 됩니다.

따라서 우리가 기계에게 단순히 단어가 가진 의미만을 부여하는게 아니라 문장에서의 관계까지 별도로 전달하기 위해서 일부러 역할을 구분한 것이 아닐까 생각이 들었습니다.

 

이제 이걸 식으로 표현하면 아래와 같이 표현할 수 있다.

 

 

3. Multi-head Attention

 

Query, Key, Value를 거쳐서 하나의 벡터를 내뱉는데 이를 Attention Head라고 한다. 사실 하나의 모델이 모든 것을 다 전달한다는 보장은 없기 때문에 마치 앙상블 모델처럼 여러개의 어텐션 헤드를 붙이는 기법을 썼다.

 

4. 코잘 마스킹

 

딥러닝의 장점은 병렬 연산입니다. 설명을 위해 단어 한개 중심으로 설명했지만 실제 학습시켜야할 데이터셋은 너무 많기 때문에 한번에 하나의 문장 속 모든 단어들에 대한 어텐션 헤드를 계산하도록 학습시킵니다. 여기서 주황색이 key이고, 빨간색이 Query이다. 

 

다만, 이 때 Decoder(새로운 문장을 만들어내는 기기)의 경우에는 해당 쿼리 다음에 출력할 단어가 정답인데 이를 이미 알려준채로 학습시키면 안되기 때문에 아래와 같이 회색 처리를 해서 Masking을 해서 알려주지 않고 신경망을 통하게 한다.

 

 

 

def causal_attention_mask(batch_size, n_dest, n_src, dtype):
    i = tf.range(n_dest)[:,None]
    j = tf.range(n_src)
    m = i <= j - n_src + n_dest
    mask = tf.cast(m, dtype)
    mask = tf.reshape(mask, [1, n_dest, nsrc])
    mult = tf.concat(
        [tf.expand_dims(batch_size, -1), tf.constant([1,1], dtype = tf.int32)], 0)
    return tf.tile(mask, mult)

np.transpose(causal_attention_mask(1, 10, 10, dtype = tf.int32)[0])

 

 

5. Layer Normalization

 

Batch Normalization이 아닌 Layer normalization을 해주는데 문장마다 나오는 값들에 대해서 정규화를 해준다고 생각하면 이해가 간다. 문장의 의미들을 파악하는 작업이기 때문에 각 문장으로 부터 layer에서 나오는 값들을 정규화해야 차이 없이 이해한다고 볼 수 있다.

 

 

6. 위치 인코딩

 

사실 우리가 벡터를 순서대로 입력하긴 했지만, 실제로 연산하는 과정에서는 병렬로 계산하기 때문에 단어의 순서에 대한 정보는 빠진채로 학습된다. 따라서 이 정보를 제공하려고 하는데 여기서는 토큰 임베딩과 위치 인코딩을 더해서 학습을 시킨다.

 

우리가 로지스틱 회귀분석을 할 때 0에서 1로 수렴하고, 일정 값 이상에서 확실하게 0과 1에 가까워진다는 점을 활용해서 시그모이드 함수를 binary 예측 문제에서 도입했듯이 위치 인코딩에 활용되는 함수도 우리가 원하는 특성과 닮았기 때문에 저 함수를 도입했다고 보면 된다. 일종의 테크닉이다. 

 

위치를 표현하려면 어떤 특징을 가져야할까?

  1. 같은 위치의 토큰은 항상 같은 위치 벡터값을 가져야 한다. 
  2. 하지만 서로 다른 위치의 토큰은 위치벡터 값이 달라야 한다. 
  3. 모든 위치 값이 너무 커지면 안된다. 너무 커지게 되면 단어가 가지고 있는 임베딩 벡터의 정보보다는 위치 벡터의 정보가 너무 커질 수 있기 때문이다. (주기 함수처럼 일정 범위 안에 존재하는게 좋다.)

이 모든 것을 고민했을 때 나온 결과물이 코사인, 사인 함수의 조합을 사용했고 공식은 아래와 같다. 

 

 

 

7. 트랜스포머 블록

 

위에서 얘기한 모든 것들을 하나씩 쌓아가서 표현해보려고 합니다. 일단 트랜스 포머 블록은 아래와 같이 표현할 수 있습니다.

 

 

class TransformerBlock(layers.Layer):
    def __init__(self, num_heads, key_dim, embed_dim, ff_dim, dropout_rate = 0.1):
        super(TransformerBlock, self).__init__()
        self.num_heads = num_heads
        self.key_dim = key_dim
        self.embed_dim = embed_dim
        self.ff_dim = ff_dim
        self.droupout_rate = dropout_rate
        self.attn = layers.MultiHeadAttention(
            num_heads, key_dim, output_shape = embed_dim)
        self.dropout_1 = layers.Dropout(self.dropout_rate)
        self.ln_1 = layers.LayerNormalization(epsilon = 1e-6)
        self.ffn_1 = layers.Dense(self.ff_dim, activation = "relu")
        self.ffn_2 = layers.Dense(self.embed_dim)
        self.dropout_2 = layers.Dropout(self.dropout_rate)
        self.ln_2 = layers.LayerNormalization(epsilon = 1e-6)
    
    
    def call(self, inputs):
        input_shape = tf.shape(inputs)
        batch_size = input_shape[0]
        seq_len = input_shape[1]
        causal_mask = causal_attention_mask(
            batch_size, seq_len, seq_len, tf.bool)
        attention_output, attention_scores = self.attn(
            inputs, inputs, attention_mask = causal_mask, return_attention_scores = True)
        attention_output = self.dropout_1(attention_output)
        out1 = self.ln1(inputs + attention_output)
        ffn_1 = self.ffn_1(out1)
        ffn_2 = self.ffn_2(ffn_1)
        ffn_output = self.dropout_2(ffn_2)
        return (self.ln_2(out1 + ffn_output), attention_scores)

 

 

8. 전체 구조

 

 

GPT

1. Transformer -> GPT

 

방대한 양의 텍스트 데이터 -> 시퀀스의 다음 단어를 예측하도록 트랜스포머 구조를 훈련한 다음 특정 후속 작업에 맞게 미세튜닝했습니다.

세부적인 구조는 계속 변경되었겠지만 기본적인 구조는 유지된채로 GPT 3.5, 4 이런식으로 숫자가 늘어나면서 더 많은 데이터셋들을 추가하면서 발전해왔습니다. (우선 핵심은 데이터의 크기가 늘어날수록 성능은 좋아진다는 것입니다.)

 

사실 처음에 의문이 들었던 점은 Transformer 논문에서 만들어졌던 모델은 번역을 잘하는 모델인 반면에 LLM들은 실제로는 대화가 가능한 모델이어서 어떻게 가능할까 생각했는데 결론적으로는 무수히 많은 데이터셋을 넣어서 "지능이 높은" pre trained model을 만든 후에 이제 세부 task가 가능하게 추가적인 학습을 시킨거라고 보면 될 것 같다.

 

2. attention_score의 해석

 

책에 나오는 예시인데, 이렇게 각 단어 다음으로 가장 확률이 높은 단어를 출력한다고 볼 수 있다. 또한 이 단어를 출력하는데 있어서 어떤 단어가 가장 영향을 미쳤는지 명암으로 이해할 수 있다. 

 

 

 

3. 다른 트랜스포머

 

전체 모델에서 디코더, 인코더가 각각 어떻게 구성되어 있는지에 따라 분류할 수 있습니다.

 

1. T5

 

위에서 우리가 배운 트랜스포머 논문과 거의 동일하게 인코더와 디코더가 동시에 있는 모델이다. 특이하게 GPT는 디코더만 있다. 하지만 디코더 자체에도 마스크드 멀티헤드 & 전체 문장에 대해 볼 수 있는 멀티헤드 어텐션도 따로 있어서 전체 맥락을 파악할 수 있는 것으로 보인다.

 

2. GPT 3, 4

 

사실 GPT 3까지는 정말 데이터 수 빼고는 거의 비슷한 구조로 훈련된다. 하지만 4의 경우에 RLHF라는 기술을 사용해서 좀 더 의도한대로 답변을 내놓도록 해서 결과의 성능을 높였다. 

 

간단한 작동 구조는 아래 그림을 살펴보시면 이해가 된다.

 

기본적으로 RLHF 역시도 신경망을 학습시키는 것이고, 핵심은 하나하나 출력에 대해서 점수를 평가하는게 데이터를 얻기 어렵기 때문에 각 답변들에 대해서 등수를 평가하는 형태로 문제를 바꾼 것이다.

 

ChatGPT의 추가 튜닝 방식

  1. 1차적으로 평가자들이 좋다고 생각하는 프롬프트와 데이터(직접 작성할 수도 있음)에 대해서 추가 학습시킨다.
  2. 여러 모델들에 대해 비교 데이터를 수집하고 보상 모델(여러 답 중에 어떤 답이 좋은지 평가하는)을 훈련시킵니다.
  3. 1번에서 최적화된 모델에 대해서, 답을 내면 이 답에 대해 보상 모델이 점수를 출력합니다. 이 때 Proximal Policy Optimization으로 보상을 더 올리기 위해 가중치를 조정하는 식으로 작동한다.

 

 

 

 

참고 자료