3장. Attention 과 Transformer, GPT 직접 구현
Attention 의 원리
도서관에서 자료를 찾는 모습을 떠올려 보세요. 손에는 "배송 지연에 관한 자료"라는 질문 쪽지가 있습니다. 서가의 책마다 제목표가 붙어 있고, 질문과 잘 맞는 제목표의 책일수록 더 자세히 읽어 내용을 모읍니다. Attention 이 하는 일이 이것입니다.
1번 질문(Q)은 지금 글자가 찾고 싶은 것, 2번은 다른 글자들의 제목표(K)와 얼마나 맞는지 점수를 내는 단계입니다. 3번에서 점수를 합이 1 인 비율로 바꾸고, 4번에서 그 비율만큼 각 글자의 내용(V)을 섞습니다. 여기서 질문 쪽지가 쿼리, 제목표가 키, 책의 내용이 밸류입니다.
Q, K, V 는 어디서 오나
글자마다 임베딩 벡터가 있습니다. 이 벡터에 서로 다른 가중치를 곱해 Q, K, V 세 벡터를 만듭니다. 세 가중치는 2장의 nn.Linear 와 같은 층이고, 학습으로 정해집니다.
| 벡터 | 만드는 법 | 도서관으로 치면 |
|---|---|---|
| Q(쿼리) | 임베딩 × Wq | 내가 들고 있는 질문 쪽지 |
| K(키) | 임베딩 × Wk | 책마다 붙은 제목표 |
| V(밸류) | 임베딩 × Wv | 책의 실제 내용 |
점수를 비율로: softmax
견주기 점수는 아무 크기나 될 수 있습니다. softmax 는 이것을 "몇 퍼센트씩 참고할지"로 바꿉니다.
import torch
scores = torch.tensor([2.0, 1.0, 0.1]) # 세 글자와 견준 점수
weights = torch.softmax(scores, dim=0) # 0~1 사이, 합이 1 인 비율로
print(weights.round(decimals=3))
print(weights.sum())점수 2.0, 1.0, 0.1 이 비율 0.659, 0.242, 0.099 로 바뀌고, 합은 1 입니다. 점수가 높은 글자를 더 많이, 그러나 다른 글자도 조금씩 참고합니다.
코드에 나오는 함수
| 코드 | 쉬운 뜻 |
|---|---|
torch.softmax(scores, dim=0) | 점수를 0과 1 사이의 비율로 바꾸고, 그 합이 1이 되게 한다. 점수가 클수록 비율도 크다 |
dim=0 | 어느 축을 따라 합을 1로 맞출지 정한다. 여기서는 숫자가 한 줄뿐이라 0번 축 전체가 대상이다 |
.round(decimals=3) | 소수 셋째 자리까지 반올림해 보기 좋게 한다. 값을 출력할 때만 쓴다 |
weights.sum() | 모든 값을 더한다. softmax 의 결과라 1이 나온다 |
softmax 는 점수의 차이를 키우는 성질이 있습니다. 2.0 과 1.0 은 두 배 차이지만, 비율은 0.659 와 0.242 로 두 배보다 크게 벌어집니다.
이 코드가 뜻하는 것
도서관 비유로 읽으면, 질문 쪽지와 세 책의 제목표를 견준 점수가 2.0, 1.0, 0.1 입니다. softmax 를 지나면 "첫째 책에서 65.9%, 둘째 책에서 24.2%, 셋째 책에서 9.9% 를 가져와 섞는다"는 비율표가 됩니다. 점수가 가장 높은 책만 읽는 것이 아니라, 덜 맞는 책도 조금씩 참고한다는 점이 중요합니다. 이 비율표가 위 흐름도의 3번이고, 4번에서 이 비율대로 내용(V)을 섞습니다.
| 줄 | 하는 일 | 도서관으로 치면 |
|---|---|---|
scores = torch.tensor([2.0, 1.0, 0.1]) | 견주기 점수 셋을 텐서로 만든다 | 세 책의 제목표가 질문과 맞는 정도 |
torch.softmax(scores, dim=0) | 점수를 합이 1 인 비율로 바꾼다 | 책마다 얼마나 읽을지 나눈 비율표 |
weights.sum() | 비율을 모두 더해 1 인지 확인한다 | 나눈 몫을 모으면 전부(100%)가 된다 |
softmax 는 어떻게 계산하나
softmax 의 계산은 세 걸음입니다. 점수마다 2장 "활성화 함수" 절에서 본 exp(e 를 그 점수만큼 곱한 값)를 구하고, 모두 더한 뒤, 각자를 그 합으로 나눕니다.
그림은 왼쪽에서 오른쪽으로 읽습니다. 1번의 점수가 2번에서 exp 를 지나 모두 양수가 되고, 3번에서 셋의 합 11.213 으로 나눠 비율이 됩니다. 나눈 몫을 모두 더하면 꼭 1 이 됩니다.
import torch
scores = torch.tensor([2.0, 1.0, 0.1])
e = torch.exp(scores) # 1) 점수마다 exp: 모두 양수가 된다
total = e.sum() # 2) 모두 더한다
manual = e / total # 3) 각자를 합으로 나눈다
print(e.round(decimals=3), total.round(decimals=3))
print(manual.round(decimals=3))
print(torch.softmax(scores, dim=0).round(decimals=3)) # 함수의 결과와 같다실행하면 exp 의 결과는 7.389, 2.718, 1.105 이고 합은 11.213 입니다. 세 걸음을 직접 적어 계산한 값(manual)과 torch.softmax 의 값이 모두 0.659, 0.242, 0.099 로 같습니다.
이 코드는 torch.softmax 한 줄 안에서 일어나는 계산을 우리가 한 줄씩 직접 적어 보고, 그 값이 함수의 결과와 같은지 견주는 코드입니다. 변수 이름 manual 은 영어로 "손으로 하는"이라는 뜻으로, 종이에 연필로 계산했다는 말이 아니라 "함수에 맡기지 않고 단계를 직접 적어 구했다"는 뜻입니다.
| 줄 | 하는 일 | 값 |
|---|---|---|
scores = torch.tensor([2.0, 1.0, 0.1]) | 세 책과 견준 점수를 텐서로 만든다 | 2.0, 1.0, 0.1 |
e = torch.exp(scores) | 1걸음. 점수마다 e(약 2.718)를 그 점수만큼 곱한 값을 구한다. 결과는 언제나 양수다 | 7.389, 2.718, 1.105 |
total = e.sum() | 2걸음. 세 값을 모두 더한다 | 11.213 |
manual = e / total | 3걸음. 칸마다 합으로 나눈다. 텐서를 숫자 하나로 나누면 모든 칸이 같은 수로 나뉜다 | 0.659, 0.242, 0.099 |
.round(decimals=3) | 찍을 때 소수 셋째 자리까지 반올림해 보기 좋게 한다. 계산한 값 자체는 바뀌지 않는다 | |
torch.softmax(scores, dim=0) | 같은 계산을 함수 한 줄로 한다 | 0.659, 0.242, 0.099 |
3걸음을 숫자로 풀면 7.389 ÷ 11.213 = 0.659, 2.718 ÷ 11.213 = 0.242, 1.105 ÷ 11.213 = 0.099 입니다. 셋을 더하면 1 이 되어, 전체를 100% 로 보고 몫을 나눈 것이 됩니다. 함수와 직접 계산한 값이 똑같으므로, 앞으로 torch.softmax 를 보면 "exp 로 키우기, 모두 더하기, 합으로 나누기"의 세 걸음을 떠올리면 됩니다.
왜 exp 를 쓰나
| 까닭 | 설명 |
|---|---|
| 음수도 비율로 만들 수 있다 | exp 는 언제나 양수를 내므로, 점수가 -1, -2 처럼 음수여도 비율이 된다. 점수를 그대로 합으로 나누면 음수 비율이 생긴다 |
| 큰 점수를 더 크게 키운다 | 점수 차이가 1 이면 exp 의 차이는 약 2.7배가 된다. 그래서 2.0 과 1.0 은 두 배 차이인데 비율은 0.659 와 0.242 로 2.7배 벌어진다 |
| 미분하기 좋다 | 부드러운 곡선이라 기울기를 구하기 쉽다. 역전파로 학습해야 하는 신경망에 맞다 |
이름의 뜻: 부드러운 최댓값
가장 큰 점수 하나만 고르고 나머지는 버리는 것을 영어로 max(또는 argmax)라고 합니다. 점수 2.0, 1.0, 0.1 에 이것을 쓰면 비율은 1, 0, 0 이 됩니다. softmax 는 이것을 부드럽게(soft) 만든 것이라, 가장 큰 점수에 가장 많이 주되 다른 점수에도 조금씩 나눠 줍니다. 그래서 이름이 soft + max 입니다.
| 방법 | 점수 2.0, 1.0, 0.1 의 결과 | 읽는 법 |
|---|---|---|
| 가장 큰 것만(argmax) | 1, 0, 0 | 첫째 책만 읽는다 |
| softmax | 0.659, 0.242, 0.099 | 첫째 책을 주로, 나머지도 조금 읽는다 |
| 점수에 3 을 곱한 뒤 softmax | 0.950, 0.047, 0.003 | 점수 차이를 키우면 argmax 쪽으로 다가간다 |
마지막 줄처럼 점수를 키우면 softmax 가 한쪽으로 쏠리고, 줄이면 고르게 퍼집니다. 이 성질을 글쓰기에 쓰는 것이 뒤의 "다음 글자 고르기: 생성과 온도" 절의 온도입니다.
알아 두면 좋은 성질
- 모든 점수에 같은 수를 더해도 결과가 같습니다. 2.0, 1.0, 0.1 에 10 을 더한 12.0, 11.0, 10.1 도, 음수인 -1.0, -2.0, -2.9 도 비율은 똑같이 0.659, 0.242, 0.099 입니다. 점수의 크기 자체가 아니라 점수끼리의 차이만 보기 때문입니다.
- 2차원 표에 쓸 때는
dim이 중요합니다. 다음 절 "셀프 어텐션 계산"에서는 글자마다 한 줄씩 점수가 있는 표에dim=-1(마지막 축, 곧 줄마다)을 줘서, 줄마다 합이 1 이 되게 합니다.
주의할 점
- 비유에서는 사람이 질문을 정하지만, Attention 에서는 Q, K, V 를 만드는 가중치를 학습이 정합니다. 무엇을 질문할지도 모델이 배웁니다.
- Attention 의 결과는 "가장 맞는 글자 하나"가 아니라 여러 글자의 내용을 비율대로 섞은 벡터입니다.