اسلاید قبلی
اسلاید بعدی

فصل دهم-Optimizers

Optimizers

هنگامی که شیب را محاسبه کردیم، می توانیم از این اطلاعات برای تنظیم وزن ها و بایاس ها برای کاهش اندازه گیری ضرر استفاده کنیم. در یک مثال اسباب بازی قبلی، ما نشان دادیم که چگونه می توانیم با موفقیت خروجی تابع فعال سازی نورون (ReLU) را به این روش کاهش دهیم. به یاد بیاورید که ما کسری از گرادیان را برای هر پارامتر وزن و بایاس کم کردیم. در حالی که بسیار ابتدایی است، اما هنوز هم یک بهینه ساز رایج به نام Stochastic Gradient Descent (SGD) است. همانطور که به زودی متوجه خواهید شد، اکثر بهینه سازها فقط انواع SGDهستند.

 

Stochastic Gradient Descent (SGD)

برخی از قراردادهای نامگذاری با این بهینه ساز وجود دارد که می تواند گیج کننده باشد، بنابراین بیایید ابتدا آنها را بررسی کنیم. ممکن است نام های زیر را بشنوید:

  1. نزول گرادیان تصادفی، SGD
  2. نزول گرادیان وانیل ، نزول گرادیان ، GD یا نزول گرادیان دسته ای ، BGD
  3. نزول گرادیان مینی دسته ای ، MBGD

نام اول، Stochastic Gradient Descent، از نظر تاریخی به یک بهینه ساز اشاره دارد که در یک زمان برای یک نمونه واحد قرار می گیرد. بهینه ساز دوم، Batch Gradient Descent، یک بهینه ساز است که برای قرار دادن یک مجموعه داده کامل به طور همزمان استفاده می شود. آخرین بهینه ساز، Mini-batch Gradient Descent، برای قرار دادن برش های یک مجموعه داده استفاده می شود، که ما آن را دسته ها را در زمینه خود می نامیم. قرارداد نامگذاری در اینجا به دلایل متعددی می تواند گیج کننده باشد.

اول، در زمینه یادگیری عمیق و این کتاب، ما برش هایی از دسته های داده را می نامیم، جایی که، از نظر تاریخی، اصطلاح اشاره به برش هایی از داده ها در زمینه نزول گرادیان تصادفی مینی دسته بود. در زمینه ما، فرقی نمی کند که دسته شامل یک نمونه، تکه ای از مجموعه داده یا مجموعه داده کامل باشد – به عنوان دسته ای از داده ها. علاوه بر این، با کد فعلی، ما مجموعه داده کامل را متناسب می کنیم. با پیروی از این قرارداد نامگذاری، از Batch Gradient Descent استفاده می کنیم. در فصل آینده، برش ها یا دسته های داده  را معرفی خواهیم کرد، بنابراین باید با استفاده از  بهینه ساز گرادیان گرادیان کوچک شروع کنیم. گفته می شود، روندها و قراردادهای نامگذاری فعلی با گرادیان تصادفی که امروزه با یادگیری عمیق استفاده می شود، همه این انواع را ادغام و نرمال کرده اند، تا جایی که ما بهینه  ساز گرادیان تصادف  را به عنوان یکی از داده ها فرض می کنیم، خواه آن دسته یک نمونه باشد، هر نمونه در یک مجموعه داده،  یا زیرمجموعه ای از مجموعه داده کامل در یک زمان.

در مورد گرادیان تصادفی، نرخ یادگیری مانند 1.0 را انتخاب می کنیم. سپس learning_rate · parameter_gradients را از مقادیر پارامتر واقعی کم می کنیم. اگر نرخ یادگیری ما 1 باشد، مقدار دقیق گرادیان را از پارامترهای خود کم می کنیم. ما می خواهیم با 1 شروع کنیم تا نتایج را ببینیم، اما به زودی بیشتر به میزان یادگیری می پردازیم. بیایید کد کلاس بهینه ساز SGD را ایجاد کنیم. روش مقداردهی اولیه فراپارامترهایی را می گیرد که با نرخ یادگیری شروع می شود، در حال حاضر، آنها را در ویژگی های کلاس ذخیره می کند.  روش update_params، با توجه به یک شی لایه، ابتدایی ترین بهینه سازی را انجام می دهد، به همان روشی که در فصل قبل آن را انجام دادیم، – گرادیان های ذخیره شده در لایه ها را در نرخ یادگیری نفی شده ضرب می کند و نتیجه را به پارامترهای لایه اضافه می کند. به نظر می رسد که در فصل قبل، بهینه سازی SGD را بدون اطلاع از آن انجام داده ایم. کلاس کامل تا کنون:

class Optimizer_SGD:

    # راه اندازی بهینه ساز – تنظیمات را تنظیم کنید،

    # میزان یادگیری 1. پیش فرض برای این بهینه ساز است

    def __init__(self, learning_rate=1.0):

        self.learning_rate = learning_rate

    # Update parameters

    def update_params(self, layer):

        layer.weights += -self.learning_rate * layer.dweights

        layer.biases += -self.learning_rate * layer.dbiases

برای استفاده از این، باید یک شی بهینه ساز ایجاد کنیم:

optimizer = Optimizer_SGD()

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

optimizer.update_params(dense1)

optimizer.update_params(dense2)

به یاد بیاورید که شی لایه شامل پارامترهای آن (وزن ها و بایاس ها) و همچنین در این مرحله گرادیانی است که در طول پس انتشار محاسبه می شود. ما اینها را در ویژگی های لایه ذخیره می کنیم تا بهینه ساز بتواند از آنها استفاده کند. در کد اصلی شبکه عصبی خود، بهینه سازی را پس از پس انتشار وارد می کنیم. بیایید یک شبکه عصبی با اتصال متراکم 1×64 (1 لایه پنهان با 64 نورون) بسازیم و از همان مجموعه داده قبلی استفاده کنیم:

# مجموعه داده ایجاد کنید

X, y = spiral_data(samples=100, classes=3)

# ایجاد لایه متراکم با 2 ویژگی ورودی و 64 مقدار خروجی

dense1 = Layer_Dense(2, 64)

# ایجاد فعال سازی ReLU (برای استفاده با لایه متراکم):

activation1 = Activation_ReLU()

# ایجاد لایه متراکم دوم با 64 ویژگی ورودی (همانطور که خروجی می گیریم

# لایه قبلی در اینجا) و 3 مقدار خروجی (مقادیر خروجی)

dense2 = Layer_Dense(64, 3)

# از دست دادن و فعال سازی ترکیبی طبقه بندی کننده Softmax را ایجاد کنید

loss_activation = Activation_Softmax_Loss_CategoricalCrossentropy()

مرحله بعدی ایجاد شی بهینه ساز است:

# بهینه ساز ایجاد کنید

optimizer = Optimizer_SGD()

Then perform a forward pass of our sample data:

# یک پاس رو به جلو از داده های آموزشی خود را از طریق این لایه انجام دهید

dense1.forward(X)

# عملکرد فعال سازی را از طریق عبور رو به جلو انجام دهید

# خروجی اولین لایه متراکم را در اینجا می گیرد

activation1.forward(dense1.output)

# یک عبور رو به جلو از لایه دوم Dense انجام دهید

# خروجی های تابع فعال سازی لایه اول را به عنوان ورودی می گیرد

dense2.forward(activation1.output)

# یک عبور رو به جلو از طریق عملکرد فعال سازی/از دست دادن انجام دهید

# خروجی لایه متراکم دوم را در اینجا می گیرد و ضرر را برمی گرداند

loss = loss_activation.forward(dense2.output, y)

# بیایید مقدار از دست دادن را چاپ کنیم

print(‘loss:’, loss)

# محاسبه دقت از خروجی فعال سازی2 و اهداف

# مقادیر را در امتداد محور اول محاسبه کنید

predictions = np.argmax(loss_activation.output, axis=1)

if len(y.shape) == 2:

    y = np.argmax(y, axis=1)

accuracy = np.mean(predictions==y)

print(‘acc:’, accuracy)

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

# Backward pass

loss_activation.backward(loss_activation.output, y)

dense2.backward(loss_activation.dinputs)

activation1.backward(dense2.dinputs)

dense1.backward(activation1.dinputs)

سپس در نهایت از بهینه ساز خود برای به روز رسانی وزن ها و سوگیری ها استفاده می کنیم:

# وزن ها و سوگیری ها را به روز کنید

optimizer.update_params(dense1)

optimizer.update_params(dense2)

این همه چیزی است که ما برای آموزش مدل خود نیاز داریم! اما چرا ما فقط یک بار این بهینه سازی را انجام می دهیم، در حالی که می توانیم آن را بارها با استفاده از قابلیت های حلقه ای پایتون انجام دهیم؟ ما بارها و بارها یک پاس رو به جلو، پاس به عقب و بهینه سازی را انجام می دهیم تا زمانی که به نقطه توقف برسیم. هر عبور کامل از تمام داده های آموزشی یک دوره نامیده می شود. در اکثر وظایف یادگیری عمیق، یک شبکه عصبی برای چندین دوره آموزش داده می شود، اگرچه سناریوی ایده آل این است که یک مدل کامل با وزن ها و سوگیری های ایده آل تنها پس از یک دوره داشته باشید. برای افزودن چندین دوره آموزشی به کد خود، مدل خود را مقداردهی اولیه می کنیم و یک حلقه را در اطراف تمام کدهایی که محاسبات پاس رو به جلو، پاس به عقب و بهینه سازی را انجام می دهند، اجرا می کنیم:

# مجموعه داده ایجاد کنید

X, y = spiral_data(samples=100, classes=3)

# ایجاد لایه متراکم با 2 ویژگی ورودی و 64 مقدار خروجی

dense1 = Layer_Dense(2, 64)

# ایجاد فعال سازی ReLU (برای استفاده با لایه متراکم):

activation1 = Activation_ReLU()

# ایجاد لایه متراکم دوم با 64 ویژگی ورودی (همانطور که خروجی می گیریم

# لایه قبلی در اینجا) و 3 مقدار خروجی (مقادیر خروجی)

dense2 = Layer_Dense(64, 3)

# از دست دادن و فعال سازی ترکیبی طبقه بندی کننده Softmax را ایجاد کنید

loss_activation = Activation_Softmax_Loss_CategoricalCrossentropy()

# بهینه ساز ایجاد کنید

optimizer = Optimizer_SGD()

# قطار در حلقه

for epoch in range(10001):

    # یک پاس رو به جلو از داده های آموزشی خود را از طریق این لایه انجام دهید

    dense1.forward(X)

    # عملکرد فعال سازی را از طریق عبور رو به جلو انجام دهید

    # خروجی اولین لایه متراکم را در اینجا می گیرد

    activation1.forward(dense1.output)

    # یک عبور رو به جلو از لایه دوم Dense انجام دهید

    # خروجی های تابع فعال سازی لایه اول را به عنوان ورودی می گیرد

    dense2.forward(activation1.output)

    # یک عبور رو به جلو از طریق عملکرد فعال سازی/از دست دادن انجام دهید

    # خروجی لایه متراکم دوم را در اینجا می گیرد و ضرر را برمی گرداند

    loss = loss_activation.forward(dense2.output, y)

    # محاسبه دقت از خروجی فعال سازی2 و اهداف

    # مقادیر را در امتداد محور اول محاسبه کنید

    predictions = np.argmax(loss_activation.output, axis=1)

    if len(y.shape) == 2:

        y = np.argmax(y, axis=1)

    accuracy = np.mean(predictions==y)

    if not epoch % 100:

        print(f‘epoch: {epoch}, ‘ +

              f‘acc: {accuracy:.3f}, ‘ +

              f‘loss: {loss:.3f}’)

    # Backward pass

    loss_activation.backward(loss_activation.output, y)

    dense2.backward(loss_activation.dinputs)

    activation1.backward(dense2.dinputs)

    dense1.backward(activation1.dinputs)

    # وزن ها و biasesرا به روز کنید

    optimizer.update_params(dense1)

    optimizer.update_params(dense2)

این به ما به روز رسانی می کند که کجا هستیم (دوره ها)، دقت مدل، و از دست دادن هر 100 دوره. در ابتدا، می توانیم شاهد بهبود مداوم باشیم:

epoch: 0, acc: 0.360, loss: 1.099

epoch: 100, acc: 0.400, loss: 1.087

epoch: 200, acc: 0.417, loss: 1.077

epoch: 1000, acc: 0.407, loss: 1.058

epoch: 2000, acc: 0.403, loss: 1.038

epoch: 2100, acc: 0.447, loss: 1.022

epoch: 2200, acc: 0.467, loss: 1.023

epoch: 2300, acc: 0.437, loss: 1.005

epoch: 2400, acc: 0.497, loss: 0.993

epoch: 2500, acc: 0.513, loss: 0.981

epoch: 9500, acc: 0.590, loss: 0.865

epoch: 9600, acc: 0.627, loss: 0.863

epoch: 9700, acc: 0.630, loss: 0.830

epoch: 9800, acc: 0.663, loss: 0.844

epoch: 9900, acc: 0.627, loss: 0.820

epoch: 10000, acc: 0.633, loss: 0.848

علاوه بر این، ما انیمیشن هایی را برای کمک به تجسم فرآیند آموزش و انتقال تأثیر بهینه سازهای مختلف و فراپارامترهای آنها آماده کرده ایم. قسمت سمت چپ بوم انیمیشن حاوی نقاطی است که در آن رنگ نشان دهنده هر یک از 3 کلاس داده ها است، مختصات عبارتند از ویژگی ها و رنگ های پس زمینه مناطق پیش بینی مدل را نشان می دهند. در حالت ایده آل، رنگ نقاط و پس زمینه باید مطابقت داشته باشد اگر مدل به درستی طبقه بندی شود. منطقه اطراف نیز باید از “روند” داده ها پیروی کند – چیزی که ما آن را تعمیم می نامیم – توانایی مدل برای پیش بینی صحیح داده های دیده نشده. مربع های رنگارنگ سمت راست وزن ها و تعصبات را نشان می دهند – قرمز برای مثبت و آبی برای مقادیر منفی. نواحی منطبق درست در زیر نوار Dense 1 و در کنار نوار Dense 2 به روزرسانی هایی را که بهینه ساز برای لایه ها انجام می دهد، نشان می دهد. به روز رسانی ها ممکن است در مقایسه با وزن ها و تعصبات بیش از حد قوی به نظر برسند، اما این به این دلیل است که ما آنها را به صورت بصری به حداکثر مقدار نرمال کرده ایم، در غیر این صورت تقریبا نامرئی خواهند بود زیرا به روز رسانی ها در یک زمان بسیار کوچک هستند. 3 نمودار دیگر مقادیر از دست دادن، دقت و نرخ یادگیری فعلی را همراه با زمان آموزش، دوره ها در این مورد نشان می دهد.

شکل 10.01: آموزش مدل با بهینه ساز گرادیان نزولی تصادفی.

هشدار صرع، رنگ های چشمک زن سریع در انیمیشن وجود دارد:

Anim 10.01: https://nnfs.io/pup

شبکه عصبی ما عمدتا در حدود از دست دادن 1 و بعدا 0.85-0.9 و دقت در حدود 0.60 گیر می کند. این انیمیشن همچنین دارای یک اثر “تکان دادن پر زرق و برق” است، که به احتمال زیاد به این معنی است که ما نرخ یادگیری بسیار بالایی را انتخاب کرده ایم. با توجه به اینکه ضرر زیاد کاهش نیافته است، می توان فرض کرد که این نرخ یادگیری، بسیار بالا، باعث شده است که مدل در حداقل محلی گیر کند، که به زودی در مورد آن بیشتر خواهیم آموخت. تکرار دوره های بیشتر در این مرحله مفید به نظر نمی رسد، که به ما می گوید که احتمالا در بهینه سازی خود گیر کرده ایم. آیا این بدان معناست که این بیشترین چیزی است که می توانیم از بهینه ساز خود در این مجموعه داده به دست آوریم؟

به یاد بیاورید که ما وزن ها و تعصبات خود را با اعمال کسری در این مورد، 1.0، به گرادیان و کم کردن آن از وزن ها و تعصبات تنظیم می کنیم. این کسر نرخ یادگیری (LR) نامیده می شود و پارامتر قابل تنظیم اولیه برای بهینه ساز است زیرا تلفات را کاهش می دهد. برای به دست آوردن شهودی برای تنظیم، برنامه ریزی یا تنظیم اولیه نرخ یادگیری، ابتدا باید بفهمیم که چگونه نرخ یادگیری بر بهینه ساز و خروجی تابع ضرر تأثیر می گذارد.

 

میزان یادگیری

تا کنون، ما یک گرادیان از یک مدل و تابع ضرر با توجه به همه پارامترها داریم و می خواهیم کسری از این گرادیان را روی پارامترها اعمال کنیم تا مقدار ضرر را پایین بیاوریم.

در بیشتر موارد، ما گرادیان منفی را همانطور که هست اعمال نمی کنیم، زیرا جهت تندترین نزول تابع به طور مداوم در حال تغییر است و این مقادیر معمولا برای بهبود معنی دار مدل بسیار بزرگ خواهند بود. در عوض، ما می خواهیم مراحل کوچکی را انجام دهیم – محاسبه گرادیان، به روز رسانی پارامترها با کسر منفی از این گرادیان و تکرار آن در یک حلقه. گام های کوچک تضمین می کنند که ما در حال دنبال کردن جهت تندترین فرود هستیم، اما این مراحل نیز می توانند بسیار کوچک باشند و باعث رکود یادگیری شوند – به زودی این را توضیح خواهیم داد.

بیایید برای مدتی فراموش کنیم که ما در حال انجام نزول گرادیان یک تابع n بعدی (تابع از دست دادن ما) هستیم، که در آن n پارامترهای عددی (وزن ها و بایاس ها) است که مدل در آن وجود دارد، و فرض کنیم که ما فقط یک بعد برای تابع از دست دادن داریم (یک ورودی مفرد). هدف ما از تصاویر و انیمیشن های زیر تجسم برخی مفاهیم و به دست آوردن شهود است. بنابراین، ما از تنظیمات بهینه ساز خاصی استفاده نخواهیم کرد یا ارائه نخواهیم کرد، و در عوض چیزها را به صورت کلی تر در نظر خواهیم گرفت. با این حال، ما از یک بهینه ساز SGD واقعی در یک تابع واقعی برای آماده سازی همه مثال های زیر استفاده کرده ایم. در اینجا تابعی است که می خواهیم تعیین کنیم چه ورودی به آن منجر به کمترین خروجی ممکن می شود:

شکل 10.02: تابع مثال برای به حداقل رساندن خروجی.

ما می توانیم حداقل جهانی این تابع را ببینیم که کمترین  مقدار y ممکن است که این تابع می تواند خروجی دهد. این هدف است – به حداقل رساندن خروجی تابع برای یافتن حداقل جهانی. مقادیر محورها در این مورد مهم نیستند. هدف فقط نشان دادن عملکرد و مفهوم سرعت یادگیری است. همچنین، به یاد داشته باشید که این مثال تابع یک بعدی صرفا برای کمک به تجسم استفاده می شود. حل این تابع با ریاضیات ساده تر از آنچه برای حل تابع از دست دادن بسیار بزرگتر n بعدی برای شبکه های عصبی مورد نیاز است، آسان خواهد بود، جایی که n (که تعداد وزن ها و بایاس ها است) می تواند میلیون ها یا حتی میلیاردها (یا بیشتر) باشد. وقتی میلیون ها بعد یا بیشتر داریم، نزول گرادیان شناخته شده ترین راه برای جستجوی حداقل جهانی است.

ما از سمت چپ این نمودار شروع به پایین آمدن می کنیم. با یک مثال نرخ یادگیری:

شکل 10.03: در اولین حداقل محلی گیر کرده است.

انیمیشن 10.03: https://nnfs.io/and

میزان یادگیری خیلی کم بود. به روز رسانی های کوچک پارامترها باعث رکود در یادگیری مدل شد – مدل در حداقل محلی گیر کرد. حداقل محلی حداقل است که نزدیک به جایی است که ما نگاه می کنیم اما لزوما حداقل جهانی نیست، که پایین ترین نقطه مطلق برای یک تابع است. با مثال ما در اینجا، و همچنین با بهینه سازی شبکه های عصبی کامل، نمی دانیم حداقل جهانی کجاست. چگونه بفهمیم که به حداقل جهانی رسیده ایم یا حداقل به آن نزدیک شده ایم؟ تابع ضرر اندازه گیری می کند که مدل با پیش بینی های خود نسبت به مقادیر هدف واقعی چقدر فاصله دارد، بنابراین، تا زمانی که مقدار ضرر 0 یا بسیار نزدیک به 0 نباشد، و مدل یادگیری را متوقف کند، ما در حداقل محلی هستیم.  در واقع، ما تقریبا هرگز به دلایل مختلف به از دست دادن 0 نزدیک نمی شویم. یکی از دلایل این امر ممکن است فراپارامترهای ناقص شبکه عصبی باشد. دلیل دیگر این امر ممکن است داده های ناکافی باشد. اگر با یک شبکه عصبی به از دست دادن 0 رسیدید، باید آن را مشکوک بدانید، به دلایلی که بعدا در این کتاب به آن خواهیم پرداخت.

ما می توانیم سعی کنیم نرخ یادگیری را اصلاح کنیم:

شکل 10.04: در دومین حداقل محلی گیر کرده است.

انیمیشن 10.04: https://nnfs.io/xor

این بار، مدل از این حداقل محلی فرار کرد اما در مدل دیگری گیر کرد. بیایید یک مثال دیگر را پس از تغییر نرخ یادگیری دیگر ببینیم:

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

Anim 10.05: https://nnfs.io/tho

این بار مدل در حداقل محلی نزدیک به حداقل جهانی گیر کرد. این مدل توانست از حداقل های محلی “عمیق تر” فرار کند، بنابراین ممکن است غیرمنطقی باشد که چرا در اینجا گیر کرده است. به یاد داشته باشید، این مدل از جهت تندترین فرود تابع تلفات پیروی می کند، مهم نیست که فرود چقدر بزرگ یا جزئی باشد. به همین دلیل، ما حرکت و تکنیک های دیگر را برای جلوگیری از چنین شرایطی معرفی خواهیم کرد.

تکانه، در یک بهینه ساز، چیزی را به گرادیان اضافه می کند که در دنیای فیزیکی می توانیم آن را اینرسی بنامیم، به عنوان مثال، ما می توانیم یک توپ را به سمت سربالایی پرتاب کنیم و با یک تپه به اندازه کافی کوچک یا نیروی اعمال شده به اندازه کافی بزرگ، توپ می تواند به سمت دیگر تپه غلت بزند. بیایید ببینیم که این مدل در آموزش چگونه به نظر می رسد:

شکل 10.06: به حداقل جهانی رسید، نرخ یادگیری بسیار پایین.

Anim 10.06: https://nnfs.io/pog

ما در اینجا از نرخ یادگیری بسیار کمی با یک حرکت بزرگ استفاده کردیم. تغییر رنگ از سبز، از طریق نارنجی به قرمز نشان دهنده پیشرفت فرآیند نزول گرادیان، مراحل است. می بینیم که این مدل به هدف دست یافته و حداقل جهانی را پیدا کرده است، اما این گام های زیادی را برداشته است. آیا می توان این کار را بهتر انجام داد؟

شکل 10.07: به حداقل جهانی رسید، نرخ یادگیری بهتر.

Anim 10.07: https://nnfs.io/jog

و حتی بیشتر:

شکل 10.08: به حداقل جهانی رسید، به طور قابل توجهی نرخ یادگیری بهتر.

Anim 10.08: https://nnfs.io/mog

با استفاده از این مثال ها توانستیم با اصلاح نرخ یادگیری و تکانه، حداقل جهانی را به ترتیب در حدود 200، 100 و 50 مرحله پیدا کنیم. این امکان وجود دارد که با تنظیم پارامترهای بهینه ساز، زمان آموزش را به میزان قابل توجهی کوتاه کرد. با این حال، ما باید مراقب این تنظیمات هایپرپارامتر باشیم، زیرا این لزوما همیشه به مدل کمک نمی کند:

شکل 10.09: مدل ناپایدار، نرخ یادگیری خیلی بزرگ.

Anim 10.09: https://nnfs.io/log

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

در این مورد، مدل در حال “پرش” در اطراف برخی از حداقل ها بود و این ممکن است به این معنی باشد که ما باید سعی کنیم نرخ یادگیری را کاهش دهیم، شتاب را افزایش دهیم یا احتمالا کاهش نرخ یادگیری (کاهش نرخ یادگیری در طول آموزش) را اعمال کنیم، که در این فصل توضیح خواهیم داد. اگر نرخ یادگیری را خیلی بالا تنظیم کنیم:

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

Anim 10.10: https://nnfs.io/sog

در این شرایط، مدل شروع به “پریدن” می کند و در مسیری حرکت می کند که ممکن است به عنوان جهت های تصادفی مشاهده کنیم. این نمونه ای از “overshooting” با هر مرحله است – جهت تغییر صحیح است، اما مقدار گرادیان اعمال شده بیش از حد زیاد است. در یک شرایط شدید، می توانیم باعث انفجار گرادیان شویم:

شکل 10.11: مدل شکسته، نرخ یادگیری بسیار زیاد است.

Anim 10.11: https://nnfs.io/bog

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

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

شکل 10.12: مدل آموخته شده، میزان یادگیری خوب، می تواند بهتر باشد.

Anim 10.12: https://nnfs.io/cog

این بار زمان بسیار کمتری طول کشید تا مدل حداقل جهانی را پیدا کند، اما همیشه می تواند بهتر باشد:

شکل 10.13: یک مثال یادگیری کارآمد.

Anim 10.13: https://nnfs.io/rog

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

برای خلاصه ای از نرخ های یادگیری – اگر ضرر را در امتداد یک محور از مراحل ترسیم کنیم:

شکل 10.14: نمودارهای از دست دادن در تابعی از مراحل، نرخ های مختلف

ما می توانیم نمونه های مختلفی از نرخ یادگیری نسبی را ببینیم و اینکه از دست دادن به طور ایده آل به عنوان یک نمودار در طول زمان (مراحل) آموزش به نظر می رسد.

دانستن اینکه میزان یادگیری باید چقدر باشد تا بیشترین بهره را از فرآیند آموزش خود ببرید امکان پذیر نیست، اما یک قانون خوب این است که آموزش اولیه شما از نرخ یادگیری بیشتری برای برداشتن سریعتر گام های اولیه بهره مند می شود. اگر با مراحلی که خیلی کوچک هستند شروع کنید، ممکن است در یک حداقل محلی گیر کنید و به دلیل عدم به روز رسانی به اندازه کافی در پارامترها، نتوانید آن را ترک کنید. به عنوان مثال، اگر نرخ یادگیری را به جای 1.0 با بهینه ساز SGD 0.85 کنیم، چه؟

# مجموعه داده ایجاد کنید

X, y = spiral_data(samples=100, classes=3)

# ایجاد لایه متراکم با 2 ویژگی ورودی و 64 مقدار خروجی

dense1 = Layer_Dense(2, 64)

# ایجاد فعال سازی ReLU (برای استفاده با لایه متراکم):

activation1 = Activation_ReLU()

# ایجاد لایه متراکم دوم با 64 ویژگی ورودی (همانطور که خروجی می گیریم

# لایه قبلی در اینجا) و 3 مقدار خروجی (مقادیر خروجی)

dense2 = Layer_Dense(64, 3)

# از دست دادن و فعال سازی ترکیبی طبقه بندی کننده Softmax را ایجاد کنید

loss_activation = Activation_Softmax_Loss_CategoricalCrossentropy()

# بهینه ساز ایجاد کنید

optimizer = Optimizer_SGD(learning_rate=.85)

# Train in loop

for epoch in range(10001):

    # Perform a forward pass of our training data through this layer

    dense1.forward(X)

    # عملکرد فعال سازی را از طریق عبور رو به جلو انجام دهید

    # خروجی اولین لایه متراکم را در اینجا می گیرد

    activation1.forward(dense1.output)

    # یک عبور رو به جلو از لایه دوم Dense انجام دهید

    # خروجی های تابع فعال سازی لایه اول را به عنوان ورودی می گیرد

    dense2.forward(activation1.output)

    # یک عبور رو به جلو از طریق عملکرد فعال سازی/از دست دادن انجام دهید

    # خروجی لایه متراکم دوم را در اینجا می گیرد و ضرر را برمی گرداند

    loss = loss_activation.forward(dense2.output, y)

    # محاسبه دقت از خروجی فعال سازی2 و اهداف

    # مقادیر را در امتداد محور اول محاسبه کنید

    predictions = np.argmax(loss_activation.output, axis=1)

    if len(y.shape) == 2:

        y = np.argmax(y, axis=1)

    accuracy = np.mean(predictions==y)

    if not epoch % 100:

        print(f‘epoch: {epoch}, ‘ +

              f‘acc: {accuracy:.3f}, ‘ +

              f‘loss: {loss:.3f}’)

    # Backward pass

    loss_activation.backward(loss_activation.output, y)

    dense2.backward(loss_activation.dinputs)

    activation1.backward(dense2.dinputs)

    dense1.backward(activation1.dinputs)

    # وزن ها و biases را به روز کنید

    optimizer.update_params(dense1)

    optimizer.update_params(dense2)

>>> 

epoch: 0, acc: 0.360, loss: 1.099

epoch: 100, acc: 0.403, loss: 1.091

epoch: 2000, acc: 0.437, loss: 1.053

epoch: 2100, acc: 0.443, loss: 1.026

epoch: 2200, acc: 0.377, loss: 1.050

epoch: 2300, acc: 0.433, loss: 1.016

epoch: 2400, acc: 0.460, loss: 1.000

epoch: 2500, acc: 0.493, loss: 1.010

epoch: 2600, acc: 0.527, loss: 0.998

epoch: 2700, acc: 0.523, loss: 0.977

epoch: 7100, acc: 0.577, loss: 0.941

epoch: 7200, acc: 0.550, loss: 0.921

epoch: 7300, acc: 0.593, loss: 0.943

epoch: 7400, acc: 0.593, loss: 0.940

epoch: 7500, acc: 0.557, loss: 0.907

epoch: 7600, acc: 0.590, loss: 0.949

epoch: 7700, acc: 0.590, loss: 0.935

epoch: 9100, acc: 0.597, loss: 0.860

epoch: 9200, acc: 0.630, loss: 0.842

epoch: 10000, acc: 0.657, loss: 0.816

شکل 10.15: آموزش مدل با بهینه ساز SGD و کاهش نرخ یادگیری.

Epilepsy Warning (quick flashing colors).

Anim 10.15: https://nnfs.io/cup

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

یک راه حل رایج برای بزرگ نگه داشتن به روزرسانی های اولیه و بررسی نرخ های مختلف یادگیری در طول آموزش، پیاده سازی کاهش نرخ یادگیری است.

 

زوال نرخ یادگیری

ایده کاهش نرخ یادگیری این است که با یک نرخ یادگیری زیاد شروع کنیم، مثلا در مورد ما 1.0، و سپس آن را در طول آموزش کاهش دهیم. چند روش برای انجام این کار وجود دارد. یکی کاهش نرخ یادگیری در پاسخ به از دست دادن در طول دوره ها است – به عنوان مثال، اگر ضرر شروع به تراز شدن کند/فلات کند یا شروع به “پریدن” بر روی دلتاهای بزرگ کند. شما می توانید این نظارت بر رفتار را به صورت منطقی برنامه ریزی کنید یا به سادگی از دست دادن خود را در طول زمان ردیابی کنید و به صورت دستی نرخ یادگیری را در صورت مناسب کاهش دهید. گزینه دیگری که ما پیاده سازی خواهیم کرد، برنامه ریزی نرخ پوسیدگی است که به طور پیوسته نرخ یادگیری را در هر دسته یا دوره کاهش می دهد.

بیایید در هر مرحله برای پوسیدگی برنامه ریزی کنیم. این را می توان به عنوان پوسیدگی 1/t یا پوسیدگی نمایی نیز نامید. اساسا، ما می خواهیم نرخ یادگیری هر مرحله را با متقابل کسر تعداد قدم ها به روز کنیم. این کسر یک فراپارامتر جدید است که به بهینه ساز اضافه می کنیم که به آن زوال نرخ یادگیری می گویند. نحوه عملکرد این پوسیدگی این است که پله و نسبت پوسیدگی را برمی دارد و آنها را ضرب می کند. هر چه بیشتر در تمرین، مرحله بزرگتر است و نتیجه بزرگتر این ضرب است. سپس متقابل آن را می گیریم (هرچه بیشتر در آموزش باشد، مقدار کمتر است) و میزان یادگیری اولیه را در آن ضرب می کنیم. 1 اضافه  شده اطمینان حاصل می کند که الگوریتم حاصل هرگز نرخ یادگیری را افزایش نمی دهد. به عنوان مثال، برای مرحله اول، ممکن است 1 را بر نرخ یادگیری،  به عنوان مثال 0.001 تقسیم کنیم، که منجر به نرخ یادگیری فعلی 1000 می شود. قطعا این چیزی نبود که ما می خواستیم. 1 تقسیم بر 1+ کسر تضمین می کند که نتیجه، کسری از نرخ یادگیری شروع، همیشه کمتر یا مساوی 1 خواهد بود و با گذشت زمان کاهش می یابد. این نتیجه مطلوب است – با نرخ یادگیری فعلی شروع کنید و با گذشت زمان آن را کوچکتر کنید. کد تعیین نرخ واپاشی فعلی:

starting_learning_rate = 1.

learning_rate_decay = 0.1

step = 1

learning_rate = starting_learning_rate * \

                (1. / (1 + learning_rate_decay * step))

print(learning_rate)

>>> 

0.9090909090909091

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

starting_learning_rate = 1.

learning_rate_decay = 0.1

step = 20

learning_rate = starting_learning_rate * \

                (1. / (1 + learning_rate_decay * step))

print(learning_rate)

>>> 

0.3333333333333333

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

starting_learning_rate = 1.

learning_rate_decay = 0.1

for step in range(20):

    learning_rate = starting_learning_rate * \

                    (1. / (1 + learning_rate_decay * step))

    print(learning_rate)

>>> 

1.0

0.9090909090909091

0.8333333333333334

0.7692307692307692

0.7142857142857143

0.6666666666666666

0.625

0.588235294117647

0.5555555555555556

0.5263157894736842

0.5

0.47619047619047616

0.45454545454545453

0.4347826086956522

0.41666666666666663

0.4

0.3846153846153846

0.37037037037037035

0.35714285714285715

0.3448275862068965

این طرح زوال نرخ یادگیری، نرخ یادگیری را در هر مرحله با استفاده از فرمول ذکر شده کاهش می دهد. در ابتدا، سرعت یادگیری به سرعت کاهش می یابد، اما تغییر در نرخ یادگیری هر مرحله را کاهش می دهد و به مدل اجازه می دهد تا حد امکان به حداقل برسد. این مدل نزدیک به پایان آموزش به به روزرسانی های کوچکی نیاز دارد تا بتواند تا حد امکان به این نقطه نزدیک شود. اکنون می توانیم کلاس بهینه ساز SGD خود را به روز کنیم تا امکان کاهش سرعت یادگیری را فراهم کنیم:

# بهینه ساز SGD

class Optimizer_SGD:

    # راه اندازی بهینه ساز – تنظیمات را تنظیم کنید،

    # میزان یادگیری 1. پیش فرض برای این بهینه ساز است

    def __init__(self, learning_rate=1., decay=0.):

        self.learning_rate = learning_rate

        self.current_learning_rate = learning_rate

        self.decay = decay

        self.iterations = 0

    # قبل از به روز رسانی پارامتر یک بار تماس بگیرید

    def pre_update_params(self):

        if self.decay:

            self.current_learning_rate = self.learning_rate * \

                (1. / (1. + self.decay * self.iterations))

    # پارامترهای به روز رسانی

    def update_params(self, layer):

        layer.weights += -self.current_learning_rate * layer.dweights

        layer.biases += -self.current_learning_rate * layer.dbiases

    # پس از هر به روز رسانی پارامتر یک بار تماس بگیرید

    def post_update_params(self):

        self.iterations += 1

ما چند مورد را در کلاس SGD به روز کرده ایم. ابتدا در  روش __init__، مدیریت نرخ یادگیری فعلی را اضافه کردیم و اکنون نرخ یادگیری اولیه self.learning_rate است. ما همچنین ویژگی هایی را برای ردیابی نرخ پوسیدگی و تعداد تکرارهایی که بهینه ساز پشت سر گذاشته است، اضافه کردیم. در مرحله بعد، متد جدیدی به نام pre_update_params اضافه کردیم. این روش، اگر نرخ پوسیدگی غیر از 0 داشته باشیم، self.current_learning_rate خود  را با استفاده از فرمول قبلی به روز می کند.  متد update_params بدون تغییر باقی می ماند، اما ما یک  متد post_update_params جدید  داریم که به  ردیابی خود تکرار  ما اضافه می کند. با کلاس بهینه ساز SGD به روز شده خود، چاپ نرخ یادگیری فعلی را اضافه کرده ایم و تماس های روش بهینه ساز قبل و بعد را اضافه کرده ایم. بیایید از نرخ واپاشی 1e-2 (0.01) استفاده کنیم و مدل خود را دوباره آموزش دهیم:

# مجموعه داده ایجاد کنید

X, y = spiral_data(samples=100, classes=3)

# ایجاد لایه متراکم با 2 ویژگی ورودی و 64 مقدار خروجی

dense1 = Layer_Dense(2, 64)

# ایجاد فعال سازی ReLU (برای استفاده با لایه متراکم):

activation1 = Activation_ReLU()

# ایجاد لایه متراکم دوم با 64 ویژگی ورودی (همانطور که خروجی می گیریم

# لایه قبلی در اینجا) و 3 مقدار خروجی (مقادیر خروجی)

dense2 = Layer_Dense(64, 3)

# از دست دادن و فعال سازی ترکیبی طبقه بندی کننده Softmax را ایجاد کنید

loss_activation = Activation_Softmax_Loss_CategoricalCrossentropy()

# بهینه ساز ایجاد کنید

optimizer = Optimizer_SGD(decay=1e-2)

# Train in loop

for epoch in range(10001):

    # یک پاس رو به جلو از داده های آموزشی خود را از طریق این لایه انجام دهید

    dense1.forward(X)

    # عملکرد فعال سازی را از طریق عبور رو به جلو انجام دهید

    # خروجی اولین لایه متراکم را در اینجا می گیرد

    activation1.forward(dense1.output)

    # یک عبور رو به جلو از لایه دوم Dense انجام دهید

    # خروجی های تابع فعال سازی لایه اول را به عنوان ورودی می گیرد

    dense2.forward(activation1.output)

    # یک عبور رو به جلو از طریق عملکرد فعال سازی/از دست دادن انجام دهید

    # خروجی لایه متراکم دوم را در اینجا می گیرد و ضرر را برمی گرداند

    loss = loss_activation.forward(dense2.output, y)

    # محاسبه دقت از خروجی فعال سازی2 و اهداف

    # مقادیر را در امتداد محور اول محاسبه کنید

    predictions = np.argmax(loss_activation.output, axis=1)

    if len(y.shape) == 2:

        y = np.argmax(y, axis=1)

    accuracy = np.mean(predictions==y)

    if not epoch % 100:

        print(f‘epoch: {epoch}, ‘ +

              f‘acc: {accuracy:.3f}, ‘ +

              f‘loss: {loss:.3f}, ‘ +

              f‘lr: {optimizer.current_learning_rate}’)

    # Backward pass

    loss_activation.backward(loss_activation.output, y)

    dense2.backward(loss_activation.dinputs)

    activation1.backward(dense2.dinputs)

    dense1.backward(activation1.dinputs)

    # وزن ها و biasesرا به روز کنید

    optimizer.pre_update_params()

    optimizer.update_params(dense1)

    optimizer.update_params(dense2)

    optimizer.post_update_params()

>>> 

epoch: 0, acc: 0.360, loss: 1.099, lr: 1.0

epoch: 100, acc: 0.403, loss: 1.095, lr: 0.5025125628140703

epoch: 200, acc: 0.397, loss: 1.084, lr: 0.33444816053511706

epoch: 300, acc: 0.400, loss: 1.080, lr: 0.2506265664160401

epoch: 400, acc: 0.407, loss: 1.078, lr: 0.2004008016032064

epoch: 500, acc: 0.420, loss: 1.078, lr: 0.1669449081803005

epoch: 600, acc: 0.420, loss: 1.077, lr: 0.14306151645207438

epoch: 700, acc: 0.417, loss: 1.077, lr: 0.1251564455569462

epoch: 800, acc: 0.413, loss: 1.077, lr: 0.11123470522803114

epoch: 900, acc: 0.410, loss: 1.077, lr: 0.10010010010010009

epoch: 1000, acc: 0.417, loss: 1.077, lr: 0.09099181073703366

epoch: 2000, acc: 0.420, loss: 1.076, lr: 0.047641734159123386

epoch: 3000, acc: 0.413, loss: 1.075, lr: 0.03226847370119393

epoch: 4000, acc: 0.407, loss: 1.075, lr: 0.02439619419370578

epoch: 5000, acc: 0.403, loss: 1.074, lr: 0.019611688566385566

epoch: 7000, acc: 0.400, loss: 1.073, lr: 0.014086491055078181

epoch: 10000, acc: 0.397, loss: 1.072, lr: 0.009901970492127933

شکل 10.16: آموزش مدل با بهینه ساز SGD و کاهش سرعت یادگیری بسیار بالا تنظیم شده است.

Epilepsy Warning (quick flashing colors)

Anim 10.16: https://nnfs.io/zuk

این مدل قطعا گیر کرده است و دلیل آن تقریبا به طور قطع این است که سرعت یادگیری خیلی سریع کاهش یافت و خیلی کوچک شد و مدل را در حداقل محلی به دام انداخت. به احتمال زیاد به همین دلیل است که به جای تکان دادن، دقت و از دست دادن ما اصلا تغییر نمی کند.

در عوض، می توانیم سعی کنیم با تبدیل پوسیدگی خود به عدد کوچکتر، کمی کندتر پوسیده شویم. به عنوان مثال، بیایید با 1e-3 (0.001) برویم:

# مجموعه داده ایجاد کنید

X, y = spiral_data(samples=100, classes=3)

# ایجاد لایه متراکم با 2 ویژگی ورودی و 64 مقدار خروجی

dense1 = Layer_Dense(2, 64)

# ایجاد فعال سازی ReLU (برای استفاده با لایه متراکم):

activation1 = Activation_ReLU()

# ایجاد لایه متراکم دوم با 64 ویژگی ورودی (همانطور که خروجی می گیریم

# لایه قبلی در اینجا) و 3 مقدار خروجی (مقادیر خروجی)

dense2 = Layer_Dense(64, 3)

# از دست دادن و فعال سازی ترکیبی طبقه بندی کننده Softmax را ایجاد کنید

loss_activation = Activation_Softmax_Loss_CategoricalCrossentropy()

# بهینه ساز ایجاد کنید

optimizer = Optimizer_SGD(decay=1e-3)

# Train in loop

for epoch in range(10001):

    # یک پاس رو به جلو از داده های آموزشی خود را از طریق این لایه انجام دهید

    dense1.forward(X)

    # عملکرد فعال سازی را از طریق عبور رو به جلو انجام دهید

    # خروجی اولین لایه متراکم را در اینجا می گیرد

    activation1.forward(dense1.output)

    # یک عبور رو به جلو از لایه دوم Dense انجام دهید

    # خروجی های تابع فعال سازی لایه اول را به عنوان ورودی می گیرد

    dense2.forward(activation1.output)

    # یک عبور رو به جلو از طریق عملکرد فعال سازی/از دست دادن انجام دهید

    # خروجی لایه متراکم دوم را در اینجا می گیرد و ضرر را برمی گرداند

    loss = loss_activation.forward(dense2.output, y)

    # محاسبه دقت از خروجی فعال سازی2 و اهداف

    # مقادیر را در امتداد محور اول محاسبه کنید

    predictions = np.argmax(loss_activation.output, axis=1)

    if len(y.shape) == 2:

        y = np.argmax(y, axis=1)

    accuracy = np.mean(predictions==y)

    if not epoch % 100:

        print(f‘epoch: {epoch}, ‘ +

              f‘acc: {accuracy:.3f}, ‘ +

              f‘loss: {loss:.3f}, ‘ +

              f‘lr: {optimizer.current_learning_rate}’)

    # Backward pass

    loss_activation.backward(loss_activation.output, y)

    dense2.backward(loss_activation.dinputs)

    activation1.backward(dense2.dinputs)

    dense1.backward(activation1.dinputs)

    # Update weights and biases

    optimizer.pre_update_params()

    optimizer.update_params(dense1)

    optimizer.update_params(dense2)

    optimizer.post_update_params()

>>> 

epoch: 0, acc: 0.360, loss: 1.099, lr: 1.0

epoch: 100, acc: 0.400, loss: 1.088, lr: 0.9099181073703367

epoch: 200, acc: 0.423, loss: 1.078, lr: 0.8340283569641367

epoch: 1700, acc: 0.450, loss: 1.025, lr: 0.3705075954057058

epoch: 1800, acc: 0.470, loss: 1.017, lr: 0.35727045373347627

epoch: 1900, acc: 0.460, loss: 1.008, lr: 0.3449465332873405

epoch: 2000, acc: 0.463, loss: 1.000, lr: 0.33344448149383127

epoch: 2100, acc: 0.490, loss: 1.005, lr: 0.32268473701193934

epoch: 3200, acc: 0.493, loss: 0.983, lr: 0.23815194093831865

epoch: 5000, acc: 0.577, loss: 0.900, lr: 0.16669444907484582

epoch: 6000, acc: 0.633, loss: 0.860, lr: 0.1428775539362766

epoch: 8000, acc: 0.647, loss: 0.799, lr: 0.11112345816201799

epoch: 9800, acc: 0.663, loss: 0.773, lr: 0.09260116677470137

epoch: 9900, acc: 0.663, loss: 0.772, lr: 0.09175153683824203

epoch: 10000, acc: 0.667, loss: 0.771, lr: 0.09091735612328393

شکل 10.17: آموزش مدل با بهینه ساز SGD و کاهش نرخ یادگیری مناسب تر.

Epilepsy Warning (quick flashing colors)

Anim 10.17: https://nnfs.io/muk

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

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

 

نزول گرادیان تصادفی با تکانه

تکانه یک میانگین نورد از گرادیان ها را در تعدادی از به روز رسانی ها ایجاد می کند و از این میانگین با گرادیان منحصر به فرد در هر مرحله استفاده می کند. راه دیگر برای درک این موضوع این است که یک توپ را تصور کنید که از یک تپه پایین می رود – حتی اگر یک سوراخ یا تپه کوچک پیدا کند، تکانه به آن اجازه می دهد مستقیما از طریق آن به سمت حداقل پایین تر – پایین این تپه حرکت کند. این می تواند در مواردی که در حداقل محلی (یک سوراخ) گیر کرده اید و به جلو و عقب می پرید، کمک کند. با حرکت، یک مدل به احتمال زیاد از حداقل های محلی عبور می کند و ضرر را بیشتر کاهش می دهد. به عبارت ساده، حرکت ممکن است همچنان به سمت جهت نزولی گرادیان جهانی اشاره کند.

این وضعیت را از ابتدای این فصل به یاد بیاورید:

با به روزرسانی های منظم، بهینه ساز SGD ممکن است تعیین کند که بهترین مرحله بعدی، مرحله ای است که مدل را در حداقل محلی نگه می دارد. به یاد داشته باشید که گرادیان به سمت تندترین صعود از دست دادن فعلی برای آن مرحله اشاره می کند – گرفتن منفی بردار گرادیان آن را به سمت تندترین فرود فعلی می چرخاند، که ممکن است لزوما به دنبال فرود به سمت حداقل جهانی نباشد – شیب دار ترین فرود فعلی ممکن است به سمت حداقل محلی اشاره کند. بنابراین این مرحله ممکن است ضرر آن به روز رسانی را کاهش دهد اما ممکن است ما را از حداقل محلی خارج نکند. ممکن است با یک گرادیان به پایان برسیم

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

مثال دیگری را که در این فصل نشان داده شده است به یاد بیاورید:

ما از تکانه با تنظیم یک پارامتر بین 0 و 1 استفاده می کنیم، نشان دهنده کسری از به روز رسانی پارامتر قبلی برای حفظ و کم کردن (اضافه کردن منفی) گرادیان واقعی ما، ضرب در نرخ یادگیری (مانند قبل)، از آن. به روز رسانی شامل بخشی از گرادیان مراحل قبلی به عنوان تکانه ما (جهت تغییرات قبلی) و تنها بخشی از گرادیان فعلی است. این بخش ها با هم تغییر واقعی در پارامترهای ما را تشکیل می دهند و هرچه نقش آن حرکت در به روز رسانی بزرگتر باشد، به روز رسانی می تواند جهت را کندتر تغییر دهد. هنگامی که کسر تکانه را خیلی بالا تنظیم می کنیم، مدل ممکن است اصلا یادگیری را متوقف کند زیرا جهت به روز رسانی ها نمی تواند از نزول گرادیان کلی پیروی کند. کد این کار به شرح زیر است:

weight_updates = self.momentum * layer.weight_momentums – \

                 self.current_learning_rate * layer.dweights

فراپارامتر، self.momentum، در ابتدا انتخاب می شود و layer.weight_momentums  به عنوان همه صفر شروع می شود اما در طول آموزش به صورت زیر تغییر می کند:

layer.weight_momentums = weight_updates

این بدان معنی است که حرکت همیشه به روز رسانی قبلی پارامترها است. ما همان عملیات بالا را با تعصبات انجام خواهیم داد. سپس می توانیم روش update_params کلاس بهینه ساز SGD خود  را با محاسبه تکانه به روز کنیم، با پارامترها اعمال کنیم و آنها را برای مراحل بعدی به عنوان زنجیره عملیات جایگزین برای کد فعلی حفظ کنیم. تفاوت در این است که ما فقط به روز رسانی ها را محاسبه می کنیم و این به روز رسانی ها را با کد مشترک اضافه می کنیم:

    # Update parameters

    def update_params(self, layer):

        # اگر از حرکت استفاده کنیم

        if self.momentum:

            # اگر لایه حاوی آرایه های حرکتی نیست، آنها را ایجاد کنید

            # پر از صفر

            if not hasattr(layer, ‘weight_momentums’):

                layer.weight_momentums = np.zeros_like(layer.weights)

                # اگر آرایه حرکتی برای وزن ها وجود نداشته باشد

                # آرایه هنوز برای سوگیری وجود ندارد.

                layer.bias_momentums = np.zeros_like(layer.biases)

            # بروزرسانی های وزن را با حرکت بسازید – قبلی را بگیرید

            # به روز رسانی در فاکتور حفظ ضرب می شود و با به روز رسانی می شود

            # گرادیان های فعلی

            weight_updates = \

                self.momentum * layer.weight_momentums – \

                self.current_learning_rate * layer.dweights

            layer.weight_momentums = weight_updates

            # بروزرسانی های سوگیری بسازید

            bias_updates = \

                self.momentum * layer.bias_momentums – \

                self.current_learning_rate * layer.dbiases

            layer.bias_momentums = bias_updates

        # به روز رسانی Vanilla SGD (مانند قبل از به روز رسانی حرکت)

        else:

            weight_updates = -self.current_learning_rate * \

                             layer.dweights

            bias_updates = -self.current_learning_rate * \

                           layer.dbiases

        # وزن ها و سوگیری ها را با استفاده از هر کدام به روز کنید

        # به روز رسانی وانیل یا مومنتوم

        layer.weights += weight_updates

        layer.biases += bias_updates

ساخت کلاس بهینه ساز SGD کامل:

# بهینه ساز SGD

class Optimizer_SGD:

    # راه اندازی بهینه ساز – تنظیمات را تنظیم کنید،

    # میزان یادگیری 1. پیش فرض برای این بهینه ساز است

    def __init__(self, learning_rate=1., decay=0., momentum=0.):

        self.learning_rate = learning_rate

        self.current_learning_rate = learning_rate

        self.decay = decay

        self.iterations = 0

        self.momentum = momentum

    # قبل از به روز رسانی پارامتر یک بار تماس بگیرید

    def pre_update_params(self):

        if self.decay:

            self.current_learning_rate = self.learning_rate * \

                (1. / (1. + self.decay * self.iterations))

    # پارامترهای به روز رسانی

    def update_params(self, layer):

        # اگر از حرکت استفاده کنیم

        if self.momentum:

            # اگر لایه حاوی آرایه های حرکتی نیست، آنها را ایجاد کنید

            # پر از صفر

            if not hasattr(layer, ‘weight_momentums’):

                layer.weight_momentums = np.zeros_like(layer.weights)

                # اگر آرایه حرکتی برای وزن ها وجود نداشته باشد

                # آرایه هنوز برای سوگیری وجود ندارد.

                layer.bias_momentums = np.zeros_like(layer.biases)

            # بروزرسانی های وزن را با حرکت بسازید – قبلی را بگیرید

            # به روز رسانی در فاکتور حفظ ضرب می شود و با به روز رسانی می شود

            # گرادیان های فعلی

            weight_updates = \

                self.momentum * layer.weight_momentums – \

                self.current_learning_rate * layer.dweights

            layer.weight_momentums = weight_updates

            # بروزرسانی های سوگیری بسازید

            bias_updates = \

                self.momentum * layer.bias_momentums – \

                self.current_learning_rate * layer.dbiases

            layer.bias_momentums = bias_updates

        # به روز رسانی Vanilla SGD (مانند قبل از به روز رسانی حرکت)

        else:

            weight_updates = -self.current_learning_rate * \

                             layer.dweights

            bias_updates = -self.current_learning_rate * \

                           layer.dbiases

        # وزن ها و سوگیری ها را با استفاده از هر کدام به روز کنید

        # به روز رسانی وانیل یا مومنتوم

        layer.weights += weight_updates

        layer.biases += bias_updates

    # پس از هر به روز رسانی پارامتر یک بار تماس بگیرید

    def post_update_params(self):

        self.iterations += 1

بیایید مثالی را نشان دهیم که نشان می دهد چگونه افزودن تکانه فرآیند یادگیری را تغییر می دهد. حفظ همان نرخ یادگیری  شروع (1) و زوال (1e-3) از تلاش آموزشی قبلی و با استفاده از تکانه 0.5:

# مجموعه داده ایجاد کنید

X, y = spiral_data(samples=100, classes=3)

# ایجاد لایه متراکم با 2 ویژگی ورودی و 64 مقدار خروجی

dense1 = Layer_Dense(2, 64)

# ایجاد فعال سازی ReLU (برای استفاده با لایه متراکم):

activation1 = Activation_ReLU()

# ایجاد لایه متراکم دوم با 64 ویژگی ورودی (همانطور که خروجی می گیریم

# لایه قبلی در اینجا) و 3 مقدار خروجی (مقادیر خروجی)

dense2 = Layer_Dense(64, 3)

# از دست دادن و فعال سازی ترکیبی طبقه بندی کننده Softmax را ایجاد کنید

loss_activation = Activation_Softmax_Loss_CategoricalCrossentropy()

# بهینه ساز ایجاد کنید

optimizer = Optimizer_SGD(decay=1e-3, momentum=0.5)

# Train in loop

for epoch in range(10001):

    # Perform a forward pass of our training data through this layer

    dense1.forward(X)

    # عملکرد فعال سازی را از طریق عبور رو به جلو انجام دهید

    # خروجی اولین لایه متراکم را در اینجا می گیرد

    activation1.forward(dense1.output)

    # یک عبور رو به جلو از لایه دوم Dense انجام دهید

    # خروجی های تابع فعال سازی لایه اول را به عنوان ورودی می گیرد

    dense2.forward(activation1.output)

    # یک عبور رو به جلو از طریق عملکرد فعال سازی/از دست دادن انجام دهید

    # خروجی لایه متراکم دوم را در اینجا می گیرد و ضرر را برمی گرداند

    loss = loss_activation.forward(dense2.output, y)

    # محاسبه دقت از خروجی فعال سازی2 و اهداف

    # مقادیر را در امتداد محور اول محاسبه کنید

    predictions = np.argmax(loss_activation.output, axis=1)

    if len(y.shape) == 2:

        y = np.argmax(y, axis=1)

    accuracy = np.mean(predictions==y)

    if not epoch % 100:

        print(f‘epoch: {epoch}, ‘ +

              f‘acc: {accuracy:.3f}, ‘ +

              f‘loss: {loss:.3f}, ‘ +

              f‘lr: {optimizer.current_learning_rate}’)

    # Backward pass

    loss_activation.backward(loss_activation.output, y)

    dense2.backward(loss_activation.dinputs)

    activation1.backward(dense2.dinputs)

    dense1.backward(activation1.dinputs)

    # وزن ها و biases را به روز کنید

    optimizer.pre_update_params()

    optimizer.update_params(dense1)

    optimizer.update_params(dense2)

    optimizer.post_update_params()

>>> 

epoch: 0, acc: 0.360, loss: 1.099, lr: 1.0

epoch: 100, acc: 0.427, loss: 1.078, lr: 0.9099181073703367

epoch: 200, acc: 0.423, loss: 1.075, lr: 0.8340283569641367

epoch: 1800, acc: 0.483, loss: 0.978, lr: 0.35727045373347627

epoch: 1900, acc: 0.547, loss: 0.984, lr: 0.3449465332873405

epoch: 3100, acc: 0.593, loss: 0.883, lr: 0.2439619419370578

epoch: 3200, acc: 0.570, loss: 0.878, lr: 0.23815194093831865

epoch: 3300, acc: 0.563, loss: 0.863, lr: 0.23261223540358225

epoch: 3400, acc: 0.607, loss: 0.860, lr: 0.22732439190725165

epoch: 4600, acc: 0.670, loss: 0.761, lr: 0.1786033220217896

epoch: 4700, acc: 0.690, loss: 0.749, lr: 0.1754693805930865

epoch: 6000, acc: 0.743, loss: 0.661, lr: 0.1428775539362766

epoch: 8000, acc: 0.763, loss: 0.586, lr: 0.11112345816201799

epoch: 10000, acc: 0.800, loss: 0.539, lr: 0.09091735612328393

شکل 10.18: آموزش مدل با بهینه ساز SGD، زوال سرعت یادگیری و حرکت.

Epilepsy Warning (quick flashing colors)

Anim 10.18: https://nnfs.io/ram

این مدل به کمترین ضرر و بالاترین دقت که تاکنون دیده ایم دست یافته است، اما آیا می توانیم حتی بهتر عمل کنیم؟ مطمئنا ما می توانیم! بیایید سعی کنیم تکانه را روی 0.9 تنظیم کنیم:

# مجموعه داده ایجاد کنید

X, y = spiral_data(samples=100, classes=3)

# ایجاد لایه متراکم با 2 ویژگی ورودی و 64 مقدار خروجی

dense1 = Layer_Dense(2, 64)

# ایجاد فعال سازی ReLU (برای استفاده با لایه متراکم):

activation1 = Activation_ReLU()

# ایجاد لایه متراکم دوم با 64 ویژگی ورودی (همانطور که خروجی می گیریم

# لایه قبلی در اینجا) و 3 مقدار خروجی (مقادیر خروجی)

dense2 = Layer_Dense(64, 3)

# از دست دادن و فعال سازی ترکیبی طبقه بندی کننده Softmax را ایجاد کنید

loss_activation = Activation_Softmax_Loss_CategoricalCrossentropy()

# بهینه ساز ایجاد کنید

optimizer = Optimizer_SGD(decay=1e-3, momentum=0.9)

# Train in loop

for epoch in range(10001):

    # Perform a forward pass of our training data through this layer

    dense1.forward(X)

    # عملکرد فعال سازی را از طریق عبور رو به جلو انجام دهید

    # خروجی اولین لایه متراکم را در اینجا می گیرد

    activation1.forward(dense1.output)

    # یک عبور رو به جلو از لایه دوم Dense انجام دهید

    # خروجی های تابع فعال سازی لایه اول را به عنوان ورودی می گیرد

    dense2.forward(activation1.output)

    # یک عبور رو به جلو از طریق عملکرد فعال سازی/از دست دادن انجام دهید

    # خروجی لایه متراکم دوم را در اینجا می گیرد و ضرر را برمی گرداند

    loss = loss_activation.forward(dense2.output, y)

    # محاسبه دقت از خروجی فعال سازی2 و اهداف

    # مقادیر را در امتداد محور اول محاسبه کنید

    predictions = np.argmax(loss_activation.output, axis=1)

    accuracy = np.mean(predictions==y)

    if not epoch % 100:

        print(f‘epoch: {epoch}, ‘ +

              f‘acc: {accuracy:.3f}, ‘ +

              f‘loss: {loss:.3f}, ‘ +

              f‘lr: {optimizer.current_learning_rate}’)

    # Backward pass

    loss_activation.backward(loss_activation.output, y)

    dense2.backward(loss_activation.dinputs)

    activation1.backward(dense2.dinputs)

    dense1.backward(activation1.dinputs)

    # وزن ها و biases را به روز کنید

    optimizer.pre_update_params()

    optimizer.update_params(dense1)

    optimizer.update_params(dense2)

    optimizer.post_update_params()

>>> 

epoch: 0, acc: 0.360, loss: 1.099, lr: 1.0

epoch: 100, acc: 0.443, loss: 1.053, lr: 0.9099181073703367

epoch: 200, acc: 0.497, loss: 0.999, lr: 0.8340283569641367

epoch: 300, acc: 0.603, loss: 0.810, lr: 0.7698229407236336

epoch: 400, acc: 0.700, loss: 0.700, lr: 0.7147962830593281

epoch: 500, acc: 0.750, loss: 0.595, lr: 0.66711140760507

epoch: 600, acc: 0.810, loss: 0.496, lr: 0.6253908692933083

epoch: 700, acc: 0.810, loss: 0.466, lr: 0.5885815185403178

epoch: 800, acc: 0.847, loss: 0.384, lr: 0.5558643690939411

epoch: 900, acc: 0.850, loss: 0.364, lr: 0.526592943654555

epoch: 1000, acc: 0.877, loss: 0.344, lr: 0.5002501250625312

epoch: 2200, acc: 0.900, loss: 0.242, lr: 0.31259768677711786

epoch: 2900, acc: 0.910, loss: 0.216, lr: 0.25647601949217746

epoch: 3800, acc: 0.920, loss: 0.202, lr: 0.20837674515524068

epoch: 7100, acc: 0.930, loss: 0.181, lr: 0.12347203358439313

epoch: 10000, acc: 0.933, loss: 0.173, lr: 0.09091735612328393

شکل 10.19: آموزش مدل با بهینه ساز SGD، زوال نرخ یادگیری و حرکت (تنظیم شده).

Epilepsy Warning (quick flashing colors)

Anim 10.19: https://nnfs.io/map

این یک مثال به اندازه کافی مناسب است که نشان می دهد چگونه حرکت می تواند مفید باشد. این مدل در 1000 دوره اول به دقت تقریبا 88 درصد دست یافت و بیشتر بهبود یافت و با دقت 93.3 درصد و از دست دادن 0.173 به پایان رسید. این نتایج پیشرفت بزرگی است. بهینه ساز SGD با تکانه معمولا یکی از 2 انتخاب اصلی برای یک بهینه ساز در عمل در کنار بهینه ساز آدام است که به زودی در مورد آن صحبت خواهیم کرد. اول، ما 2 بهینه ساز دیگر داریم که باید در مورد آنها صحبت کنیم. اصلاح بعدی در Stochastic Gradient Descent AdaGrad است.

 

AdaGrad

AdaGrad، مخفف گرادیان تطبیقی، به جای یک نرخ مشترک جهانی، نرخ یادگیری هر پارامتر را ایجاد می کند. ایده در اینجا عادی سازی به روزرسانی های انجام شده در ویژگی ها است. در طول فرآیند تمرین، برخی از وزنه ها می توانند به طور قابل توجهی افزایش یابند، در حالی که برخی دیگر تمایل دارند تغییر چندانی نداشته باشند. معمولا بهتر است وزنه ها در مقایسه با سایر وزنه ها خیلی بالا نروند و ما در این مورد با تکنیک های منظم سازی صحبت خواهیم کرد. AdaGrad راهی برای عادی سازی به روز رسانی پارامترها با نگه داشتن تاریخچه به روزرسانی های قبلی ارائه می دهد – هرچه مجموع به روز رسانی ها بیشتر باشد، در هر جهت (مثبت یا منفی)، به روزرسانی های کوچکتر در آموزش بیشتر انجام می شود. این اجازه می دهد تا پارامترهای کمتر به روز شده برای نگه داشتن تغییرات و به طور موثر استفاده از نورون های بیشتر برای آموزش. مفهوم آداگراد را می توان در دو خط کد زیر گنجانده است:

cache += parm_gradient ** 2

parm_updates = learning_rate * parm_gradient / (sqrt(cache) + eps)

 حافظه پنهان تاریخچه ای از گرادیان های مربع را در خود جای داده است و parm_updates تابعی از نرخ یادگیری ضرب در گرادیان (SGD پایه تا کنون) است و سپس بر جذر حافظه پنهان به اضافه مقداری  مقدار اپسیلون تقسیم می شود  . عملیات تقسیم انجام شده با حافظه پنهان دائما در حال افزایش ممکن است باعث شود که یادگیری با کوچکتر شدن به روزرسانی ها با گذشت زمان، به دلیل ماهیت یکنواخت به روز رسانی، متوقف شود. به همین دلیل است که این بهینه ساز به جز برخی از کاربردهای خاص به طور گسترده مورد استفاده قرار نمی گیرد. اپسیلون یک فراپارامتر (تنظیم دستگیره کنترل قبل از آموزش) است که از تقسیم 0 جلوگیری می کند. مقدار اپسیلون معمولا یک مقدار کوچک است، مانند 1e-7، که ما به طور پیش فرض آن را انجام خواهیم داد. همچنین ممکن است متوجه شوید که ما مقدار مربع را جمع می کنیم، فقط برای محاسبه ریشه مربع بعدا، که ممکن است در مورد اینکه چرا این کار را انجام می دهیم غیرمنطقی به نظر برسد. ما مقادیر مربع را اضافه می کنیم و ریشه مربع را می گیریم ، که فقط با اضافه کردن مقدار یکسان نیست ، به عنوان مثال:

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

برای پیاده سازی آداگراد، با کپی و پیست کردن کلاس بهینه ساز SGD، تغییر نام، افزودن یک ویژگی برای epsilon با پیش فرض 1e-7 به  متد __init__ و حذف مومنتوم شروع می کنیم. در مرحله بعد، در  داخل متد update_params، کد حرکت را با موارد زیر جایگزین می کنیم:

    # Update parameters

    def update_params(self, layer):

        # اگر لایه حاوی آرایه های کش نباشد،

        # آنها را پر از صفر ایجاد کنید

        if not hasattr(layer, ‘weight_cache’):

            layer.weight_cache = np.zeros_like(layer.weights)

            layer.bias_cache = np.zeros_like(layer.biases)

        # به روز رسانی حافظه پنهان با گرادیان های جریان مربع

        layer.weight_cache += layer.dweights**2

        layer.bias_cache += layer.dbiases**2

        # به روز رسانی پارامتر Vanilla SGD + عادی سازی

        # with square rooted cache

        layer.weights += -self.current_learning_rate * \

                         layer.dweights / \

                         (np.sqrt(layer.weight_cache) + self.epsilon)

        layer.biases += -self.current_learning_rate * \

                        layer.dbiases / \

                        (np.sqrt(layer.bias_cache) + self.epsilon)

کش و به روز رسانی های آن را اضافه کردیم، سپس تقسیم به روزرسانی ها بر جذر حافظه پنهان را اضافه کردیم. کد کامل برای بهینه ساز AdaGrad:

# بهینه ساز AdaGrad

class Optimizer_Adagrad:

    # راه اندازی بهینه ساز – تنظیمات را تنظیم کنید

    def __init__(self, learning_rate=1., decay=0., epsilon=1e-7):

        self.learning_rate = learning_rate

        self.current_learning_rate = learning_rate

        self.decay = decay

        self.iterations = 0

        self.epsilon = epsilon

    # قبل از به روز رسانی پارامتر یک بار تماس بگیرید

    def pre_update_params(self):

        if self.decay:

            self.current_learning_rate = self.learning_rate * \

                (1. / (1. + self.decay * self.iterations))

    # پارامترهای به روز رسانی

    def update_params(self, layer):

        # اگر لایه حاوی آرایه های کش نباشد،

        # آنها را پر از صفر ایجاد کنید

        if not hasattr(layer, ‘weight_cache’):

            layer.weight_cache = np.zeros_like(layer.weights)

            layer.bias_cache = np.zeros_like(layer.biases)

        # به روز رسانی حافظه پنهان با گرادیان های جریان مربع

        layer.weight_cache += layer.dweights**2

        layer.bias_cache += layer.dbiases**2

        # به روز رسانی پارامتر Vanilla SGD + عادی سازی

        # با کش ریشه مربعی

        layer.weights += -self.current_learning_rate * \

                         layer.dweights / \

                         (np.sqrt(layer.weight_cache) + self.epsilon)

        layer.biases += -self.current_learning_rate * \

                        layer.dbiases / \

                        (np.sqrt(layer.bias_cache) + self.epsilon)

    # پس از هر به روز رسانی پارامتر یک بار تماس بگیرید

    def post_update_params(self):

        self.iterations += 1

آزمایش این بهینه ساز در حال حاضر با تنظیم پوسیدگی روی 1e-4 و همچنین 1e-5 بهتر از 1e-3 است که قبلا از آن استفاده کرده ایم. این بهینه ساز با مجموعه داده ما با پوسیدگی کمتر بهتر کار می کند:

# مجموعه داده ایجاد کنید

X, y = spiral_data(samples=100, classes=3)

# ایجاد لایه متراکم با 2 ویژگی ورودی و 64 مقدار خروجی

dense1 = Layer_Dense(2, 64)

# ایجاد فعال سازی ReLU (برای استفاده با لایه متراکم):

activation1 = Activation_ReLU()

# ایجاد لایه متراکم دوم با 64 ویژگی ورودی (همانطور که خروجی می گیریم

# لایه قبلی در اینجا) و 3 مقدار خروجی (مقادیر خروجی)

dense2 = Layer_Dense(64, 3)

# از دست دادن و فعال سازی ترکیبی طبقه بندی کننده Softmax را ایجاد کنید

loss_activation = Activation_Softmax_Loss_CategoricalCrossentropy()

# بهینه ساز ایجاد کنید

#optimizer = Optimizer_SGD (پوسیدگی = 8e-8 ، تکانه = 0.9)

optimizer = Optimizer_Adagrad(decay=1e-4)

# Train in loop

for epoch in range(10001):

    # یک پاس رو به جلو از داده های آموزشی خود را از طریق این لایه انجام دهید

    dense1.forward(X)

    # عملکرد فعال سازی را از طریق عبور رو به جلو انجام دهید

    # خروجی اولین لایه متراکم را در اینجا می گیرد

    activation1.forward(dense1.output)

    # یک عبور رو به جلو از لایه دوم Dense انجام دهید

    # خروجی های تابع فعال سازی لایه اول را به عنوان ورودی می گیرد

    dense2.forward(activation1.output)

    # یک عبور رو به جلو از طریق عملکرد فعال سازی/از دست دادن انجام دهید

    # خروجی لایه متراکم دوم را در اینجا می گیرد و ضرر را برمی گرداند

    loss = loss_activation.forward(dense2.output, y)

    # محاسبه دقت از خروجی فعال سازی2 و اهداف

    # مقادیر را در امتداد محور اول محاسبه کنید

    predictions = np.argmax(loss_activation.output, axis=1)

    if len(y.shape) == 2:

        y = np.argmax(y, axis=1)

    accuracy = np.mean(predictions==y)

    if not epoch % 100:

        print(f‘epoch: {epoch}, ‘ +

              f‘acc: {accuracy:.3f}, ‘ +

              f‘loss: {loss:.3f}, ‘ +

              f‘lr: {optimizer.current_learning_rate}’)

    # Backward pass

    loss_activation.backward(loss_activation.output, y)

    dense2.backward(loss_activation.dinputs)

    activation1.backward(dense2.dinputs)

    dense1.backward(activation1.dinputs)

    # وزن ها و biases را به روز کنید

    optimizer.pre_update_params()

    optimizer.update_params(dense1)

    optimizer.update_params(dense2)

    optimizer.post_update_params()

>>> 

epoch: 0, acc: 0.360, loss: 1.099, lr: 1.0

epoch: 100, acc: 0.457, loss: 1.012, lr: 0.9901970492127933

epoch: 200, acc: 0.527, loss: 0.936, lr: 0.9804882831650161

epoch: 300, acc: 0.600, loss: 0.874, lr: 0.9709680551509855

epoch: 1200, acc: 0.700, loss: 0.640, lr: 0.892936869363336

epoch: 1700, acc: 0.750, loss: 0.579, lr: 0.8547739123001966

epoch: 4700, acc: 0.800, loss: 0.464, lr: 0.6803183890060548

epoch: 5100, acc: 0.810, loss: 0.454, lr: 0.6622955162593549

epoch: 6700, acc: 0.820, loss: 0.426, lr: 0.5988382537876519

epoch: 7500, acc: 0.830, loss: 0.412, lr: 0.5714612263557918

epoch: 9900, acc: 0.847, loss: 0.381, lr: 0.5025378159706518

epoch: 10000, acc: 0.847, loss: 0.379, lr: 0.5000250012500626

شکل 10.20: آموزش مدل با بهینه ساز AdaGrad.

Epilepsy Warning (quick flashing colors)

Anim 10.20: https://nnfs.io/bop

AdaGrad در اینجا بسیار خوب کار کرد، اما نه به خوبی SGD با شتمن، و می توانیم ببینیم که از دست دادن به طور مداوم در کل فرآیند آموزش کاهش می یابد. جالب است بدانید که AdaGrad در ابتدا چند دوره دیگر طول کشید تا به نتایجی مشابه با تصادف گرادیان نزول با تکانه برسد.

RMSProp

در ادامه سازگاری های گرادیان تصادفی، به RMSProp می رسیم که مخفف Root Mean Square Propagation است. مشابه AdaGrad، RMSProp نرخ یادگیری تطبیقی را در هر پارامتر محاسبه می کند. فقط به روشی متفاوت از AdaGrad محاسبه می شود.

جایی که AdaGrad حافظه پنهان را به صورت زیر محاسبه می کند:

cache += gradient ** 2

RMSProp حافظه پنهان را به صورت زیر محاسبه می کند:

cache = rho * cache + (1 – rho) * gradient ** 2

توجه داشته باشید که این شبیه به حرکت با بهینه ساز SGD و کش با AdaGrad است. RMSProp مکانیزمی شبیه به حرکت اضافه می کند، اما نرخ یادگیری تطبیقی به ازای هر پارامتر را نیز اضافه می کند، بنابراین تغییرات سرعت یادگیری روان تر است. این به حفظ جهت جهانی تغییرات کمک می کند و تغییرات جهت را کند می کند. به جای اضافه کردن مداوم گرادیان مربعات به کش (مانند آداگراد)، از میانگین متحرک کش استفاده می کند. هر به روز رسانی حافظه پنهان بخشی از حافظه پنهان را حفظ می کند و آن را با کسری از گرادیان های جدید و مربعی به روز می کند. به این ترتیب، محتویات کش با داده ها به موقع “جابجایی” می کنند و یادگیری متوقف نمی شود. در مورد این بهینه ساز، بسته به آخرین به روز رسانی ها و گرادیان فعلی، نرخ یادگیری هر پارامتر می تواند کاهش یابد یا افزایش یابد. RMSProp حافظه پنهان را به همان روشی که AdaGrad اعمال می کند، اعمال می کند.

ابرپارامتر جدید در اینجا rho است. Rho نرخ پوسیدگی حافظه کش است. از آنجایی که این بهینه ساز، با مقادیر پیش فرض، حرکت زیادی از گرادیان و به روز رسانی نرخ یادگیری تطبیقی را حمل می کند، حتی به روزرسانی های گرادیان کوچک نیز برای ادامه آن کافی هستند. بنابراین، نرخ یادگیری پیش فرض 1 بسیار زیاد است و باعث بی ثباتی فوری مدل می شود. نرخ یادگیری که دوباره پایدار می شود و به روزرسانی های کافی سریع ارائه می دهد حدود 0.001 است (این نیز مقدار پیش فرض برای این بهینه ساز است که در چارچوب های یادگیری ماشین شناخته شده استفاده می شود). این همان چیزی است که از این به بعد نیز به عنوان پیش فرض استفاده خواهیم کرد. در زیر کد کامل کلاس بهینه ساز RMSProp آمده است:

# بهینه ساز RMSprop

class Optimizer_RMSprop:

    # راه اندازی بهینه ساز – تنظیمات را تنظیم کنید

    def __init__(self, learning_rate=0.001, decay=0., epsilon=1e-7,

                 rho=0.9):

        self.learning_rate = learning_rate

        self.current_learning_rate = learning_rate

        self.decay = decay

        self.iterations = 0

        self.epsilon = epsilon

        self.rho = rho

    # قبل از به روز رسانی پارامتر یک بار تماس بگیرید

    def pre_update_params(self):

        if self.decay:

            self.current_learning_rate = self.learning_rate * \

                (1. / (1. + self.decay * self.iterations))

    # پارامترهای به روز رسانی

    def update_params(self, layer):

        # اگر لایه حاوی آرایه های کش نباشد،

        # آنها را پر از صفر ایجاد کنید

        if not hasattr(layer, ‘weight_cache’):

            layer.weight_cache = np.zeros_like(layer.weights)

            layer.bias_cache = np.zeros_like(layer.biases)

        # به روز رسانی حافظه پنهان با گرادیان های جریان مربع

        layer.weight_cache = self.rho * layer.weight_cache + \

            (1 – self.rho) * layer.dweights**2

        layer.bias_cache = self.rho * layer.bias_cache + \

            (1 – self.rho) * layer.dbiases**2

        # به روز رسانی پارامتر Vanilla SGD + عادی سازی

        # with square rooted cache

        layer.weights += -self.current_learning_rate * \

                         layer.dweights / \

                         (np.sqrt(layer.weight_cache) + self.epsilon)

        layer.biases += -self.current_learning_rate * \

                        layer.dbiases / \

                        (np.sqrt(layer.bias_cache) + self.epsilon)

    # پس از هر به روز رسانی پارامتر یک بار تماس بگیرید

    def post_update_params(self):

        self.iterations += 1

تغییر بهینه ساز مورد استفاده در کد تست شبکه عصبی اصلی ما:

optimizer = Optimizer_RMSprop(decay=1e-4) و اجرای این کد به ما می دهد:

>>> 

epoch: 0, acc: 0.360, loss: 1.099, lr: 0.001

epoch: 100, acc: 0.417, loss: 1.077, lr: 0.0009901970492127933

epoch: 200, acc: 0.457, loss: 1.072, lr: 0.0009804882831650162

epoch: 300, acc: 0.480, loss: 1.062, lr: 0.0009709680551509856

epoch: 1000, acc: 0.597, loss: 0.961, lr: 0.0009091735612328393

epoch: 4800, acc: 0.703, loss: 0.767, lr: 0.0006757213325224677

epoch: 5800, acc: 0.713, loss: 0.744, lr: 0.0006329514526235838

epoch: 7100, acc: 0.720, loss: 0.718, lr: 0.0005848295221942804

epoch: 10000, acc: 0.730, loss: 0.668, lr: 0.0005000250012500625

شکل 10.21: آموزش مدل با بهینه ساز RMSProp.

Epilepsy Warning (quick flashing colors)

Anim 10.21: https://nnfs.io/pun

نتایج بهترین نیستند ، اما می توانیم فراپارامترها را کمی تغییر دهیم:

optimizer = Optimizer_RMSprop(learning_rate=0.02, decay=1e-5,

                              rho=0.999)

>>> 

epoch: 0, acc: 0.360, loss: 1.099, lr: 0.02

epoch: 100, acc: 0.467, loss: 1.014, lr: 0.01998021958261321

epoch: 200, acc: 0.530, loss: 0.959, lr: 0.019960279044701046

epoch: 600, acc: 0.623, loss: 0.762, lr: 0.019880913329158343

epoch: 1000, acc: 0.710, loss: 0.634, lr: 0.019802176259170884

epoch: 1800, acc: 0.810, loss: 0.475, lr: 0.01964655841412981

epoch: 3800, acc: 0.850, loss: 0.351, lr: 0.01926800836231563

epoch: 6200, acc: 0.870, loss: 0.286, lr: 0.018832569044906263

epoch: 6600, acc: 0.903, loss: 0.262, lr: 0.018761902081633034

epoch: 7100, acc: 0.900, loss: 0.274, lr: 0.018674310684506857

epoch: 9500, acc: 0.890, loss: 0.244, lr: 0.018265006986365174

epoch: 9600, acc: 0.893, loss: 0.241, lr: 0.018248341681949654

epoch: 9700, acc: 0.743, loss: 0.794, lr: 0.018231706761228456

epoch: 9800, acc: 0.917, loss: 0.213, lr: 0.018215102141185255

epoch: 9900, acc: 0.907, loss: 0.225, lr: 0.018198527739105907

epoch: 10000, acc: 0.910, loss: 0.221, lr: 0.018181983472577025

شکل 10.22: آموزش مدل با بهینه ساز RMSProp (تنظیم شده).

Epilepsy Warning (quick flashing colors)

Anim 10.22: https://nnfs.io/not

نتیجه بسیار خوب، نزدیک به SGD با شتاب اما نه به همان خوب. ما هنوز یک سازگاری نهایی با نزول گرادیان تصادفی برای پوشش داریم.

 

 

Adam

Adam، مخفف Adaptive Momentum، در حال حاضر پرکاربردترین بهینه ساز است و در بالای RMSProp ساخته شده است، با مفهوم حرکت از SGD که دوباره به آن اضافه شده است. این بدان معنی است که به جای اعمال گرادیان های فعلی، ما می خواهیم تکانه هایی مانند بهینه ساز SGD را با تکانه اعمال کنیم، سپس یک نرخ یادگیری تطبیقی به ازای هر وزن را با حافظه پنهان اعمال می کنیم، همانطور که در RMSProp انجام می شود.

بهینه ساز Adam علاوه بر این یک مکانیسم تصحیح بایاس اضافه می کند. این را با بایاس لایه اشتباه نگیرید. مکانیسم تصحیح بایاس روی حافظه پنهان و تکانه اعمال می شود و مقادیر صفر اولیه را قبل از گرم شدن با مراحل اولیه جبران می کند. برای دستیابی به این اصلاح، هر دو حرکت و کش بر 1 بتااستپ تقسیم می شوند. با بالا رفتن پله ، بتستپ به 0 نزدیک می شود (کسری از توان یک مقدار افزایشی کاهش می یابد) ، کل این عبارت را در اولین مراحل به کسری حل می کند و  با پیشرفت آموزش به 1 نزدیک می شود. به عنوان مثال، بتا 1، کسری از تکانه برای اعمال، به طور پیش فرض 0.9 است. این بدان معنی است که ، در مرحله اول ، مقدار اصلاح برابر است:

با پیشرفت آموزش، با افزایش تعداد قدمها:

همین امر در مورد حافظه پنهان و بتا 2 نیز صدق می کند – در این مورد، مقدار شروع 0.001 است و همچنین به 1 نزدیک می شود. این مقادیر به ترتیب تکانه ها و حافظه پنهان را تقسیم می کنند. تقسیم بر کسری باعث می شود که آنها چندین برابر بزرگتر شوند و به طور قابل توجهی سرعت تمرین را در مراحل اولیه قبل از گرم شدن هر دو میز در چندین مرحله اولیه افزایش می دهد. همچنین قبلا اشاره کردیم که هر دوی این ضرایب تصحیح سوگیری با پیشرفت آموزش به سمت مقدار 1 می روند و به روزرسانی های پارامترها را برای مراحل آموزشی بعدی به مقادیر معمول خود برمی گردانند. برای دریافت به روز رسانی پارامترها، تکانه مقیاس شده را بر حافظه پنهان ریشه مربعی مقیاس شده تقسیم می کنیم.

کد Adam Optimizer بر اساس بهینه ساز RMSProp است. حافظه پنهان دیده شده از SGD را به همراه  پارامتر بتا 1 اضافه می کند  . در مرحله بعد، مکانیسم تصحیح بایاس را هم برای تکانه و هم برای حافظه پنهان معرفی می کند. ما همچنین نحوه محاسبه به روز رسانی پارامترها را تغییر داده ایم – با استفاده از تکانه های اصلاح شده و کش های اصلاح شده، به جای گرادیان ها و حافظه پنهان. لیست کامل تغییرات ایجاد شده از RMSProp پس از کد زیر ارسال می شود:

# بهینه ساز Adam

class Optimizer_Adam:

    # راه اندازی بهینه ساز – تنظیمات را تنظیم کنید

    def __init__(self, learning_rate=0.001, decay=0., epsilon=1e-7,

                 beta_1=0.9, beta_2=0.999):

        self.learning_rate = learning_rate

        self.current_learning_rate = learning_rate

        self.decay = decay

        self.iterations = 0

        self.epsilon = epsilon

        self.beta_1 = beta_1

        self.beta_2 = beta_2

    # قبل از به روز رسانی پارامتر یک بار تماس بگیرید

    def pre_update_params(self):

        if self.decay:

            self.current_learning_rate = self.learning_rate * \

                (1. / (1. + self.decay * self.iterations))

    # پارامترهای به روز رسانی

    def update_params(self, layer):

        # اگر لایه حاوی آرایه های کش نباشد،

        # آنها را پر از صفر ایجاد کنید

        if not hasattr(layer, ‘weight_cache’):

            layer.weight_momentums = np.zeros_like(layer.weights)

            layer.weight_cache = np.zeros_like(layer.weights)

            layer.bias_momentums = np.zeros_like(layer.biases)

            layer.bias_cache = np.zeros_like(layer.biases)

        # حرکت را با گرادیان های فعلی به روز کنید

        layer.weight_momentums = self.beta_1 * \

                                 layer.weight_momentums + \

                                 (1 – self.beta_1) * layer.dweights

        layer.bias_momentums = self.beta_1 * \

                               layer.bias_momentums + \

                               (1 – self.beta_1) * layer.dbiases

        # حرکت اصلاح شده را دریافت کنید

        # self.iteration در اولین pass 0 است

        # و باید در اینجا با 1 شروع کنیم

        weight_momentums_corrected = layer.weight_momentums / \

            (1 – self.beta_1 ** (self.iterations + 1))

        bias_momentums_corrected = layer.bias_momentums / \

            (1 – self.beta_1 ** (self.iterations + 1))

        # به روز رسانی حافظه پنهان با گرادیان های جریان مربع

        layer.weight_cache = self.beta_2 * layer.weight_cache + \

            (1 – self.beta_2) * layer.dweights**2

        layer.bias_cache = self.beta_2 * layer.bias_cache + \

            (1 – self.beta_2) * layer.dbiases**2

        # حافظه پنهان تصحیح شده را دریافت کنید

        weight_cache_corrected = layer.weight_cache / \

            (1 – self.beta_2 ** (self.iterations + 1))

        bias_cache_corrected = layer.bias_cache / \

            (1 – self.beta_2 ** (self.iterations + 1))

        # به روز رسانی پارامتر Vanilla SGD + عادی سازی

        # with square rooted cache

        layer.weights += -self.current_learning_rate * \

                         weight_momentums_corrected / \

                         (np.sqrt(weight_cache_corrected) +

                             self.epsilon)

        layer.biases += -self.current_learning_rate * \

                         bias_momentums_corrected / \

                         (np.sqrt(bias_cache_corrected) +

                             self.epsilon)

    # پس از هر به روز رسانی پارامتر یک بار تماس بگیرید

    def post_update_params(self):

        self.iterations += 1

تغییرات زیر از کپی کردن کد کلاس RMSProp ایجاد شده است:

  1. renamed class from Optimizer_RMSprop to Optimizer_Adam
  2. تغییر نام  Rho hyperparameter و ویژگی به beta_2 در __init__
  3. اضافه شدن beta_1 هایپرپارامتر و ویژگی در __init__
  4. ایجاد  آرایه حرکتی در update_params() اضافه شده است
  5.  محاسبه تکانه اضافه  شده است
  6. تغییر نام self.rho به self.beta_2 با کد محاسبه کش در update_params
  7. متغیرهای *_corrected به عنوان تکانه ها و وزن های تصحیح شده اضافه  شد
  8.  Layer.dWeights، Layer.dbiases، layer.weight_cache و layer.bias_cache را با آرایه های اصلاح شده مقادیر در به روز رسانی پارامتر با   momentum arrays

بازگشت به کد اصلی شبکه عصبی ما. اکنون می توانیم بهینه ساز خود را روی Adam تنظیم کنیم، کد را اجرا کنیم و ببینیم این تغییرات چه تاثیری داشته اند:

optimizer = Optimizer_Adam(learning_rate=0.02, decay=1e-5)

با تنظیمات پیش فرض خود، با موارد زیر به پایان می رسانیم:

>>> 

epoch: 0, acc: 0.360, loss: 1.099, lr: 0.02

epoch: 100, acc: 0.683, loss: 0.772, lr: 0.01998021958261321

epoch: 200, acc: 0.793, loss: 0.560, lr: 0.019960279044701046

epoch: 300, acc: 0.850, loss: 0.458, lr: 0.019940378268975763

epoch: 400, acc: 0.873, loss: 0.374, lr: 0.01992051713662487

epoch: 500, acc: 0.897, loss: 0.321, lr: 0.01990069552930875

epoch: 600, acc: 0.893, loss: 0.286, lr: 0.019880913329158343

epoch: 700, acc: 0.900, loss: 0.260, lr: 0.019861170418772778

epoch: 1700, acc: 0.930, loss: 0.164, lr: 0.019665876753950384

epoch: 2600, acc: 0.950, loss: 0.132, lr: 0.019493367381748363

epoch: 9900, acc: 0.967, loss: 0.078, lr: 0.018198527739105907

epoch: 10000, acc: 0.963, loss: 0.079, lr: 0.018181983472577025

شکل 10.23: آموزش مدل با بهینه ساز آدام.

Epilepsy Warning (quick flashing colors)

Anim 10.23: https://nnfs.io/you

این بهترین نتیجه تا کنون است، اما بیایید نرخ یادگیری را کمی بالاتر تنظیم کنیم، به 0.05 و واپاشی را به 5e-7 تغییر دهیم:

optimizer = Optimizer_Adam(learning_rate=0.05, decay=5e-7)

در این مورد، از دست دادن و دقت کمی بهبود یافت و به پایان رسید:

>>> 

epoch: 0, acc: 0.360, loss: 1.099, lr: 0.05

epoch: 100, acc: 0.713, loss: 0.684, lr: 0.04999752512250644

epoch: 200, acc: 0.827, loss: 0.511, lr: 0.04999502549496326

epoch: 700, acc: 0.907, loss: 0.264, lr: 0.049982531105378675

epoch: 800, acc: 0.897, loss: 0.278, lr: 0.04998003297682575

epoch: 900, acc: 0.923, loss: 0.230, lr: 0.049977535097973466

epoch: 2000, acc: 0.930, loss: 0.170, lr: 0.04995007490013731

epoch: 3300, acc: 0.950, loss: 0.136, lr: 0.04991766081847992

epoch: 7800, acc: 0.973, loss: 0.089, lr: 0.04980578235171948

epoch: 7900, acc: 0.970, loss: 0.089, lr: 0.04980330185930667

epoch: 8000, acc: 0.980, loss: 0.088, lr: 0.04980082161395499

epoch: 9900, acc: 0.983, loss: 0.074, lr: 0.049753743844839965

epoch: 10000, acc: 0.983, loss: 0.074, lr: 0.04975126853296942

شکل 10.24: آموزش مدل با Adam بهینه ساز (تنظیم شده).

Epilepsy Warning (quick flashing colors)

Anim 10.24 : https://nnfs.io/car

خیلی بهتر نمی شود، هم از نظر دقت و هم از نظر از دست دادن. در حالی که آدام در اینجا بهترین عملکرد را داشته است و معمولا بهترین بهینه ساز در بین موارد نشان داده شده است، همیشه اینطور نیست. معمولا ایده خوبی است که ابتدا آدام بهینه ساز را امتحان کنید، اما بقیه را نیز امتحان کنید، به خصوص اگر نتایجی را که امیدوار بودید به دست نمی آورید. گاهی اوقات SGD ساده یا SGD + حرکت بهتر از آدام عمل می کند. دلایل آن متفاوت خواهد بود، اما این را در نظر داشته باشید.

ما انتخاب فراپارامترهای مختلف (مانند نرخ یادگیری) را هنگام آموزش پوشش خواهیم داد، اما نرخ یادگیری شروع کلی برای SGD 1.0 است، با کاهش تا 0.1. برای آدام، یک LR شروع خوب 0.001 (1e-3) است که به 0.0001 (1e-4) کاهش می یابد. مشکلات مختلف ممکن است در اینجا به مقادیر متفاوتی نیاز داشته باشند، اما شروع آنها مناسب هستند.

ما در این بخش به دقت 98.3 درصد در مجموعه داده های تولید شده و از دست دادن نزدیک به کمال (0) دست یافتیم. به جای هیجان زدن، به زودی یاد خواهید گرفت که از نتایج خوب بترسید یا حداقل با احتیاط به آنها نزدیک شوید. مواردی وجود دارد که شما واقعا می توانید به نتایج معتبری به خوبی این دست پیدا کنید، اما در این مورد، ما یک مفهوم اصلی در یادگیری ماشین را نادیده گرفته ایم: داده های تست خارج از نمونه (که می تواند بیش از حد برازش را روشن کند)، که موضوع بخش بعدی است.

کد کامل تا این مرحله:

import numpy as np

import nnfs

from nnfs.datasets import spiral_data

nnfs.init()

# لایه متراکم

class Layer_Dense:

    # مقداردهی اولیه لایه

    def __init__(self, n_inputs, n_neurons):

        # وزن ها و سوگیری ها را مقداردهی اولیه کنید

        self.weights = 0.01 * np.random.randn(n_inputs, n_neurons)

        self.biases = np.zeros((1, n_neurons))

    # Forward pass

    def forward(self, inputs):

        # مقادیر ورودی را به خاطر بسپارید

        self.inputs = inputs

        # مقادیر خروجی را از ورودی ها، وزن ها و بایاس ها محاسبه کنید

        self.output = np.dot(inputs, self.weights) + self.biases

    # Backward pass

    def backward(self, dvalues):

        # گرادیان در پارامترها

        self.dweights = np.dot(self.inputs.T, dvalues)

        self.dbiases = np.sum(dvalues, axis=0, keepdims=True)

        # Gradient on values

        self.dinputs = np.dot(dvalues, self.weights.T)

# فعال سازی ReLU

class Activation_ReLU:

    # Forward pass

    def forward(self, inputs):

        # مقادیر ورودی را به خاطر بسپارید

        self.inputs = inputs

        # مقادیر خروجی را از ورودی ها محاسبه کنید

        self.output = np.maximum(0, inputs)

    # Backward pass

    def backward(self, dvalues):

        # از آنجایی که ما نیاز به تغییر متغیر اصلی داریم،

        # بیایید ابتدا یک کپی از مقادیر بسازیم

        self.dinputs = dvalues.copy()

        # گرادیان صفر که در آن مقادیر ورودی منفی بودند

        self.dinputs[self.inputs <= 0] = 0

# فعال سازی Softmax

class Activation_Softmax:

    # Forward pass

    def forward(self, inputs):

        # مقادیر ورودی را به خاطر بسپارید

        self.inputs = inputs

        # احتمالات غیرعادی را دریافت کنید

        exp_values = np.exp(inputs – np.max(inputs, axis=1,

                                            keepdims=True))

        # آنها را برای هر نمونه عادی کنید

        probabilities = exp_values / np.sum(exp_values, axis=1,

                                            keepdims=True)

        self.output = probabilities

    # Backward pass

    def backward(self, dvalues):

        # آرایه راه اندازی نشده ایجاد کنید

        self.dinputs = np.empty_like(dvalues)

        # خروجی ها و گرادیان ها را برشمارید

        for index, (single_output, single_dvalues) in \

                enumerate(zip(self.output, dvalues)):

            # Flatten output array

            single_output = single_output.reshape(-1, 1)

            # ماتریس Jacobian خروجی را محاسبه کنید و

            jacobian_matrix = np.diagflat(single_output) – \

                              np.dot(single_output, single_output.T)

            # گرادیان نمونه را محاسبه کنید

            # و آن را به آرایه گرادیان های نمونه اضافه کنید

            self.dinputs[index] = np.dot(jacobian_matrix,

                                         single_dvalues)

# بهینه ساز SGD

class Optimizer_SGD:

    # راه اندازی بهینه ساز – تنظیمات را تنظیم کنید،

    # میزان یادگیری 1. پیش فرض برای این بهینه ساز است

    def __init__(self, learning_rate=1., decay=0., momentum=0.):

        self.learning_rate = learning_rate

        self.current_learning_rate = learning_rate

        self.decay = decay

        self.iterations = 0

        self.momentum = momentum

    # قبل از به روز رسانی پارامتر یک بار تماس بگیرید

    def pre_update_params(self):

        if self.decay:

            self.current_learning_rate = self.learning_rate * \

                (1. / (1. + self.decay * self.iterations))

    # پارامترهای به روز رسانی

    def update_params(self, layer):

        # اگر از حرکت استفاده کنیم

        if self.momentum:

            # اگر لایه حاوی آرایه های حرکتی نیست، آنها را ایجاد کنید

            # پر از صفر

            if not hasattr(layer, ‘weight_momentums’):

                layer.weight_momentums = np.zeros_like(layer.weights)

                # اگر آرایه حرکتی برای وزن ها وجود نداشته باشد

                # آرایه هنوز برای سوگیری وجود ندارد.

                layer.bias_momentums = np.zeros_like(layer.biases)

            # بروزرسانی های وزن را با حرکت بسازید – قبلی را بگیرید

            # به روز رسانی در فاکتور حفظ ضرب می شود و با به روز رسانی می شود

            # گرادیان های فعلی

            weight_updates = \

                self.momentum * layer.weight_momentums – \

                self.current_learning_rate * layer.dweights

            layer.weight_momentums = weight_updates

            # بروزرسانی های bias بسازید

            bias_updates = \

                self.momentum * layer.bias_momentums – \

                self.current_learning_rate * layer.dbiases

            layer.bias_momentums = bias_updates

        # به روز رسانی Vanilla SGD (مانند قبل از به روز رسانی حرکت)

        else:

            weight_updates = -self.current_learning_rate * \

                             layer.dweights

            bias_updates = -self.current_learning_rate * \

                           layer.dbiases

        # وزن ها و سوگیری ها را با استفاده از هر کدام به روز کنید

        # به روز رسانی وانیل یا مومنتوم

        layer.weights += weight_updates

        layer.biases += bias_updates

    # پس از هر به روز رسانی پارامتر یک بار تماس بگیرید

    def post_update_params(self):

        self.iterations += 1

# بهینه ساز Adagrad

class Optimizer_Adagrad:

    # راه اندازی بهینه ساز – تنظیمات را تنظیم کنید

    def __init__(self, learning_rate=1., decay=0., epsilon=1e-7):

        self.learning_rate = learning_rate

        self.current_learning_rate = learning_rate

        self.decay = decay

        self.iterations = 0

        self.epsilon = epsilon

    # قبل از به روز رسانی پارامتر یک بار تماس بگیرید

    def pre_update_params(self):

        if self.decay:

            self.current_learning_rate = self.learning_rate * \

                (1. / (1. + self.decay * self.iterations))

    # پارامترهای به روز رسانی

    def update_params(self, layer):

        # اگر لایه حاوی آرایه های کش نباشد،

        # آنها را پر از صفر ایجاد کنید

        if not hasattr(layer, ‘weight_cache’):

            layer.weight_cache = np.zeros_like(layer.weights)

            layer.bias_cache = np.zeros_like(layer.biases)

        # به روز رسانی حافظه پنهان با گرادیان های جریان مربع

        layer.weight_cache += layer.dweights**2

        layer.bias_cache += layer.dbiases**2

        # به روز رسانی پارامتر Vanilla SGD + عادی سازی

        # with square rooted cache

        layer.weights += -self.current_learning_rate * \

                         layer.dweights / \

                         (np.sqrt(layer.weight_cache) + self.epsilon)

        layer.biases += -self.current_learning_rate * \

                        layer.dbiases / \

                        (np.sqrt(layer.bias_cache) + self.epsilon)

    # پس از هر به روز رسانی پارامتر یک بار تماس بگیرید

    def post_update_params(self):

        self.iterations += 1

# بهینه ساز RMSprop

class Optimizer_RMSprop:

    # راه اندازی بهینه ساز – تنظیمات را تنظیم کنید

    def __init__(self, learning_rate=0.001, decay=0., epsilon=1e-7,

                 rho=0.9):

        self.learning_rate = learning_rate

        self.current_learning_rate = learning_rate

        self.decay = decay

        self.iterations = 0

        self.epsilon = epsilon

        self.rho = rho

    # قبل از به روز رسانی پارامتر یک بار تماس بگیرید

    def pre_update_params(self):

        if self.decay:

            self.current_learning_rate = self.learning_rate * \

                (1. / (1. + self.decay * self.iterations))

    # پارامترهای به روز رسانی

    def update_params(self, layer):

        # If layer does not contain cache arrays,

        # آنها را پر از صفر ایجاد کنید

        if not hasattr(layer, ‘weight_cache’):

            layer.weight_cache = np.zeros_like(layer.weights)

            layer.bias_cache = np.zeros_like(layer.biases)

        # به روز رسانی حافظه پنهان با گرادیان های جریان مربع

        layer.weight_cache = self.rho * layer.weight_cache + \

            (1 – self.rho) * layer.dweights**2

        layer.bias_cache = self.rho * layer.bias_cache + \

            (1 – self.rho) * layer.dbiases**2

        # به روز رسانی پارامتر Vanilla SGD + عادی سازی

        # with square rooted cache

        layer.weights += -self.current_learning_rate * \

                         layer.dweights / \

                         (np.sqrt(layer.weight_cache) + self.epsilon)

        layer.biases += -self.current_learning_rate * \

                        layer.dbiases / \

                        (np.sqrt(layer.bias_cache) + self.epsilon)

    # پس از هر به روز رسانی پارامتر یک بار تماس بگیرید

    def post_update_params(self):

        self.iterations += 1

# بهینه ساز Adam

class Optimizer_Adam:

    # راه اندازی بهینه ساز – تنظیمات را تنظیم کنید

    def __init__(self, learning_rate=0.001, decay=0., epsilon=1e-7,

                 beta_1=0.9, beta_2=0.999):

        self.learning_rate = learning_rate

        self.current_learning_rate = learning_rate

        self.decay = decay

        self.iterations = 0

        self.epsilon = epsilon

        self.beta_1 = beta_1

        self.beta_2 = beta_2

    # قبل از به روز رسانی پارامتر یک بار تماس بگیرید

    def pre_update_params(self):

        if self.decay:

            self.current_learning_rate = self.learning_rate * \

                (1. / (1. + self.decay * self.iterations))

    # پارامترهای به روز رسانی

    def update_params(self, layer):

        # If layer does not contain cache arrays,

        # آنها را پر از صفر ایجاد کنید

        if not hasattr(layer, ‘weight_cache’):

            layer.weight_momentums = np.zeros_like(layer.weights)

            layer.weight_cache = np.zeros_like(layer.weights)

            layer.bias_momentums = np.zeros_like(layer.biases)

            layer.bias_cache = np.zeros_like(layer.biases)

        # حرکت را با گرادیان های فعلی به روز کنید

        layer.weight_momentums = self.beta_1 * \

                                 layer.weight_momentums + \

                                 (1 – self.beta_1) * layer.dweights

        layer.bias_momentums = self.beta_1 * \

                               layer.bias_momentums + \

                               (1 – self.beta_1) * layer.dbiases

        # حرکت اصلاح شده را دریافت کنید

        # self.iteration در اولین پاس 0 است

        # و باید در اینجا با 1 شروع کنیم

        weight_momentums_corrected = layer.weight_momentums / \

            (1 – self.beta_1 ** (self.iterations + 1))

        bias_momentums_corrected = layer.bias_momentums / \

            (1 – self.beta_1 ** (self.iterations + 1))

        # به روز رسانی حافظه پنهان با گرادیان های جریان مربع

        layer.weight_cache = self.beta_2 * layer.weight_cache + \

            (1 – self.beta_2) * layer.dweights**2

        layer.bias_cache = self.beta_2 * layer.bias_cache + \

            (1 – self.beta_2) * layer.dbiases**2

        # حافظه پنهان تصحیح شده را دریافت کنید

        weight_cache_corrected = layer.weight_cache / \

            (1 – self.beta_2 ** (self.iterations + 1))

        bias_cache_corrected = layer.bias_cache / \

            (1 – self.beta_2 ** (self.iterations + 1))

        # به روز رسانی پارامتر Vanilla SGD + عادی سازی

        # with square rooted cache

        layer.weights += -self.current_learning_rate * \

                         weight_momentums_corrected / \

                         (np.sqrt(weight_cache_corrected) +

                             self.epsilon)

        layer.biases += -self.current_learning_rate * \

                         bias_momentums_corrected / \

                         (np.sqrt(bias_cache_corrected) +

                             self.epsilon)

    # پس از هر به روز رسانی پارامتر یک بار تماس بگیرید

    def post_update_params(self):

        self.iterations += 1

# کلاس ضرر مشترک

class Loss:

    # داده ها و تلفات منظم سازی را محاسبه می کند

    # خروجی مدل داده شده و مقادیر حقیقت زمین

    def calculate(self, output, y):

        # تلفات نمونه را محاسبه کنید

        sample_losses = self.forward(output, y)

        # میانگین ضرر را محاسبه کنید

        data_loss = np.mean(sample_losses)

        # ضرر برگشتی

        return data_loss

# از دست دادن آنتروپی متقاطع

class Loss_CategoricalCrossentropy(Loss):

    # Forward pass

    def forward(self, y_pred, y_true):

        # تعداد نمونه ها در یک دسته

        samples = len(y_pred)

        # کلیپ داده ها برای جلوگیری از تقسیم بر 0

        # هر دو طرف را کلیپ کنید تا میانگین را به سمت هیچ مقداری نکشید

        y_pred_clipped = np.clip(y_pred, 1e-7, 1 – 1e-7)

        # احتمالات برای مقادیر هدف –

        # فقط اگر برچسب های طبقه بندی شده باشد

        if len(y_true.shape) == 1:

            correct_confidences = y_pred_clipped[

                range(samples),

                y_true

            ]

        # Mask values – only for one-hot encoded labels

        elif len(y_true.shape) == 2:

            correct_confidences = np.sum(

                y_pred_clipped * y_true,

                axis=1

            )

        # ضرر و زیان

        negative_log_likelihoods = -np.log(correct_confidences)

        return negative_log_likelihoods

    # Backward pass

    def backward(self, dvalues, y_true):

        # تعداد نمونه ها

        samples = len(dvalues)

        # تعداد برچسب ها در هر نمونه

        # ما از اولین نمونه برای شمارش آنها استفاده خواهیم کرد

        labels = len(dvalues[0])

        # اگر برچسب ها پراکنده هستند، آنها را به یک بردار داغ تبدیل کنید

        if len(y_true.shape) == 1:

            y_true = np.eye(labels)[y_true]

        # شیب را محاسبه کنید

        self.dinputs = -y_true / dvalues

        # شیب را عادی کنید

        self.dinputs = self.dinputs / samples

# طبقه بندی کننده Softmax – فعال سازی ترکیبی Softmax

# و از دست دادن آنتروپی متقاطع برای گام عقب سریعتر

class Activation_Softmax_Loss_CategoricalCrossentropy():

    # اشیاء عملکرد فعال سازی و از دست دادن را ایجاد می کند

    def __init__(self):

        self.activation = Activation_Softmax()

        self.loss = Loss_CategoricalCrossentropy()

    # Forward pass

    def forward(self, inputs, y_true):

        # عملکرد فعال سازی لایه خروجی

        self.activation.forward(inputs)

        # خروجی را تنظیم کنید

        self.output = self.activation.output

        # مقدار ضرر را محاسبه و برگردانید

        return self.loss.calculate(self.output, y_true)

    # Backward pass

    def backward(self, dvalues, y_true):

        # تعداد نمونه ها

        samples = len(dvalues)

        # If labels are one-hot encoded,

        # آنها را به مقادیر گسسته تبدیل کنید

        if len(y_true.shape) == 2:

            y_true = np.argmax(y_true, axis=1)

        # کپی کنید تا بتوانیم با خیال راحت تغییر دهیم

        self.dinputs = dvalues.copy()

        # شیب را محاسبه کنید

        self.dinputs[range(samples), y_true] -= 1

        # شیب را عادی کنید

        self.dinputs = self.dinputs / samples

# مجموعه داده ایجاد کنید

X, y = spiral_data(samples=100, classes=3)

# ایجاد لایه متراکم با 2 ویژگی ورودی و 64 مقدار خروجی

dense1 = Layer_Dense(2, 64)

# ایجاد فعال سازی ReLU (برای استفاده با لایه متراکم):

activation1 = Activation_ReLU()

# ایجاد لایه متراکم دوم با 64 ویژگی ورودی (همانطور که خروجی می گیریم

# لایه قبلی در اینجا) و 3 مقدار خروجی (مقادیر خروجی)

dense2 = Layer_Dense(64, 3)

# از دست دادن و فعال سازی ترکیبی طبقه بندی کننده Softmax را ایجاد کنید

loss_activation = Activation_Softmax_Loss_CategoricalCrossentropy()[1] 

# بهینه ساز ایجاد کنید

optimizer = Optimizer_Adam(learning_rate=0.05, decay=5e-7)

# Train in loop

for epoch in range(10001):

    # Perform a forward pass of our training data through this layer

    dense1.forward(X)

    # عملکرد فعال سازی را از طریق عبور رو به جلو انجام دهید

    # خروجی اولین لایه متراکم را در اینجا می گیرد

    activation1.forward(dense1.output)

    # یک عبور رو به جلو از لایه دوم Dense انجام دهید

    # خروجی های تابع فعال سازی لایه اول را به عنوان ورودی می گیرد

    dense2.forward(activation1.output)

    # یک عبور رو به جلو از طریق عملکرد فعال سازی/از دست دادن انجام دهید

    # خروجی لایه متراکم دوم را در اینجا می گیرد و ضرر را برمی گرداند

    loss = loss_activation.forward(dense2.output, y)

    # محاسبه دقت از خروجی فعال سازی2 و اهداف

    # مقادیر را در امتداد محور اول محاسبه کنید

    predictions = np.argmax(loss_activation.output, axis=1)

    if len(y.shape) == 2:

        y = np.argmax(y, axis=1)

    accuracy = np.mean(predictions==y)

    if not epoch % 100:

        print(f‘epoch: {epoch}, ‘ +

              f‘acc: {accuracy:.3f}, ‘ +

              f‘loss: {loss:.3f}, ‘ +

              f‘lr: {optimizer.current_learning_rate}’)

    # Backward pass

    loss_activation.backward(loss_activation.output, y)

    dense2.backward(loss_activation.dinputs)

    activation1.backward(dense2.dinputs)

    dense1.backward(activation1.dinputs)

    # وزن ها و biases را به روز کنید

    optimizer.pre_update_params()

    optimizer.update_params(dense1)

    optimizer.update_params(dense2)

    optimizer.post_update_params()

مطالب تکمیلی: https://nnfs.io/ch10 کد فصل، منابع بیشتر و اشتباهات این فصل.

فصل 11 – تست داده


I was curious and tried to use the ‘unoptimized’ version of this :

activation2 = ActivationSoftmax()

lossCalculator = LossCategoricalCrossEntropy()

At one point in the process (epoch 1427 to be exact) an error occurs where the program encounters a division by zero. Has anyone tried the same thing ?

فصل نهم-پس انتشار

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

فصل هشتم-گرادیان ها، مشتقات جزئی و قانون زنجیره ای

گرادیان ها، مشتقات جزئی و قانون زنجیره ای دو تا از آخرین قطعات پازل، قبل از ادامه کدنویسی شبکه عصبی، مفاهیم مرتبط گرادیان ها و مشتقات جزئی هستند. مشتقاتی که تاکنون حل کرده ایم مواردی بوده اند که فقط یک متغیر مستقل در تابع وجود دارد – یعنی نتیجه فقط به x بستگی  دارد. با این حال ، شبکه عصبی ما به عنوان مثال از نورون ها تشکیل شده است که دارای ورودی های متعدد هستند. هر ورودی در وزن مربوطه ضرب می شود (تابعی از 2 پارامتر) ، و آنها با بایاس جمع می شوند (تابعی از پارامترهای به

فصل هفتم-مشتقات

مشتقات تغییر تصادفی و جستجوی وزن ها و سوگیری های بهینه به یک دلیل اصلی مثمر ثمر نبود: تعداد ترکیبات احتمالی وزن ها و سوگیری ها بی نهایت است و برای دستیابی به هر موفقیتی به چیزی هوشمندانه تر از شانس خالص نیاز داریم. هر وزن و سوگیری نیز ممکن است درجات مختلفی از تأثیر بر کاهش داشته باشد – این تأثیر به خود پارامترها و همچنین به نمونه فعلی بستگی دارد که ورودی لایه اول است. سپس این مقادیر ورودی در وزن ها ضرب می شوند، بنابراین داده های ورودی بر خروجی نورون تأثیر می گذارند و بر تأثیری

فصل ششم-معرفی بهینه سازی

معرفی بهینه سازی اکنون که شبکه عصبی ساخته شده است، می تواند داده ها را از آن عبور دهد و قادر به محاسبه ضرر باشد، گام بعدی تعیین نحوه تنظیم وزن ها و بایاس ها برای کاهش تلفات است. یافتن روشی هوشمندانه برای تنظیم وزن و سوگیری ورودی نورون ها برای به حداقل رساندن ضرر، مشکل اصلی شبکه های عصبی است. اولین گزینه ای که ممکن است به آن فکر کنید این است که به طور تصادفی وزن ها را تغییر دهید، ضرر را بررسی کنید و این کار را تکرار کنید تا زمانی که با کمترین ضرر یافت شده

فصل پنجم-محاسبه خطای شبکه با ضرر

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

فصل چهارم-توابع فعال سازی

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

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

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *