BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding
chullhwan-song opened this issue · 3 comments
chullhwan-song commented
- paper : https://arxiv.org/abs/1810.04805
- good post - 이해가 잘안가 아래 포스트도 함께..
- Attention 개념정리 부제 : 게임의 구조로 해석한 attention
- The Illustrated Transformer
- How the Embedding Layers in BERT Were Implemented
- BERT 논문정리
- NLP: Contextualized word embeddings from BERT
- Attention is all you need 간단리뷰: My Review
- Neural Machine Translation by Jointly Learning to Align and Translate 간단리뷰
- Google BERT — Pre Training and Fine Tuning for NLP Tasks
- 딥 러닝을 이용한 자연어 처리 입문 14. 트랜스포머(Transformer) 1) 트랜스포머(Transformer)
chullhwan-song commented
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
- 일반적으로 우리는 보통?
- 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)에 도입하여 단어가 다른 단어와 어느정도 가까이 있는지를 표현하는 것
- 문자에서 word(token)에 대한 위치에 대한 정보를 주입 > Positional Encoding
- 위의 빨간색 그림.
- 이는 학습과 상관없으면서, 미리 정의된 sinusoid function를 이용한다.
- 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()
BERT 의 INPUT
- bert는 transformer와 달리, Positional Encoding이 아니라 Positional Embedding 용어로 사용한다라고 언급
- 실제론 Positional Encoding == Positional Embedding 같은 의미인듯.?
- 하다보니, 구성에 잘 안맞게 위의 Position Embeddings를 먼저 설명하는 모양새가 됬다.
- 위의 그림에서 첫번째 입력값을 보면, 즉, 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” 이면,
- 그림에서보면, 나머지 두개의 embedding과 shape(or dim)을 맞추기위해, 0와 1로 채우는듯하다. 즉, 768차원을 문장 구분하여 첫번째 문장에 0으로, 두번째 문장에 1로, 세번째 문자에 2으로..채우는듯하다.(더 봐야함.)
- 참고로 2가 아니라 0과 1로만 할수도 있을수도...? 좀더 이부분확인필요!!
- 그림에서보면, 나머지 두개의 embedding과 shape(or dim)을 맞추기위해, 0와 1로 채우는듯하다. 즉, 768차원을 문장 구분하여 첫번째 문장에 0으로, 두번째 문장에 1로, 세번째 문자에 2으로..채우는듯하다.(더 봐야함.)
- Position Embeddings > 위에서 설명
Transformers
- Multi Head Attention
- Scaled Dot Product Attention > Multi Head Attention를 구성하는 core concept
- 자세히 보면 입력값이 3개: K? Q? V? >짜증나게 논문에서는 설명이 거의 없다.
- Q: Query vector, K:Key vector, V: Value vector > 라고 나와있는데 당췌!!! 와닿지 않는다.
- 같은 입력값에 각각 3개의 linear transformation 나온 결과를 K, Q, V로
- 그래서, 왼쪽 그림 : Scaled Dot Product Attention 을 다음 수식으로 나타내고, 하나의 Head를 의미.
- 오른쪽 그림은 이 Scaled Dot Product Attention를 concat한 결과, 즉 Multi Head Attention에 대한 수식은
- 이 그림보면 구조에 대한 이해도가 올라갈듯~
- Q: Query vector, K:Key vector, V: Value vector > 라고 나와있는데 당췌!!! 와닿지 않는다.
- 다시 > 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
- 야구라고 가정하면, 이미 룰에 의해 정해진 1~9번타자가 존재하는데 바로 이 틀을 Q = Qi = {Q1,,Q9}
- 앞에서 Q와 K의 관계가 중요하다고 했는데, 이를 다시 말한다면, Q-K의 alignment가 매우 중요하다는것을 의미한다.
- 이제 야구가 아니고, 문장의 입장에서 보면, Q는 품사(주어, 목적어, 동사..등 또한 품사는 문장의 위치와도 매우 관련성이 크다?) 정도 일거고, 들어오는값 >(X가 hidden layer를 통해 변형된 S(X....=====...S)>) K가 어떤 품사 Q 적합한지에 대한 relation이라고 봐야할듯하다.
- 이 그림을 자세히 보면 이해가 더 쉬울수 있다. 위의 attention 수식과 맞물려 생각하면 좋음.
- Q와 K는 초기의 attention 연구:Neural Machine Translation by Jointly Learning to Align and Translate 간단리뷰와 비교해보면 다음과 같다는 의미.
- 이제 attention 수식은 다음과 같이 생각해볼수 있다.
- 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? >짜증나게 논문에서는 설명이 거의 없다. (위에서부터 다시..다른설명)
- Scaled dot-product Attention
- q*k : 어떤 k와 유사한지 check
- 여기에 scaling 개념 추가 > 위의 Q, K, V의 차원 n=64의 값에다 root를 쒸어 나누어줌. > 실제적으론 dk이므로 > K의 차원임.
- 이는 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를 계산
- 위의 과정을 따로따로 아닌 행렬단위로 한다면(실제 코딩에서는 당연히 이렇게..)
* 소스
# 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)
* 이 결과(num_heads=8)의 각 결과들을 concat
* 소스
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 commented
궁금중..
- decoder의 input은? > shifted right
- 참고) 링크
- shifted right 의 의미는 번역의 정답을 shift하는듯..그림보면, shift되어 <s> 로 시작되는것을 볼수 있다.
- 참고) 링크
chullhwan-song commented
- 위의 많은 논문/POST들에 대한 정리들을 참조한것입니다.