트랜스포머·PEFT 2일 과정1장 · 준비 수학과 Transformer 개요14 / 17← 이전목차다음 →MetaContents

1장. 준비 수학과 Transformer 개요

실습 2 — 행렬곱, 내적, Softmax 계산하기

행렬곱의 shape 규칙과 오류, 내적의 부호, Softmax 의 세 단계를 차례로 확인합니다. 노트북 1일차의 셀 10, 12, 14입니다. 실습 1의 1단계(import numpy as np)를 먼저 실행해 둡니다.

코드가 세 셀로 나뉘어 있어, 먼저 실행 흐름을 한 장으로 봅니다. 위에서 아래로 화살표를 따라 읽습니다.

실습 2 코드의 실행 흐름도. 시작에서 세 갈래로 나뉜다. A 는 A 와 B 를 matmul 로 곱해 (2, 2) 를 찍고, A @ A 를 시도해 ValueError 가 나면 오류 문장을 찍는다. B 는 np.dot 으로 32 와 -14 를 찍는다. C 는 softmax 함수를 정의하고 z 를 확률 p 로 바꿔 합 1.0 을 찍는다. 세 갈래가 모여 끝난다
갈래단계반복과 조건핵심 한 줄
A. 행렬곱1try 안의 np.matmul(A, A) 에서 "ValueError 가 났나?"를 묻고, 났으면 except 아래에서 오류 문장을 찍는다C = np.matmul(A, B)
B. 내적2없음np.dot(u, v)
C. Softmax3없음. softmax 안에도 반복문이 없고, NumPy 가 세 칸을 한꺼번에 계산한다e / e.sum(axis=-1, keepdims=True)
  • 세 갈래: 짙은 청록(A), 살구(B), 숯 회색(C) 세 갈래는 서로의 변수를 쓰지 않는 따로 된 셀입니다. 셋 다 실습 1의 np 만 있으면 어느 것부터 실행해도 됩니다.
  • A 의 마름모: try ~ except 는 조건문의 한 가지입니다. 오류가 나면 "예" 쪽으로 내려가 print("오류:", e) 를 하고, 나지 않으면 "아니요" 쪽으로 건너뜁니다. 이 실습은 일부러 shape 가 맞지 않게 곱하므로 언제나 "예" 쪽을 지납니다.
  • C 의 정의와 실행: def softmax(z) 는 요리법을 적어 두기만 하고, 실제 계산은 p = softmax(z) 를 부를 때 합니다. 결과 [0.66 0.24 0.1 ] 의 합이 1.0 인지 마지막 네모에서 확인합니다.

1단계. 행렬곱과 shape 규칙

2행 3열 A 와 3행 2열 B 를 곱합니다. 점수표에 가중치표를 곱해 총점을 내는 것과 같은 계산입니다. 안쪽 숫자가 다른 A @ A 는 일부러 오류를 내 봅니다.

Python
# A = 2행 3열 행렬. 바깥 대괄호 안에 행이 두 개, 행마다 숫자 세 개
A = np.array([[1, 2, 3],
              [4, 5, 6]])            # A는 2행 3열 행렬입니다.
# B = 3행 2열 행렬. 행이 세 개, 행마다 숫자 두 개
B = np.array([[1, 0],
              [0, 1],
              [1, 1]])               # B는 3행 2열 행렬입니다.

# np.matmul = matrix multiplication(행렬곱). A @ B 와 같다
# (2, 3) @ (3, 2): 안쪽 3이 같아 곱할 수 있고, 결과는 바깥 숫자를 따라 (2, 2)
C = np.matmul(A, B)                  # 안쪽 크기 3이 같으므로 곱할 수 있고, 결과는 2행 2열입니다.
# 결과: [[ 4  5]
#        [10 11]]  → 첫 칸은 [1, 2, 3]·[1, 0, 1] = 1 + 0 + 3 = 4
print("A @ B =\n", C)
# 결과: 결과 shape: (2, 2) → (2, 2)
print("결과 shape:", C.shape, "→ (2, 2)")

# try ~ except = 오류가 나도 멈추지 않고 except 아래를 실행한다
try:  # 안쪽 크기가 다른 행렬을 곱하면 어떤 오류가 나는지 확인합니다.
    # (2, 3) @ (2, 3): 앞의 열 수 3과 뒤의 행 수 2가 달라 곱할 수 없다
    np.matmul(A, A)                  # A @ A는 안쪽 크기 3과 2가 달라 계산할 수 없습니다.
# ValueError = 값(여기서는 shape)이 맞지 않을 때 나는 오류. as e 로 오류 내용을 e 에 담는다
except ValueError as e:
    # 결과: 오류: matmul: Input operand 1 has a mismatch in its core dimension 0, ... (size 2 is different from 3)
    print("오류:", e)
출력
A @ B =
 [[ 4  5]
 [10 11]]
결과 shape: (2, 2) → (2, 2)
오류: matmul: Input operand 1 has a mismatch in its core dimension 0, with gufunc signature (n?,k),(k,m?)->(n?,m?) (size 2 is different from 3)

2단계. 내적으로 비슷한 정도 재기

두 벡터의 같은 자리 숫자끼리 곱해 더합니다. 두 사람의 취향이 얼마나 같은 쪽인지 재는 점수입니다.

Python
# u, v = 숫자 3개짜리 벡터 두 개
u = np.array([1, 2, 3])
v = np.array([4, 5, 6])
# np.dot = dot product(내적). 같은 자리끼리 곱해 모두 더한다
# 결과: u · v = 32 → 1×4 + 2×5 + 3×6 = 32
print("u · v =", np.dot(u, v), "→ 1×4 + 2×5 + 3×6 = 32")
# -u = u 의 모든 숫자에 -1 을 곱한 반대 방향 벡터 [-1, -2, -3]
# 결과: u와 반대 방향 벡터(-u)의 내적 = -14 → 음수가 나옵니다
print("u와 반대 방향 벡터(-u)의 내적 =", np.dot(u, -u), "→ 음수가 나옵니다")
출력
u · v = 32 → 1×4 + 2×5 + 3×6 = 32
u와 반대 방향 벡터(-u)의 내적 = -14 → 음수가 나옵니다

3단계. Softmax 로 점수를 확률로

점수 세 개를 합이 1인 확률로 바꿉니다. 득표수를 투표 비율로 바꾸는 일입니다. 강사자료 그림 softmax_steps 의 세 단계(점수, e^z, 합으로 나누기)를 숫자로 찍습니다.

Python
# def softmax(z) = 점수 배열 z 를 받아 확률 배열을 돌려주는 함수를 만든다
def softmax(z):  # 마지막 축(행마다) 기준으로 확률을 만듭니다.
    # np.exp = exponential. e^z 를 구한다(e ≈ 2.718)
    # z.max(axis=-1, keepdims=True) = 마지막 축의 최댓값. keepdims=True 는 빼기를 할 수 있게 축을 남겨 둔다
    e = np.exp(z - z.max(axis=-1, keepdims=True))  # 큰 값에서 넘치지 않도록 최댓값을 빼 줍니다.
    # e.sum(axis=-1, keepdims=True) = 마지막 축으로 더한 합. 이것으로 나누면 합이 1
    return e / e.sum(axis=-1, keepdims=True)  # 합으로 나누면 합이 1인 확률이 됩니다.

# 클래스 A, B, C 의 점수(logit). 강사자료 그림의 2.0, 1.0, 0.1 과 같다
z = np.array([2.0, 1.0, 0.1])  # 클래스 3개에 대한 점수입니다.
p = softmax(z)  # 세 점수를 각 클래스의 확률로 바꿉니다.
# 결과: 점수 z     : [2.  1.  0.1]
print("점수 z     :", z)
# np.round(배열, 2) = 소수 둘째 자리까지 반올림
# 결과: e^z        : [7.39 2.72 1.11]
print("e^z        :", np.round(np.exp(z), 2))
# 결과: 확률 p     : [0.66 0.24 0.1 ]  → 7.39 ÷ 11.22 ≈ 0.66
print("확률 p     :", np.round(p, 2))
# p.sum() = 확률을 모두 더한 값
# 결과: 확률의 합  : 1.0
print("확률의 합  :", p.sum())
출력
점수 z     : [2.  1.  0.1]
e^z        : [7.39 2.72 1.11]
확률 p     : [0.66 0.24 0.1 ]
확률의 합  : 1.0

이 실습이 뜻하는 것

점수표에 가중치표를 곱하고(1단계), 두 사람 취향이 같은 쪽인지 재고(2단계), 득표 점수를 투표 비율로 바꾼(3단계) 일입니다. 셋을 이으면 Attention 의 뼈대가 됩니다. 내적으로 관련도 점수를 매기고, Softmax 로 비율을 정하고, 행렬곱으로 내용을 섞습니다. 여기서 점수표가 A, 가중치표가 B, 취향 줄이 u, v, 득표 점수가 z, 투표 비율이 p 입니다.

단계출력뜻
1. 행렬곱[[ 4 5] [10 11]], (2, 2), 오류 문장안쪽 숫자가 같아야 곱해지고, 결과는 바깥 숫자를 따른다
2. 내적32, -14같은 쪽이면 양수, 반대 쪽이면 음수
3. Softmax[0.66 0.24 0.1 ], 합 1.0점수가 합 1인 확률이 되었다

한 줄로 줄이면, Transformer 안의 계산은 거의 모두 "곱하고 더하기(행렬곱, 내적)"와 "비율로 바꾸기(Softmax)"의 조합입니다.

코드 설명

  • 1단계의 오류 문장 size 2 is different from 3 은 "뒤 행렬의 행 수 2가 앞 행렬의 열 수 3과 다르다"는 뜻입니다. shape 오류가 나면 두 행렬의 안쪽 숫자부터 확인합니다.
  • C 의 칸 하나는 A 의 행 하나와 B 의 열 하나의 내적입니다. 둘째 행 첫 칸은 [4, 5, 6]·[1, 0, 1] = 4 + 0 + 6 = 10 입니다.
  • 2단계의 -14 는 1×(-1) + 2×(-2) + 3×(-3) = -1 - 4 - 9 입니다. 반대 방향 벡터와의 내적은 음수입니다.
  • 3단계의 softmax 는 axis=-1 로 마지막 축을 기준으로 계산하므로 점수가 표로 들어와도 행마다 합이 1이 됩니다. 3장의 Attention 표에서 이 성질을 씁니다.
  • 확률 출력의 0.1 은 0.10 을 NumPy 가 줄여 찍은 것입니다. 확률의 합 1.0 은 반올림 전의 값을 더한 것입니다.
← 이전 절1장 · 실습 1 — 벡터와 행렬을 NumPy 로 만들기다음 절 →1장 · 프로젝트 1 — 취향 점수를 추천 확률로 바꾸는 도우미