هوش مصنوعی
درک شبکههای عصبی از صفر
میتوانید سالها از طریق یک فریمورک از شبکههای عصبی استفاده کنید و باز هم حس کنید جادو هستند. سریعترین درمان این حس، ساختن یکی با چیزی جز NumPy است — بدون autograd، بدون nn.Module، فقط آرایه و حساب. وقتی با کد خودتان دیدید پسانتشار وزنها را تکان میدهد، رمز و راز فرومیپاشد.
یک نورون واقعاً چیست
هیاهو را که کنار بزنید، یک نورون بهطرز خجالتآوری ساده است: ورودیها را در وزنها ضرب کن، یک بایاس اضافه کن و نتیجه را از یک تابع غیرخطی عبور بده. همین.
z = w · x + b
a = activation(z)
بخش حیاتی، همان غیرخطیبودن است. عملیات خطی را روی هم بگذارید، باز هم فقط یک عملیات خطی دیگر میگیرید، فارغ از اینکه چند لایه داشته باشد. تابع فعالسازی — ReLU، سیگموید، tanh — همان چیزی است که به یک شبکه اجازه میدهد خم شود و شکلهای پیچیده را برازش کند.
بدون غیرخطیبودن، یک شبکه صدلایه از نظر ریاضی معادل یک مدل خطی تنهاست. «عمیق» فقط بهخاطر همان پیچوخمِ بین لایهها اهمیت دارد.
گذر رو به جلو: ساختن یک پیشبینی
یک گذر رو به جلو فقط زنجیر کردن آن عملیات نورون، لایهبهلایه است. این یک شبکه دولایه حداقلی در NumPy است:
import numpy as np
def sigmoid(z):
return 1 / (1 + np.exp(-z))
def forward(X, W1, b1, W2, b2):
z1 = X @ W1 + b1
a1 = sigmoid(z1)
z2 = a1 @ W2 + b2
a2 = sigmoid(z2)
return a2, (z1, a1, z2, a2)
این کل «هوش» شبکه در زمان استنتاج است. همه چیز جالب در حین آموزش اتفاق میافتد، وقتی وزنها را تنظیم میکنیم.
زیان: چقدر اشتباه کردهایم؟
برای یادگیری، شبکه به عددی نیاز دارد که خطایش را اندازه بگیرد. برای مسائل دودویی، آنتروپی متقاطع دودویی استاندارد است. مدل ذهنی کلیدی: زیان یک منظره است، وزنها مختصاتاند و آموزش، عمل سرازیر شدن به پایین تپه است.
پسانتشار: بخشی که همه از آن میترسند
پسانتشار ترسناک به نظر میرسد، اما فقط قاعده زنجیرهای حساب دیفرانسیل است که با دقت اعمال شده. میپرسیم: هر وزن چقدر در خطا سهم داشت؟ بعد هر وزن را کمی در جهتی که خطا را کاهش میدهد حرکت میدهیم.
def backward(X, y, cache, W2):
z1, a1, z2, a2 = cache
m = X.shape[0]
dz2 = a2 - y # گرادیان در خروجی
dW2 = a1.T @ dz2 / m
db2 = dz2.mean(axis=0)
dz1 = (dz2 @ W2.T) * (a1 * (1 - a1)) # قاعده زنجیرهای از میان سیگموید
dW1 = X.T @ dz1 / m
db1 = dz1.mean(axis=0)
return dW1, db1, dW2, db2
همان یک خط dz2 = a2 - y یکی از آن نتایج زیباست که وقتی سیگموید را با آنتروپی متقاطع جفت میکنید از دل ریاضیات بیرون میافتد. دیدن کارکردنش در کد خودتان یک مکاشفه کوچک است.
گرادیان کاهشی: حلقه آموزش
حالا کنار هم میگذاریم. رو به جلو، محاسبه زیان، رو به عقب، بهروزرسانی، تکرار:
for epoch in range(1000):
preds, cache = forward(X, W1, b1, W2, b2)
dW1, db1, dW2, db2 = backward(X, y, cache, W2)
W1 -= lr * dW1; b1 -= lr * db1
W2 -= lr * dW2; b2 -= lr * db2
هر صد epoch زیان را چاپ کنید و افتادنش را تماشا کنید. همان عدد نزولی کل بازی است. هر چیز پیچیدهتری — Adam، نرمالسازی دستهای، dropout — یک پالایش این حلقه است، نه جایگزینش.
اشتباهات رایج
- نرخ یادگیری خیلی بالا یا خیلی پایین. خیلی بالا زیان منفجر میشود؛ خیلی پایین میخزد. این اولین پیچی است که باید تنظیم کنید.
- فراموش کردن نرمالسازی ورودیها. مقیاسهای بهشدت متفاوت ویژگیها آموزش را ناپایدار میکنند.
- مقداردهی اولیه بد وزنها. همه وزنها را صفر کنید و هر نورون همان چیز را یاد میگیرد. از مقادیر تصادفی کوچک استفاده کنید.
بهترین شیوهها
- اول نسخه کوچک را بسازید؛ فقط وقتی فهمیدید چه چیزی را خودکار میکند به سراغ فریمورک بروید.
- عمداً یک دیتاست کوچک را بیشبرازش کنید. اگر شبکه نتواند ده نمونه را حفظ کند، باگ دارید.
- هر بار منحنی زیان را مصور کنید. این نوار قلب اجرای آموزش شماست.
جمعبندی
شبکههای عصبی تا وقتی یکی را پیادهسازی کنید جادو به نظر میرسند و بعد به حساب با دفترداری خوب تبدیل میشوند. یک نورون یک جمع وزندار بهعلاوه یک پیچوخم است؛ پسانتشار قاعده زنجیرهای است؛ آموزش سرازیر شدن روی منظره زیان است. یکبار نسخه NumPy را بسازید و فریمورکها دیگر هرگز نمیترسانندتان. همین آخر هفته یک فایل خالی باز کنید و زیان را پایین بیاورید — آن یک ساعت دستبهکار شدن، ارزش ده ساعت تماشا را دارد.