بسیاری از مدلهای یادگیری عمیق پیش از آنکه اولین گام بهینهسازی را بردارند، در مرحلهی مقداردهی اولیه شکست میخورند. اگر وزنها را اشتباه تنظیم کنید، سیگنال اکتیویشنها در همان چند لایهی اول یا به صفر سقوط میکند یا با مقادیری نجومی منفجر میشود؛ در نتیجه گرادیانها ناپدید میشوند و عملاً هیچ یادگیری رخ نمیدهد.
درک ریاضی رفتار واریانس در گذر رو به جلو و پسانتشار به شما امکان میدهد دلیل طراحی روشهای معروفی مثل Xavier/Glorot و Kaiming/He را متوجه شوید. تطبیق دقیق تابع اکتیویشن (مانند ReLU یا Tanh) با استراتژی مقداردهی، شرط اصلی پایدار ماندن آموزش در شبکههای بسیار عمیق و ترنسفورمرها است.
پیادهسازی این درس را میتوانید بدون نیاز به منابع پردازشی خاص یا GPU ابری، بهراحتی روی پردازندهی سیستم محلی خود اجرا کرده و اثر مقیاس وزنها را بر پایداری آموزش ارزیابی کنید.
وزندهی اولیه را اشتباه انجام دهید، آموزش هرگز شروع نمیشود. آن را درست انجام دهید، شبکهای با 50 لایه به همان روانیِ 3 لایه آموزش میبیند.
اهداف یادگیری
- پیادهسازی استراتژیهای مقداردهی اولیه صفر، تصادفی، Xavier/Glorot و Kaiming/He و سنجش اثر آنها روی بزرگی اکتیویشنها در 50 لایه
- اثبات اینکه چرا در مقداردهی اولیه Xavier از رابطهی Var(w) = 2/(fan_in + fan_out) و در Kaiming از Var(w) = 2/fan_in استفاده میشود
- نشان دادن مسئلهی تقارن (symmetry problem) با مقداردهی اولیه صفر و توضیح اینکه چرا مقیاس تصادفی بهتنهایی کافی نیست
- تطبیق استراتژی مقداردهی اولیه مناسب با تابع اکتیویشن: Xavier برای sigmoid/tanh و Kaiming برای ReLU/GELU
صورت مسئله
همهی وزنها را صفر بگذارید؛ هیچچیز آموزش نمیبیند. تکتک نورونها تابع یکسانی را محاسبه میکنند، گرادیان برابری میگیرند و دقیقاً مثل هم بهروزرسانی میشوند. بعد از 10000 ایپاک (epoch)، لایهی پنهان 512 نورونی شما هنوز 512 کپی از همان یک نورون است. هزینهی 512 پارامتر را دادهاید، اما عملاً 1 پارامتر نصیبتان شده است.
وزنها را بیش از حد بزرگ انتخاب کنید؛ اکتیویشنها در طول شبکه منفجر میشوند. تا لایهی 10، مقادیر به 1e15 میرسند. تا لایهی 20، سرریز میکنند و بینهایت میشوند. گرادیانها هم در مسیر برگشت دقیقاً همین مسیر را میروند.
آنها را بهطور تصادفی از یک توزیع نرمال استاندارد بردارید؛ برای 3 لایه جواب میدهد. در 50 لایه، بسته به اینکه مقیاس تصادفی کمی بیش از حد کوچک بوده یا کمی بیش از حد بزرگ، سیگنال یا به صفر سقوط میکند یا تا بینهایت منفجر میشود. مرز بین «کار میکند» و «خراب است» به باریکی مو است.
مقداردهی اولیهی وزنها (weight initialization) دستکمگرفتهشدهترین تصمیم در یادگیری عمیق است. معماری شبکه مقاله میگیرد؛ برای بهینهسازها پست وبلاگی مینویسند؛ اما به مقداردهی اولیه فقط در پاورقی اشاره میشود. با این حال، اگر آن را اشتباه انتخاب کنید هیچچیز دیگری اهمیتی نخواهد داشت؛ شبکهتان پیش از شروع آموزش مرده است.
مفاهیم پایه
مسئلهی تقارن
همهی نورونها در یک لایه ساختار یکسانی دارند: ورودیها را در وزنها ضرب کن، بایاس (bias) را اضافه کن، تابع اکتیویشن را اعمال کن. اگر همهی وزنها با مقدار یکسانی شروع شوند (صفر حالت افراطی آن است)، همهی نورونها خروجی یکسانی تولید میکنند. در پسانتشار (backpropagation)، به هر نورون گرادیان کاملاً یکسانی میرسد. در گام بهروزرسانی، وزنهای هر نورون به یک اندازه تغییر میکنند.
اینجا گیر میافتید. شبکه صدها پارامتر دارد، اما همگی هماهنگ و یکدست جلو میروند. به این حالت تقارن میگویند و مقداردهی اولیهی تصادفی راهکاری سرراست برای شکستن آن است. هر نورون از نقطهی متفاوتی در فضای وزنها شروع میکند؛ در نتیجه هر کدام ویژگی متفاوتی را یاد میگیرند.
اما «تصادفی بودن» کافی نیست. مقیاس (scale) این تصادفی بودن مشخص میکند که آیا شبکه آموزش میبیند یا نه.
انتشار واریانس در لایهها
یک لایهی تکی با fan_in ورودی را در نظر بگیرید:
اگر هر وزن از توزیعی با واریانس نمونهبرداری شود و هر ورودی دارای واریانس باشد، واریانس خروجی برابر است با:
اگر و باشد، واریانس خروجی 512 برابر واریانس ورودی میشود. پس از 10 لایه: . سیگنال شما منفجر شده است.
اگر باشد، واریانس خروجی در هر لایه با ضریب کوچک میشود. پس از 10 لایه: . سیگنال شما ناپدید شده است.
هدف: انتخاب بهگونهای که برقرار باشد، تا بزرگی سیگنال در تمام لایهها ثابت بماند.
مقداردهی اولیهی Xavier/Glorot
گلوروت و بنجیو (2010) فرمول را برای توابع اکتیویشن sigmoid و tanh استخراج کردند. برای ثابت نگه داشتن واریانس، هم در گذر رو به جلو (forward pass) و هم در پسانتشار:
در عمل، وزنها از توزیع زیر نمونهبرداری میشوند:
یا:
این روش جواب میدهد؛ چون sigmoid و tanh در نزدیکی صفر (جایی که اکتیویشنهای دارای مقداردهی اولیهی درست قرار میگیرند) رفتاری تقریباً خطی دارند. واریانس در طول دهها لایه پایدار میماند.
مقداردهی اولیهی Kaiming/He
تابع ReLU نیمی از خروجیها را صفر میکند (هر مقدار منفی صفر میشود). مقدار مؤثر fan_in نصف میشود، چون بهطور میانگین نیمی از ورودیها صفر شدهاند. مقداردهی اولیهی Xavier این موضوع را در نظر نمیگیرد و واریانس مورد نیاز را کمتر از واقعیت تخمین میزند.
هه و همکاران (2015) فرمول را اصلاح کردند:
وزنها از این توزیع نمونهبرداری میشوند:
ضریب 2 صفر شدن نیمی از اکتیویشنها توسط ReLU را جبران میکند. بدون این ضریب، سیگنال در هر لایه حدود 0.5 برابر کوچک میشود. در 50 لایه: . مقداردهی Kaiming جلوی این افت را میگیرد.
مقداردهی اولیهی ترنسفورمر
مدل GPT-2 الگوی متفاوتی را معرفی کرد. اتصالات باقیمانده (residual connections) خروجی هر زیرلایه را به ورودیاش اضافه میکنند:
هر جمع، واریانس را افزایش میدهد. با وجود لایهی باقیمانده، واریانس متناسب با رشد میکند. GPT-2 وزنهای لایههای باقیمانده را با ضریب مقیاسبندی میکند که در آن تعداد لایهها است. این کار بزرگی سیگنال جمعشده را پایدار نگه میدارد.
مدل Llama 3 (با 405 میلیارد پارامتر و 126 لایه) از روش مشابهی استفاده میکند. بدون این مقیاسبندی، جریان باقیمانده در طول 126 لایه از بلوکهای اتنشن (attention) و پیشخور (feedforward) بدون مهار رشد میکرد.
بزرگی اکتیویشن در 50 لایه
انتخاب روش مقداردهی مناسب
پیادهسازی گامبهگام
گام 1: استراتژیهای مقداردهی اولیه
چهار روش برای مقداردهی اولیهی ماتریس وزن. هر کدام یک لیست از لیستها (ماتریس دوبعدی) با fan_in ستون و fan_out سطر برمیگردانند.
import math
import random
def zero_init(fan_in, fan_out):
return [[0.0 for _ in range(fan_in)] for _ in range(fan_out)]
def random_init(fan_in, fan_out, scale=1.0):
return [[random.gauss(0, scale) for _ in range(fan_in)] for _ in range(fan_out)]
def xavier_init(fan_in, fan_out):
std = math.sqrt(2.0 / (fan_in + fan_out))
return [[random.gauss(0, std) for _ in range(fan_in)] for _ in range(fan_out)]
def kaiming_init(fan_in, fan_out):
std = math.sqrt(2.0 / fan_in)
return [[random.gauss(0, std) for _ in range(fan_in)] for _ in range(fan_out)]
گام 2: توابع اکتیویشن
برای آزمایش هر استراتژی با اکتیویشن هدفش، به sigmoid، tanh و ReLU نیاز داریم.
def sigmoid(x):
x = max(-500, min(500, x))
return 1.0 / (1.0 + math.exp(-x))
def tanh_act(x):
return math.tanh(x)
def relu(x):
return max(0.0, x)
گام 3: گذر رو به جلو در 50 لایه
دادههای تصادفی را از یک شبکهی عمیق عبور دهید و میانگین بزرگی اکتیویشن را در هر لایه اندازه بگیرید.
def forward_deep(init_fn, activation_fn, n_layers=50, width=64, n_samples=100):
random.seed(42)
layer_magnitudes = []
inputs = [[random.gauss(0, 1) for _ in range(width)] for _ in range(n_samples)]
for layer_idx in range(n_layers):
weights = init_fn(width, width)
biases = [0.0] * width
new_inputs = []
for sample in inputs:
output = []
for neuron_idx in range(width):
z = sum(weights[neuron_idx][j] * sample[j] for j in range(width)) + biases[neuron_idx]
output.append(activation_fn(z))
new_inputs.append(output)
inputs = new_inputs
magnitudes = []
for sample in inputs:
magnitudes.append(sum(abs(v) for v in sample) / width)
mean_mag = sum(magnitudes) / len(magnitudes)
layer_magnitudes.append(mean_mag)
return layer_magnitudes
گام 4: اجرای آزمایش
همهی ترکیبها را اجرا کنید: مقداردهی صفر، تصادفی N(0,1)، تصادفی N(0,0.01)، مقداردهی Xavier با sigmoid، مقداردهی Xavier با tanh و Kaiming با ReLU. بزرگی مقادیر را در لایههای کلیدی چاپ کنید.
def run_experiment():
configs = [
("Zero init + Sigmoid", lambda fi, fo: zero_init(fi, fo), sigmoid),
("Random N(0,1) + ReLU", lambda fi, fo: random_init(fi, fo, 1.0), relu),
("Random N(0,0.01) + ReLU", lambda fi, fo: random_init(fi, fo, 0.01), relu),
("Xavier + Sigmoid", xavier_init, sigmoid),
("Xavier + Tanh", xavier_init, tanh_act),
("Kaiming + ReLU", kaiming_init, relu),
]
print(f"{'Strategy':<30} {'L1':>10} {'L5':>10} {'L10':>10} {'L25':>10} {'L50':>10}")
print("-" * 80)
for name, init_fn, act_fn in configs:
mags = forward_deep(init_fn, act_fn)
row = f"{name:<30}"
for idx in [0, 4, 9, 24, 49]:
val = mags[idx]
if val > 1e6:
row += f" {'EXPLODED':>10}"
elif val < 1e-6:
row += f" {'VANISHED':>10}"
else:
row += f" {val:>10.4f}"
print(row)
گام 5: نمایش مسئلهی تقارن
نشان دهید که مقداردهی صفر نورونهای کاملاً یکسان تولید میکند.
def symmetry_demo():
random.seed(42)
weights = zero_init(2, 4)
biases = [0.0] * 4
inputs = [0.5, -0.3]
outputs = []
for neuron_idx in range(4):
z = sum(weights[neuron_idx][j] * inputs[j] for j in range(2)) + biases[neuron_idx]
outputs.append(sigmoid(z))
print("\nSymmetry Demo (4 neurons, zero init):")
for i, out in enumerate(outputs):
print(f" Neuron {i}: output = {out:.6f}")
all_same = all(abs(outputs[i] - outputs[0]) < 1e-10 for i in range(len(outputs)))
print(f" All identical: {all_same}")
print(f" Effective parameters: 1 (not {len(weights) * len(weights[0])})")
گام 6: گزارش لایهبهلایهی بزرگی مقادیر
نمودار میلهای بصری از بزرگی اکتیویشنها در طول 50 لایه چاپ کنید.
def magnitude_report(name, magnitudes):
print(f"\n{name}:")
for i, mag in enumerate(magnitudes):
if i % 5 == 0 or i == len(magnitudes) - 1:
if mag > 1e6:
bar = "X" * 50 + " EXPLODED"
elif mag < 1e-6:
bar = "." + " VANISHED"
else:
bar_len = min(50, max(1, int(mag * 10)))
bar = "#" * bar_len
print(f" Layer {i+1:3d}: {bar} ({mag:.6f})")
کاربرد در پروژههای واقعی
فریمورک PyTorch این قابلیتها را در قالب توابع توکار ارائه میدهد:
import torch
import torch.nn as nn
layer = nn.Linear(512, 256)
nn.init.xavier_uniform_(layer.weight)
nn.init.xavier_normal_(layer.weight)
nn.init.kaiming_uniform_(layer.weight, nonlinearity='relu')
nn.init.kaiming_normal_(layer.weight, nonlinearity='relu')
nn.init.zeros_(layer.bias)
وقتی nn.Linear(512, 256) را صدا میزنید، PyTorch بهطور پیشفرض از مقداردهی اولیهی یکنواخت (uniform) Kaiming استفاده میکند. دلیل اینکه بیشتر شبکههای ساده بدون دردسر کار میکنند همین است؛ PyTorch از قبل انتخاب درست را انجام داده است. اما وقتی معماریهای سفارشی میسازید یا عمق شبکه را از 20 لایه فراتر میبرید، باید دقیقاً بدانید چه اتفاقی در جریان است و در صورت نیاز رفتار پیشفرض را بازنویسی کنید.
در ترنسفورمرها، مدلهای HuggingFace معمولاً مقداردهی اولیه را در متد _init_weights مدیریت میکنند. پیادهسازی GPT-2 لایههای تصویر کردن (projections) باقیمانده را با ضریب مقیاسبندی میکند. اگر ترنسفورمری را از صفر مینویسید، باید این بخش را خودتان اضافه کنید.
تحویل پروژه
این درس فایل زیر را تولید میکند:
outputs/prompt-init-strategy.md-- پرامپتی که مشکلات مقداردهی اولیهی وزن را ریشهیابی کرده و استراتژی مناسب را پیشنهاد میدهد
تمرینها
-
مقداردهی اولیهی LeCun را اضافه کنید (، طراحیشده برای تابع اکتیویشن SELU). آزمایش 50 لایهای را با مقداردهی LeCun به همراه tanh اجرا و با Xavier به همراه tanh مقایسه کنید.
-
مقیاسبندی باقیماندهی GPT-2 را پیادهسازی کنید: خروجی هر لایه را قبل از اضافه کردن به جریان باقیمانده در ضرب کنید. شبکه را در 50 لایه با مقیاسبندی و بدون آن اجرا کنید و بسنجید بزرگی سیگنال باقیمانده با چه سرعتی رشد میکند.
-
تابعی تحت عنوان «بررسی سلامت مقداردهی اولیه» بسازید که ابعاد لایهها و نوع اکتیویشن شبکه را دریافت کند، سپس مقداردهی اولیهی درست را پیشنهاد دهد و در صورتی که مقداردهی فعلی مشکلی ایجاد میکند هشدار دهد.
-
آزمایش را با در برابر اجرا کنید. روشهای Xavier و Kaiming با مقدار fan_in سازگار میشوند، اما مقداردهی تصادفی ساده اینطور نیست. نشان دهید که با بزرگتر شدن لایهها، شکاف بین حالت «کار میکند» و «خراب میشود» چطور عمیقتر خواهد شد.
-
مقداردهی اولیهی متعامد (orthogonal initialization) را پیادهسازی کنید (یک ماتریس تصادفی بسازید، تجزیهی مقدارهای منفرد یا SVD آن را حساب کنید و از ماتریس متعامد U استفاده کنید). آن را در 50 لایه با Kaiming برای شبکههای ReLU مقایسه کنید.
اصطلاحات کلیدی
| اصطلاح | در گفتگوهای عامیانه | معنای دقیق فنی |
|---|---|---|
| Weight initialization (مقداردهی اولیهی وزن) | «تنظیم تصادفی وزنهای شروع» | استراتژی انتخاب مقادیر اولیهی وزنها که تعیین میکند آیا شبکه اصلاً امکان آموزش دیدن دارد یا نه |
| Symmetry breaking (شکستن تقارن) | «متمایز کردن نورونها از هم» | استفاده از مقداردهی اولیهی تصادفی برای اطمینان از اینکه نورونها توابع یکسانی را محاسبه نکنند و ویژگیهای متفاوتی را یاد بگیرند |
| Fan-in | «تعداد ورودیهای یک نورون» | تعداد اتصالات ورودی که مشخص میکند واریانس ورودی چطور در مجموع وزندار انباشته میشود |
| Fan-out | «تعداد خروجیهای یک نورون» | تعداد اتصالات خروجی که برای حفظ واریانس گرادیان حین پسانتشار اهمیت دارد |
| Xavier/Glorot init | «مقداردهی اولیهی مخصوص سیگموئید» | رابطه ، طراحیشده برای حفظ واریانس در طول توابع اکتیویشن sigmoid و tanh |
| Kaiming/He init | «مقداردهی اولیهی مخصوص رلو» | رابطه ، با در نظر گرفتن صفر شدن نیمی از اکتیویشنها توسط ReLU |
| Variance propagation (انتشار واریانس) | «چگونگی رشد یا افت سیگنال در لایهها» | تحلیل ریاضی چگونگی تغییر واریانس اکتیویشنها در هر لایه بر اساس مقیاس وزنها |
| Residual scaling (مقیاسبندی باقیمانده) | «ترفند مقداردهی GPT-2» | مقیاسبندی وزنهای اتصالات باقیمانده با ضریب برای جلوگیری از رشد واریانس در طول لایهی ترنسفورمر |
| Dead network (شبکهی مرده) | «هیچچیز یاد نمیگیرد» | شبکهای که در آن مقداردهی ضعیف باعث صفر شدن همهی گرادیانها یا اشباع کامل همهی اکتیویشنها شده است |
| Exploding activations (انفجار اکتیویشنها) | «مقادیر تا بینهایت بالا میروند» | حالتی که در آن واریانس وزنها بیش از حد بالا است و باعث رشد نمایی بزرگی اکتیویشنها در طول لایهها میشود |
برای مطالعهی بیشتر
- مقالهی گلوروت و بنجیو با عنوان "Understanding the difficulty of training deep feedforward neural networks" (2010) -- مقالهی اصلی مقداردهی اولیهی Xavier به همراه تحلیل واریانس
- مقالهی هه و همکاران با عنوان "Delving Deep into Rectifiers" (2015) -- معرفی مقداردهی اولیهی Kaiming برای شبکههای با اکتیویشن ReLU
- مقالهی ردفورد و همکاران با عنوان "Language Models are Unsupervised Multitask Learners" (2019) -- مقالهی مدل GPT-2 با مقداردهی اولیهی مقیاسبندی باقیمانده
- مقالهی میشکین و ماتاس با عنوان "All You Need is a Good Init" (2016) -- مقداردهی اولیهی واریانس واحد لایهبهلایه، رویکردی تجربی در برابر فرمولهای تحلیلی
