سلام :) امیدوار مطالب قسمت اول براتون مفید بوده باشه، در قسمت دوم میخوام به این موضوع بپردازم که اساسا چطور شبکه های عصبی رو آموزش میدیم. با من همراه باشید.
چطور شبکه های عصبی رو آموزش بدیم؟
میدونیم شبکه های عصبی عمدتا با الگوریتم backpropagation انجام میشه. آموزش شبکه عصبی دو مرحله داره : 1- Forward propagation: ورودی اعمال میشه و خروجی بدست میاد که در مرحله بعد استفاده میشه 2-backward propagation: خطای نتایج با نتایج واقعی بدست میاد و این خطا رو در شبکه اعمال می کنیم تا گرادیان ها رو بدست بیاریم. حالا توی پایتورچ ابزاری که اجازه میده که این کار رو انجام بدیم . ابزار AutoGrad هست.
در واقع در پایتورچ به این صورت عمل می کنیم که هر تابعی که داخل این کتابخانه پیاده سازی شده و هر operator ای که می تونیم استفاده کنیم gradient اش تعریف شده است، و AutoGrad میاد این گرادیان ها رو حساب میکنه و ذخیره میکنه.
بذارید یک شبکه رو مثال بزنید، فرض کنید از کتابخانه Torchvision مدل از پیش آموزش داده شده resnet18 رو استفاده می کنیم. ورودی رندوم رو میسازم . مدل رو میسازیم.
ورودی رو به شبکه میدیم و مجموع خطا رو در تنسوری به اسم loss ذخیره می کنیم و بعد در فاز backward با استفاده از تابع backward تمام گرادیان ها به صورت backward بدست میاد.
روش های مختلفی برای به روزرسانی وزن های شبکه وجود داره، معروف ترین آنها Stochastic Gradient Descent هست، حالا تابع optim که فراخوانی میشه وزن های شبکه یک مرحله به روزرسانی میشه.
و در نهایت با تابع stop عملکرد آموزش شبکه متوقف میشه.
در این مرحله یک گام از الگوریتم نزول گرادیان اجرا شد!
برای درک بهتر AutoGrad یک مثال میزنم.
ما دو تنسور A و B داریم که آنها را تعریف کردیم. نکته ای که وجود داره این هست که از کجا متوجه میشه گرادیان رو برای پارامترهای شبکه حساب میکنه. وقتی شبکه رو تعریف می کنید مجموعه ای از وزن ها هست که هر وزن هم در یک تنسور ذخیره میشه
در مجموع برای تنسورهای AutoGrad گرادیان رو حساب میکنه که مقدار requires_grad برابر True باشد شبیه کاری که برای تنسور A و B کردیم.
یک تابع درجه 3 به اسم Q تعریف می کنیم ، حاالا می خوایم گرادیان رو به روش تحلیلی و با استفاده از AutoGrad استفاده کنیم که متوجه بشیم آیا AutoGrad درست کار میکنه یا نه.
همانطور که می دونید گرادیان Q نسبت به a میشه 9 ضرب در a به توان 2 و گرادیان Q نسبت به b میشه 2b .
بردار ورودی رو میدیم و مقایسه می کنیم آیا این دو تا هم با هم مساوی هستن یا نه! و خوب نتیجه True هست.
ابزار AutoGrad برای محاسبه گرادیان ها از مفهوم گراف محاسباتی استفاده میکنه، در واقع گراف محاسباتی گرافی جهت دار و بدون دور هست که فرآیند رسیدن از ورودی ها به خروجی ها رو نشون میده. تو این گراف جهت دار برگ های گراف تنسورهای ورودی هستند و ریشه ها تنسورهای خروجی هستند و کافی هست که ما از ریشه ها به سمت برگ ها حرکت کنیم تا متوجه بشیم چه مسیری طی شده و با به کارگیری قاعده مشتق زنجیره ای بتونیم گرادیان ها رو بدست بیاریم.
یادتون باشه در پایتورچ هر operation ای که استفاده می کنید گرادیانش تعریف شدس. پس در نظر داشته باشید اگه بخواهید operation جدید تعریف کنید باید تابع گرادیانش هم پیاده سازی کنید تا AutoGrad از اون تابع گرادیان استفاده کنه.
وقتی در گام فوروارد قرار دارید کاری که AutoGrad انجام میده دو بخش همزمان هست : 1 - در واقع operation هایی که شما تعریف کردید رو محاسبه میکنه و تنسورهای خروجی رو میسازه 2- به صورت همزمان تابع گرادیان هر operation رو هم ذخیره میکنه تا بتونه در گام backward از اونها استفاده کنه.
همانطور که گفته شد برای اینکه بتونید گرادیان خروجی رو نسبت به پارامترهای شبکه استفاده کنید باید از تابع backward استفاده کنید ، کافیه تابع backward روی ریشه گراف محاسباتی پیاده سازی کنید.
تابع backward که به ازای هر operation استفاده میشه تابع grad_fn هست ، هر operation به ازاش یک تابع grad_fn وجود داره. وقتی grad_fn رو محاسبه می کنید با استفاده از قاعده زنجیره ای گرادیان ها به دست میاد و از بالا به پایین propagate میشه. نکته ای که باید درنظر داشته باشد وقتی backward رو فراخوانی می کنید این مقادیر گرادیان ها در نودها انباشته میشه وقتی دو بار فراخوانی می کنید گرادیان ها که در دو مرحله به دست اومدند با هم جمع میشن. پس اگه می خواید تاریخچه گرادیان ها جمع نشه باید مقادیر گرادیان ها رو قبل از تابع backward صفر کنید..
به عنوان مثال یه گراف محاسباتی رو مشاهده کنید:
نکته ای که در مورد پایتورچ وجود داره، پایتورچ از نظر ساخت گراف محاسباتی یک کتابخانه داینامیک هست، گراف محاسباتی زمانی ساخته میشه که تابع backward رو فراخوانی کنید. بسته به شرایط اجرا میتونه گراف متفاوتی ساخته بشه و به همین دلیل گراف داینامیک یا همان پویا هست.
همانطور که گفته شد AutoGrad با backward رو فراخوانی می کنیم برای پارامترهایی گرادیان رو محاسبه میکنه که requires_grad برابر True باشه، اگه نمی خواهید حساب بشه باید False کنید. حالا چرا باید لازم بشه؟
در دو حالت می تونیم بگیم امکان حساب نکردن گرادیان یا خارج کردن تنسور از گراف محاسباتی به ما کمک میکنه:
1- درنظر بگیرید وقتی یک شبکه چند لایه دارید، از جایی به بعد نمی خواهید بعضی لایه ها به روزرسانی بشن و لایه های خاصی فقط به روز بشن مثلا می دونیم وزن ها در لایه های پایینی همگرا شده و حساب کردن آنها در گراف محاسباتی صرفا سربار محاسباتی هست.
2- وقتی شما از شبکه های pretrain استفاده می کنید که اینها رو می تونیم به دو بخش feature extraction و Classification تقسیم می کنیم، گاهی مدل قبلا رو مجموعه داده بزرگی آ»وزش دیده و ما در اینجا feature extraction رو مجددا آموزش نمی دیم و فقط لایه classification رو مجدد آموزش میدیم.
مثلا اینجا ما RESnet18 رو استفاده می کنیم، میایم پارامترها رو freeze می کنیم، پارامترهای شبکه با استفاده از تابع model.parameters بدست میاد.
یک لایه fully connected هم در ادامه اضافه می کنیم
دقت داشته باشید ها رو فقط به ازای لایه fully connected محاسبه می کنیم.
با توجه به حجم مطالب مورد نیاز نوشته به 4 قسمت تقسیم شده و لطفا برای مطالعه قسمت اول به این لینک و برای مطالعه قسمت سوم به این لینک مراجعه کنید و در نهایت برای مطالعه قسمت پایانی به این لینک مراجعه کنید.