chullhwan-song/Reading-Paper

BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding

chullhwan-song opened this issue · 3 comments

Abstract

  • Bert : Bidirectional Encoder Representations from Transformers
  • transformer를 기본 base로 하고 있음.
  • transfer learning
  • 즉, BERT는 Attention is all you need제안한 Transformer(Encoder:밑의 Transformer그림 참조)를 사용 > pre-training & transfer learning하는 동안 아키텍처를 조금 다르게하여 Transfer Learning을 용이하게 만드는 것이 핵심

Bert에서의 Transformer 적용 - Transformer

Input

  • 일반적으로 우리는 보통?
    image
    • 30522개의 vocabulary(어휘)에 768 dim의 embedding vector를 의미
    • 이제 여기에다가 Positional Embedding 개념을 adding
    • 모든 sentence의 첫번째 token은 언제나 [CLS](special classification token) . 이 [CLS] token은 transformer 전체층을 다 거치고 나면 token sequence의 결합된 의미를 가짐.
      • 여기에 간단한 classifier를 붙이면 단일 문장, 또는 연속된 문장의 classification을 쉽게 할 수 있게 됩니다. 만약 classification task가 아니라면 이 token은 무시.

Positional Embedding

  • transformer 에서..> Positional Encoding
  • 이는 RNN계열을 사용하지 않아서 발생한 문제라 볼수있다.
    • Seq 입력값을 받아 처리하는 RNN을 사용
    • 나는 너를 믿는다 <> 믿는다 너를 나는 : 이 케이스는 구분하지(?)못한다. 즉, 위치별 정보를 주지 않는다면, 같다고 인식하기 때문이다.
    • RNN구조를 포기함으로써 나타나는 부작용인 sequential 순서 정보를 보존하기 위한 것
    • 단어 위치 index를 삼각함수(sinusoid function)에 도입하여 단어가 다른 단어와 어느정도 가까이 있는지를 표현하는 것
      image
    • 문자에서 word(token)에 대한 위치에 대한 정보를 주입 > Positional Encoding
    • 위의 빨간색 그림.
    • 이는 학습과 상관없으면서, 미리 정의된 sinusoid function를 이용한다.
      • sine & cosine 함수를 번갈아가면 적용(홀수번째, 짝수번째...)
        image
        image
        image
    • sinusoid function 이용한다는 것은 [-1, 1] 사이값을 입력값의 토큰마다 추가
      • 이는 원래 embedding된 input(=w2v을 이용한 일반적인 입력값)에다 이 값을 추가한다는 의미
    • 이는 상대적위치 + 절대적 위치를 Network가 이해하도록하기 위해..
    • 결국, 단어가 위치에 따라 다른값을 가지게 되고, 같은 단어라도 위치가 다르면 다른 값을 가진다.
    • 소스
def position_encoding(sequence_length, dim_model):
        # First part of the PE function: sin and cos argument
        position_enc = np.array([[pos / (10000 ** (2 * i / dim_model)) for i in range(dim_model)] for pos in range(sequence_length)])

        # Second part, apply the cosine to even columns and sin to odds.
        position_enc[:, 0::2] = np.sin(position_enc[:, 0::2])  # dim 2i
        position_enc[:, 1::2] = np.cos(position_enc[:, 1::2])  # dim 2i+1
        output = tf.convert_to_tensor(position_enc, dtype=tf.float32)

        return output
def get_angles(pos, i, d_model):
  angle_rates = 1 / np.power(10000, (2 * (i//2)) / np.float32(d_model))
  return pos * angle_rates

def positional_encoding(position, d_model):
  angle_rads = get_angles(np.arange(position)[:, np.newaxis],
                          np.arange(d_model)[np.newaxis, :],
                          d_model)

  # 배열의 짝수 인덱스(2i)에는 사인 함수 적용
  sines = tf.math.sin(angle_rads[:, 0::2])
  # 배열의 홀수 인덱스(2i+1)에는 코사인 함수 적용
  cosines = tf.math.cos(angle_rads[:, 1::2])
  pos_encoding = np.concatenate([sines, cosines], axis=-1)
  pos_encoding = pos_encoding[np.newaxis, ...]    
  return tf.cast(pos_encoding, dtype=tf.float32)
 
# 이를 실행
pos_encoding = positional_encoding(50, 512)
print (pos_encoding.shape) # 크기 출력

plt.pcolormesh(pos_encoding[0], cmap='RdBu')
plt.xlabel('Depth')
plt.xlim((0, 512))
plt.ylabel('Position')
plt.colorbar()
plt.show()
  • 소스2를 보면
    • 50 × 512의 크기를 가지는 포지셔널 인코딩 행렬을 시각화
    • 각 단어가 512차원의 임베딩 벡터를 가질 때 사용
      image

BERT 의 INPUT

  • bert는 transformer와 달리, Positional Encoding이 아니라 Positional Embedding 용어로 사용한다라고 언급
    • 실제론 Positional Encoding == Positional Embedding 같은 의미인듯.?
    • 하다보니, 구성에 잘 안맞게 위의 Position Embeddings를 먼저 설명하는 모양새가 됬다.
      image
    • 위의 그림에서 첫번째 입력값을 보면, 즉, Sentence pair는 합쳐져서 single sequence로 입력. 다시 말해 그 입력값은 여러개의 문장으로 이루어질수 있고 단 하나의 문장일수도 있다.
      • 다만, 두개의 문장을 구분하기 위해 [SEP] token을 적용. > QA의 경우 [Question, Paragraph]인데 이를 구분할때도 적용한다.
      • 뒤에 나오지만 또한번 문장을 구분하기 위해, Segment embedding를 적용한다.
    • 바로 위의 그림을 보면, bert는 Token Embeddings+Segment Embeddings+Position Embeddings형태로 확장
    • Token Embeddings는 이미 첫번째 이미지로 이미 설명
    • Segment embedding ?
      • 이 경우 갑자기 두개의 sentence (Pair)를 받는다고 나와있어서..당황스러운데(위에서 설명)..그건 실제 코드에서..확인해야할듯..
      • Segment embedding을 사용하여 앞의 문장에는 sentence A embedding, 뒤의 문장에는 sentence B embedding을 더해줍니다.(모두 고정된 값) ? BERT 논문정리 에서 이런말을 했는데..??
      • 일단, 두개의 문장이 서로 의미적으로 유사한지 판단하기 위해,
      • 예를들어, “I like cats”, “I like dogs” 이면,
        image
        • 그림에서보면, 나머지 두개의 embedding과 shape(or dim)을 맞추기위해, 0와 1로 채우는듯하다. 즉, 768차원을 문장 구분하여 첫번째 문장에 0으로, 두번째 문장에 1로, 세번째 문자에 2으로..채우는듯하다.(더 봐야함.)
          • 참고로 2가 아니라 0과 1로만 할수도 있을수도...? 좀더 이부분확인필요!!
    • Position Embeddings > 위에서 설명

Transformers

  • Multi Head Attention
    image
    • Scaled Dot Product Attention > Multi Head Attention를 구성하는 core concept
    • 자세히 보면 입력값이 3개: K? Q? V? >짜증나게 논문에서는 설명이 거의 없다.
      • Q: Query vector, K:Key vector, V: Value vector > 라고 나와있는데 당췌!!! 와닿지 않는다.
        image
        • 같은 입력값에 각각 3개의 linear transformation 나온 결과를 K, Q, V로
      • 그래서, 왼쪽 그림 : Scaled Dot Product Attention 을 다음 수식으로 나타내고, 하나의 Head를 의미.
        image
        image
      • 오른쪽 그림은 이 Scaled Dot Product Attention를 concat한 결과, 즉 Multi Head Attention에 대한 수식은
        image
      • 이 그림보면 구조에 대한 이해도가 올라갈듯~
        image
    • 다시 > Q, K, V에 대해 알아보자..당췌 몬지 모르겠다. 여기서 부터는 Attention 개념정리 부제 : 게임의 구조로 해석한 attention를 자료를 참조함.
      • 앞에서 보면, Q,K,V > 원래 BERT의 입격값 = Token Embeddings+Segment Embeddings+Position Embeddings = X를 각각 이X 받아 각각 독립된 변수를 가진 linear transform(wx+b)를 실행한 결과가 Q,K,V 라고 이해가 된다.
      • Q, K의 관계가 중요한것같다.
      • Q는 어떤 정해진 frame같고, K는 그 Q안에 들어가야할 적당(어울리는)한 값들을 의미하는것같다.
        • 야구라고 가정하면, 이미 룰에 의해 정해진 1~9번타자가 존재하는데 바로 이 틀을 Q = Qi = {Q1,,Q9}
          • 어떠한 기준을 의미하는듯 > 최적의 값은 정해져 있지 않고, 어떤 상대팀을 만나느냐에 따라 변한다.
        • Q는 이틀에 적당한 실 선수들 : Xj = { K1,,, k9} > 선수들이므로 꼭 9명은 아닐듯..더많을수도
        • 그래서, attention관점에서보면 Ci = Attn(Xj, Qi) > 이는 각 야구에서의 포지션별 적합도 즈그 cosine distance( dot product )에 의해 그 적합도를 판단 > 위의 그림에서, MatMul
      • 앞에서 Q와 K의 관계가 중요하다고 했는데, 이를 다시 말한다면, Q-K의 alignment가 매우 중요하다는것을 의미한다.
      • 이제 야구가 아니고, 문장의 입장에서 보면, Q는 품사(주어, 목적어, 동사..등 또한 품사는 문장의 위치와도 매우 관련성이 크다?) 정도 일거고, 들어오는값 >(X가 hidden layer를 통해 변형된 S(X....=====...S)>) K가 어떤 품사 Q 적합한지에 대한 relation이라고 봐야할듯하다.
      • 이 그림을 자세히 보면 이해가 더 쉬울수 있다. 위의 attention 수식과 맞물려 생각하면 좋음.
        image
      • Q와 K는 초기의 attention 연구:Neural Machine Translation by Jointly Learning to Align and Translate 간단리뷰와 비교해보면 다음과 같다는 의미.
        image
      • 이제 attention 수식은 다음과 같이 생각해볼수 있다.
        image
        • X가 각 품사에 대한 확률(attention)에 대한 곱으로 정의(확장..) > 위의 그림/수식들을 상기시켜보면 알수 있음. > X가 Q에 alignment될 확률 P를 곱하다는 의미
        • Q-K의 relation인 Z를 discrete한 latent variable로 생각한다면, atttention p(z|x, q)는 CRF(conditional random field)로 볼 수 있다.
  • 한번더!!!! : 딥 러닝을 이용한 자연어 처리 입문 14. 트랜스포머(Transformer) 1) 트랜스포머(Transformer) 포스트를 보고, 좀더(다른관점?에서) 이해해보자.
    • 입력 - 위의 Positional Embedding > 소스2
    • 자세히 보면 입력값이 3개: K? Q? V? >짜증나게 논문에서는 설명이 거의 없다. (위에서부터 다시..다른설명)
      • attention함수는 주어진 '쿼리(Query)'에 대해서 모든 '키(Key)'와의 유사도를 각각 구하고,
      • 이를(이 유사도를) 가중치로 하여 Key와 맵핑되어있는 각각의 '값(Value)'에 반영,
      • 그리고 유사도가 반영된 '값(Value)'을 모두 가중합하여 리턴.
      • 이는 wx+b형태고 생성.
        image
        • 논문과 같이 dmodel=512이고 num_heads=8라면, 각 벡터에 3개의 서로 다른 가중치 행렬을 곱하고 64의 크기를 가지는 Q, K, V 벡터를 획득 = 64*8=512
    • Scaled dot-product Attention
      • q*k : 어떤 k와 유사한지 check
      • 여기에 scaling 개념 추가 > 위의 Q, K, V의 차원 n=64의 값에다 root를 쒸어 나누어줌. > 실제적으론 dk이므로 > K의 차원임.
        image
        • 이는 Attention score를 계산하는 과정임
        • "I am a student" 에 대한 예
        • Q(query):단어의 I는 K에 대한 연관된 정도(attention weight)를 계산 > I, am, a, student를 각각 계산한다.
        • (query):단어의 I에 대해서만 나오는데, 나머지도 똑같은과정을 걷쳐야한다.
      • Attention Score를 계산했으니, attention distribution을 계산. > softmax을 이용함 이후 . >이 attention distribution x V 를 계산하면 attention value를 계산
        • 정리 : Attention Score(Q, K) >Attention distribution = softmax(Attention Score(Q, K)) > Attention value = softmax(Attention Score(Q, K))xV
          image
          • 언급한데로 Q:단어I 외, 다른 단어들도 똑같은 과정을 걷힘.
      • 위의 과정을 따로따로 아닌 행렬단위로 한다면(실제 코딩에서는 당연히 이렇게..)
        image
        image
        image
        * 소스
# Q 행렬, K 행렬, V 행렬, 마스크 수행 여부
def scaled_dot_product_attention(query, key, value, mask): 
  matmul_qk = tf.matmul(query, key, transpose_b=True)
  # Q 행렬과 K 행렬을 곱한다. 즉, 어텐션 스코어 행렬을 얻는다.

  dk = tf.cast(tf.shape(key)[-1], tf.float32)
  logits = matmul_qk / tf.math.sqrt(dk)
  # 스케일링.

  if mask is not None:
    logits += (mask * -1e9)
    # 필요하다면 마스크를 수행한다. 해당 조건문이 어떤 의미인지는 뒤에서 설명하며 현재는 무시.

  attention_weights = tf.nn.softmax(logits, axis=-1)
  # 소프트맥스 함수를 사용하여 어텐션 가중치들. 즉, 어텐션 분포를 얻는다.

  output = tf.matmul(attention_weights, value)
  # 어텐션 분포 행렬과 V 행렬을 곱하여 최종 결과를 얻는다.

  return output, attention_weights
  # 최종 결과와 어텐션 분포 리턴. 어텐션 분포 또한 리턴하는 이유는 아래에서 값을 출력해보며 함수 테스트를 위함.
* Multi-head Attention > Scaled dot-product Attention를 여러번? 실행
    * dmodel 의 차원을 가진 단어 벡터를 num_heads로 나눈 차원을 가지는 Q, K, V벡터로 바꾸고 어텐션을 수행
        * 논문에서는 512의 차원의 각 단어 벡터를 8로 나누어 64차원의 Q, K, V 벡터로 바꾸어서 어텐션을 수행
        * 그럼 8개의 multi-head(=num_heads)

image
* 이 결과(num_heads=8)의 각 결과들을 concat
image
* 소스

class MultiHeadAttention(tf.keras.layers.Layer):

  def __init__(self, d_model, num_heads, name="multi_head_attention"): # 정의하기
    super(MultiHeadAttention, self).__init__(name=name)
    self.num_heads = num_heads # 8
    self.d_model = d_model # 512

    assert d_model % self.num_heads == 0

    self.depth = d_model // self.num_heads

    self.query_dense = tf.keras.layers.Dense(units=d_model) #WQ
    self.key_dense = tf.keras.layers.Dense(units=d_model) #WK
    self.value_dense = tf.keras.layers.Dense(units=d_model) #WV

    self.dense = tf.keras.layers.Dense(units=d_model) #WO

  def split_heads(self, inputs, batch_size): # 아래의 call 함수에서 헤드를 나누기 위해서 호출
    inputs = tf.reshape(
        inputs, shape=(batch_size, -1, self.num_heads, self.depth))
    return tf.transpose(inputs, perm=[0, 2, 1, 3])

  def call(self, inputs):
    query, key, value, mask = inputs['query'], inputs['key'], inputs[
        'value'], inputs['mask']
    batch_size = tf.shape(query)[0]

    # 1. WQ, WK, WV에 해당하는 밀집층 지나기
    query = self.query_dense(query) # (batch_size, seq_len, d_model) 
    key = self.key_dense(key) # (batch_size, seq_len, d_model)
    value = self.value_dense(value) # (batch_size, seq_len, d_model)

    # 2. 헤드 나누기
    query = self.split_heads(query, batch_size) # (batch_size, num_heads, seq_len, d_model/num_heads)
    key = self.split_heads(key, batch_size) # (batch_size, num_heads, seq_len, d_model/num_heads)
    value = self.split_heads(value, batch_size) # (batch_size, num_heads, seq_len, d_model/num_heads)

    # 3. 스케일드 닷 프로덕트 어텐션. 앞서 구현한 함수 사용.
    scaled_attention = scaled_dot_product_attention(query, key, value, mask)
    scaled_attention = tf.transpose(scaled_attention, perm=[0, 2, 1, 3])

    # 4. 헤드 연결(concatenate)하기
    concat_attention = tf.reshape(scaled_attention,
                                  (batch_size, -1, self.d_model))

    # 5. WO에 해당하는 밀집층 지나기
    outputs = self.dense(concat_attention)

    return outputs # 최종 결과 리턴

궁금중..

  • decoder의 input은? > shifted right
    • 참고) 링크
      image
      • shifted right 의 의미는 번역의 정답을 shift하는듯..그림보면, shift되어 <s> 로 시작되는것을 볼수 있다.

PPT정리

  • 위의 많은 논문/POST들에 대한 정리들을 참조한것입니다.