Attention 구현(1/3)
참고문헌: 책 『밑바닥부터 시작하는 딥러닝2』 Chapter8. 어텐션
~ 목차 ~
0. 서론- 왜 Attention을 구현하고 있는가
- Attention 개념
1.1 seq2seq의 문제점
1.2 Encoder 개선
1.3 Decoder 개선1- 가중합(Weight Sum) 계층 (c)
1.4 Decoder 개선2- 가중치(Attention Weight) 계층 (a)
1.5 Decoder 개선3- 가중합 계층, 가중치 계층 합치기
0. 서론- 왜 Attention을 구현하고 있는가
- 이것 역시 연휴맞이 기본기 다지기의 일환이다. 기본기라고 하기엔 너무 기본적인 이론인가 싶지만.. 쌓아가야지 뭐
- 이번에도 국룰 책 『밑바닥부터 시작하는 딥러닝2』의 Chapter8. 어텐션을 참고했다.
- 너무 길어져서 3번에 걸쳐서 끊어가기로 했다.
1. Attention 개념
- Attention: 필요한 정보에만 주목해서 그 정보로부터 시계열 변환을 수행하는 구조
(이 설명으로만 봐선 크게 와닿지는 않는다..)
1.1 seq2seq의 문제점
- Encoder의 출력이 입력 문장의 길이에 관계없이 고정길이벡터라는 점
→ 항상 같은 길이의 벡터로 변환되기 때문에 정보 손실이 일어날 수 있음
1.2 Encoder 개선
- 입력문장의 길이와 Encoder의 출력길이가 비례하도록 하기 위해 Encoder LSTM 계층의 모든 은닉상태벡터를 가져와서 아래 그림처럼 행렬 hs로 출력할 수 있음
- LSTM이 출력한 각각의 은닉상태벡터들은 직전에 입력한 단어의 영향을 가장 크게 받기 때문에, 각각의 은닉상태벡터들은 각 단어에 해당하는 벡터들의 집합이라고 볼 수 있음
1.3 Decoder 개선1- 가중합(Weight Sum) 계층 (c)
- 개선 전: hs의 마지막 줄만 빼서 Decoder에 전달한 뒤 나온 은닉상태를 그대로 Affine 계층으로 출력
- 개선 후: Affine 계층 전에 '어떤 계산'(입력값: hs전부, 은닉상태)을 수행하는 계층을 추가하고 필요한 정보만 골라서 Affine 계층으로 출력
- '어떤 계산'의 역할: 단어의 alignment(대응관계) 추출. 원래는 사람이 alignment를 지정했지만, 그림의' 어떤 계산'을 통해서 alignment를 자동화하고자 함. 즉, Decoder에 입력된 단어와 대응관계인 벡터를 hs에서 선택하기
ex) 'I'가 입력되었을 때 hs에서 '나'에 대응하는 벡터를 선택- but '나'벡터를 선택작업은 미분이 불가능하므로 다른 방법을 사용해야 함
어떤 방법을 사용할까?
→ 모든 단어를 선택(hs)한 뒤 가중치(a)와의 원소별 곱을 계산하여 가중합을 구해서, 맥락벡터(c)를 구하는 방법!- [그림8-6]을 보면 '나'에 대응하는 가중치가 0.8로, 맥락벡터 c에는 '나'벡터의 성분이 많이 포함되어 있다는 걸 알 수 있음. 즉 '나'벡터를 선택했다고 볼 수 있음
- but '나'벡터를 선택작업은 미분이 불가능하므로 다른 방법을 사용해야 함
- [그림8-11]을 구현하면 다음과 같음(특별할 것 없이 그냥 설명 그대로 구현한거임)
class WeightSum:
def __init__(self):
self.params, self.grads = [],[]
self.cache = None
def forward(self, hs, a):
N, T, H = hs.shape
ar = a.reshape(N, T, 1).repeat(H, axis=2)
t = hs * ar
c = np.sum(t, axis=1)
self.cache = (hs, ar)
return c
def backward(self, dc):
hs, ar = self.cache
N, T, H = hs.shape
dt = dc.reshape(N, 1, H).repeat(T, axis=1)
dar = dt * hs
dhs = dt * ar
da = np.sum(dar, axis=2)
return dhs, da
1.4 Decoder 개선2- 가중치(Attention Weight) 계층 (a)
- Decoder의 첫 번째 LSTM 계층이 은닉상태벡터를 출력할 때의 처리는 아래 그림과 같음
- 목표: LSTM 계층의 은닉상태벡터 h가 hs의 각 단어벡터와 얼마나 비슷한가, 즉 유사도를 수치로 나타내기
→ 방법: 내적 사용할거임(다른 방법도 사용 가능함) - 먼저 hs와 h를 내적해서 s를 얻은 뒤, 이를 softmax함수를 통해 정규화해서 가중치 a를 얻음
- 과정을 그림으로 나타내면 [그림8-15]가 됨
- [그림8-15]을 구현하면 다음과 같음(특별할 것 없이 그냥 설명 그대로 구현한거임)
class AttentionWeight:
def __init__(self):
self.params, self.grads = [],[]
self.softmax = Softmax()
self.cache = None
def forward(self, hs, h):
N, T, H = hs.shape
hr = h.reshape(N,1,H).repeat(T, axis=1)
t = hs * hr
s = np.sum(t, axis=2)
a = self.softmax.forward(s)
self.cache = (hs,hr)
return a
def backward(self, da):
hs, hr = self.cache
N, T, H = hs.shape
ds = self.softmax.backward(da)
dt = ds.reshape(N, T, 1).repeat(H, axis=2)
dhs = dt * hr
dhr = dt * hs
dh = np.sum(dhr, axis=1)
return dhs, dh
1.5 Decoder 개선3- 가중합 계층, 가중치 계층 합치기
- 위에서 구현한 Weight Sum 계층과 Attention Weight 계층을 합쳐서 Attention 계층을 구현할 수 있음. 아래 그림은 합쳐진 설계도..
- 이를 코드로 구현하면 다음과 같음(그냥 그대로 합치면 됨, 특별한 건 없음)
class Attention:
def __init__(self):
self.params, self.grads = [],[]
self.attention_weight_layer = AttentionWeight()
self.weight_sum_layer = WeightSum()
self.attention_weight = None
def forward(self, hs, h):
a = self.attention_weight_layer.forward(hs, h)
out = self.weight_sum_layer.forward(hs, a)
# 각 단어의 가중치를 나중에 참조할 수 있도록 attention_weight에 저장
self.attention_weight = a
return out
def backward(self,dout):
dhs0, da = self.weight_sum_layer.backward(dout)
dhs1, dh = self.attention_weight_layer.backward(da)
dhs = dhs0 + dhs1
return dhs, dh
+앞서 나왔던 '어떤 계산'의 정체는 바로 Attention이었다!
'기본기 다지기 > CNN부터 Attention까지 구현' 카테고리의 다른 글
[기본이론] Attention 구현(3/3) (1) | 2024.09.27 |
---|---|
[기본이론] Attention 구현(2/3) (1) | 2024.09.27 |
[기본이론] seq2seq 구현 (0) | 2024.09.23 |
[밑시딥] LSTM 구현하기 (0) | 2024.09.23 |
[밑시딥] RNN 구현하기 (0) | 2024.09.23 |