پرش به محتوا
3/30فصل 3 از 30

سرازیر: Gradient Descent و دو گامی که همه جا می‌اندازند

سقف دقیق نرخ یادگیری را حساب کنید و ببینید جست‌وجوی brute-force بین ۳۶۰۰ جهت، gradient را دوباره کشف می‌کند.

در این صفحه

فصل قبل با یک دره تمام شد.

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

پس یک چشم‌انداز داریم که کفی دارد، و دلیلی داریم که باور کنیم کف همان جای درست است. چیزی که نداریم راهی برای رسیدن به آنجاست.

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

  • چرا علامت منفی. به‌روزرسانی، gradient را کم می‌کند. هر آموزشی آن را می‌نویسد؛ خیلی کم پیش می‌آید کسی بگوید چرا gradient جهتی است که بالا می‌رود، و همین تنها واقعیتی است که علامت منفی را از یک عمل ایمانی به چیزی واقعی تبدیل می‌کند.
  • چه اندازه گام. «خیلی بزرگ واگرا می‌شود، خیلی کوچک کند است» درست و بی‌فایده است. یک عدد دقیق وجود دارد، از روی loss قابل محاسبه است، و این فصل آن را دو بار حساب می‌کند — یک بار برای یک سهمی اسباب‌بازی و یک بار برای داده واقعی.

چیدمان مسئله، و چرا نمی‌توانید فقط جست‌وجو کنید

لینک به بخش: چیدمان مسئله، و چرا نمی‌توانید فقط جست‌وجو کنید

برای اینکه این فصل مستقل بایستد، مسئله را دوباره بیان می‌کنیم: همان هشت قطعه از تسمه نقاله فصل 1، اما با پرسشی متفاوت. نه قبول یا رد — آن بعداً برمی‌گردد — بلکه پیش‌بینی وزن یک قطعه از روی عرض آن.

belt.pyPYTHON
import numpy as np

WIDTH  = np.array([18.0, 19.5, 20.2, 21.0, 24.0, 25.5, 23.0, 26.0])
WEIGHT = np.array([47.0, 52.0, 49.0, 55.0, 61.0, 66.0, 70.0, 58.0])

x = WIDTH  - WIDTH.mean()      # 22.15 mm
y = WEIGHT - WEIGHT.mean()     # 57.25 g

اندازه‌گیری‌ها مرکززدایی شده‌اند، دقیقاً مثل فصل 1 و به دلیلی که پیش از پایان این فصل با بهره برمی‌گردد. مدل یک خط است، y^=ax+b\hat{y} = a x + b، و loss همان میانگین خطای مربعی است که فصل قبل مشتق کرد:

L(a,b)=1ni=1n(axi+byi)2L(a, b) = \frac{1}{n} \sum_{i=1}^{n} \left(a x_i + b - y_i\right)^2

دو پارامتر. چرا صرفاً مقدارهای زیادی را امتحان نکنیم؟ بیایید واقعاً این کار را بکنیم — یک grid از a=0a = 0 تا 55 و از b=5b = -5 تا 55، با گام‌های 0.010.01:

TEXT
grid 501 x 1001 = 501,501 evaluations in 3.67 s
  best found: a = 2.1000, b = -0.0000, L = 24.592450

نیم میلیون ارزیابی برای اینکه دو عدد را تا دو رقم اعشار میخکوب کنیم — و آن ثانیه هم زمان دیواری روی یک ماشین است، پس اجرای دوباره ممکن است هرجا بین سه تا شش بنشیند؛ تعداد ارزیابی‌ها و minimum همان بخشی است که بازتولید می‌شود. Gradient descent، در پایان این فصل، در هشت گام به چهار رقم اعشار و در سی‌وشش گام به پاسخ کامل float64 می‌رسد.

اما سرعت استدلال اصلی نیست، و این همان نکته‌ای است که کل دوره را تعیین می‌کند. Grid search برای PP پارامتر با kk مقدار برای هرکدام، kPk^P ارزیابی هزینه دارد. با هزار مقدار برای هر محور:

مدلپارامترهاارزیابی‌های grid
این خط210610^{6}
شبکه XOR در فصل 59102710^{27}
یک شبکه چندلایه کوچک20,0001060,00010^{60{,}000}

ردیف سوم یک عدد بزرگ نیست، یک عدد بی‌معنا است — در جهان قابل مشاهده تقریباً 108010^{80} اتم وجود دارد. جست‌وجو با بزرگ‌شدن مدل‌ها کندتر نمی‌شود؛ از هستی ساقط می‌شود. هرچه از اینجا به بعد می‌آید به‌خاطر همان جدول وجود دارد.

مشتق اندازه‌گیری‌ای است که می‌توانید انجام دهید

لینک به بخش: مشتق اندازه‌گیری‌ای است که می‌توانید انجام دهید

برای لحظه‌ای b=0b = 0 را ثابت نگه دارید تا یک پارامتر و یک منحنی داشته باشیم؛ همان تصویری که فصل قبل برایتان گذاشت. نقطه‌ای روی آن بردارید، a=1a = 1، و بپرسید: اگر aa را به‌اندازه کوچکی مثل hh تکان بدهم، loss به ازای هر واحد تکان چقدر حرکت می‌کند؟

L(a+h)L(a)h\frac{L(a + h) - L(a)}{h}

این نسبت همان خیز بر پیش‌روی است — شیب خط مستقیمی که از دو نقطه روی منحنی می‌گذرد. هرچه hh کوچک‌تر می‌شود، دو نقطه به هم می‌لغزند و خط به مماس تبدیل می‌شود. شیب آن مشتق L(a)L'(a) است: نرخ تغییر loss به ازای هر واحد تغییر در aa. نه تقریب چیزی، و نه کمیتی بی‌نهایت کوچک. حدی از نسبت‌های معمولی.

ارزش اجرا کردن دارد، چون عددها چیزی می‌گویند که تعریف نمی‌گوید:

slope.pyPYTHON
def loss1(a):
    return np.mean((a * x - y) ** 2)

for h in [1.0, 1e-2, 1e-4, 1e-6, 1e-8, 1e-10, 1e-12, 1e-14]:
    q = (loss1(1.0 + h) - loss1(1.0)) / h
    print(f"h = {h:<8.0e}   slope estimate = {q:.10f}   error = {abs(q + 16.385):.3e}")
TEXT
h = 1e+00      slope estimate = -8.9400000000    error = 7.445e+00
h = 1e-02      slope estimate = -16.3105500000   error = 7.445e-02
h = 1e-04      slope estimate = -16.3842555001   error = 7.445e-04
h = 1e-06      slope estimate = -16.3849925556   error = 7.444e-06
h = 1e-08      slope estimate = -16.3850003787   error = 3.787e-07
h = 1e-10      slope estimate = -16.3850444324   error = 4.443e-05
h = 1e-12      slope estimate = -16.3851154866   error = 1.155e-04
h = 1e-14      slope estimate = -17.0530256582   error = 6.680e-01

اینجا دو اتفاق می‌افتد و هر دو باربرند.

خطا به‌طور مبهم با hh متناسب نیست — دقیقاً 7.445h7.445\,h است. hh را بر صد تقسیم کنید، خطا هم هر بار تا چهار رقم معنادار بر صد تقسیم می‌شود. آن ثابت تزئین نیست: نصف مشتق دوم loss است، و نخستین ظهور ایده‌ای است که دو بخش دیگر می‌بینیم — اینکه یک منحنی نزدیک یک نقطه شبیه یک خط است، به‌علاوه تصحیحی متناسب با h2h^2.

و بعد الگو می‌شکند. پایین‌تر از h=108h = 10^{-8} تخمین بدتر می‌شود، و در 101410^{-14} در رقم دوم غلط است. هیچ اتفاق ریاضی‌ای نیفتاد؛ جعبه floating-point فصل قبل افتاد وسط. L(a+h)L(a+h) و L(a)L(a) در ده رقم اولشان با هم موافق‌اند، کم‌کردنشان آن رقم‌ها را نابود می‌کند، و تقسیم ویرانه باقی‌مانده بر عددی کوچک، آنچه مانده را تقویت می‌کند. یک hh بهینه وجود دارد — اینجا حوالی 10810^{-8}، تقریباً ریشه دوم machine epsilon — و کوچک‌تر رفتن دقیق‌تر بودن نیست، کمتر دقیق بودن است. یادتان بماند؛ تابعی در پایان این فصل به آن وابسته است.

شیب دقیق، از حسابان نه اندازه‌گیری، 16.385-16.385 است. پس می‌توانیم اندازه‌گیری را متوقف کنیم و مشتق‌گیری را شروع کنیم.

ترکیب، و قاعده زنجیره‌ای

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

ایده‌ای که بقیه دوره روی آن ساخته می‌شود اینجاست، یک‌بار و ساده.

ترکیب دو تابع یعنی خوراندن یکی به دیگری: (fg)(x)=f(g(x))(f \circ g)(x) = f(g(x)). همین و بس.

یک شبکه عمیق شبیه ترکیب نیست. دقیقاً خودِ ترکیب است. یک layer یک تابع است؛ چیدن layerها روی هم یعنی ترکیب آن‌ها؛ «عمق» تعداد تابع‌های زنجیره است. وقتی فصل 5 یک شبکه می‌سازد، دارد f4f3f2f1f_4 \circ f_3 \circ f_2 \circ f_1 می‌سازد و هیچ چیز دیگر. یعنی مهم‌ترین قاعده حسابان برای هدف ما همان قاعده‌ای است که از ترکیب مشتق می‌گیرد:

ddxf(g(x))=f(g(x))g(x)\frac{d}{dx} f(g(x)) = f'(g(x)) \cdot g'(x)

نرخ‌ها در هم ضرب می‌شوند. اگر gg سه برابر سریع‌تر از xx تغییر کند، و ff دو برابر سریع‌تر از gg، آن‌وقت ff شش برابر سریع‌تر از xx تغییر می‌کند. تمام محتوا همین است، و به همین دلیل سیگنالی که از ده layer به عقب عبور می‌کند در ده عدد ضرب می‌شود — و به همین دلیل فصل 6 بخشی را صرف این می‌کند که وقتی آن عددها همگی کمی کمتر از یک باشند چه می‌شود.

آن را روی loss خودمان به کار ببرید. residual را ri=axi+byir_i = a x_i + b - y_i بنویسید، طوری که L=1nri2L = \frac{1}{n}\sum r_i^2. هر rir_i از طریق تابع درونی axia x_i به aa وابسته است، و مشتق آن xix_i است. قاعده زنجیره‌ای، جمله‌به‌جمله:

La=1ni2rixi,Lb=1ni2ri1\frac{\partial L}{\partial a} = \frac{1}{n}\sum_i 2 r_i \cdot x_i, \qquad \frac{\partial L}{\partial b} = \frac{1}{n}\sum_i 2 r_i \cdot 1

آن نمادهای خمیده \partial یک مشتق جزئی را نشان می‌دهند: نسبت به یک متغیر مشتق بگیرید و هر چیز دیگر را ثابت فرض کنید. اتفاق تازه‌ای نمی‌افتد — همان حد قبلی است، فقط در امتداد یک محور گرفته می‌شود. مشتق‌های جزئی را در یک بردار جمع کنید و gradient را دارید:

L=(La, Lb)\nabla L = \left( \frac{\partial L}{\partial a},\ \frac{\partial L}{\partial b} \right)

در نقطه (a,b)=(1,4)(a, b) = (1, 4) آن بردار (16.385, 8.0)(-16.385,\ 8.0) است. دو عدد. سؤال این است که معنایشان چیست، و این نخستین گامی است که همه جا می‌اندازند.

چرا gradient رو به بالا اشاره می‌کند

لینک به بخش: چرا gradient رو به بالا اشاره می‌کند

gradient برداری از شیب‌ها در امتداد محور‌هاست. همین را ثابت کرده‌ایم. بدیهی نیست — و نباید هم بدیهی باشد — که کنار هم گذاشتنشان در یک بردار چیزی بسازد که به جهت خاصی اشاره کند.

پس چیزی را تعریف کنیم که واقعاً می‌خواهیم. یک بردار واحد u\mathbf{u} انتخاب کنید، یک جهت. مشتق جهتی نرخ تغییر loss وقتی در آن جهت راه می‌روید است:

DuL=limh0L(θ+hu)L(θ)hD_{\mathbf{u}} L = \lim_{h \to 0} \frac{L(\boldsymbol{\theta} + h\mathbf{u}) - L(\boldsymbol{\theta})}{h}

قاعده زنجیره‌ای این را به چیزی قابل محاسبه تبدیل می‌کند. راه‌رفتن در امتداد u\mathbf{u}، aa را با نرخ u1u_1 و bb را با نرخ u2u_2 تغییر می‌دهد، و سهم‌ها با هم جمع می‌شوند:

DuL=Lau1+Lbu2=LuD_{\mathbf{u}} L = \frac{\partial L}{\partial a} u_1 + \frac{\partial L}{\partial b} u_2 = \nabla L \cdot \mathbf{u}

نرخ تغییر در هر جهت، dot productِ gradient با آن جهت است. و حالا ضربه نهایی، که یک خط هندسه است. اگر dot product را با زاویه ϕ\phi بین بردارها بنویسیم،

Lu=Lucosϕ=Lcosϕ\nabla L \cdot \mathbf{u} = \lVert \nabla L \rVert \, \lVert \mathbf{u} \rVert \cos\phi = \lVert \nabla L \rVert \cos\phi

چون u\mathbf{u} طول 1 دارد. تنها چیزی که کنترل می‌کنید cosϕ\cos\phi است، که در ϕ=0\phi = 0 بیشینه و در نیم‌دور، یعنی ϕ=180\phi = 180 درجه، کمینه می‌شود. پس:

  • تندترین صعود در امتداد خود L\nabla L است، و شیب آنجا دقیقاً L\lVert \nabla L \rVert است.
  • تندترین نزول در امتداد L-\nabla L است، و شیب آنجا L-\lVert \nabla L \rVert است.
  • عمود بر gradient، loss اصلاً تغییر نمی‌کند. به همین دلیل خطوط یک نقشه کانتور gradient را با زاویه قائمه قطع می‌کنند.

این همان علامت منفی است. نه یک قرارداد، نه وارونه‌کردن علامتی که کسی انتخاب کرده باشد: جهت سریع‌ترین کاهش، gradient منفی است چون cosϕ\cos\phi در نیم‌دور کمینه می‌شود، و به هیچ دلیل دیگری.

چون این ادعا درباره همه جهت‌هاست، آن را در برابر همه جهت‌ها آزمایش کنید. 3,600 جهت نمونه بگیرید، یکی برای هر دهم درجه، و هرکدام را با یک تکان کوچک اندازه بگیرید:

directions.pyPYTHON
theta = np.array([1.0, 4.0])
g = grad(theta)
print("gradient      ", g)
print("its length    ", np.linalg.norm(g))
print("its angle     ", np.degrees(np.arctan2(g[1], g[0])) % 360, "degrees")

best = max(
    ((loss(theta + 1e-6 * u) - loss(theta - 1e-6 * u)) / 2e-6, np.degrees(ang))
    for ang, u in (
        (a, np.array([np.cos(a), np.sin(a)])) for a in np.arange(3600) * 2 * np.pi / 3600
    )
)
print("steepest slope", best[0], "at", best[1], "degrees")
TEXT
gradient       [-16.385   8.   ]
its length     18.23371122399386
its angle      153.97598928042032 degrees
steepest slope 18.233709624837502 at 154.0 degrees

جست‌وجویی که هیچ چیز درباره gradientها نمی‌داند، در میان 3,600 جهت، تندترین صعودش را در 154.0 درجه پیدا می‌کند — جهت خود gradient، در حد تفکیک 0.1 درجه‌ای جست‌وجو. و شیبی که آنجا پیدا می‌کند، 18.2337، طول gradient تا شش رقم است. قضیه داستانی درباره معنای gradientها نیست؛ واقعیتی قابل اندازه‌گیری است، و این هم اندازه‌گیری آن.

چرا یک گام کوچک رو به پایین واقعاً کمک می‌کند

لینک به بخش: چرا یک گام کوچک رو به پایین واقعاً کمک می‌کند

حالا دومین گام حذف‌شده. می‌دانیم کدام طرف پایین است. از این نتیجه نمی‌شود که راه‌رفتن در آن جهت loss را کم می‌کند، چون «پایین» گزاره‌ای درباره یک تکان بی‌نهایت کوچک است و گام بی‌نهایت کوچک نیست.

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

L(θ+δ)=L(θ)+Lδ+O(δ2)L(\boldsymbol{\theta} + \boldsymbol{\delta}) = L(\boldsymbol{\theta}) + \nabla L \cdot \boldsymbol{\delta} + O(\lVert\boldsymbol{\delta}\rVert^2)

این بسط تیلور مرتبه اول است. جمله حذف‌شده O(δ2)O(\lVert\boldsymbol{\delta}\rVert^2) همان curvature است — همان جمله‌ای که باعث شد تخمین جدول شیب دقیقاً به‌اندازه 7.445h7.445\,h خطا داشته باشد. گامی را که قصد داریم برداریم جای‌گذاری کنید، δ=ηL\boldsymbol{\delta} = -\eta \nabla L:

L(θηL)L(θ)ηL2L(\boldsymbol{\theta} - \eta \nabla L) \approx L(\boldsymbol{\theta}) - \eta \lVert \nabla L \rVert^2

loss به‌اندازه ηL2\eta \lVert \nabla L \rVert^2 پایین می‌آید. هر بخش آن نامنفی است، پس وعده واقعی است — برای η\eta به‌اندازه کافی کوچک، چون جمله نادیده‌گرفته‌شده مثل η2\eta^2 رشد می‌کند و سرانجام آن را می‌بلعد. کل نظریه همین است. اینجا وعده اول وفا می‌شود و بعد می‌شکند:

TEXT
eta = 0.2       promised    66.49364500   delivered   -16.01619240   ratio -0.240868
eta = 0.1       promised    33.24682250   delivered    12.61936315   ratio  0.379566
eta = 0.01      promised     3.32468225   delivered     3.11840766   ratio  0.937957
eta = 0.001     promised     0.33246822   delivered     0.33040548   ratio  0.993796
eta = 0.0001    promised     0.03324682   delivered     0.03322620   ratio  0.999380
eta = 1e-05     promised     0.00332468   delivered     0.00332448   ratio  0.999938

از پایین بخوانید. هرچه η\eta کوچک می‌شود، افت محقق‌شده به افت وعده‌داده‌شده همگرا می‌شود — نسبت 0.99938، بعد 0.99994 — و این یعنی قضیه تیلور درست عمل می‌کند. از بالا بخوانید و در η=0.2\eta = 0.2 «افت» محقق‌شده منفی شانزده است. گام رو به پایین رفت و loss بالا رفت.

پس قاعده به‌روزرسانی این است

θθηL(θ)\boldsymbol{\theta} \leftarrow \boldsymbol{\theta} - \eta \nabla L(\boldsymbol{\theta})

و شرطی همراهش دارد که هیچ‌کس بیان نمی‌کند: η\eta باید به‌اندازه کافی کوچک باشد. به‌اندازه کافی کوچک نسبت به چه چیزی دقیقاً، موضوع بخش بعدی است.

نرخ یادگیری سقف دارد، و قابل محاسبه است

لینک به بخش: نرخ یادگیری سقف دارد، و قابل محاسبه است

با ساده‌ترین دره ممکن شروع کنید، f(x)=x2f(x) = x^2، که در آن f(x)=2xf'(x) = 2x. یک گام gradient descent این است

xxη2x=x(12η)x \leftarrow x - \eta \cdot 2x = x\,(1 - 2\eta)

موقعیت در هر گام در (12η)(1 - 2\eta) ضرب می‌شود. این یک دنباله هندسی است، و دنباله‌های هندسی دقیقاً یک قاعده دارند: وقتی ضریب از نظر قدرمطلق کمتر از 1 باشد کوچک می‌شوند و در غیر این صورت رشد می‌کنند. پس 12η<1\lvert 1 - 2\eta \rvert < 1، که یعنی 0<η<10 < \eta < 1.

مرز دقیقاً در η=1\eta = 1 است. نه «حوالی 1»، نه «1 معمولاً زیادی بزرگ است». در η=1\eta = 1 ضریب 1-1 است و نقطه برای همیشه بین xx و x-x بالا و پایین می‌پرد، نه نزدیک می‌شود و نه می‌گریزد. پایین‌تر از آن، همگرا می‌شود؛ بالاتر از آن، واگرا. بازه دوباره در η=0.5\eta = 0.5 شکافته می‌شود، جایی که ضریب علامت عوض می‌کند: پایین‌تر از آن نزدیک‌شدن یکنواخت است، بالاتر از آن نقطه از حد می‌گذرد و طرفین را یکی‌درمیان عوض می‌کند، و دقیقاً در 0.50.5 ضریب 0 است و یک گام تنها روی minimum فرود می‌آید.

چهار رژیم، از چهار خط جبر. خودتان بروید و مرزها را رد کنید:

14 گام، پایان در x = -0.0836.

مشاهده داده‌ها به‌صورت جدول
گامxf(x)
0⁨-1.9000⁩⁨3.6100⁩
1⁨-1.5200⁩⁨2.3104⁩
2⁨-1.2160⁩⁨1.4787⁩
3⁨-0.9728⁩⁨0.9463⁩
4⁨-0.7782⁩⁨0.6057⁩
5⁨-0.6226⁩⁨0.3876⁩
6⁨-0.4981⁩⁨0.2481⁩
7⁨-0.3985⁩⁨0.1588⁩
8⁨-0.3188⁩⁨0.1016⁩
9⁨-0.2550⁩⁨0.0650⁩
10⁨-0.2040⁩⁨0.0416⁩
11⁨-0.1632⁩⁨0.0266⁩
12⁨-0.1306⁩⁨0.0170⁩
13⁨-0.1045⁩⁨0.0109⁩
14⁨-0.0836⁩⁨0.0070⁩
نزول گرادیان، تعاملی

چهارده گام با نرخ 0.1، از x=1.9x = -1.9، با پایان در 0.0836-0.0836. نرخ را به 0.5 برسانید و همان گام اول روی کف فرود می‌آید. آن را به 0.9 برسانید و به همان 0.0836-0.0836 می‌رسد که 0.1 رسید — همان فاصله، سبک مخالف، چون 12η\lvert 1 - 2\eta \rvert برای هر دو 0.8 است — اما به‌جای پایین‌رفتن از یک سمت، با زیگزاگ‌زدن در عرض دره به آنجا می‌رسد.

و حالا مورد جالب:

14 گام، پایان در x = -1.9000.

مشاهده داده‌ها به‌صورت جدول
گامxf(x)
0⁨-1.9000⁩⁨3.6100⁩
1⁨1.9000⁩⁨3.6100⁩
2⁨-1.9000⁩⁨3.6100⁩
3⁨1.9000⁩⁨3.6100⁩
4⁨-1.9000⁩⁨3.6100⁩
5⁨1.9000⁩⁨3.6100⁩
6⁨-1.9000⁩⁨3.6100⁩
7⁨1.9000⁩⁨3.6100⁩
8⁨-1.9000⁩⁨3.6100⁩
9⁨1.9000⁩⁨3.6100⁩
10⁨-1.9000⁩⁨3.6100⁩
11⁨1.9000⁩⁨3.6100⁩
12⁨-1.9000⁩⁨3.6100⁩
13⁨1.9000⁩⁨3.6100⁩
14⁨-1.9000⁩⁨3.6100⁩
نزول گرادیان، تعاملی

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

حالا قاعده عمومی، که از همان استدلال بیرون می‌افتد. ضریب 12η1 - 2\eta در واقع 1ηf1 - \eta f'' بود، و نزدیک یک minimum، یک loss چندپارامتری برای هر جهت یکی از این عددها دارد — eigenvalueهای ماتریس مشتق‌های دوم. همه جهت‌ها باید هم‌زمان پایدار باشند، پس سقف را بزرگ‌ترینشان تعیین می‌کند:

η<2λmax\eta < \frac{2}{\lambda_{\max}}

برای f(x)=x2f(x) = x^2، f=2f'' = 2، سقف 1 است، که همان چیزی است که تازه مشتق کردیم. برای تسمه ما، ماتریس مشتق دوم 2nAA\frac{2}{n} A^{\top} A است، با AA به‌عنوان ماتریس دو‌ستونه inputها، و eigenvalueهای آن 2 و 14.89 هستند، پس سقف 2/14.89=0.134322 / 14.89 = 0.13432 است. این پیش‌بینی‌ای با پنج رقم معنادار است. آزمایشش کنید:

TEXT
lr=0.1343    -> L =      24.5924
lr=0.13431   -> L =      24.5924
lr=0.13432   -> L =       4707.8       BLEW UP
lr=0.13433   -> L =  4.00452e+16       BLEW UP
lr=0.1344    -> L = 1.18229e+107       BLEW UP

پنج رقم اعشار توافق بین یک خط جبر خطی و صد هزار iteration از یک حلقه for.

و اینجاست که فصل 1 برمی‌گردد. همه چیز بالا از اندازه‌گیری‌های مرکززدایی‌شده استفاده کرد. همان code را روی میلی‌مترها و گرم‌های خام اجرا کنید و eigenvalueها به‌جای 2 و 14.89، برابر 0.0298 و 998.1 می‌شوند. سقف از 0.134 به 0.002004 فرو می‌ریزد — همان‌قدر دقیق، با همگرایی در lr=0.002003 و انفجار در lr=0.002004.

بدتر از سقف، نسبت بین eigenvalueهاست. condition number اندازه می‌گیرد دره چقدر از گردبودن دور است: یک گودال بلند و باریک، نرخی را تحمیل می‌کند که برای دیواره‌های شیب‌دار به‌اندازه کافی کوچک باشد، و بعد کف گودال هم با همان خزیدن پیموده می‌شود. مقدار ما از 7.44 در حالت مرکززدایی‌شده به 33,452 در حالت خام می‌رسد. با بهترین نرخی که هر نسخه می‌تواند بردارد:

ویژگی‌هاcondition numberبهترین نرخگام‌ها تا فاصله 1% از optimum
مرکززدایی‌شده7.440.118410
میلی‌مترها و گرم‌های خام33,4520.002003779,513

همان داده، همان code، همان پاسخ در انتها — و هشت‌هزار برابر کار، چون هیچ‌کس میانگین را کم نکرد. در فصل 1 همین حذف، برای perceptron شش‌هزار برابر epoch هزینه داشت، و تشخیص آنجا هندسی بود: داده از مبدأ دور شناور بود. اینجا همان هندسه است در لباس optimisation، و به همین دلیل input normalisation توصیه بهداشتی نیست، حساب است.1

هیچ‌چیز از آنچه گذشت به کتابخانه نیاز نداشت. این کل optimiser است.

descent.pyPYTHON
def loss(theta):
    a, b = theta
    return np.mean((a * x + b - y) ** 2)


def grad(theta):
    a, b = theta
    residual = a * x + b - y
    return np.array([np.mean(2 * residual * x), np.mean(2 * residual)])


def descend(theta, lr, steps):
    theta = np.array(theta, dtype=float)
    for _ in range(steps):
        theta = theta - lr * grad(theta)   
    return theta


theta = descend([0.0, 0.0], lr=0.05, steps=60)
print(theta, loss(theta))
TEXT
[ 2.10040296e+00 -2.76445533e-15] 24.592448791134984

پاسخ least-squares بسته برای این هشت نقطه a=2.100403a = 2.100403، b=0b = 0 است، با loss برابر 24.59244924.592449. حلقه بدون اینکه بداند فرم بسته‌ای وجود دارد، آن را تا هشت رقم معنادار پیدا کرد — و این مهم است، چون از فصل 5 به بعد دیگر چنین فرمی وجود نخواهد داشت.

مسیر حرکت، چون دیدنش اصل ماجراست:

TEXT
   0 a=0.000000 b=0.000000 L=57.437500
   1 a=1.563750 b=0.000000 L=26.736582
   2 a=1.963288 b=-0.000000 L=24.732418
   5 a=2.098116 b=-0.000000 L=24.592488
  10 a=2.100400 b=-0.000000 L=24.592449
  60 a=2.100403 b=-0.000000 L=24.592449

بیشتر فاصله در دو گام اول طی می‌شود، چون وقتی از کف دورتر هستید gradient بزرگ‌تر است و با نزدیک‌شدن کوچک می‌شود. Gradient descent نزدیک یک minimum خودکار کند می‌شود. این یک ویژگی است و در فصل 6، یک مشکل هم هست.

جاهای دیگری که شیب صفر است

لینک به بخش: جاهای دیگری که شیب صفر است

استدلال تا اینجا یک سوراخ دارد. گام وقتی می‌ایستد که L=0\nabla L = \mathbf{0}، و ما به آن «minimum» گفته‌ایم. نقطه‌ای با gradient صفر یک نقطه بحرانی است، و minimum بودن فقط یکی از راه‌های بحرانی بودن است:

  • یک minimum محلی: در هر جهت رو به بالا، اما شاید پایین‌ترین چنین نقطه‌ای در کل فضا نباشد؛
  • یک maximum محلی: در هر جهت رو به پایین؛
  • یک نقطه زینی: در بعضی جهت‌ها رو به بالا و در بعضی دیگر رو به پایین. سطح f(x,y)=x2y2f(x,y) = x^2 - y^2 دارای f=(2x,2y)\nabla f = (2x, -2y) است، که در مبدأ صفر می‌شود؛ همان‌جا تابع هم‌زمان در امتداد محور xx یک minimum و در امتداد محور yy یک maximum است.

Gradient descent نمی‌تواند این‌ها را از هم تشخیص دهد، چون همیشه فقط به gradient نگاه می‌کند، و gradient در هر سه صفر است.

خط ما یک نقطه بحرانی دارد و همان پاسخ است — loss خطای مربعی روی یک مدل خطی convex است، یک کاسه واحد، و descent روی آن نمی‌تواند در یافتن minimum سراسری شکست بخورد. این خاصیت با تماس با این دوره دوام نمی‌آورد. loss یک شبکه عصبی convex نیست، و از فصل 5 به بعد «the minimum» چیزی نیست که وجود داشته باشد: تعداد زیادی minimum با عمق‌های متفاوت وجود دارد، و اینکه کدام را می‌گیرید به نقطه شروع بستگی دارد. این یک جمله است و یک جمله می‌ماند، چون نظریه بزرگ است و پیامد عملی کوچک.

می‌توانید کل پیامد را روی یک منحنی ببینید. f(x)=x44x22+x10f(x) = \tfrac{x^4}{4} - \tfrac{x^2}{2} + \tfrac{x}{10} را بردارید، که دو دره با عمق‌های متفاوت دارد:

TEXT
   x =  -1.046681   f(x) =  -0.352386   minimum
   x =   0.101031   f(x) =   0.005026   maximum
   x =   0.945649   f(x) =  -0.152639   minimum

40 گام، پایان در x = 0.9456.

مشاهده داده‌ها به‌صورت جدول
گامxf(x)
0⁨0.1100⁩⁨0.0050⁩
1⁨0.1122⁩⁨0.0050⁩
2⁨0.1149⁩⁨0.0049⁩
3⁨0.1182⁩⁨0.0049⁩
4⁨0.1223⁩⁨0.0048⁩
5⁨0.1275⁩⁨0.0047⁩
6⁨0.1338⁩⁨0.0045⁩
7⁨0.1416⁩⁨0.0042⁩
8⁨0.1513⁩⁨0.0038⁩
9⁨0.1633⁩⁨0.0032⁩
10⁨0.1781⁩⁨0.0022⁩
11⁨0.1962⁩⁨0.0007⁩
12⁨0.2183⁩⁨-0.0014⁩
13⁨0.2453⁩⁨-0.0046⁩
14⁨0.2779⁩⁨-0.0093⁩
15⁨0.3170⁩⁨-0.0160⁩
16⁨0.3633⁩⁨-0.0253⁩
17⁨0.4172⁩⁨-0.0377⁩
18⁨0.4783⁩⁨-0.0535⁩
19⁨0.5455⁩⁨-0.0721⁩
20⁨0.6163⁩⁨-0.0922⁩
21⁨0.6869⁩⁨-0.1116⁩
22⁨0.7526⁩⁨-0.1277⁩
23⁨0.8092⁩⁨-0.1393⁩
24⁨0.8540⁩⁨-0.1463⁩
25⁨0.8868⁩⁨-0.1499⁩
26⁨0.9091⁩⁨-0.1516⁩
27⁨0.9236⁩⁨-0.1522⁩
28⁨0.9325⁩⁨-0.1525⁩
29⁨0.9379⁩⁨-0.1526⁩
30⁨0.9411⁩⁨-0.1526⁩
31⁨0.9430⁩⁨-0.1526⁩
32⁨0.9441⁩⁨-0.1526⁩
33⁨0.9448⁩⁨-0.1526⁩
34⁨0.9451⁩⁨-0.1526⁩
35⁨0.9454⁩⁨-0.1526⁩
36⁨0.9455⁩⁨-0.1526⁩
37⁨0.9455⁩⁨-0.1526⁩
38⁨0.9456⁩⁨-0.1526⁩
39⁨0.9456⁩⁨-0.1526⁩
40⁨0.9456⁩⁨-0.1526⁩
نزول گرادیان، تعاملی

چهل گام از x=0.11x = 0.11، با قرارگرفتن در 0.94560.9456 — کم‌عمق‌ترِ دو دره. حالا نقطه شروع را یک پله به چپ ببرید، به 0.100.10. همان نرخ، همان چهل گام، و این بار در 1.0461-1.0461 قرار می‌گیرد، جایی که loss 0.199747 کمتر است. خط آب‌پخشان همان برآمدگی در 0.1010310.101031 است، و کل تفاوت بین دو پاسخ فقط این است که اتفاقاً از کدام سمت آن شروع کرده‌اید.

فرود در دره کم‌عمق از نظر loss، 56.7% بدتر است، و الگوریتم راهی برای دانستن ندارد، چون از داخل یک دره هر جهت رو به بالا است. در gradient descent هیچ تعمیرکاری برای این وجود ندارد و قرار هم نیست بیاید. آنچه در عمل وجود دارد، این یافته است که موضوع بسیار کمتر از چیزی که این تصویر نشان می‌دهد اهمیت دارد — در ابعاد بسیار بالای یک شبکه واقعی، بیشتر نقاط بحرانی به‌جای تله، نقطه زینی از آب درمی‌آیند،2 و فصل 5 اندازه می‌گیرد یک شبکه کوچک واقعاً چند بار گیر می‌کند.

گام‌های ارزان‌تر: stochastic، minibatch، momentum

لینک به بخش: گام‌های ارزان‌تر: stochastic، minibatch، momentum

یک چیز درباره grad بالا باید اذیتتان کند: برای هر گام روی کل dataset جمع می‌زند. هشت قطعه چیزی نیست. یک میلیون یعنی یک میلیون محاسبه gradient برای اینکه پارامترها یک بار حرکت کنند.

راه فرار این است که gradient یک میانگین است، و میانگین را می‌شود از یک نمونه تخمین زد. آن را روی یک مشت تصادفی — یک minibatch — حساب کنید و با آن گام بردارید. تخمین نویزی است؛ اما unbiased هم هست، و صدها گام نویزی ارزان بر یک گام دقیق گران می‌چربند. روی صد هزار قطعه مصنوعی، اگر به‌جای گام‌ها gradientهای هر مثال را بشماریم:

روشگام‌ها تا فاصله 0.1% از optimumgradientهای هر مثال
full batch7700,000
minibatch با اندازه 321003,200
هر بار یک مثال17,58017,580

دویست‌ونوزده برابر حساب کمتر برای رسیدن به همان جا. و حالت افراطی — هر بار یک مثال، همان تقریب stochastic اولیه رابینز و مونرو3برنده نیست: پنج برابر بدتر از batchهای 32 است، چون روی سخت‌افزاری که ماتریس ضرب می‌کند، 32 مثال تقریباً هیچ هزینه بیشتری از یک مثال ندارد، در حالی که نویز با ریشه دوم اندازه batch پایین می‌آید. همین بده‌بستان دلیل آن است که هر training scriptی که ever بخوانید یک batch_size در خودش دارد.

Momentum اصلاح ارزان دیگر است، و دقیقاً به گودال نشانه می‌رود. در یک دره بدشرط، گام‌ها در عرض جهت باریک زیگزاگ می‌زنند و در امتداد جهت بلند می‌خزند. Momentum میانگین جاری‌ای از gradientهای گذشته نگه می‌دارد، تا مؤلفه‌های نوسانی همدیگر را خنثی کنند و مؤلفه پایدار جمع شود:4

vβv+L(θ),θθηv\mathbf{v} \leftarrow \beta \mathbf{v} + \nabla L(\boldsymbol{\theta}), \qquad \boldsymbol{\theta} \leftarrow \boldsymbol{\theta} - \eta \mathbf{v}

دو خط اضافه. روی تسمه خام و مرکززدایی‌نشده — condition number برابر 33,452، بدترین موردی که داریم — با بهترین نرخی که descent ساده می‌تواند بردارد:

TEXT
momentum beta=0.0   ->    79,513 steps to 1%
momentum beta=0.9   ->     1,609 steps to 1%
momentum beta=0.99  ->       461 steps to 1%

ضریب 172 برای دو خط code. فصل 6 این را به Adam تبدیل می‌کند؛ سازوکارش همین‌جاست.

بررسی‌ای که در فصل 5 لازم خواهید داشت

لینک به بخش: بررسی‌ای که در فصل 5 لازم خواهید داشت

هر gradient در این فصل با دست مشتق شد و بنابراین می‌توانست غلط باشد. راه‌حل همان جدول شیب ابتدای فصل است: مشتق را عددی اندازه بگیرید و مقایسه کنید. از تفاضل مرکزی استفاده کنید، L(θ+h)L(θh)2h\frac{L(\theta+h) - L(\theta-h)}{2h}، که جمله خطای پیشرو را حذف می‌کند و برای همان hh بسیار دقیق‌تر است.

gradcheck.pyPYTHON
def numeric_grad(f, theta, h=1e-5):
    theta = np.asarray(theta, dtype=float)
    out = np.zeros_like(theta)
    for i in range(theta.size):
        bump = np.zeros_like(theta)
        bump[i] = h
        out[i] = (f(theta + bump) - f(theta - bump)) / (2 * h)     
    return out


def gradcheck(f, df, theta, h=1e-5):
    analytic = np.asarray(df(theta), dtype=float)
    numeric = numeric_grad(f, theta, h)
    return np.max(np.abs(analytic - numeric) / np.maximum(1e-8, np.abs(analytic) + np.abs(numeric)))

شکل نسبی مقایسه مهم است: اختلاف مطلق 10410^{-4} روی gradientی با اندازه 10310^{-3} فاجعه است و روی یکی با اندازه 10610^{6} بی‌اهمیت.

TEXT
relative error: 1.8929136036763527e-11
with 2 dropped: 0.33333333331650744

خط اول همان gradient دستی بالاست. خط دوم همان تابع است با جاافتادن ضریب 2 در یک مؤلفه — یک typo تک‌کاراکتری — و check فوراً آن را می‌گیرد. هر چیزی پایین‌تر از حدود 10710^{-7} توافق است؛ هر چیزی بالاتر از 10410^{-4} bug است. این تابع را نگه دارید: فصل 5 از آن برای debug کردن یک موتور automatic differentiation استفاده می‌کند، و تنها دلیل پیدا‌شدن یک gradient غلط همین است.

همه چیز در این فصل روی یک فرض سوار بود که هرگز بیان نشد: اینکه بتوانید L/θ\partial L / \partial \theta را بنویسید.

برای یک خط با دو پارامتر، این یک خط جبر بود. تقریباً بلافاصله از این حالت خارج می‌شود. از یک سیستم جبر نمادین بخواهید مشتق loss یک شبکه را نسبت به یک weight واحد در layer اول، برای یک مثال واحد، بدهد و تعداد عملیات حسابی در جواب را بشمارید:

شبکهعملیات در یک مشتق جزئی
چهار واحد پنهان، یک layer40
چهار واحد پنهان، دو layer301
چهار واحد پنهان، سه layer1,717

ردیف سوم شبکه‌ای با 57 پارامتر است — شبکه‌ای آن‌قدر کوچک که در فصل 6 فقط یک پاورقی می‌شد — و نوشتن gradient آن با دست یعنی حدود 97,869 عملیات برای یک مثال آموزشی. هیچ نمادی این را نجات نمی‌دهد. چیزی که نجاتش می‌دهد مشاهده این است که قاعده زنجیره‌ای وقتی روی یک ترکیب اعمال می‌شود ساختار عظیمی دارد، همان کمیت‌های میانی بارها و بارها ظاهر می‌شوند، و محاسبه آن‌ها با ترتیب درست همه مشتق‌ها را تقریباً به قیمت یک forward pass می‌دهد. این فصل 5 است.

اما اول یک مشکل کوچک‌تر هست، و بلافاصله منتظرمان است.

حالا ماشینی داریم که روی هر loss مشتق‌پذیری به پایین می‌غلتد. آن را به سؤال اصلی تسمه نشانه بروید — قبول یا رد، هدفی که 1 یا 0 است — روی خروجی یک sigmoid بگذارید تا احتمال پیش‌بینی کند، و خطای مربعی را minimize کنید. اجرا می‌شود. اما درست وقتی بیش از همه اشتباه می‌کند به‌سختی حرکت می‌کند، و gradient می‌گوید چرا:

خروجی zzپیش‌بینیحقیقتgradient با خطای مربعیgradient با cross-entropy
000.500012.5×1012.5 \times 10^{-1}5.0×1015.0 \times 10^{-1}
2-20.119211.850×1011.850 \times 10^{-1}8.808×1018.808 \times 10^{-1}
6-60.002514.921×1034.921 \times 10^{-3}9.975×1019.975 \times 10^{-1}
10-104.54×1054.54 \times 10^{-5}19.079×1059.079 \times 10^{-5}1.0001.000

مدلی که با اطمینان، فاجعه‌بار غلط است — وقتی پاسخ 1 است مقدار 0.0000454 را پیش‌بینی می‌کند — gradient خطای مربعی برابر 9×1059 \times 10^{-5} تولید می‌کند. هیچ ایده‌ای ندارد که گرفتار دردسر است. ستون دیگر، از lossی که هنوز مشتق نکرده‌ایم، مقدار 1.0 گزارش می‌کند: بیشینه فوریت، دقیقاً همان‌جا که سزاوار است.

و این پرسشی را پیش می‌کشد که فصل بعد با آن آغاز می‌شود. فصل قبل گفت loss فرضی درباره نویز است، و خطای مربعی نویز گاوسی را فرض می‌کند. پاسخ بله-یا-خیر چه مدل نویزی دارد — و وقتی همان مشتق‌گیری را روی آن اجرا کنید چه lossی بیرون می‌آید؟


این روش از همه این‌ها قدیمی‌تر است: Cauchy در یادداشتی به Académie des Sciences در 1847 آن را توصیف کرد، به‌عنوان راهی برای حل دستگاه‌های معادله با پایین‌رفتن روی مجموع residualهای مربعی‌شان. خواندن این‌ها کنار این فصل هم ارزش دارد: An overview of gradient descent optimization algorithms از Sebastian Ruder (arXiv:1609.04747)، که momentum تا Adam را در چهارده صفحه خواندنی پوشش می‌دهد؛ فصل 3 از Numerical Optimization نوشته Nocedal و Wright (ویرایش دوم، Springer، 2006)، که قضیه 3.3 آن نرخ همگرایی steepest descent روی یک quadratic را برحسب condition number می‌دهد — همان نظریه پشت این است که چرا conditioning تعداد گام‌ها را تعیین می‌کند، هرچند به‌جای سقف fixed-step 2/λmax2/\lambda_{\max} اندازه‌گیری‌شده بالا، line search را بررسی می‌کند؛ یا §5.8 و §7.1 از Mathematics for Machine Learning نوشته Deisenroth، Faisal و Ong برای همان زمین با ابزار کمتر؛ §6.1 از Understanding Deep Learning نوشته Prince و §4.3 از Deep Learning نوشته Goodfellow، Bengio و Courville؛ Dive into Deep Learning §12.1–12.3، که تحلیل minibatch را با اندازه‌گیری‌هایی بیشتر از جا در اینجا ارائه می‌کند؛ و فصل 4 از Hands-On Machine Learning نوشته Géron (ویرایش سوم)، عملی‌ترین پرداخت به learning rate به‌عنوان چیزی که tune می‌کنید نه مشتق. یادداشت‌های MIT 6.390 هم gradient descent را پیش از classification می‌آورند، همان‌طور که این دوره می‌آورد و به همان دلیل.

  1. LeCun, Y., Bottou, L., Orr, G. B. and Müller, K.-R. Efficient BackProp, in Neural Networks: Tricks of the Trade (Springer, 1998), pp. 9–50. بخش 4.3 توصیه را می‌دهد و بخش 5.1 استدلالی را که در جعبه جزئیات بالا استفاده شد: مرکززدایی و مقیاس‌کردن inputها eigenvalueهای ماتریس مشتق دوم، و بنابراین تعداد گام‌ها را تغییر می‌دهد، نه صرفاً راحتی عددی را.

  2. Dauphin, Y. N., Pascanu, R., Gulcehre, C., Cho, K., Ganguli, S. and Bengio, Y. Identifying and attacking the saddle point problem in high-dimensional non-convex optimization, arXiv:1406.2572 (2014). استدلال اینکه در ابعاد بالا نقاط بحرانی به‌طور غالب نقطه زینی‌اند نه minimum محلی، چون minimum بودن مستلزم آن است که هر یک از هزاران جهت هم‌زمان رو به بالا خم شوند.

  3. Robbins, H. and Monro, S. A Stochastic Approximation Method. Annals of Mathematical Statistics 22(3), pp. 400–407 (1951). مقاله‌ای که تثبیت کرد تخمین نویزی یک gradient کافی است، به شرط آنکه اندازه گام به شکل درست کوچک شود.

  4. Polyak, B. T. Some methods of speeding up the convergence of iteration methods. USSR Computational Mathematics and Mathematical Physics 4(5), pp. 1–17 (1964). روش heavy-ball، که همان به‌روزرسانی momentum بالاست، بیست‌ودو سال پیش از آنکه backpropagation به این حوزه برسد.


تهیه‌شده توسط

David Vicente Campos

بنیان‌گذار NeuraLIA Labs و هم‌بنیان‌گذار MyRealFood

من مهندس کامپیوتر و فارغ‌التحصیل دانشگاه لئون هستم. هم‌بنیان‌گذار MyRealFood بودم، جایی که به‌عنوان مدیر ارشد فناوری اپلیکیشنی را ساختم که میلیون‌ها نفر برای سالم‌تر غذا خوردن از آن استفاده کرده‌اند، و NeuraLIA Labs را بنیان‌گذاری کردم؛ جایی که محصولات هوش مصنوعی می‌سازم. اینجا از چیزهایی می‌نویسم که در طول مسیر باید می‌فهمیدم، همان‌طور که دوست داشتم کسی برایم توضیح می‌داد.

بیشتر درباره نویسنده

منتشرشده توسط NeuraLIA Labs.

پست‌های جدید را در ایمیل خود دریافت کنید

اخبار AI، راهنماها و به‌روزرسانی‌های محصول — هر وقت چیزی ارزشمند منتشر کنیم، یک ایمیل کوتاه می‌فرستیم.

فهرست دوره

Abstract software decision engine with branching paths, probability nodes, and glowing gates.
jev12 دقیقه مطالعه

مدل هوش مصنوعی Jev برای تصمیم ساخته شده، نه نثر

Jev از TypeSafe AI توجه‌ها را جلب کرده چون هوشمندی نرم‌افزار را مسئله‌ای احتمالاتی می‌بیند: شاخه درست را انتخاب کنید، میزان اطمینان را کنار آن بگذارید، و وقتی کد به یک تصمیم نیاز دارد برای نوشتن متن به یک LLM پول ندهید.

Abstract agent runtime sorting documents, memory blocks and pointer nodes inside a bounded context frame.
context-engineering13 دقیقه مطالعه

مهندسی کانتکست برای عامل‌های AI بلندافق

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

آماده‌اید انتخاب مدل را به LIA بسپارید؟

با همه مدل‌های هوش مصنوعی در یک جا بسازید — همین امروز رایگان شروع کنید.