عنوان

سیر تحول شبکه‌های عصبی برای داده‌های ترتیبی؛ از RNN و LSTM تا Transformer و یادگیری توجه

در مقالهٔ قبلی دیدیم یک شبکهٔ عصبی چطور یاد می‌گیره: یک جواب می‌ده، اشتباهش را اندازه می‌گیره، با backpropagation سهم وزن‌ها را پیدا می‌کنه و وزن‌ها را کمی تغییر می‌ده.

اما آنجا بیشتر طوری حرف زدیم که انگار ورودی یک چیز ثابت است؛ مثلاً مشخصات یک خانه یا پیکسل‌های یک تصویر. حالا فرض کن ورودی یک جمله است که کلمه‌هایش پشت سر هم می‌آیند، یا قیمت بیت‌کوین در روزهای متوالی.

اینجا فقط اینکه «چه عددهایی داریم» مهم نیست؛ ترتیبشان هم بخشی از مسئله است. قیمت‌های ۱۰۰، ۱۱۰ و ۱۲۰ با همان سه قیمت در ترتیب برعکس، یک داستان نمی‌گویند. جملهٔ «سگ، مرد را گاز گرفت» هم با «مرد، سگ را گاز گرفت» کلمه‌های تقریباً یکسانی دارد، اما معنایش عوض می‌شود.

این مقاله داستان تلاش مدل‌ها برای فهم همین ترتیب است: اول با یک MLP ساده، بعد با حافظهٔ RNN، بعد با دروازه‌های LSTM و GRU و در نهایت با Attention و Transformer.

اول: دادهٔ ترتیبی دقیقاً چیست؟

یک توالی را می‌توانیم این‌طور بنویسیم:

نمایش یک توالی

sequence (x₁, x₂, …, xₜ)
prediction f(x₁, x₂, …, xₜ)

xₜ می‌تواند قیمت روز t، یک کلمه، دمای یک ساعت یا بردار چند ویژگی در یک لحظه باشد. هدف هم ممکن است پیش‌بینی قیمت فردا باشد یا حدس‌زدن کلمهٔ بعدی.

سؤال اصلی این است: مدل چطور می‌فهمد کدام قسمت گذشته برای جواب فعلی مهم است؟

با MLP معمولی هم می‌شود سری زمانی پیش‌بینی کرد

گاهی گفته می‌شود MLP ترتیب را نمی‌فهمد. این جمله دقیق نیست. اگر جایگاه ورودی‌ها را ثابت نگه داریم، MLP می‌تواند یاد بگیرد که عدد اول پنجره «قدیمی‌تر» و عدد آخر «جدیدتر» است.

فرض کن قیمت شش روز این‌هاست:

10, 12, 11, 14, 15, 13

می‌خواهیم مدل هر بار سه روز گذشته را ببیند و روز بعد را حدس بزند. پس از یک سری، چند نمونهٔ آموزشی می‌سازیم:

ورودی، از قدیمی به جدید جواب واقعی روز بعد
۱۰، ۱۲، ۱۱ ۱۴
۱۲، ۱۱، ۱۴ ۱۵
۱۱، ۱۴، ۱۵ ۱۳

به این روش پنجرهٔ لغزان می‌گویند. هر ردیف را به یک MLP می‌دهیم؛ مدل پیش‌بینی می‌کند، loss می‌گیریم و وزن‌ها را با backpropagation تغییر می‌دهیم. دقیقاً همان منطق مقالهٔ قبلی.

پس MLP می‌تواند از سری زمانی استفاده کند. محدودیتش این است که شکل ورودی از قبل ثابت است. اگر پنجرهٔ ما ۳۰ روزه باشد، مدل برای همان ۳۰ جایگاه ساخته شده است. خود MLP هم حالتی ندارد که با رسیدن هر روز جدید، همراه توالی جلو برود و به‌روز شود.

همین سؤال طبیعی ما را به RNN می‌رساند: آیا مدل می‌تواند یک حافظهٔ داخلی داشته باشد که با هر ورودی تازه تغییر کند؟

حوالی ۱۹۹۰؛ RNN و حافظه‌ای که همراه توالی حرکت می‌کند

در RNN، هر گام فقط ورودی جدید را نمی‌بیند؛ وضعیت یا حافظهٔ گام قبل را هم می‌گیرد.

هستهٔ یک RNN ساده

hₜ tanh(Wₓxₜ + Wₕhₜ₋₁ + b)
ŷₜ Wᵧhₜ + c

xₜ ورودی فعلی، hₜ₋₁ حالت قبلی و hₜ حالت تازه است. وزن‌های Wₓ و Wₕ در همهٔ گام‌ها مشترک‌اند؛ برای هر روز یا هر کلمه شبکهٔ جدیدی نمی‌سازیم.

مثلاً در جملهٔ زیر، مدل کلمه‌ها را یکی‌یکی می‌بیند:

هوا → امروز → بارانی → است

هر کلمه hidden state را عوض می‌کند. پس حالتِ بعد از «بارانی» باید چیزی از «هوا» و «امروز» هم در خودش داشته باشد.

این حافظه یک آرشیو دقیق از گذشته نیست؛ یک بردار فشرده و یادگرفتنی است. مدل خودش در آموزش می‌فهمد چه چیزهایی را نگه دارد.

یک RNN خیلی کوچک با عدد

برای دیدن این حرکت، فعلاً activation را کنار می‌گذاریم:

حافظهٔ سادهٔ مثال

h₀ 0
hₜ 0.5 × hₜ₋₁ + xₜ
(x₁, x₂, x₃) (2, 0, 1)

سه گام حرکت حافظه

h₁ 0.5 × 0 + 2 = 2
h₂ 0.5 × 2 + 0 = 1
h₃ 0.5 × 1 + 1 = 1.5

در h₃ هنوز اثر x₁ وجود دارد. اگر فرمول را باز کنیم:

ردّ ورودی‌های قدیمی در حالت فعلی

رابطهٔ اصلی

h₃ = 0.5 × (0.5 × x₁ + x₂) + x₃
0.25 × x₁ + 0.5 × x₂ + x₃

جایگذاری مقادیر

0.25 × 2 + 0.5 × 0 + 1
1.5

ایدهٔ RNN همین است: گذشته را در یک state فشرده کن، سپس با ورودی تازه آن را به‌روز کن.

RNN چطور یاد می‌گیرد و چرا گذشته‌های دور سخت‌اند؟

برای آموزش RNN، شبکه را در طول زمان باز می‌کنیم: یک نسخه از همان سلول در گام اول، یکی در گام دوم و همین‌طور تا آخر. وزن‌ها میان همهٔ نسخه‌ها مشترک‌اند.

گرادیان از loss به عقب برمی‌گردد و از گام‌های زمانی عبور می‌کند. نام این فرایند Backpropagation Through Time یا BPTT است.

اگر پاسخ امروز به اتفاقی در صد گام قبل وابسته باشد، gradient باید از صد اتصال زمانی عبور کند. در مسیر، مشتق‌های محلی در هم ضرب می‌شوند. اگر بسیاری از آن‌ها کوچک‌تر از یک باشند، سیگنال یادگیری کوچک می‌شود:

تصویر ساده از vanishing gradient

local_derivative 0.5
steps 10
gradient_factor 0.5¹⁰ ≈ 0.00098

به این وضعیت vanishing gradient می‌گوییم. حالت مخالف، یعنی خیلی بزرگ‌شدن حاصل‌ضرب، exploding gradient است. در نتیجه، RNN ساده ممکن است یادگرفتن وابستگی‌های دور را سخت پیدا کند.

سال ۱۹۹۷؛ LSTM و کنترل بهتر حافظه

LSTM یا Long Short-Term Memory آمد تا مدل کنترل دقیق‌تری روی حافظه داشته باشد. به‌جای یک مسیر ساده برای h، یک حافظهٔ جدا به نام cell state یا c دارد و چند دروازه تصمیم می‌گیرند چه چیزی بماند، چه چیزی وارد شود و چه چیزی در خروجی دیده شود.

چهار محاسبهٔ اصلی در LSTM

fₜ sigmoid(Wf × [hₜ₋₁, xₜ] + bf)
iₜ sigmoid(Wi × [hₜ₋₁, xₜ] + bi)
gₜ tanh(Wg × [hₜ₋₁, xₜ] + bg)
oₜ sigmoid(Wo × [hₜ₋₁, xₜ] + bo)

هر باکس وزن‌های خودش را دارد. f می‌گوید چقدر از حافظهٔ قبلی بماند، i می‌گوید چقدر از پیشنهاد تازه وارد شود، g خودِ محتوای پیشنهادی است و o تصمیم می‌گیرد چه بخشی از حافظه در خروجی دیده شود.

به‌روزرسانی حافظه و خروجی LSTM

cₜ fₜ ⊙ cₜ₋₁ + iₜ ⊙ gₜ
hₜ oₜ ⊙ tanh(cₜ)

⊙ یعنی ضرب عضو‌به‌عضو. مثلاً اگر حافظهٔ قبلی 0.8 باشد:

یک گام تک‌بعدی در LSTM

رابطهٔ اصلی

cₜ = fₜ × cₜ₋₁ + iₜ × gₜ
0.9 × 0.8 + 0.2 × 0.5
0.72 + 0.10
0.82

مدل در این گام بیشتر حافظهٔ قبلی را نگه داشته و فقط کمی اطلاعات تازه وارد کرده است. عددهای دروازه‌ها را کسی دستی تعیین نمی‌کند؛ با forward pass ساخته و با BPTT یاد گرفته می‌شوند.

مسیر جمعیِ c کمک می‌کند اطلاعات و gradient در بعضی شرایط بهتر از یک زنجیرهٔ ضربی ساده حرکت کنند. اما LSTM هم حافظهٔ نامحدود و بی‌نقص نیست.

سال ۲۰۱۴؛ GRU، نسخه‌ای جمع‌وجورتر

GRU هم یک مدل بازگشتیِ دروازه‌دار است، اما از LSTM ساده‌تر است. به‌جای cell state جدا، بیشتر کار را با hidden state انجام می‌دهد.

طرح کلی GRU

zₜ sigmoid(Wz × xₜ + Uz × hₜ₋₁ + bz)
rₜ sigmoid(Wr × xₜ + Ur × hₜ₋₁ + br)
h̃ₜ tanh(Wh × xₜ + Uh × (rₜ ⊙ hₜ₋₁) + bh)
hₜ (1 − zₜ) ⊙ hₜ₋₁ + zₜ ⊙ h̃ₜ

z دروازهٔ update است: تصمیم می‌گیرد چه مقدار از حالت تازه جای حالت قدیمی را بگیرد. r دروازهٔ reset است: تعیین می‌کند هنگام ساخت candidate، چقدر از گذشته وارد شود.

LSTM و GRU رقیب‌هایی با یک برندهٔ همیشگی نیستند. هر دو برای کنترل جریان اطلاعات در طول زمان ساخته شده‌اند؛ انتخاب واقعی به داده، اندازهٔ مدل و آزمایش بستگی دارد.

سال ۲۰۱۴؛ Seq2Seq و تبدیل یک توالی به توالی دیگر

برای پیش‌بینی قیمت فردا شاید یک عدد خروجی بخواهیم. اما در ترجمه، ورودی یک توالی است و خروجی هم توالی دیگری با طول احتمالاً متفاوت.

I love cats
↓
J’aime les chats

ایدهٔ Encoder–Decoder این بود:

Encoder: جملهٔ ورودی را بخوان و خلاصه کن
Decoder: با آن خلاصه، جملهٔ خروجی را کلمه‌به‌کلمه بساز

در نسخهٔ سادهٔ اولیه، Encoder بعد از خواندن کل ورودی فقط حالت آخرش را به Decoder می‌داد:

Seq2Seq ساده بدون Attention

encoder_states (h₁, h₂, h₃)
context h₃
decoder_initial_state context

برای ورودی کوتاه شاید جواب بدهد. اما برای جملهٔ بلند، Decoder مجبور می‌شود تمام جزئیات را از یک بردار ثابت بیرون بکشد. انگار کل یک کتاب را در یک برگه خلاصه کرده‌ایم و بعد از Decoder می‌خواهیم با همان برگه کتاب را ترجمه کند.

همین گلوگاه ما را به Attention می‌رساند.

سال‌های ۲۰۱۴ و ۲۰۱۵؛ Attention در کنار RNN

Attention گفت چرا Decoder فقط به h₃ دسترسی داشته باشد؟ همهٔ حالت‌های Encoder را نگه داریم و Decoder در هر گام تصمیم بگیرد کدام‌ها مهم‌ترند.

Attention در مدل بازگشتی Encoder–Decoder

scoreₜⱼ compare(sₜ₋₁, hⱼ)
αₜⱼ softmax(scoreₜⱼ across j)
contextₜ Σⱼ αₜⱼ × hⱼ

sₜ₋₁ وضعیت فعلی Decoder است. hⱼ حالت مربوط به جایگاه j در Encoder. وزن αₜⱼ می‌گوید هنگام ساخت خروجیِ گام t، هر بخش ورودی چقدر سهم دارد.

مثلاً هنگام تولید chats، Decoder ممکن است بیشتر به بخشی از ورودی نگاه کند که نمایندهٔ cats است. هنگام تولید فعل، وزن توجه به love بیشتر شود.

پس Attention یک خلاصهٔ ثابت نمی‌سازد؛ برای هر گام Decoder، یک context تازه می‌سازد.

چرا Softmax؟

scoreها می‌توانند هر عددی باشند. Softmax آن‌ها را به وزن‌های مثبت تبدیل می‌کند که جمعشان یک است:

دو score و وزن‌های Attention

score₁ 2
score₂ 1
α₁ e² ÷ (e² + e¹) ≈ 0.731
α₂ e¹ ÷ (e² + e¹) ≈ 0.269

این وزن‌ها شبیه احتمال‌اند، اما لازم نیست آن‌ها را «احتمال درست‌بودن یک کلمه» بدانیم. اینجا معنای اصلی‌شان سهم هر نمایش در ساختن context است.

نکتهٔ تاریخی مهم این است که Attention پیش از Transformer و کنار RNNها استفاده می‌شد. Transformer، Attention را اختراع نکرد؛ آن را از یک افزونه به مرکز معماری تبدیل کرد.

Transformer اصلاً چیست؟

Transformer یک نوع معماری شبکهٔ عصبی برای داده‌های ترتیبی است. همان‌طور که RNN یک معماری بود، Transformer هم یک معماری است؛ فقط روش جابه‌جایی اطلاعات در آن فرق می‌کند.

RNN اطلاعات را از یک زنجیره رد می‌کرد:

x₁ → h₁ → h₂ → h₃

Transformer گفت لازم نیست اطلاعات از همهٔ گام‌های وسط عبور کند. اجازه بدهیم هر جایگاه مستقیماً جایگاه‌های دیگر را بررسی کند. این ایده، Self-Attention است.

یک Transformer فقط Attention نیست. در هر block، Attention ارتباط بین توکن‌ها را تعیین می‌کند و یک MLP اطلاعات هر توکن را پردازش می‌کند.

Attention = اطلاعات کدام توکن‌ها با هم ترکیب شوند؟
MLP = این اطلاعات ترکیب‌شده چطور پردازش شوند؟

Self-Attention؛ هر توکن به توکن‌های همان دنباله نگاه می‌کند

فرض کن سه نمایش ورودی داریم:

x₁, x₂, x₃

برای هرکدام سه نسخهٔ متفاوت می‌سازیم:

ساخت Query و Key و Value

Q XW_Q
K XW_K
V XW_V

W_Q، W_K و W_V سه لایهٔ خطی با وزن‌های یادگرفتنی هستند. این‌ها همان ضرب ورودی در وزن‌اند که در MLP دیده‌ایم؛ فقط برای همهٔ توکن‌ها با وزن مشترک اجرا می‌شوند.

x₂ → WQ → q₂
x₂ → WK → k₂
x₂ → WV → v₂

می‌توانیم این سه را شهودی این‌طور بخوانیم:

Query: من دنبال چه نوع اطلاعاتی هستم؟
Key: من چه نوع اطلاعاتی برای پیدا شدن دارم؟
Value: اگر مهم بودم، چه اطلاعاتی منتقل کنم؟

برای ساختن نمایش جدید توکن دوم، q₂ را با Key همهٔ توکن‌ها مقایسه می‌کنیم:

سه score برای توکن دوم

score₂₁ q₂ · k₁
score₂₂ q₂ · k₂
score₂₃ q₂ · k₃

بعد Softmax وزن‌ها را می‌سازد و Valueها با همان وزن‌ها ترکیب می‌شوند:

ساخت نمایش contextual برای توکن دوم

(α₂₁, α₂₂, α₂₃) softmax(score₂₁, score₂₂, score₂₃)
z₂ α₂₁v₁ + α₂₂v₂ + α₂₃v₃

z₂ نسخهٔ جدید x₂ است؛ حالا فقط معنی خام خودش را ندارد و از توکن‌های دیگر اطلاعات گرفته است.

در شکل ماتریسی، تمام این کارها برای همهٔ توکن‌ها هم‌زمان نوشته می‌شود:

Scaled Dot-Product Attention

Attention(Q, K, V) softmax(QKᵀ ÷ √dₖ) V

dₖ تعداد عددهای هر Key و Query است. تقسیم بر √dₖ کمک می‌کند scoreها در ابعاد بزرگ بیش‌ازحد بزرگ نشوند و Softmax خیلی زود به صفر و یک‌های افراطی نچسبد.

سه نوع Attention را از کجا تشخیص بدهیم؟

تفاوت اصلی فقط این است که Q، K و V از کجا می‌آیند.

Encoder Self-Attention:
Q, K, V همگی از Encoder

Decoder Masked Self-Attention:
Q, K, V همگی از Decoder، اما آینده Mask می‌شود

Cross-Attention:
Q از Decoder؛ K و V از Encoder

Encoder Self-Attention؛ فهمیدن کل ورودی

در Encoder، کل جملهٔ ورودی از اول در دسترس است. بنابراین هر توکن می‌تواند همهٔ توکن‌های ورودی را ببیند.

q₁ → k₁, k₂, k₃
q₂ → k₁, k₂, k₃
q₃ → k₁, k₂, k₃

هیچ Maskی وجود ندارد. برای فهمیدن یک جمله، دیدن همهٔ جمله تقلب نیست؛ همان کاری است که Encoder باید انجام دهد.

Decoder Masked Self-Attention؛ نگاه‌نکردن به آینده

Decoder خروجی را کلمه‌به‌کلمه می‌سازد. فرض کن ورودیِ جابه‌جاشدهٔ آن این باشد:


<START> | J’aime | les

مدل باید به‌ترتیب این‌ها را پیش‌بینی کند:

J’aime | les | chats

اگر جایگاه دوم بتواند les یا chats آینده را ببیند، مدل در آموزش تقلب می‌کند. پس causal mask چنین محدودیتی می‌سازد:

جایگاه ۱ → فقط ۱ را می‌بیند
جایگاه ۲ → ۱ و ۲ را می‌بیند
جایگاه ۳ → ۱، ۲ و ۳ را می‌بیند

خانه‌های ممنوعِ score را قبل از Softmax برابر −∞ می‌کنیم. چون e⁻∞ = 0، وزن توجه آن‌ها بعد از Softmax صفر می‌شود.

نمونهٔ Mask برای جایگاه دوم

scores_before_mask (1, 3, 5)
scores_after_mask (1, 3, −∞)
attention_weights (0.12, 0.88, 0)

پس جایگاه دوم هیچ اطلاعاتی از جایگاه سوم نمی‌گیرد.

Cross-Attention؛ پل میان Decoder و Encoder

بعد از اینکه Decoder ترجمهٔ قبلی خودش را بررسی کرد، باید دوباره به جملهٔ اصلی رجوع کند. Cross-Attention همین پل است.

برای تولید chats در ترجمهٔ I love cats داریم:

Query از Decoder:
الان برای ساختن کلمهٔ بعدی دنبال چه اطلاعاتی هستم؟

Key و Value از Encoder:
کدام بخش جملهٔ انگلیسی مرتبط است و چه اطلاعاتی منتقل کند؟

Cross-Attention

qₜ decoder_stateₜW_Q
kᵢ encoder_outputᵢW_K
vᵢ encoder_outputᵢW_V
contextₜ Σᵢ γₜᵢvᵢ

اگر Decoder برای ساختن chats بیشترین ارتباط را با نمایش مربوط به cats پیدا کند، وزن آن Value بیشتر می‌شود. هنگام ساختن فعل، ممکن است الگوی وزن‌ها به سمت love جابه‌جا شود.

Cross-Attention در Transformer اصلی سال ۲۰۱۷ هم وجود داشت. مقاله آن را encoder-decoder attention نامیده بود؛ نام رایج امروزی‌اش Cross-Attention است.

چرا Multi-Head Attention داریم؟

یک Attention فقط یک نوع نگاه یاد می‌گیرد. Transformer چند کپی هم‌شکل از Attention را موازی اجرا می‌کند:

Multi-Head Attention

headᵢ Attention(XW_Qᵢ, XW_Kᵢ, XW_Vᵢ)
output Concat(head₁, …, headₕ)W_O

همهٔ headها از نظر فرمول یکی هستند، اما وزن‌هایشان یکی نیست.

Head 1: WQ₁, WK₁, WV₁
Head 2: WQ₂, WK₂, WV₂

درست مثل نورون‌های یک لایه: فرمول مشترک است، اما وزن‌ها متفاوت‌اند. اگر وزن headها دقیقاً برابر باشند، خروجی‌شان هم یکی می‌شود و داشتن چند head بی‌فایده است.

مدل ممکن است یاد بگیرد headهای مختلف از زاویه‌های متفاوتی به رابطه‌ها نگاه کنند، اما لازم نیست از قبل به آن‌ها نقش انسانی و ثابت بدهیم.

وقتی RNN حذف شد، ترتیب از کجا می‌آید؟

Self-Attention به‌تنهایی ترتیب را نمی‌فهمد. اگر به مدل نگوییم چه چیزی اول یا آخر بوده، توالی زیر فقط مجموعه‌ای از توکن‌ها به نظر می‌رسد:

I love cats
cats love I

پس به embedding هر جایگاه، اطلاعات موقعیت اضافه می‌کنیم:

ورودی همراه با جایگاه

inputₜ token_embeddingₜ + positional_encodingₜ

در مقالهٔ اصلی، positional encoding با سینوس و کسینوس ساخته شد. هدف مهم‌تر از فرمول است: هر توکن علاوه بر معنی خودش، جایگاهش را هم حمل می‌کند.

در سری زمانی نیز این بخش مهم است. مدل باید بفهمد مشاهدهٔ مربوط به دیروز با مشاهدهٔ ده روز قبل فرق دارد؛ گاهی حتی فاصلهٔ واقعی زمان‌ها هم باید وارد مدل شود.

بعد از Attention، یک MLP معمولی داریم

Transformer فقط Attention نیست. پس از هر Attention، خروجی هر توکن وارد یک Feed-Forward Network می‌شود؛ همان یک MLP کوچک و مشترک برای تمام جایگاه‌ها.

Feed-Forward Network در Transformer

hidden activation(xW₁ + b₁)
output hiddenW₂ + b₂

این MLP روی هر توکن جداگانه اجرا می‌شود:

z₁ → MLP → u₁
z₂ → MLP → u₂
z₃ → MLP → u₃

پس نقش‌ها تقسیم شده‌اند:

Attention: اطلاعات بین توکن‌ها را ترکیب می‌کند
MLP: اطلاعات ترکیب‌شدهٔ هر توکن را پردازش می‌کند

Residual Connection و Layer Normalization

برای اینکه شبکه‌های عمیق بهتر آموزش ببینند، ورودی هر زیرلایه را به خروجی آن اضافه می‌کنیم:

Residual Connection

output x + Sublayer(x)

بعد Layer Normalization مقدارهای بردار را پایدارتر می‌کند:

Layer Normalization به‌شکل ساده

normalized (x − mean(x)) ÷ √(variance(x) + ε)
output γ × normalized + β

γ و β پارامترهای یادگرفتنی‌اند. در تصویرهای Transformer این دو مرحله را معمولاً با عبارت Add & Norm می‌بینی.

یک Encoder Block و یک Decoder Block کامل

یک Encoder block در Transformer اصلی چنین است:

Embedding + Position
↓
Multi-Head Self-Attention
↓
Add & Norm
↓
Feed-Forward MLP
↓
Add & Norm

یک Decoder block این‌طور است:

Shifted target tokens + Position
↓
Masked Multi-Head Self-Attention
↓
Add & Norm
↓
Cross-Attention با خروجی‌های Encoder
↓
Add & Norm
↓
Feed-Forward MLP
↓
Add & Norm
↓
Linear + Softmax
↓
احتمال توکن بعدی

چند Encoder block روی هم Encoder را می‌سازند و چند Decoder block هم Decoder را. هر block نمایش‌ها را یک لایه عمیق‌تر می‌کند.

Forward pass و Backpropagation در Transformer

در آموزش ترجمه، جملهٔ ورودی و ترجمهٔ درست هر دو موجودند.

Source: I love cats
Target: J’aime les chats

Encoder سه نمایش contextual می‌سازد:

z₁, z₂, z₃

Decoder ورودیِ جابه‌جاشده را می‌گیرد:


<START> | J’aime | les

و احتمال جواب‌های درست را برای همهٔ جایگاه‌ها می‌سازد. loss معمولاً با cross-entropy محاسبه می‌شود:

Loss برای یک توکن درست

target_token chats
predicted_probability P(chats)
loss −log(P(chats))

اگر P(chats) زیاد باشد، loss کم است. اگر مدل به chats احتمال کمی بدهد، loss زیاد می‌شود.

Backpropagation از loss به عقب می‌رود:

Loss
↓
Softmax و Linear نهایی
↓
Decoder MLP و Attentionها
↓
Cross-Attention
↓
Encoder Self-Attention و MLPها
↓
Embeddingها و وزن‌های WQ, WK, WV, WO

Attention weightها خودِ پارامتر نیستند؛ در هر forward pass از Q و K ساخته می‌شوند. چیزهایی که یاد گرفته می‌شوند وزن‌های لایه‌های خطی‌اند، مثل WQ، WK و WV.

Transformer همهٔ مسئله‌ها را حل نکرد

Self-Attention معمولی برای هر جفت جایگاه score می‌سازد. برای توالی با طول T، ماتریس score اندازه‌ای تقریباً برابر T × T دارد. پس با زیادشدن طول توالی، هزینهٔ حافظه و محاسبه سریع بالا می‌رود.

همچنین Attention نمی‌تواند اطلاعاتی بسازد که در داده وجود ندارد. اگر در گذشتهٔ یک سری مالی سیگنال قابل‌اعتمادی دربارهٔ فردا نباشد، معماری بهتر به‌تنهایی معجزه نمی‌کند.

RNN و LSTM هم یک‌شبه بی‌استفاده نشدند. آن‌ها برای بعضی داده‌ها، حجم‌های کوچک‌تر و محدودیت‌های سخت‌افزاری هنوز انتخاب‌های معقولی‌اند.

این داستان چه ربطی به سری زمانی دارد؟

در سری زمانی، توکن‌ها لازم نیست کلمه باشند. می‌توانند قیمت‌های روزانه، بازده‌ها، حجم معاملات یا تکه‌هایی از یک پنجرهٔ زمانی باشند.

RNN:
گذشته را در state حمل می‌کند

LSTM و GRU:
تصمیم می‌گیرند چه چیز از گذشته بماند

Attention:
برای جواب فعلی به بخش‌های مختلف گذشته وزن متفاوت می‌دهد

Transformer:
این ارتباط مستقیم میان بخش‌های توالی را هستهٔ معماری می‌کند

اما هنوز یک پله تا TimesFM فاصله داریم. حالا Transformer را می‌فهمیم؛ مقالهٔ بعدی دربارهٔ این است که یک مدل چطور روی تعداد زیادی سری زمانی پیش‌آموزش می‌بیند و به یک Foundation Model برای پیش‌بینی تبدیل می‌شود.

جمع‌بندی

مسیر تاریخ را می‌توانیم این‌طور نگه داریم:

MLP با پنجرهٔ لغزان
↓
RNN: گذشته را با hidden state حمل کن
↓
LSTM و GRU: جریان حافظه را با دروازه‌ها کنترل کن
↓
Seq2Seq: یک توالی را به توالی دیگر تبدیل کن
↓
Attention: برای هر پاسخ به بخش مناسب ورودی رجوع کن
↓
Transformer: ارتباط بین توکن‌ها را با Self-Attention و MLP بساز

اگر همهٔ این مقاله را در یک جمله جمع کنیم:

مدل اول یاد گرفت گذشته را با خودش حمل کند، بعد یاد گرفت چه چیزی را نگه دارد، و در نهایت یاد گرفت برای جواب فعلی مستقیماً به کدام بخش از اطلاعات موجود رجوع کند.

منابع

این مقاله با روایت و مثال‌های عددیِ تألیفی نوشته شده است. منابع زیر برای تاریخچه، فرمول‌ها و ادعاهای معماری استفاده شده‌اند:

  1. Jeffrey L. Elman، Finding Structure in Time، Cognitive Science، ۱۹۹۰.
  2. Yoshua Bengio، Patrice Simard و Paolo Frasconi، Learning Long-Term Dependencies with Gradient Descent Is Difficult، IEEE Transactions on Neural Networks، ۱۹۹۴.
  3. Sepp Hochreiter و Jürgen Schmidhuber، Long Short-Term Memory، Neural Computation، ۱۹۹۷.
  4. Felix A. Gers، Jürgen Schmidhuber و Fred Cummins، Learning to Forget: Continual Prediction with LSTM، Neural Computation، ۲۰۰۰.
  5. Kyunghyun Cho و همکاران، Learning Phrase Representations using RNN Encoder–Decoder for Statistical Machine Translation، EMNLP، ۲۰۱۴.
  6. Ilya Sutskever، Oriol Vinyals و Quoc V. Le، Sequence to Sequence Learning with Neural Networks، NeurIPS، ۲۰۱۴.
  7. Dzmitry Bahdanau، Kyunghyun Cho و Yoshua Bengio، Neural Machine Translation by Jointly Learning to Align and Translate، ICLR، ۲۰۱۵.
  8. Ashish Vaswani و همکاران، Attention Is All You Need، NeurIPS، ۲۰۱۷.