فاز 1 · درس 4

حسابان برای یادگیری ماشین: درک شهودی گرادیان و بهینه‌سازی

نوع
مفهومی
زمان
حدود 60 دقیقه
زبان‌ها
Python
یک شیب‌سنج فلزی و زاویه‌یاب مکانیکی روی کاغذ میلی‌متری در کنار نموداری از خطوط تراز یک کاسه
فهرست مطالب
  1. اهداف یادگیری
  2. صورت مسئله
  3. مفهوم اصلی
  4. مشتق چیست؟
  5. مشتق‌های جزئی: یک متغیر در هر لحظه
  6. گرادیان: بردار تمام مشتق‌های جزئی
  7. ارتباط با بهینه‌سازی
  8. مشتق عددی در مقایسه با مشتق تحلیلی
  9. مشتق دستی توابع ساده
  10. قاعده‌ی زنجیره‌ای
  11. ماتریس هسیان
  12. تقریب سری تیلور
  13. انتگرال در یادگیری ماشین
  14. قاعده‌ی زنجیره‌ای چندمتغیره در گراف محاسباتی
  15. ماتریس ژاکوبی
  16. چرا این موضوع در شبکه‌های عصبی اهمیت دارد؟
  17. پیاده‌سازی عملی
  18. گام 1: مشتق عددی از صفر
  19. گام 2: مشتق‌های جزئی و گرادیان‌ها
  20. گام 3: گرادیان کاهشی برای یافتن کمینه‌ی f(x) = x^2
  21. گام 4: گرادیان کاهشی روی یک تابع 2بعدی
  22. گام 5: مقایسه‌ی مشتق‌های عددی و تحلیلی
  23. گام 6: محاسبه‌ی عددی ماتریس هسیان
  24. گام 7: بررسی تقریب تیلور در عمل
  25. گام 8: چرا این موضوع برای شبکه عصبی اهمیت دارد؟
  26. استفاده‌ی عملی
  27. تمرین‌ها
  28. راهنمایی‌ها
  29. اصطلاحات کلیدی
  30. برای مطالعه‌ی بیشتر

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

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

تمام کدهای این درس با تکیه بر فرمول‌های پایه و کتابخانه‌ی NumPy روی پردازنده‌ی معمولی سیستم شخصی شما اجرا می‌شوند و نیازی به کارت‌های گرافیکی گران‌قیمت یا دسترسی به سرویس‌های ابری خارجی ندارید.

مشتق‌ها مشخص می‌کنند کدام مسیر به سمت سرازیری است. برای یادگیری یک شبکه عصبی، دانستن همین نکته کافی است.

اهداف یادگیری

  • محاسبه‌ی مشتق عددی و تحلیلی برای توابع رایج در یادگیری ماشین (x2x^2، سیگموئید و انتروپی متقاطع)
  • پیاده‌سازی گرادیان کاهشی (gradient descent) از صفر برای کمینه‌سازی تابع زیان در حالت‌های 1بعدی و 2بعدی
  • به‌دست آوردن گرادیان مدل رگرسیون خطی و آموزش آن با به‌روزرسانی دستی وزن‌ها
  • تشریح ماتریس هسیان، تقریب‌های سری تیلور و ارتباط آن‌ها با روش‌های بهینه‌سازی

صورت مسئله

یک شبکه عصبی با میلیون‌ها وزن دارید. هر وزن مثل یک پیچ تنظیم است. باید بفهمید هرکدام از این پیچ‌ها را به کدام سمت بچرخانید تا خطای مدل اندکی کمتر شود. حسابان (calculus) این جهت را مشخص می‌کند.

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

مفهوم اصلی

مشتق چیست؟

مشتق، نرخ تغییرات را اندازه می‌گیرد. برای تابع y=f(x)y = f(x)، مشتق f′(x)f'(x) می‌گوید: اگر xx را به اندازه‌ای بسیار ناچیز تغییر دهید، مقدار yy چقدر تغییر می‌کند؟

از نظر هندسی، مشتق همان شیب خط مماس بر منحنی در یک نقطه است.

f(x)=x2f(x) = x^2:

xx f(x)f(x) f′(x)f'(x) (شیب)
0 0 0 (تخت، در پایین‌ترین نقطه)
1 1 2
2 4 4 (شیب خط مماس در این نقطه)
3 9 6

در نقطه‌ی x=2x=2، شیب برابر با 4 است. اگر xx را اندکی به سمت راست ببرید، مقدار yy تقریباً 4 برابرِ آن افزایش می‌یابد. در نقطه‌ی x=0x=0، شیب 0 است؛ یعنی درست در کف کاسه قرار دارید.

تعریف صوری مشتق:

f′(x)=lim⁡h→0f(x+h)−f(x)hf'(x) = \lim_{h \to 0} \frac{f(x + h) - f(x)}{h}

در کدنویسی، حد را کنار می‌گذاریم و صرفاً از مقداری بسیار کوچک برای hh استفاده می‌کنیم؛ به این کار مشتق عددی (numerical derivative) می‌گویند.

مشتق‌های جزئی: یک متغیر در هر لحظه

توابع واقعی چندین ورودی دارند. زیان یک شبکه عصبی به هزاران وزن وابسته است. مشتق جزئی (partial derivative) همه‌ی متغیرها جز یکی را ثابت نگه می‌دارد و بعد نسبت به همان یک متغیر مشتق می‌گیرد.

f(x,y)=x2+3xy+y2∂f∂x=2x+3y(treat y as a constant)∂f∂y=3x+2y(treat x as a constant)\begin{aligned} f(x, y) &= x^2 + 3xy + y^2 \\[4pt] \frac{\partial f}{\partial x} &= 2x + 3y \quad \text{(treat } y \text{ as a constant)} \\ \frac{\partial f}{\partial y} &= 3x + 2y \quad \text{(treat } x \text{ as a constant)} \end{aligned}

هر مشتق جزئی به این پرسش پاسخ می‌دهد: اگر فقط همین یک وزن را کمی تغییر دهم، تابع زیان چقدر تغییر می‌کند؟

گرادیان: بردار تمام مشتق‌های جزئی

گرادیان (gradient)، تمام مشتق‌های جزئی را در یک بردار جمع می‌کند. برای تابع f(x,y,z)f(x, y, z)، گرادیان عبارت است از:

∇f=[∂f∂x,∂f∂y,∂f∂z]\nabla f = \left[ \frac{\partial f}{\partial x}, \frac{\partial f}{\partial y}, \frac{\partial f}{\partial z} \right]

گرادیان در جهتی قرار دارد که بیشترین شیب صعودی را می‌سازد. بنابراین برای کمینه‌سازی تابع، باید در جهت عکس آن حرکت کنید.

نمودار کانتور (خطوط تراز) تابع f(x,y)=x2+y2f(x,y) = x^2 + y^2:

این تابع شکل کاسه‌ای با خطوط تراز دایره‌ای هم‌مرکز است. نقطه‌ی کمینه در (0, 0) قرار دارد.

نقطه ∇f\nabla f −∇f-\nabla f (جهت کاهش)
(1, 1) [2, 2] (رو به بالا، دور از کمینه) [-2, -2] (رو به پایین، به سمت کمینه)
(0, 0) [0, 0] (تخت، در نقطه‌ی کمینه) [0, 0]

این تصویر کل ایده‌ی گرادیان کاهشی است: گرادیان را حساب کنید، علامتش را منفی کنید و یک گام بردارید.

ارتباط با بهینه‌سازی

آموزش شبکه عصبی همان بهینه‌سازی است. یک تابع زیان L(w1,w2,…,wn)L(w_1, w_2, \ldots, w_n) دارید که میزان خطای مدل را اندازه می‌گیرد و هدف شما کمینه‌کردن آن است.

Gradient descent update rule:

  w_new = w_old - learning_rate * dL/dw

For every weight:
  1. Compute the partial derivative of loss with respect to that weight
  2. Subtract a small multiple of it from the weight
  3. Repeat

نرخ یادگیری (learning rate) طول گام‌ها را کنترل می‌کند. اگر زیادی بزرگ باشد، از کمینه رد می‌شوید؛ اگر خیلی کوچک باشد، فوق‌العاده کند پیش می‌روید.

چشم‌انداز زیان (برش 1بعدی):

تابع زیان L(w)L(w) با تغییر وزن ww، منحنی‌ای پر از قله‌ها و دره‌ها می‌سازد.

ویژگی توضیحات
کمینه‌ی سراسری (Global minimum) پایین‌ترین نقطه در کل منحنی؛ بهترین جواب ممکن
کمینه‌ی محلی (Local minimum) دره‌ای که از همسایه‌هایش پایین‌تر است اما پایین‌ترین نقطه‌ی کل نمودار نیست
شیب گرادیان کاهشی از هر نقطه‌ی شروعی، شیب را به سمت پایین دنبال می‌کند

گرادیان کاهشی شیب را رو به پایین دنبال می‌کند. این روش ممکن است در کمینه‌های محلی گیر بیفتد، اما در فضاهای چندبعدی بزرگ (با میلیون‌ها وزن)، این موضوع در عمل به‌ندرت دردسرساز می‌شود.

مشتق عددی در مقایسه با مشتق تحلیلی

برای محاسبه‌ی مشتق دو راه وجود دارد:

تحلیلی: با استفاده‌ی دستی از قواعد حسابان. برای f(x)=x2f(x) = x^2، مشتق برابر با f′(x)=2xf'(x) = 2x است؛ دقیق و سریع.

عددی: تقریب زدن با تعریف مشتق. مقادیر f(x+h)f(x+h) و f(x−h)f(x-h) را برای مقداری ناچیز از hh حساب کنید و بعد از تفاضل آن‌ها کمک بگیرید.

Numerical (central difference):

f'(x) ~= f(x + h) - f(x - h)
          -----------------------
                  2h

h = 0.0001 works well in practice

مشتق‌های عددی کندترند اما برای هر تابعی جواب می‌دهند. مشتق‌های تحلیلی سریع‌اند اما باید فرمول آن‌ها را خودتان به دست آورید. فریم‌ورک‌های شبکه عصبی سراغ روش سومی می‌روند: مشتق‌گیری خودکار (automatic differentiation) که مشتق‌های دقیق را به‌صورت مکانیکی حساب می‌کند. این روش را در فاز 3 خواهید دید.

مشتق دستی توابع ساده

این‌ها همان مشتق‌هایی هستند که در یادگیری ماشین مدام با آن‌ها سروکار دارید:

Function        Derivative       Used in
--------        ----------       -------
f(x) = x^2     f'(x) = 2x      Loss functions (MSE)
f(x) = wx + b  f'(w) = x        Linear layer (gradient w.r.t. weight)
                f'(b) = 1        Linear layer (gradient w.r.t. bias)
                f'(x) = w        Linear layer (gradient w.r.t. input)
f(x) = e^x     f'(x) = e^x     Softmax, attention
f(x) = ln(x)   f'(x) = 1/x     Cross-entropy loss
f(x) = 1/(1+e^-x)  f'(x) = f(x)(1-f(x))   Sigmoid activation

برای f(x)=x2f(x) = x^2:

f(x) = x^2    f'(x) = 2x

  x    f(x)   f'(x)   meaning
  -2    4      -4      slope tilts left (decreasing)
  -1    1      -2      slope tilts left (decreasing)
   0    0       0      flat (minimum!)
   1    1       2      slope tilts right (increasing)
   2    4       4      slope tilts right (increasing)

برای f(w)=wx+bf(w) = wx + b با فرضیات x=3x=3 و b=1b=1:

f(w) = 3w + 1    f'(w) = 3

The derivative with respect to w is just x.
If x is big, a small change in w causes a big change in output.

قاعده‌ی زنجیره‌ای

وقتی توابع با یکدیگر ترکیب می‌شوند، قاعده‌ی زنجیره‌ای (chain rule) مشخص می‌کند چطور باید مشتق بگیرید.

If y = f(g(x)), then dy/dx = f'(g(x)) * g'(x)

Example: y = (3x + 1)^2
  outer: f(u) = u^2       f'(u) = 2u
  inner: g(x) = 3x + 1    g'(x) = 3
  dy/dx = 2(3x + 1) * 3 = 6(3x + 1)

شبکه‌های عصبی زنجیره‌ای از توابع هستند: ورودی -> لایه‌ی خطی -> تابع فعال‌سازی -> لایه‌ی خطی -> تابع فعال‌سازی -> تابع زیان. پس‌انتشار (backpropagation) چیزی نیست جز اجرای پی‌درپیِ همین قاعده‌ی زنجیره‌ای از خروجی به سمت ورودی. کل الگوریتم به همین خلاصه می‌شود.

ماتریس هسیان

گرادیان شیب را نشان می‌دهد، اما ماتریس هسیان (Hessian) انحنا را مشخص می‌کند.

ماتریس هسیان از مشتق‌های جزئی مرتبه‌ی دوم ساخته شده است. برای تابع f(x1,x2,…,xn)f(x_1, x_2, \ldots, x_n)، درایه‌ی (i,j)(i, j) هسیان به شکل زیر تعریف می‌شود:

Hij=∂2f∂xi ∂xjH_{ij} = \frac{\partial^2 f}{\partial x_i \, \partial x_j}

برای تابعی با 2 متغیر f(x,y)f(x, y):

H=[∂2f∂x2∂2f∂x ∂y∂2f∂y ∂x∂2f∂y2]H = \begin{bmatrix} \dfrac{\partial^2 f}{\partial x^2} & \dfrac{\partial^2 f}{\partial x \, \partial y} \\[8pt] \dfrac{\partial^2 f}{\partial y \, \partial x} & \dfrac{\partial^2 f}{\partial y^2} \end{bmatrix}

اطلاعاتی که ماتریس هسیان در یک نقطه‌ی بحرانی (جایی که گرادیان = 0 است) به ما می‌دهد:

ویژگی هسیان معنا نمونه‌ی سطح
معین مثبت (تمامی مقادیر ویژه > 0) کمینه‌ی محلی کاسه‌ای رو به بالا
معین منفی (تمامی مقادیر ویژه < 0) بیشینه‌ی محلی کاسه‌ای رو به پایین
نامعین (مقادیر ویژه با علامت‌های مختلف) نقطه‌ی زینی (Saddle point) شبیه به زین اسب

مثال: f(x,y)=x2−y2f(x, y) = x^2 - y^2 (یک تابع زینی)

df/dx = 2x       df/dy = -2y
d^2f/dx^2 = 2    d^2f/dy^2 = -2    d^2f/dxdy = 0

H = | 2   0 |
    | 0  -2 |

Eigenvalues: 2 and -2 (one positive, one negative)
--> Saddle point at (0, 0)

مقایسه کنید با f(x,y)=x2+y2f(x, y) = x^2 + y^2 (یک کاسه):

H = | 2  0 |
    | 0  2 |

Eigenvalues: 2 and 2 (both positive)
--> Local minimum at (0, 0)

چرا هسیان در یادگیری ماشین مهم است؟

روش نیوتن (Newton's method) با کمک ماتریس هسیان، گام‌های بهینه‌سازی بهتری نسبت به گرادیان کاهشی برمی‌دارد. به جای اینکه فقط شیب را دنبال کند، انحنا را هم در نظر می‌گیرد:

Newton’s update:wnew=wold−H−1⋅gradientGradient descent:wnew=wold−lr⋅gradient\begin{aligned} \text{Newton's update:} \quad & w_{\text{new}} = w_{\text{old}} - H^{-1} \cdot \text{gradient} \\ \text{Gradient descent:} \quad & w_{\text{new}} = w_{\text{old}} - \text{lr} \cdot \text{gradient} \end{aligned}

روش نیوتن سریع‌تر همگرا می‌شود؛ چون ماتریس هسیان مقیاس گرادیان را دوباره تنظیم می‌کند؛ یعنی در مسیرهای تند گام‌های کوچک‌تر و در مسیرهای هموار گام‌های بزرگ‌تری برمی‌دارد.

اما یک مشکل اساسی وجود دارد: برای شبکه‌ای با NN پارامتر، ابعاد ماتریس هسیان N×NN \times N خواهد بود. مدلی با 1 میلیون پارامتر به ماتریسی با 1 تریلیون درایه نیاز دارد! به همین دلیل به سراغ روش‌های تقریبی می‌رویم.

روش مواردی که استفاده می‌کند هزینه همگرایی
گرادیان کاهشی فقط مشتق‌های مرتبه اول O(N)O(N) در هر گام کند (خطی)
روش نیوتن ماتریس کامل هسیان O(N3)O(N^3) در هر گام سریع (درجه دو)
L-BFGS تقریب هسیان با تاریخچه‌ی گرادیان‌ها O(N)O(N) در هر گام متوسط (فرارفتگی خطی یا superlinear)
Adam نرخ‌های یادگیری تطبیقی برای هر پارامتر (تقریب قطری هسیان) O(N)O(N) در هر گام متوسط
گرادیان طبیعی (Natural gradient) ماتریس اطلاعات فیشر (هسیان آماری) O(N2)O(N^2) در هر گام سریع

در عمل، Adam بهینه‌ساز پیش‌فرض یادگیری عمیق (deep learning) است. این الگوریتم با ثبت میانگین متحرک و واریانس گرادیان‌ها برای هر پارامتر، اطلاعات مرتبه‌ی دوم را با هزینه‌ای ناچیز تقریب می‌زند.

تقریب سری تیلور

هر تابع همواری را می‌توان به‌صورت موضعی با یک چندجمله‌ای تقریب زد:

f(x+h)=f(x)+f′(x) h+12f′′(x) h2+16f′′′(x) h3+⋯f(x + h) = f(x) + f'(x)\,h + \frac{1}{2} f''(x)\,h^2 + \frac{1}{6} f'''(x)\,h^3 + \cdots

هرچه جملات بیشتری اضافه کنید تقریب بهتر می‌شود؛ البته فقط در نزدیکی نقطه‌ی xx.

چرا سری تیلور در یادگیری ماشین اهمیت دارد؟

  • تیلور مرتبه‌ی اول = گرادیان کاهشی. وقتی از رابطه‌ی f(x+h)≈f(x)+f′(x)hf(x + h) \approx f(x) + f'(x) h استفاده می‌کنید، در واقع تقریب خطی می‌زنید. گرادیان کاهشی همین مدل خطی را کمینه می‌کند تا h=−lr⋅f′(x)h = -\text{lr} \cdot f'(x) را انتخاب کند.

  • تیلور مرتبه‌ی دوم = روش نیوتن. با f(x+h)≈f(x)+f′(x)h+12f′′(x)h2f(x + h) \approx f(x) + f'(x) h + \frac{1}{2} f''(x) h^2 به یک مدل درجه دو می‌رسید. کمینه‌کردن آن خروجی h=−f′(x)/f′′(x)h = -f'(x)/f''(x) را به شما می‌دهد؛ یعنی همان گام نیوتن.

  • طراحی تابع زیان. توابع زیان MSE و انتروپی متقاطع هموار هستند؛ یعنی بسط تیلور آن‌ها رفتار پایداری دارد. این موضوع اتفاقی نیست؛ زیان‌های هموار بهینه‌سازی را پیش‌بینی‌پذیر می‌کنند.

Approximation order    What it captures    Optimization method
-------------------    -----------------   -------------------
0th order (constant)   Just the value      Random search
1st order (linear)     Slope               Gradient descent
2nd order (quadratic)  Curvature           Newton's method
Higher orders          Finer structure     Rarely used in ML

نکته‌ی کلیدی: در کل بهینه‌سازی‌های مبتنی بر گرادیان، تنها کاری که می‌کنیم تقریب موضعی تابع زیان و برداشتن گام به سمت نقطه‌ی کمینه‌ی همان تقریب است.

انتگرال در یادگیری ماشین

مشتق‌ها نرخ تغییرات را نشان می‌دهند و انتگرال‌ها انباشت را حساب می‌کنند؛ یعنی همان مساحت زیر منحنی.

در یادگیری ماشین به‌ندرت پیش می‌آید که انتگرال را دستی حساب کنید، اما ردپای این مفهوم همه‌جا دیده می‌شود:

احتمال. برای یک متغیر تصادفی پیوسته با چگالی p(x)p(x):

P(a<X<b)=∫abp(x) dxP(a < X < b) = \int_a^b p(x) \, dx

مساحت زیر منحنی چگالی احتمال در بازه‌ی aa تا bb، برابر با احتمال قرار گرفتن در همان بازه است.

امید ریاضی (Expected value). میانگین وزنی خروجی‌ها با استفاده از احتمال:

E[f(X)]=∫f(x) p(x) dx\mathbb{E}[f(X)] = \int f(x) \, p(x) \, dx

زیان مورد انتظار روی توزیع داده‌ها یک انتگرال است و فرایند آموزش، تقریب تجربی همین انتگرال را کمینه می‌کند.

واگرایی کولبک-لایبلر (KL divergence). میزان تفاوت میان دو توزیع را می‌سنجد:

KL(p ∥ q)=∫p(x)log⁡p(x)q(x) dx\mathrm{KL}(p \,\|\, q) = \int p(x) \log \frac{p(x)}{q(x)} \, dx

این مفهوم در VAEها، تقطیر دانش (knowledge distillation) و استنتاج بیزی کاربرد دارد.

ثابت‌های نرمال‌سازی. در استنتاج بیزی:

p(w∣data)=p(data∣w) p(w)∫p(data∣w) p(w) dwp(w \mid \text{data}) = \frac{p(\text{data} \mid w) \, p(w)}{\int p(\text{data} \mid w) \, p(w) \, dw}

مخرج کسر، یک انتگرال روی تمام مقادیر ممکن پارامترهاست. این انتگرال اغلب محاسبه‌ناپذیر است؛ به همین دلیل به سراغ تقریب‌هایی مثل MCMC و استنتاج متغیری (variational inference) می‌رویم.

مفهوم انتگرال کاربرد در یادگیری ماشین
مساحت زیر منحنی محاسبه‌ی احتمال از روی توابع چگالی
امید ریاضی توابع زیان، کمینه‌سازی ریسک
واگرایی KL مدل‌های VAE، بهینه‌سازی خط‌مشی، تقطیر دانش
نرمال‌سازی توزیع‌های پسین بیزی، مخرج تابع softmax
درست‌نمایی حاشیه‌ای (Marginal likelihood) مقایسه‌ی مدل‌ها، کران پایین شواهد (ELBO)

قاعده‌ی زنجیره‌ای چندمتغیره در گراف محاسباتی

قاعده‌ی زنجیره‌ای صرفاً برای توابع اسکالر خطی نیست. در یک شبکه عصبی، متغیرها branch می‌شوند و دوباره با هم merge می‌گردند. در تصویر زیر جریان حرکت مشتق‌ها را در یک رفت مستقیم (forward pass) ساده می‌بینید:

*w+bsigmoidتابع زیانx (ورودی)z1 = w*xz2 = w*x + ba = sigmoid(z2)L = -(y*log(a) + (1-y)*log(1-a))

مرحله‌ی بازگشت (backward pass) گرادیان‌ها را از راست به چپ محاسبه می‌کند:

dL/dada/dz2 = a(1-a)dz2/dw = xdz2/db = 1dL/dL = 1dL/da = -y/a + (1-y)/(1-a)dL/dz2 = dL/da * a(1-a)dL/dw = dL/dz2 * xdL/db = dL/dz2 * 1

در راستای هر پیکان، ضرب در مشتق محلی انجام می‌شود. گرادیان هر پارامتر برابر است با حاصل‌ضرب تمام مشتق‌های محلی در مسیر میان تابع زیان تا آن پارامتر. وقتی مسیرها branch و سپس merge شوند، سهم هرکدام را با هم جمع می‌کنید (قاعده‌ی زنجیره‌ای چندمتغیره).

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

ماتریس ژاکوبی

وقتی تابعی یک بردار را به بردار دیگر می‌نگارد (مانند یک لایه‌ی شبکه عصبی)، مشتق آن یک ماتریس خواهد بود. ماتریس ژاکوبی (Jacobian) شامل تمام مشتق‌های جزئی تک‌تک خروجی‌ها نسبت به تک‌تک ورودی‌هاست.

برای تابع f:Rn→Rmf: \mathbb{R}^n \to \mathbb{R}^m، ماتریس ژاکوبی JJ یک ماتریس m×nm \times n است:

x1x_1 x2x_2 ... xnx_n
f1f_1 ∂f1/∂x1\partial f_1/\partial x_1 ∂f1/∂x2\partial f_1/\partial x_2 ... ∂f1/∂xn\partial f_1/\partial x_n
f2f_2 ∂f2/∂x1\partial f_2/\partial x_1 ∂f2/∂x2\partial f_2/\partial x_2 ... ∂f2/∂xn\partial f_2/\partial x_n
... ... ... ... ...
fmf_m ∂fm/∂x1\partial f_m/\partial x_1 ∂fm/∂x2\partial f_m/\partial x_2 ... ∂fm/∂xn\partial f_m/\partial x_n

برای شبکه‌های عصبی نیازی نیست ژاکوبی را دستی حساب کنید؛ PyTorch خودش این کار را انجام می‌دهد. اما شناخت آن به درک ابعاد ماتریس‌ها در پس‌انتشار کمک می‌کند: اگر لایه‌ای Rn\mathbb{R}^n را به Rm\mathbb{R}^m تصویر کند، ژاکوبی آن دارای ابعاد m×nm \times n خواهد بود. گرادیان در مسیر بازگشت از ترانهاده‌ی این ماتریس عبور می‌کند.

چرا این موضوع در شبکه‌های عصبی اهمیت دارد؟

تک‌تک وزن‌های شبکه عصبی صاحب یک گرادیان می‌شوند. گرادیان می‌گوید هر وزن را چطور دست‌کاری کنید تا زیان کاهش یابد.

گذر رو به جلوورودیW1reluW2softmaxزیان
گذر رو به عقبdL/dlossdL/dW2...dL/dW1

به‌روزرسانی هر وزن به این صورت است:

  • W1 = W1 - lr * dL/dW1
  • W2 = W2 - lr * dL/dW2

در گذر پیش‌رو (forward pass) پیش‌بینی و زیان حساب می‌شوند. در گذر پس‌رو (backward pass) گرادیان زیان نسبت به تک‌تک وزن‌ها به دست می‌آید. سپس هر وزن گام کوچکی رو به پایین برمی‌دارد. تکرار این کار برای میلیون‌ها مرحله، کل داستان یادگیری عمیق است.

مشتق / خط مماسx₀ را بکشید
DERIVATIVE / TANGENTThe derivative is the slope of the tangent line. For f(x) = x³ − 3x it equals 3x² − 3, which is zero at x = ±1, the two critical points where the orange line goes flat. Between them the function falls, outside them it rises. Gradient descent reads exactly this slope to decide which way to step.
مشتق همان شیب خط مماس است. برای f(x) = x³ − 3x مقدار آن برابر با 3x² − 3 است که در x = ±1 صفر می‌شود؛ یعنی دو نقطه بحرانی که در آن‌ها خط نارنجی افقی می‌گردد. بین این دو نقطه تابع نزولی و بیرون آن‌ها صعودی است. گرادیان کاهشی دقیقاً همین شیب را می‌خواند تا جهت گام بعدی را تعیین کند.

پیاده‌سازی عملی

گام 1: مشتق عددی از صفر

def numerical_derivative(f, x, h=1e-7):
    return (f(x + h) - f(x - h)) / (2 * h)

def f(x):
    return x ** 2

for x in [-2, -1, 0, 1, 2]:
    numerical = numerical_derivative(f, x)
    analytical = 2 * x
    print(f"x={x:2d}  f'(x) numerical={numerical:.6f}  analytical={analytical:.1f}")

مشتق عددی تا چندین رقم اعشار کاملاً با مشتق تحلیلی همخوانی دارد.

گام 2: مشتق‌های جزئی و گرادیان‌ها

def numerical_gradient(f, point, h=1e-7):
    gradient = []
    for i in range(len(point)):
        point_plus = list(point)
        point_minus = list(point)
        point_plus[i] += h
        point_minus[i] -= h
        partial = (f(point_plus) - f(point_minus)) / (2 * h)
        gradient.append(partial)
    return gradient

def f_multi(point):
    x, y = point
    return x**2 + 3*x*y + y**2

grad = numerical_gradient(f_multi, [1.0, 2.0])
print(f"Numerical gradient at (1,2): {[f'{g:.4f}' for g in grad]}")
print(f"Analytical gradient at (1,2): [2*1+3*2, 3*1+2*2] = [{2*1+3*2}, {3*1+2*2}]")

گام 3: گرادیان کاهشی برای یافتن کمینه‌ی f(x) = x^2

x = 5.0
lr = 0.1
for step in range(20):
    grad = 2 * x
    x = x - lr * grad
    print(f"step {step:2d}  x={x:8.4f}  f(x)={x**2:10.6f}")

با شروع از x=5x=5، در هر گام به کمینه یعنی x=0x=0 نزدیک‌تر می‌شویم.

گام 4: گرادیان کاهشی روی یک تابع 2بعدی

def f_2d(point):
    x, y = point
    return x**2 + y**2

point = [4.0, 3.0]
lr = 0.1
for step in range(30):
    grad = numerical_gradient(f_2d, point)
    point = [p - lr * g for p, g in zip(point, grad)]
    loss = f_2d(point)
    if step % 5 == 0 or step == 29:
        print(f"step {step:2d}  point=({point[0]:7.4f}, {point[1]:7.4f})  f={loss:.6f}")

گام 5: مقایسه‌ی مشتق‌های عددی و تحلیلی

import math

test_functions = [
    ("x^2",      lambda x: x**2,          lambda x: 2*x),
    ("x^3",      lambda x: x**3,          lambda x: 3*x**2),
    ("sin(x)",   lambda x: math.sin(x),   lambda x: math.cos(x)),
    ("e^x",      lambda x: math.exp(x),   lambda x: math.exp(x)),
    ("1/x",      lambda x: 1/x,           lambda x: -1/x**2),
]

x = 2.0
print(f"{'Function':<12} {'Numerical':>12} {'Analytical':>12} {'Error':>12}")
print("-" * 50)
for name, f, df in test_functions:
    num = numerical_derivative(f, x)
    ana = df(x)
    err = abs(num - ana)
    print(f"{name:<12} {num:12.6f} {ana:12.6f} {err:12.2e}")

گام 6: محاسبه‌ی عددی ماتریس هسیان

def hessian_2d(f, x, y, h=1e-5):
    fxx = (f(x + h, y) - 2 * f(x, y) + f(x - h, y)) / (h ** 2)
    fyy = (f(x, y + h) - 2 * f(x, y) + f(x, y - h)) / (h ** 2)
    fxy = (f(x + h, y + h) - f(x + h, y - h) - f(x - h, y + h) + f(x - h, y - h)) / (4 * h ** 2)
    return [[fxx, fxy], [fxy, fyy]]

def saddle(x, y):
    return x ** 2 - y ** 2

def bowl(x, y):
    return x ** 2 + y ** 2

H_saddle = hessian_2d(saddle, 0.0, 0.0)
H_bowl = hessian_2d(bowl, 0.0, 0.0)
print(f"Saddle Hessian: {H_saddle}")  # [[2, 0], [0, -2]] -- mixed signs
print(f"Bowl Hessian:   {H_bowl}")    # [[2, 0], [0, 2]]  -- both positive

مقادیر ویژه‌ی هسیان برای تابع زینی برابر با 2 و -2 هستند (علامت‌های مختلف که نشان‌دهنده‌ی نقطه‌ی زینی است). برای تابع کاسه‌ای، مقادیر ویژه 2 و 2 هستند (هر دو مثبت که کمینه بودن نقطه را تأیید می‌کنند).

گام 7: بررسی تقریب تیلور در عمل

import math

def taylor_approx(f, f_prime, f_double_prime, x0, h, order=2):
    result = f(x0)
    if order >= 1:
        result += f_prime(x0) * h
    if order >= 2:
        result += 0.5 * f_double_prime(x0) * h ** 2
    return result

x0 = 0.0
for h in [0.1, 0.5, 1.0, 2.0]:
    true_val = math.sin(h)
    t1 = taylor_approx(math.sin, math.cos, lambda x: -math.sin(x), x0, h, order=1)
    t2 = taylor_approx(math.sin, math.cos, lambda x: -math.sin(x), x0, h, order=2)
    print(f"h={h:.1f}  sin(h)={true_val:.4f}  order1={t1:.4f}  order2={t2:.4f}")

نزدیک به نقطه‌ی x0=0x_0=0، تقریب مرتبه‌ی اول تیلور به شکل sin⁡(x)≈x\sin(x) \approx x درمی‌آید. این تقریب برای مقادیر کوچک hh عالی است اما برای مقادیر بزرگ hh به هم می‌ریزد. به همین دلیل گرادیان کاهشی با نرخ‌های یادگیری کوچک بهتر کار می‌کند؛ چون در هر گام فرض بر این است که تقریب خطی اعتبار دارد.

گام 8: چرا این موضوع برای شبکه عصبی اهمیت دارد؟

import random

random.seed(42)

w = random.gauss(0, 1)
b = random.gauss(0, 1)
lr = 0.01

xs = [1.0, 2.0, 3.0, 4.0, 5.0]
ys = [3.0, 5.0, 7.0, 9.0, 11.0]

for epoch in range(200):
    total_loss = 0
    dw = 0
    db = 0
    for x, y in zip(xs, ys):
        pred = w * x + b
        error = pred - y
        total_loss += error ** 2
        dw += 2 * error * x
        db += 2 * error
    dw /= len(xs)
    db /= len(xs)
    total_loss /= len(xs)
    w -= lr * dw
    b -= lr * db
    if epoch % 40 == 0 or epoch == 199:
        print(f"epoch {epoch:3d}  w={w:.4f}  b={b:.4f}  loss={total_loss:.6f}")

print(f"\nLearned: y = {w:.2f}x + {b:.2f}")
print(f"Actual:  y = 2x + 1")

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

استفاده‌ی عملی

با NumPy، همین کارها بسیار سریع‌تر و خلاصه‌تر انجام می‌شوند:

import numpy as np

x = np.array([1, 2, 3, 4, 5], dtype=float)
y = np.array([3, 5, 7, 9, 11], dtype=float)

w, b = np.random.randn(), np.random.randn()
lr = 0.01

for epoch in range(200):
    pred = w * x + b
    error = pred - y
    loss = np.mean(error ** 2)
    dw = np.mean(2 * error * x)
    db = np.mean(2 * error)
    w -= lr * dw
    b -= lr * db

print(f"Learned: y = {w:.2f}x + {b:.2f}")

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

تمرین‌ها

  1. تابع numerical_second_derivative(f, x) را با دو بار صدا زدن numerical_derivative پیاده‌سازی کنید. بررسی کنید که مشتق دوم x3x^3 در نقطه‌ی x=2x=2 برابر با 12 شود.
  2. با گرادیان کاهشی کمینه‌ی تابع f(x,y)=(x−3)2+(y+1)2f(x, y) = (x - 3)^2 + (y + 1)^2 را پیدا کنید. کار را از (0,0)(0, 0) شروع کنید. پاسخ نهایی باید به (3,−1)(3, -1) همگرا شود.
  3. تکانه (momentum) را به حلقه‌ی گرادیان کاهشی اضافه کنید: یک بردار سرعت (velocity) بسازید که گرادیان‌های قبلی را در خود ذخیره کند. سرعت همگرایی را با و بدون momentum روی تابع f(x)=x4−3x2f(x) = x^4 - 3x^2 مقایسه کنید.

راهنمایی‌ها

تمرین 1. تابع کمکی ساده‌ای بنویسید که خروجی numerical_derivative(f, x) را برگرداند و بعد همان را دوباره به numerical_derivative بدهید. اگر خروجی نویز داشت، در هر دو فراخوانی گام بزرگ‌تری مثل h=1e-4 بردارید؛ مقدار تابع در x=2x=2 باید به 12 بسیار نزدیک باشد.

تمرین 2. فرمول گرادیان (2(x−3),2(y+1))(2(x - 3), 2(y + 1)) را بنویسید یا از numerical_gradient استفاده کنید و سپس gradient_descent_nd را از نقطه‌ی (0,0)(0, 0) اجرا کنید. مقدار نقطه را هر 10 گام یک بار چاپ کنید؛ این مقدار باید به سمت (3,−1)(3, -1) برود و زیان به حدود 0 برسد.

تمرین 3. یک متغیر سرعت به نام v نگه دارید، آن را به شکل v = momentum * v - lr * grad به‌روزرسانی کنید و بعد v را با x جمع بزنید. تعداد گام‌هایی را بشمارید که هر روش برای رساندن گرادیان به حدود 0 نیاز دارد؛ نقطه‌ی کمینه روی x=±1.5≈±1.225x = \pm\sqrt{1.5} \approx \pm 1.225 است.

اصطلاحات کلیدی

اصطلاح در گفتار عامیانه معنای دقیق علمی
مشتق (Derivative) «شیب خط» نرخ تغییرات تابع در یک نقطه؛ نشان می‌دهد خروجی به ازای هر واحد تغییر در ورودی، چقدر تغییر می‌کند.
مشتق جزئی (Partial derivative) «مشتق یک متغیر» مشتق نسبت به یک متغیر در حالی که سایر متغیرها ثابت فرض شده‌اند.
گرادیان (Gradient) «جهت بیشترین شیب صعودی» برداری از تمام مشتق‌های جزئی؛ به سمتی اشاره می‌کند که مقدار تابع بیشترین سرعت رشد را دارد.
گرادیان کاهشی (Gradient descent) «حرکت به سمت سرازیری» کم کردن مضربی از گرادیان (با ضریب نرخ یادگیری) از پارامترها برای کاهش زیان؛ هسته‌ی اصلی آموزش شبکه‌های عصبی.
نرخ یادگیری (Learning rate) «طول گام» عددی اسکالر که اندازه‌ی هر گام گرادیان کاهشی را تعیین می‌کند. زیادی بزرگ: واگرایی؛ زیادی کوچک: سرعت حلزونی.
قاعده‌ی زنجیره‌ای (Chain rule) «ضرب زنجیره‌ای مشتق‌ها» قانون مشتق‌گیری از توابع مرکب: dfdx=dfdg⋅dgdx\frac{df}{dx} = \frac{df}{dg} \cdot \frac{dg}{dx}. پایه‌ی ریاضی پس‌انتشار.
ماتریس ژاکوبی (Jacobian) «ماتریس مشتق‌ها» وقتی تابع بردار را به بردار می‌نگارد، ژاکوبی ماتریسِ تمام مشتق‌های جزئیِ خروجی‌ها نسبت به ورودی‌ها است.
مشتق عددی (Numerical derivative) «تفاضل متناهی» تقریب مشتق با ارزیابی تابع در دو نقطه‌ی بسیار نزدیک و به‌دست آوردن شیب خط بین آن‌ها.
پس‌انتشار (Backpropagation) «مشتق‌گیری خودکار حالت معکوس» محاسبه‌ی لایه‌به‌لایه‌ی گرادیان‌ها از خروجی به ورودی بر اساس قاعده‌ی زنجیره‌ای؛ موتور اصلی یادگیری در شبکه‌های عصبی.
هسیان (Hessian) «ماتریس مشتق‌های مرتبه دوم» ماتریس تمام مشتق‌های جزئی مرتبه‌ی دوم؛ انحنای تابع را مشخص می‌کند. معین مثبت بودن هسیان در نقطه‌ی بحرانی، نشانه‌ی کمینه‌ی محلی است.
سری تیلور (Taylor series) «تقریب با چندجمله‌ای» تقریب زدن تابع در نزدیکی یک نقطه با مشتق‌های آن: f(x+h)≈f(x)+f′(x)h+12f′′(x)h2+⋯f(x+h) \approx f(x) + f'(x)h + \frac{1}{2}f''(x)h^2 + \cdots. پایه‌ای برای درک سازوکار گرادیان کاهشی و روش نیوتن.
انتگرال (Integral) «مساحت زیر منحنی» انباشت یک کمیت در یک بازه؛ در یادگیری ماشین برای تعریف احتمالات، امید ریاضی و واگرایی KL به کار می‌رود.

برای مطالعه‌ی بیشتر

خودآزمایی

به سؤال‌ها جواب بدهید تا ببینید چقدر از درس در ذهنتان مانده است.

  1. پیش از درسمشتق یک تابع در یک نقطه چه چیزی را نشان می‌دهد؟
  2. پیش از درسگرادیان در زمینه یادگیری ماشین به چه معناست؟
  3. پس از درسدر گرادیان کاهشی، قاعده به‌روزرسانی 'w = w - lr * dL/dw' چه کاری انجام می‌دهد؟
  4. پس از درسچرا روش نیوتن (که از ماتریس هسیان استفاده می‌کند) را نمی‌توان مستقیماً برای شبکه‌های عصبی با میلیون‌ها پارامتر به کار برد؟
  5. پس از درستقریب عددی (تفاضل مرکزی) برای f'(x) کدام است؟

سؤالات متداول

چرا در یادگیری ماشین به جای مشتق عددی از مشتق تحلیلی و خودکار استفاده می‌شود؟

مشتق عددی به دلیل نیاز به ارزیابی‌های مکرر تابع برای هر وزن بسیار کند است، در حالی که مشتق تحلیلی و مشتق‌گیری خودکار مقادیر دقیق را با سرعت بسیار بالاتر و محاسبات بهینه به‌دست می‌آورند.

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

ماتریس هسیان انحنای تابع را از طریق مشتق‌های مرتبه‌ی دوم نشان می‌دهد و در نقاط بحرانی مشخص می‌کند نقطه کمینه‌ی محلی، بیشینه‌ی محلی یا نقطه‌ی زینی است.

چرا روش نیوتن با وجود سرعت همگرایی بالا در شبکه‌های بزرگ استفاده نمی‌شود؟

چون ماتریس هسیان برای یک مدل با n پارامتر ابعاد n در n دارد؛ برای مدلی با 1 میلیون پارامتر این ماتریس 1 تریلیون درایه خواهد داشت که ذخیره و معکوس کردن آن به حافظه و محاسبات غیرعملی نیاز دارد.

ما رو توی گوگل بیشتر ببین

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