← คู่มือ Transformer — ลบทุกอย่าง เหลือแค่กราฟ
LEVEL 3 · ระดับสูง
กราฟที่โน้ดคุยกัน
บทนี้คือแก่นทางเทคนิคของทั้งเล่ม และเราจะเข้าถึงมันด้วยแบบจำลองความคิดหนึ่งอันที่ทำให้ทุกอย่างที่เหลือกลายเป็นเรื่องเดาได้
เลิกมอง Transformer เป็นลำดับของคำ ให้มองเป็นกราฟ โทเค็นทุกตัวคือโน้ดหนึ่งโน้ด และการทำงานของทั้งสถาปัตยกรรมคือการสลับสองเฟสไปเรื่อย ๆ
| เฟส | ชื่อทางเทคนิค | เกิดอะไรขึ้น |
|---|---|---|
| สื่อสาร | multi-head attention | โน้ดทุกตัวส่งข้อมูลถึงกันตามน้ำหนักที่คิดเอง |
| คิด | MLP / feed-forward | โน้ดแต่ละตัวแปรรูปข้อมูลของตัวเองอย่างเป็นเอกเทศ ไม่คุยกับใคร |
ชั้นหนึ่งชั้นคือสื่อสารหนึ่งครั้งแล้วคิดหนึ่งครั้ง ซ้อนกันหลายสิบชั้นก็คือสลับสื่อสาร-คิดหลายสิบรอบ เท่านั้นจริง ๆ
9.1 สามคำถามที่โน้ดทุกตัวต้องตอบ
ในเฟสสื่อสาร โน้ดแต่ละตัวสร้างเวกเตอร์สามตัวจากข้อมูลของตัวเอง และแต่ละตัวคือคำตอบของคำถามหนึ่งข้อ
- Query — "ฉันกำลังมองหาข้อมูลแบบไหน"
- Key — "ฉันมีข้อมูลลักษณะไหนอยู่กับตัว"
- Value — "ถ้ามีใครสนใจฉัน เนื้อหาที่ฉันจะส่งให้คืออะไร"
1 — หลังจากนั้น ผลลัพธ์เป็นเพียงผลรวมถ่วงน้ำหนักของ V ทุกตัว ไม่ใช่การเลือกมาตัวเดียวจากนั้นกลไกทำสี่ขั้นตรงไปตรงมา: เอา Query ของโน้ดที่ถาม ไปจับคู่กับ Key ของทุกโน้ด ด้วยการคูณแบบจุด (q @ k.T) ได้คะแนนความเข้ากัน → หารด้วยรากของขนาดช่องเพื่อคุมสเกล → ผ่าน softmax ให้กลายเป็นน้ำหนักที่รวมกันได้ 1 → เอาน้ำหนักไปถ่วง Value ของทุกโน้ดแล้วบวกกัน
💡 อุปมาฐานข้อมูล และรอยที่มันรั่ว — Q คือคำค้น K คือดรรชนี V คือเนื้อหา ดีตรงที่สื่อว่าเป็นการค้นหา แต่รั่วตรงที่ ฐานข้อมูลจริงเลือกมาหนึ่งแถวที่ตรง ส่วน attention เอาทุกแถวมาผสมกันตามน้ำหนัก ไม่เคยเลือกแค่อันเดียว คำว่า "นุ่มนวล" ใน soft search ของบทที่ 7 หมายถึงตรงนี้พอดี
9.2 ลองพิสูจน์ด้วยตาเอง
อย่าเชื่อคำอธิบายข้างบน รันเองแล้วดู โค้ดนี้ใช้ numpy ล้วน ไม่มีไลบรารีเรียนรู้ของเครื่องเลย และครบทั้งกลไก
import numpy as np
np.set_printoptions(precision=2, suppress=True)
T, C, head = 4, 6, 3 # 4 โทเค็น · 6 ช่องข้อมูล · หัวขนาด 3
x = np.arange(T * C, dtype=np.float64).reshape(T, C) / 10.0
rng = np.random.default_rng(1337)
Wq = rng.normal(size=(C, head)) * 0.5
Wk = rng.normal(size=(C, head)) * 0.5
Wv = rng.normal(size=(C, head)) * 0.5
q, k, v = x @ Wq, x @ Wk, x @ Wv # สามคำถามของทุกโน้ด
scores = q @ k.T / np.sqrt(head) # แถว i = โน้ด i ถามหาใคร
print("scores (raw) =\n", scores)
mask = np.tril(np.ones((T, T), dtype=bool))
masked = np.where(mask, scores, -np.inf) # ปิดอนาคตด้วยลบอนันต์
print("\nscores (after causal mask) =\n", masked)
e = np.exp(masked - masked.max(axis=-1, keepdims=True))
w = e / e.sum(axis=-1, keepdims=True) # softmax ทีละแถว
print("\nweights (after softmax) =\n", w)
print("row sums =", w.sum(axis=-1))
out = w @ v # ถ่วงน้ำหนักแล้วบวก
print("\nout[0] == v[0] ?", np.allclose(out[0], v[0]))ผลลัพธ์จริงที่ได้:
scores (raw) =
[[-0.14 -0.27 -0.39 -0.52]
[-0.1 -0.08 -0.06 -0.04]
[-0.06 0.11 0.28 0.44]
[-0.02 0.3 0.61 0.93]]
scores (after causal mask) =
[[-0.14 -inf -inf -inf]
[-0.1 -0.08 -inf -inf]
[-0.06 0.11 0.28 -inf]
[-0.02 0.3 0.61 0.93]]
weights (after softmax) =
[[1. 0. 0. 0. ]
[0.49 0.51 0. 0. ]
[0.28 0.33 0.39 0. ]
[0.15 0.2 0.28 0.38]]
row sums = [1. 1. 1. 1.]
out[0] == v[0] ? Trueมีสามอย่างในผลลัพธ์นี้ที่คุ้มกับการหยุดดูให้ดี
หนึ่ง — ตารางกลายเป็นสามเหลี่ยม ครึ่งบนขวาเป็น -inf ทั้งหมด เพราะเราปิดไม่ให้โน้ดมองอนาคต (บทที่ 10 จะเล่าว่าทำไม)
สอง — -inf หายไปกลายเป็น 0 พอดี นี่ไม่ใช่ความบังเอิญ softmax ต้องยกกำลัง e ของทุกค่า และ e ยกกำลังลบอนันต์เท่ากับศูนย์เป๊ะ นี่คือเหตุผลว่าทำไมต้องเป็นลบอนันต์ ไม่ใช่เลขติดลบมาก ๆ ถ้าใช้ค่าอย่างลบหนึ่งพัน มันจะเหลือน้ำหนักจิ๋วหนึ่งค้างอยู่ ซึ่งแปลว่ายังมีข้อมูลจากอนาคตรั่วเข้ามา
สาม — แถวแรกได้น้ำหนัก [1, 0, 0, 0] และ out[0] เท่ากับ v[0] เป๊ะ โน้ดตัวแรกไม่มีใครให้มองนอกจากตัวเอง น้ำหนักจึงเทไปที่ตัวเองทั้งหมด และผลลัพธ์ที่ได้คือ Value ของตัวเองแบบไม่ผสมอะไร — นี่คือหลักฐานว่ากลไกทั้งหมดเป็นเพียงการถ่วงน้ำหนักแล้วบวกกัน ไม่มีเวทมนตร์อะไรเกินกว่านั้น
9.3 แล้วเฟสคิดล่ะ
เฟสสื่อสารทำให้โน้ดได้ข้อมูลจากเพื่อน แต่ยังไม่มีใครคิดอะไรกับข้อมูลนั้น เฟสที่สองคือส่วนที่ทำงานนั้น: ข้อมูลของแต่ละโน้ดถูกส่งผ่านชั้นคำนวณเล็ก ๆ ที่ขยายออกสี่เท่าแล้วบีบกลับ โดยโน้ดแต่ละตัวทำของตัวเองอย่างเป็นเอกเทศ ไม่มีการคุยกันเลยในเฟสนี้
การแยกสองหน้าที่นี้ออกจากกันเด็ดขาดคือความงามทางการออกแบบของ Transformer — รับส่งข้อมูลเป็นเรื่องของเฟสหนึ่ง แปรรูปข้อมูลเป็นเรื่องของเฟสสอง ไม่ปนกัน