Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Вивчайте Реалізація Зворотного Поширення Помилки | Нейронна Мережа з Нуля
Вступ до нейронних мереж

bookРеалізація Зворотного Поширення Помилки

Загальний підхід

Під час прямого поширення кожен шар ll приймає вихідні значення попереднього шару, al1a^{l-1}, як вхідні дані та обчислює власні виходи. Тому метод forward() класу Layer приймає вектор попередніх виходів як єдиний параметр, а решта необхідної інформації зберігається всередині класу.

Під час зворотного поширення кожному шару ll потрібен лише dalda^l для обчислення відповідних градієнтів і повернення dal1da^{l-1}, тому метод backward() приймає вектор dalda^l як параметр. Вся інша необхідна інформація вже зберігається в класі Layer.

Похідні функцій активації

Оскільки для зворотного поширення потрібні похідні функцій активації, такі функції активації як ReLU та sigmoid слід реалізовувати у вигляді класів, а не окремих функцій. Це дозволяє визначити як:

  1. Саму функцію активації (реалізується через метод __call__()), що дозволяє застосовувати її безпосередньо в класі Layer за допомогою self.activation(z);
  2. Її похідну (реалізується через метод derivative()), що забезпечує ефективне зворотне поширення та використовується в класі Layer як self.activation.derivative(z).

Структуруючи функції активації як об'єкти, їх можна легко передавати до класу Layer та використовувати динамічно.

ReLu

Похідна функції активації ReLU виглядає наступним чином, де ziz_i — це елемент вектора преактивацій zz:

f(zi)={1,zi>00,zi0f'(z_i) = \begin{cases} 1, z_i > 0\\ 0, z_i \le 0 \end{cases}
class ReLU:
    def __call__(self, z):
        return np.maximum(0, z)

    def derivative(self, z):
        return (z > 0).astype(float)

Сигмоїда

Похідна сигмоїдної функції активації має вигляд:

f(zi)=f(zi)(1f(zi))f'(z_i) = f(z_i) \cdot (1 - f(z_i))
class Sigmoid:
    def __call__(self, x):
        return 1 / (1 + np.exp(-z))

    def derivative(self, z):
        sig = self(z)
        return sig * (1 - sig)

Для обох цих функцій активації застосування відбувається до всього вектора zz, так само і для їх похідних. NumPy внутрішньо застосовує операцію до кожного елемента вектора. Наприклад, якщо вектор zz містить 3 елементи, похідна обчислюється так:

f(z)=f([z1z2z3])=[f(z1)f(z2)f(z3)]f'(z) = f'( \begin{bmatrix} z_1\\ z_2\\ z_3 \end{bmatrix} ) = \begin{bmatrix} f'(z_1)\\ f'(z_2)\\ f'(z_3) \end{bmatrix}

Метод backward()

Метод backward() відповідає за обчислення градієнтів за допомогою наступних формул:

dzl=dalfl(zl)dWl=dzl(al1)Tdbl=dzldal1=(Wl)Tdzl\begin{aligned} dz^l &= da^l \odot f'^l(z^l)\\ dW^l &= dz^l \cdot (a^{l-1})^T\\ db^l &= dz^l\\ da^{l-1} &= (W^l)^T \cdot dz^l \end{aligned}

a^{l-1} та zlz^l зберігаються як атрибути inputs і outputs відповідно у класі Layer. Функція активації ff зберігається як атрибут activation.

Після обчислення всіх необхідних градієнтів можна оновити ваги та зміщення, оскільки вони більше не потрібні для подальших обчислень:

Wl=WlαdWlbl=blαdbl\begin{aligned} W^l &= W^l - \alpha \cdot dW^l\\ b^l &= b^l - \alpha \cdot db^l \end{aligned}

Таким чином, learning_rate (α\alpha) є ще одним параметром цього методу.

def backward(self, da, learning_rate):
    dz = ...
    d_weights = ...
    d_biases = ...
    da_prev = ...

    self.weights -= learning_rate * d_weights
    self.biases -= learning_rate * d_biases

    return da_prev
Note
Примітка

Оператор * виконує покомпонентне множення, тоді як функція np.dot() виконує скалярний добуток у NumPy. Атрибут .T транспонує масив.

question mark

Яке з наведеного найкраще описує роль методу backward() у класі Layer під час зворотного поширення помилки?

Select the correct answer

Все було зрозуміло?

Як ми можемо покращити це?

Дякуємо за ваш відгук!

Секція 2. Розділ 8

Запитати АІ

expand

Запитати АІ

ChatGPT

Запитайте про що завгодно або спробуйте одне із запропонованих запитань, щоб почати наш чат

Awesome!

Completion rate improved to 4

bookРеалізація Зворотного Поширення Помилки

Свайпніть щоб показати меню

Загальний підхід

Під час прямого поширення кожен шар ll приймає вихідні значення попереднього шару, al1a^{l-1}, як вхідні дані та обчислює власні виходи. Тому метод forward() класу Layer приймає вектор попередніх виходів як єдиний параметр, а решта необхідної інформації зберігається всередині класу.

Під час зворотного поширення кожному шару ll потрібен лише dalda^l для обчислення відповідних градієнтів і повернення dal1da^{l-1}, тому метод backward() приймає вектор dalda^l як параметр. Вся інша необхідна інформація вже зберігається в класі Layer.

Похідні функцій активації

Оскільки для зворотного поширення потрібні похідні функцій активації, такі функції активації як ReLU та sigmoid слід реалізовувати у вигляді класів, а не окремих функцій. Це дозволяє визначити як:

  1. Саму функцію активації (реалізується через метод __call__()), що дозволяє застосовувати її безпосередньо в класі Layer за допомогою self.activation(z);
  2. Її похідну (реалізується через метод derivative()), що забезпечує ефективне зворотне поширення та використовується в класі Layer як self.activation.derivative(z).

Структуруючи функції активації як об'єкти, їх можна легко передавати до класу Layer та використовувати динамічно.

ReLu

Похідна функції активації ReLU виглядає наступним чином, де ziz_i — це елемент вектора преактивацій zz:

f(zi)={1,zi>00,zi0f'(z_i) = \begin{cases} 1, z_i > 0\\ 0, z_i \le 0 \end{cases}
class ReLU:
    def __call__(self, z):
        return np.maximum(0, z)

    def derivative(self, z):
        return (z > 0).astype(float)

Сигмоїда

Похідна сигмоїдної функції активації має вигляд:

f(zi)=f(zi)(1f(zi))f'(z_i) = f(z_i) \cdot (1 - f(z_i))
class Sigmoid:
    def __call__(self, x):
        return 1 / (1 + np.exp(-z))

    def derivative(self, z):
        sig = self(z)
        return sig * (1 - sig)

Для обох цих функцій активації застосування відбувається до всього вектора zz, так само і для їх похідних. NumPy внутрішньо застосовує операцію до кожного елемента вектора. Наприклад, якщо вектор zz містить 3 елементи, похідна обчислюється так:

f(z)=f([z1z2z3])=[f(z1)f(z2)f(z3)]f'(z) = f'( \begin{bmatrix} z_1\\ z_2\\ z_3 \end{bmatrix} ) = \begin{bmatrix} f'(z_1)\\ f'(z_2)\\ f'(z_3) \end{bmatrix}

Метод backward()

Метод backward() відповідає за обчислення градієнтів за допомогою наступних формул:

dzl=dalfl(zl)dWl=dzl(al1)Tdbl=dzldal1=(Wl)Tdzl\begin{aligned} dz^l &= da^l \odot f'^l(z^l)\\ dW^l &= dz^l \cdot (a^{l-1})^T\\ db^l &= dz^l\\ da^{l-1} &= (W^l)^T \cdot dz^l \end{aligned}

a^{l-1} та zlz^l зберігаються як атрибути inputs і outputs відповідно у класі Layer. Функція активації ff зберігається як атрибут activation.

Після обчислення всіх необхідних градієнтів можна оновити ваги та зміщення, оскільки вони більше не потрібні для подальших обчислень:

Wl=WlαdWlbl=blαdbl\begin{aligned} W^l &= W^l - \alpha \cdot dW^l\\ b^l &= b^l - \alpha \cdot db^l \end{aligned}

Таким чином, learning_rate (α\alpha) є ще одним параметром цього методу.

def backward(self, da, learning_rate):
    dz = ...
    d_weights = ...
    d_biases = ...
    da_prev = ...

    self.weights -= learning_rate * d_weights
    self.biases -= learning_rate * d_biases

    return da_prev
Note
Примітка

Оператор * виконує покомпонентне множення, тоді як функція np.dot() виконує скалярний добуток у NumPy. Атрибут .T транспонує масив.

question mark

Яке з наведеного найкраще описує роль методу backward() у класі Layer під час зворотного поширення помилки?

Select the correct answer

Все було зрозуміло?

Як ми можемо покращити це?

Дякуємо за ваш відгук!

Секція 2. Розділ 8
some-alt