سرازیر: Gradient Descent و دو گامی که همه جا میاندازند
سقف دقیق نرخ یادگیری را حساب کنید و ببینید جستوجوی brute-force بین ۳۶۰۰ جهت، gradient را دوباره کشف میکند.
در این صفحه
فصل قبل با یک دره تمام شد.
نه یک دره استعاری: یک منحنی واقعی، نمودار loss برحسب یک پارامتر، که پایین میرفت و دوباره بالا میآمد. و loss زیر آن صرفاً بهخاطر مرتببودن انتخاب نشده بود — از گزارهای درباره نویز اندازهگیریها مشتق شده بود، و خطای مربعی در انتهای مسیر نه بهعنوان یک قرارداد، بلکه بهعنوان یک پیامد بیرون آمد.
پس یک چشمانداز داریم که کفی دارد، و دلیلی داریم که باور کنیم کف همان جای درست است. چیزی که نداریم راهی برای رسیدن به آنجاست.
این فصل یکی میسازد، و آن همان الگوریتمی است که هر مدلِ باقی این دوره را آموزش میدهد — همهشان، بدون استثنا، تا خود مدلهایی با صدها میلیارد پارامتر. در حدود بیست خط جا میشود. دو بخش سخت در آن بیست خط نیستند، و همان دو چیزیاند که تقریباً هر توضیحی از آنها میگذرد:
- چرا علامت منفی. بهروزرسانی، gradient را کم میکند. هر آموزشی آن را مینویسد؛ خیلی کم پیش میآید کسی بگوید چرا gradient جهتی است که بالا میرود، و همین تنها واقعیتی است که علامت منفی را از یک عمل ایمانی به چیزی واقعی تبدیل میکند.
- چه اندازه گام. «خیلی بزرگ واگرا میشود، خیلی کوچک کند است» درست و بیفایده است. یک عدد دقیق وجود دارد، از روی loss قابل محاسبه است، و این فصل آن را دو بار حساب میکند — یک بار برای یک سهمی اسباببازی و یک بار برای داده واقعی.
چیدمان مسئله، و چرا نمیتوانید فقط جستوجو کنید
لینک به بخش: چیدمان مسئله، و چرا نمیتوانید فقط جستوجو کنیدبرای اینکه این فصل مستقل بایستد، مسئله را دوباره بیان میکنیم: همان هشت قطعه از تسمه نقاله فصل 1، اما با پرسشی متفاوت. نه قبول یا رد — آن بعداً برمیگردد — بلکه پیشبینی وزن یک قطعه از روی عرض آن.
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 و به دلیلی که پیش از پایان این فصل با بهره برمیگردد. مدل یک خط است، ، و loss همان میانگین خطای مربعی است که فصل قبل مشتق کرد:
دو پارامتر. چرا صرفاً مقدارهای زیادی را امتحان نکنیم؟ بیایید واقعاً این کار را بکنیم — یک grid از تا و از تا ، با گامهای :
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 برای پارامتر با مقدار برای هرکدام، ارزیابی هزینه دارد. با هزار مقدار برای هر محور:
| مدل | پارامترها | ارزیابیهای grid |
|---|---|---|
| این خط | 2 | |
| شبکه XOR در فصل 5 | 9 | |
| یک شبکه چندلایه کوچک | 20,000 |
ردیف سوم یک عدد بزرگ نیست، یک عدد بیمعنا است — در جهان قابل مشاهده تقریباً اتم وجود دارد. جستوجو با بزرگشدن مدلها کندتر نمیشود؛ از هستی ساقط میشود. هرچه از اینجا به بعد میآید بهخاطر همان جدول وجود دارد.
مشتق اندازهگیریای است که میتوانید انجام دهید
لینک به بخش: مشتق اندازهگیریای است که میتوانید انجام دهیدبرای لحظهای را ثابت نگه دارید تا یک پارامتر و یک منحنی داشته باشیم؛ همان تصویری که فصل قبل برایتان گذاشت. نقطهای روی آن بردارید، ، و بپرسید: اگر را بهاندازه کوچکی مثل تکان بدهم، loss به ازای هر واحد تکان چقدر حرکت میکند؟
این نسبت همان خیز بر پیشروی است — شیب خط مستقیمی که از دو نقطه روی منحنی میگذرد. هرچه کوچکتر میشود، دو نقطه به هم میلغزند و خط به مماس تبدیل میشود. شیب آن مشتق است: نرخ تغییر loss به ازای هر واحد تغییر در . نه تقریب چیزی، و نه کمیتی بینهایت کوچک. حدی از نسبتهای معمولی.
ارزش اجرا کردن دارد، چون عددها چیزی میگویند که تعریف نمیگوید:
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}")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اینجا دو اتفاق میافتد و هر دو باربرند.
خطا بهطور مبهم با متناسب نیست — دقیقاً است. را بر صد تقسیم کنید، خطا هم هر بار تا چهار رقم معنادار بر صد تقسیم میشود. آن ثابت تزئین نیست: نصف مشتق دوم loss است، و نخستین ظهور ایدهای است که دو بخش دیگر میبینیم — اینکه یک منحنی نزدیک یک نقطه شبیه یک خط است، بهعلاوه تصحیحی متناسب با .
و بعد الگو میشکند. پایینتر از تخمین بدتر میشود، و در در رقم دوم غلط است. هیچ اتفاق ریاضیای نیفتاد؛ جعبه floating-point فصل قبل افتاد وسط. و در ده رقم اولشان با هم موافقاند، کمکردنشان آن رقمها را نابود میکند، و تقسیم ویرانه باقیمانده بر عددی کوچک، آنچه مانده را تقویت میکند. یک بهینه وجود دارد — اینجا حوالی ، تقریباً ریشه دوم machine epsilon — و کوچکتر رفتن دقیقتر بودن نیست، کمتر دقیق بودن است. یادتان بماند؛ تابعی در پایان این فصل به آن وابسته است.
شیب دقیق، از حسابان نه اندازهگیری، است. پس میتوانیم اندازهگیری را متوقف کنیم و مشتقگیری را شروع کنیم.
ترکیب، و قاعده زنجیرهای
لینک به بخش: ترکیب، و قاعده زنجیرهایایدهای که بقیه دوره روی آن ساخته میشود اینجاست، یکبار و ساده.
ترکیب دو تابع یعنی خوراندن یکی به دیگری: . همین و بس.
یک شبکه عمیق شبیه ترکیب نیست. دقیقاً خودِ ترکیب است. یک layer یک تابع است؛ چیدن layerها روی هم یعنی ترکیب آنها؛ «عمق» تعداد تابعهای زنجیره است. وقتی فصل 5 یک شبکه میسازد، دارد میسازد و هیچ چیز دیگر. یعنی مهمترین قاعده حسابان برای هدف ما همان قاعدهای است که از ترکیب مشتق میگیرد:
نرخها در هم ضرب میشوند. اگر سه برابر سریعتر از تغییر کند، و دو برابر سریعتر از ، آنوقت شش برابر سریعتر از تغییر میکند. تمام محتوا همین است، و به همین دلیل سیگنالی که از ده layer به عقب عبور میکند در ده عدد ضرب میشود — و به همین دلیل فصل 6 بخشی را صرف این میکند که وقتی آن عددها همگی کمی کمتر از یک باشند چه میشود.
آن را روی loss خودمان به کار ببرید. residual را بنویسید، طوری که . هر از طریق تابع درونی به وابسته است، و مشتق آن است. قاعده زنجیرهای، جملهبهجمله:
آن نمادهای خمیده یک مشتق جزئی را نشان میدهند: نسبت به یک متغیر مشتق بگیرید و هر چیز دیگر را ثابت فرض کنید. اتفاق تازهای نمیافتد — همان حد قبلی است، فقط در امتداد یک محور گرفته میشود. مشتقهای جزئی را در یک بردار جمع کنید و gradient را دارید:
در نقطه آن بردار است. دو عدد. سؤال این است که معنایشان چیست، و این نخستین گامی است که همه جا میاندازند.
چرا gradient رو به بالا اشاره میکند
لینک به بخش: چرا gradient رو به بالا اشاره میکندgradient برداری از شیبها در امتداد محورهاست. همین را ثابت کردهایم. بدیهی نیست — و نباید هم بدیهی باشد — که کنار هم گذاشتنشان در یک بردار چیزی بسازد که به جهت خاصی اشاره کند.
پس چیزی را تعریف کنیم که واقعاً میخواهیم. یک بردار واحد انتخاب کنید، یک جهت. مشتق جهتی نرخ تغییر loss وقتی در آن جهت راه میروید است:
قاعده زنجیرهای این را به چیزی قابل محاسبه تبدیل میکند. راهرفتن در امتداد ، را با نرخ و را با نرخ تغییر میدهد، و سهمها با هم جمع میشوند:
نرخ تغییر در هر جهت، dot productِ gradient با آن جهت است. و حالا ضربه نهایی، که یک خط هندسه است. اگر dot product را با زاویه بین بردارها بنویسیم،
چون طول 1 دارد. تنها چیزی که کنترل میکنید است، که در بیشینه و در نیمدور، یعنی درجه، کمینه میشود. پس:
- تندترین صعود در امتداد خود است، و شیب آنجا دقیقاً است.
- تندترین نزول در امتداد است، و شیب آنجا است.
- عمود بر gradient، loss اصلاً تغییر نمیکند. به همین دلیل خطوط یک نقشه کانتور gradient را با زاویه قائمه قطع میکنند.
این همان علامت منفی است. نه یک قرارداد، نه وارونهکردن علامتی که کسی انتخاب کرده باشد: جهت سریعترین کاهش، gradient منفی است چون در نیمدور کمینه میشود، و به هیچ دلیل دیگری.
چون این ادعا درباره همه جهتهاست، آن را در برابر همه جهتها آزمایش کنید. 3,600 جهت نمونه بگیرید، یکی برای هر دهم درجه، و هرکدام را با یک تکان کوچک اندازه بگیرید:
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")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 را کم میکند، چون «پایین» گزارهای درباره یک تکان بینهایت کوچک است و گام بینهایت کوچک نیست.
پل میان این دو خطیسازی است. نزدیک یک نقطه، یک تابع هموار برابر مماسش است بهعلاوه یک تصحیح:
این بسط تیلور مرتبه اول است. جمله حذفشده همان curvature است — همان جملهای که باعث شد تخمین جدول شیب دقیقاً بهاندازه خطا داشته باشد. گامی را که قصد داریم برداریم جایگذاری کنید، :
loss بهاندازه پایین میآید. هر بخش آن نامنفی است، پس وعده واقعی است — برای بهاندازه کافی کوچک، چون جمله نادیدهگرفتهشده مثل رشد میکند و سرانجام آن را میبلعد. کل نظریه همین است. اینجا وعده اول وفا میشود و بعد میشکند:
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از پایین بخوانید. هرچه کوچک میشود، افت محققشده به افت وعدهدادهشده همگرا میشود — نسبت 0.99938، بعد 0.99994 — و این یعنی قضیه تیلور درست عمل میکند. از بالا بخوانید و در «افت» محققشده منفی شانزده است. گام رو به پایین رفت و loss بالا رفت.
پس قاعده بهروزرسانی این است
و شرطی همراهش دارد که هیچکس بیان نمیکند: باید بهاندازه کافی کوچک باشد. بهاندازه کافی کوچک نسبت به چه چیزی دقیقاً، موضوع بخش بعدی است.
نرخ یادگیری سقف دارد، و قابل محاسبه است
لینک به بخش: نرخ یادگیری سقف دارد، و قابل محاسبه استبا سادهترین دره ممکن شروع کنید، ، که در آن . یک گام gradient descent این است
موقعیت در هر گام در ضرب میشود. این یک دنباله هندسی است، و دنبالههای هندسی دقیقاً یک قاعده دارند: وقتی ضریب از نظر قدرمطلق کمتر از 1 باشد کوچک میشوند و در غیر این صورت رشد میکنند. پس ، که یعنی .
مرز دقیقاً در است. نه «حوالی 1»، نه «1 معمولاً زیادی بزرگ است». در ضریب است و نقطه برای همیشه بین و بالا و پایین میپرد، نه نزدیک میشود و نه میگریزد. پایینتر از آن، همگرا میشود؛ بالاتر از آن، واگرا. بازه دوباره در شکافته میشود، جایی که ضریب علامت عوض میکند: پایینتر از آن نزدیکشدن یکنواخت است، بالاتر از آن نقطه از حد میگذرد و طرفین را یکیدرمیان عوض میکند، و دقیقاً در ضریب 0 است و یک گام تنها روی minimum فرود میآید.
چهار رژیم، از چهار خط جبر. خودتان بروید و مرزها را رد کنید:
و حالا مورد جالب:
حالا قاعده عمومی، که از همان استدلال بیرون میافتد. ضریب در واقع بود، و نزدیک یک minimum، یک loss چندپارامتری برای هر جهت یکی از این عددها دارد — eigenvalueهای ماتریس مشتقهای دوم. همه جهتها باید همزمان پایدار باشند، پس سقف را بزرگترینشان تعیین میکند:
برای ، ، سقف 1 است، که همان چیزی است که تازه مشتق کردیم. برای تسمه ما، ماتریس مشتق دوم است، با بهعنوان ماتریس دوستونه inputها، و eigenvalueهای آن 2 و 14.89 هستند، پس سقف است. این پیشبینیای با پنج رقم معنادار است. آزمایشش کنید:
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.44 | 0.1184 | 10 |
| میلیمترها و گرمهای خام | 33,452 | 0.0020037 | 79,513 |
همان داده، همان code، همان پاسخ در انتها — و هشتهزار برابر کار، چون هیچکس میانگین را کم نکرد. در فصل 1 همین حذف، برای perceptron ششهزار برابر epoch هزینه داشت، و تشخیص آنجا هندسی بود: داده از مبدأ دور شناور بود. اینجا همان هندسه است در لباس optimisation، و به همین دلیل input normalisation توصیه بهداشتی نیست، حساب است.1
بیست خط
لینک به بخش: بیست خطهیچچیز از آنچه گذشت به کتابخانه نیاز نداشت. این کل optimiser است.
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))[ 2.10040296e+00 -2.76445533e-15] 24.592448791134984پاسخ least-squares بسته برای این هشت نقطه ، است، با loss برابر . حلقه بدون اینکه بداند فرم بستهای وجود دارد، آن را تا هشت رقم معنادار پیدا کرد — و این مهم است، چون از فصل 5 به بعد دیگر چنین فرمی وجود نخواهد داشت.
مسیر حرکت، چون دیدنش اصل ماجراست:
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، یک مشکل هم هست.
جاهای دیگری که شیب صفر است
لینک به بخش: جاهای دیگری که شیب صفر استاستدلال تا اینجا یک سوراخ دارد. گام وقتی میایستد که ، و ما به آن «minimum» گفتهایم. نقطهای با gradient صفر یک نقطه بحرانی است، و minimum بودن فقط یکی از راههای بحرانی بودن است:
- یک minimum محلی: در هر جهت رو به بالا، اما شاید پایینترین چنین نقطهای در کل فضا نباشد؛
- یک maximum محلی: در هر جهت رو به پایین؛
- یک نقطه زینی: در بعضی جهتها رو به بالا و در بعضی دیگر رو به پایین. سطح دارای است، که در مبدأ صفر میشود؛ همانجا تابع همزمان در امتداد محور یک minimum و در امتداد محور یک maximum است.
Gradient descent نمیتواند اینها را از هم تشخیص دهد، چون همیشه فقط به gradient نگاه میکند، و gradient در هر سه صفر است.
خط ما یک نقطه بحرانی دارد و همان پاسخ است — loss خطای مربعی روی یک مدل خطی convex است، یک کاسه واحد، و descent روی آن نمیتواند در یافتن minimum سراسری شکست بخورد. این خاصیت با تماس با این دوره دوام نمیآورد. loss یک شبکه عصبی convex نیست، و از فصل 5 به بعد «the minimum» چیزی نیست که وجود داشته باشد: تعداد زیادی minimum با عمقهای متفاوت وجود دارد، و اینکه کدام را میگیرید به نقطه شروع بستگی دارد. این یک جمله است و یک جمله میماند، چون نظریه بزرگ است و پیامد عملی کوچک.
میتوانید کل پیامد را روی یک منحنی ببینید. را بردارید، که دو دره با عمقهای متفاوت دارد:
x = -1.046681 f(x) = -0.352386 minimum
x = 0.101031 f(x) = 0.005026 maximum
x = 0.945649 f(x) = -0.152639 minimumفرود در دره کمعمق از نظر loss، 56.7% بدتر است، و الگوریتم راهی برای دانستن ندارد، چون از داخل یک دره هر جهت رو به بالا است. در gradient descent هیچ تعمیرکاری برای این وجود ندارد و قرار هم نیست بیاید. آنچه در عمل وجود دارد، این یافته است که موضوع بسیار کمتر از چیزی که این تصویر نشان میدهد اهمیت دارد — در ابعاد بسیار بالای یک شبکه واقعی، بیشتر نقاط بحرانی بهجای تله، نقطه زینی از آب درمیآیند،2 و فصل 5 اندازه میگیرد یک شبکه کوچک واقعاً چند بار گیر میکند.
گامهای ارزانتر: stochastic، minibatch، momentum
لینک به بخش: گامهای ارزانتر: stochastic، minibatch، momentumیک چیز درباره grad بالا باید اذیتتان کند: برای هر گام روی کل dataset جمع میزند. هشت قطعه چیزی نیست. یک میلیون یعنی یک میلیون محاسبه gradient برای اینکه پارامترها یک بار حرکت کنند.
راه فرار این است که gradient یک میانگین است، و میانگین را میشود از یک نمونه تخمین زد. آن را روی یک مشت تصادفی — یک minibatch — حساب کنید و با آن گام بردارید. تخمین نویزی است؛ اما unbiased هم هست، و صدها گام نویزی ارزان بر یک گام دقیق گران میچربند. روی صد هزار قطعه مصنوعی، اگر بهجای گامها gradientهای هر مثال را بشماریم:
| روش | گامها تا فاصله 0.1% از optimum | gradientهای هر مثال |
|---|---|---|
| full batch | 7 | 700,000 |
| minibatch با اندازه 32 | 100 | 3,200 |
| هر بار یک مثال | 17,580 | 17,580 |
دویستونوزده برابر حساب کمتر برای رسیدن به همان جا. و حالت افراطی — هر بار یک مثال، همان تقریب stochastic اولیه رابینز و مونرو3 — برنده نیست: پنج برابر بدتر از batchهای 32 است، چون روی سختافزاری که ماتریس ضرب میکند، 32 مثال تقریباً هیچ هزینه بیشتری از یک مثال ندارد، در حالی که نویز با ریشه دوم اندازه batch پایین میآید. همین بدهبستان دلیل آن است که هر training scriptی که ever بخوانید یک batch_size در خودش دارد.
Momentum اصلاح ارزان دیگر است، و دقیقاً به گودال نشانه میرود. در یک دره بدشرط، گامها در عرض جهت باریک زیگزاگ میزنند و در امتداد جهت بلند میخزند. Momentum میانگین جاریای از gradientهای گذشته نگه میدارد، تا مؤلفههای نوسانی همدیگر را خنثی کنند و مؤلفه پایدار جمع شود:4
دو خط اضافه. روی تسمه خام و مرکززدایینشده — condition number برابر 33,452، بدترین موردی که داریم — با بهترین نرخی که descent ساده میتواند بردارد:
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 در این فصل با دست مشتق شد و بنابراین میتوانست غلط باشد. راهحل همان جدول شیب ابتدای فصل است: مشتق را عددی اندازه بگیرید و مقایسه کنید. از تفاضل مرکزی استفاده کنید، ، که جمله خطای پیشرو را حذف میکند و برای همان بسیار دقیقتر است.
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)))شکل نسبی مقایسه مهم است: اختلاف مطلق روی gradientی با اندازه فاجعه است و روی یکی با اندازه بیاهمیت.
relative error: 1.8929136036763527e-11
with 2 dropped: 0.33333333331650744خط اول همان gradient دستی بالاست. خط دوم همان تابع است با جاافتادن ضریب 2 در یک مؤلفه — یک typo تککاراکتری — و check فوراً آن را میگیرد. هر چیزی پایینتر از حدود توافق است؛ هر چیزی بالاتر از bug است. این تابع را نگه دارید: فصل 5 از آن برای debug کردن یک موتور automatic differentiation استفاده میکند، و تنها دلیل پیداشدن یک gradient غلط همین است.
بعدش کجا میرویم
لینک به بخش: بعدش کجا میرویمهمه چیز در این فصل روی یک فرض سوار بود که هرگز بیان نشد: اینکه بتوانید را بنویسید.
برای یک خط با دو پارامتر، این یک خط جبر بود. تقریباً بلافاصله از این حالت خارج میشود. از یک سیستم جبر نمادین بخواهید مشتق loss یک شبکه را نسبت به یک weight واحد در layer اول، برای یک مثال واحد، بدهد و تعداد عملیات حسابی در جواب را بشمارید:
| شبکه | عملیات در یک مشتق جزئی |
|---|---|
| چهار واحد پنهان، یک layer | 40 |
| چهار واحد پنهان، دو layer | 301 |
| چهار واحد پنهان، سه layer | 1,717 |
ردیف سوم شبکهای با 57 پارامتر است — شبکهای آنقدر کوچک که در فصل 6 فقط یک پاورقی میشد — و نوشتن gradient آن با دست یعنی حدود 97,869 عملیات برای یک مثال آموزشی. هیچ نمادی این را نجات نمیدهد. چیزی که نجاتش میدهد مشاهده این است که قاعده زنجیرهای وقتی روی یک ترکیب اعمال میشود ساختار عظیمی دارد، همان کمیتهای میانی بارها و بارها ظاهر میشوند، و محاسبه آنها با ترتیب درست همه مشتقها را تقریباً به قیمت یک forward pass میدهد. این فصل 5 است.
اما اول یک مشکل کوچکتر هست، و بلافاصله منتظرمان است.
حالا ماشینی داریم که روی هر loss مشتقپذیری به پایین میغلتد. آن را به سؤال اصلی تسمه نشانه بروید — قبول یا رد، هدفی که 1 یا 0 است — روی خروجی یک sigmoid بگذارید تا احتمال پیشبینی کند، و خطای مربعی را minimize کنید. اجرا میشود. اما درست وقتی بیش از همه اشتباه میکند بهسختی حرکت میکند، و gradient میگوید چرا:
| خروجی | پیشبینی | حقیقت | gradient با خطای مربعی | gradient با cross-entropy |
|---|---|---|---|---|
| 0.5000 | 1 | |||
| 0.1192 | 1 | |||
| 0.0025 | 1 | |||
| 1 |
مدلی که با اطمینان، فاجعهبار غلط است — وقتی پاسخ 1 است مقدار 0.0000454 را پیشبینی میکند — gradient خطای مربعی برابر تولید میکند. هیچ ایدهای ندارد که گرفتار دردسر است. ستون دیگر، از 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 اندازهگیریشده بالا، 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 میآورند، همانطور که این دوره میآورد و به همان دلیل.
ارجاعات
لینک به بخش: ارجاعات-
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های ماتریس مشتق دوم، و بنابراین تعداد گامها را تغییر میدهد، نه صرفاً راحتی عددی را. ↩
-
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 بودن مستلزم آن است که هر یک از هزاران جهت همزمان رو به بالا خم شوند. ↩
-
Robbins, H. and Monro, S. A Stochastic Approximation Method. Annals of Mathematical Statistics 22(3), pp. 400–407 (1951). مقالهای که تثبیت کرد تخمین نویزی یک gradient کافی است، به شرط آنکه اندازه گام به شکل درست کوچک شود. ↩
-
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 به این حوزه برسد. ↩