Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
chullhwan-song opened this issue Sep 17, 2019 · 3 comments

Comments

@chullhwan-song
Copy link
Owner Author

chullhwan-song commented Sep 17, 2019

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 # 최종 결과 리턴

@chullhwan-song
Copy link
Owner Author

chullhwan-song commented Oct 31, 2019

궁금중..

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

@chullhwan-song
Copy link
Owner Author

PPT정리

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant