NLP/Transformer
Transformer
Transformer
Attention모델이 기본적으로 깔려있다.
self attention에서 input vector가 자기자신과의 내적으로 인해 output vector 자기자신의 정보를 포함할 경우가 있다.
-
self Attention
사진에서 볼 수 있듯이, "i" 에 대한 sequence전체를 고려한 encoding vector를 얻기 위해서, 각각
queries, keys, values의 vector를 이용해서, 가중평균을 얻어낸다.
따라서 모든 embedding vector를 이용해서 다른 정보도 활용을 하는 것이 가능하다.
Output : weighted sum of values
Weight of each value : inner product (query, key)
-
Scaled dot-product Attention
Q(queries), K(keys), V(values)를 이용해서 가중평균을 구한다.
Q벡터와 K벡터에 대해서 attention score를 구하고, V벡터를 가중합하여 context vector를 구한다.
-
“i am a student” 에 대한 scaled dot-product Attention 과정 예시
"i" 에 대한 Q벡터 기준, score(q,k)의 과정을 보여준다.
이후 각각의 attention score에 활성함수를 적용하여 attention 분포를 구하고 각각의 V벡터와 가중합하여 attention value를 구한다.
이 attention value가 단어 "i"에 대한 context vector이다.
해당 연산을 각 단어에 대해 벡터연산으로 나누지 말고, 행렬 연산을 이용해서 일괄 계산을 한다.
\[Attention(Q, K, V) = softmax(\frac{QK^T}{\sqrt{d_k}})V\]-
Multi-Head Attention
동일한 sequence에서 다른 측면에서 여러 정보가 필요할 때 사용
여러 번의 attention을 병렬로 사용하는 방법 (head개의 병렬 attention 수행)
\(MultiHead(Q,\,K,\,V) = Concat(head_1,\,...\,,\,head_h)W^O\) \(where \;\; head_i = Attention(QW_{i}^{Q},\,KW_{i}^{K},\,VW_{i}^{V})\)
rnn과 달리 순서에 대한 특징을 가져오지 못하기 때문에(?) 각각의 문자의 순서를 구별하게 되는 순서 벡터를 encoding 벡터에 적용한다.
이러한 특징을 Positional Encoding 이라고 한다.
Multihead attention 구현 실습
import 및 데이터 전처리 과정 생략
-
Linear transformation & 여러 head로 나누기
embedding 벡터를 linear transfor시켜줄 matrix 생성
--- 각각 query, key, value를 위한 linear ---
w_q = nn.Linear(d_model, d_model)
w_k = nn.Linear(d_model, d_model)
w_v = nn.Linear(d_model, d_model)
--- 마지막 attention value를 최종적으로 합치기 위한 linear ---
w_0 = nn.Linear(d_model, d_model)
--- 선형변환 적용 ---
q = w_q(batch_emb) # (B, L, d_model)
k = w_k(batch_emb) # (B, L, d_model)
v = w_v(batch_emb) # (B, L, d_model)
multihead attention 이기 때문에, num_head의 개수에 따라 차원을 분할시켜 여러 vector로 생성
batch_size = q.shape[0]
d_k = d_model // num_heads
q = q.view(batch_size, -1, num_heads, d_k) # (B, L, num_heads, d_k)
k = k.view(batch_size, -1, num_heads, d_k) # (B, L, num_heads, d_k)
v = v.view(batch_size, -1, num_heads, d_k) # (B, L, num_heads, d_k)
--- 각 head가 L x d_k 개의 행렬을 가지게 되는 꼴로 만들어 준다. ---
q = q.transpose(1, 2) # (B, num_heads, L, d_k)
k = k.transpose(1, 2) # (B, num_heads, L, d_k)
v = v.transpose(1, 2) # (B, num_heads, L, d_k)
-
Scaled dot-product self-attention
각 head에서 self-attention을 실행한다.
attn_scores = torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(d_k) # (B, num_heads, L, L)
attn_dists = F.softmax(attn_scores, dim=-1) # (B, num_heads, L, L)
구해진 attention 분포를 통해 가중합을 구한다.
attn_values = torch.matmul(attn_dists, v)
각각 head의 결과물들을 concat하고 linear-transformation과정을 거친다.
불연속적인 주소들을 연속적인 메모리 주소로 재설정하기 위해서 contiguous를 사용한다.
attn_values = attn_values.transpose(1, 2) # (B, L, num_heads, d_k)
attn_values = attn_values.contiguous().view(batch_size, -1, d_model) # (B, L, d_model)
outputs = w_0(attn_values)
Masked Multihead attention 구현 실습
위의 전처리과정과 동일
-
Masking 적용
padding 처리된 벡터에 attention을 하는 것이 불필요한 과정이기 때문에, 이를 masking한다.
--- pad_id 와 같은 곳에 False를 주는 tensor를 만든다. ---
padding_mask = (batch != pad_id).unsqueeze(1) # (B, 1, L)
--- torch.tril를 이용해서 삼각형 모양의 Boolean 벡터를 만든다 (반은 True, 반은 False).
nopeak_mask = torch.ones([1, max_len, max_len], dtype=torch.bool) # (1, L, L)
nopeak_mask = torch.tril(nopeak_mask) # (1, L, L)
--- padding_mask 와 nopeak_mask의 and연산을 통해 padding에 masking을 적용한다. ---
mask = padding_mask & nopeak_mask # (B, L, L)
-
Masking 적용된 self-attention
masking된 곳에 매우 작은 수를 줌으로써 attention score를 계산하고 나서, softmax함수를 취했을 대, 0이 나오게 한다.
masks = mask.unsqueeze(1) # (B, 1, L, L)
# masked_fill_ : False 라고 나온곳은 매우 작은 수를 준다.
masked_attn_scores = attn_scores.masked_fill_(masks == False, -1 * inf) # (B, num_heads, L, L)