Керівництво хакера по нейронних мереж. Схеми реальних значень. Схеми з декількома логічними елементами

ЗмістЧастина 1:
Вступ
Глава 1: Схеми реальних значень
Базовий сценарій: Простий логічний елемент у схемі
Мета
Стратегія №1: Довільний локальний пошук

Частина 2
Стратегія №2: Числовий градієнт

Частина 3:
Стратегія №3: Аналітичний градієнт

Частина 4:
Схеми з декількома логічними елементами
Зворотне поширення помилки



Ви напевно скажете: «Аналітичний градієнт досить простий, якщо брати похідну для ваших простих виразів. Але це марно. Що я буду робити, коли вирази стануть набагато більше? Хіба рівняння не стануть великими та складними досить швидко?». Хороше питання. Так, вирази стають набагато складніше. Але ні, це не робить все значно важче.

Як ми побачимо далі, кожен логічний елемент буде існувати сам по собі, абсолютно не підозрюючи про нюанси величезною і складної схеми, частиною якої він є. Він буде турбуватися тільки про своїх вихідних значеннях, і буде обчислювати свої локальні похідні так само, як описано в попередньому розділі, за винятком того, що тут буде одне додаткове множення, яке йому треба буде виконати.

Одне додаткове множення перетворить один (непотрібний) логічний елемент в шестірню в складному механізмі, який і являє собою цілу нейронну мережу.


Напевно, вистачить нахвалювати його. Сподіваюся, я порушив ваш інтерес. Давайте заглибимося в деталі і включимо в наш наступний приклад два логічних елементи:

image

Вираз, який ми обчислюємо в даний момент, виглядає наступним чином: f(x,y,z)=(x+y)z. Давайте сформуємо структуру коду, щоб представити логічні елементи у вигляді функцій:

var forwardMultiplyGate = function(a, b) { 
return a * b;
};
var forwardAddGate = function(a, b) { 
return a + b;
};
var forwardCircuit = function(x,y,z) { 
var q = forwardAddGate(x, y);
var f = forwardMultiplyGate(q, z);
return f;
};

var x = -2, y = 5, z = -4;
var f = forwardCircuit(x, y, z); // результат дорівнює -12


У вищенаведеному прикладі я використовую a та b в якості локальних змінних функції логічних елементів. Таким чином, ми не будемо плутати їх з вихідними значеннями схеми x,y,z. Як і раніше, ми зацікавлені в пошуку похідних по відношенню до цих трьох вихідним значенням: x,y,z. Але як ми будемо розраховувати їх тепер, коли у нас є декілька логічних елементів?

Для початку, давайте удамо, що елемента + тут немає, і що у нас є тільки дві змінні в схемі: q,z і один логічний елемент *. Зверніть увагу, що q є результуючим значенням логічного елемента +. Якщо нам не треба турбуватися про x і y, а тільки про q і z, тоді ми повертаємося тільки до одного логічного елемента, оскільки задіяний тільки елемент *, і ми знаємо, що таке (аналітичні) похідні з попереднього розділу. Ми можемо записати їх (при цьому, замінюючи x,y, q,z) наступним чином:

image

Все досить просто: це вираження градієнта по відношенню до q і z. Але стійте, нам не потрібен градієнт по відношенню до q, а тільки по відношенню до вихідних значень: x і y. На щастя, q обчислюється як функція x і y (шляхом додавання в нашому прикладі). Ми можемо записати градієнт для логічного елемента додавання таким же чином, навіть простіше:

image

Все правильно, похідні просто дорівнюють 1, незалежно від фактичних значень x і y. Якщо ви задумалися про це, то це має сенс, так як щоб зробити результат одного логічного елемента складання вище, нам потрібно позитивне зміна x і y, незалежно від їх значень.

Зворотне поширення помилки

Ми, нарешті, готові скористатися Ланцюговим правилом: ми знаємо, як обчислювати градієнт q по відношенню до x і y (це у випадку одного логічного елемента — +). І ми знаємо, як обчислювати градієнт нашого кінцевого результату по відношенню до q. Ланцюгове правило розповідає нам, як комбінувати ці підходи, щоб отримати градієнт кінцевого результату по відношенню до x і y, в якому ми, зрештою, зацікавлені. Найкраще те, що ланцюгове правило просто стверджує, що найправильніше — це взяти і перемножити градієнти, щоб зв'язати їх. Наприклад, остаточної похідної для x буде:

image

Тут багато символів, тому це знову ж таки може здатися складним, але це всього лише два числа, помножуються між собою. Ось код:

// початкові умови
var x = -2, y = 5, z = -4;
var q = forwardAddGate(x, y); // q дорівнює 3
var f = forwardMultiplyGate(q, z); // результат дорівнює -12

// градієнт логічного елемента МНОЖЕННЯ по відношенню до його початкових значень
// wrt - це скорочення «щодо» ("with respect to")
var derivative_f_wrt_z = q; // 3
var derivative_f_wrt_q = z; // -4

// похідна логічного елемента додавання по відношенню до його початкових значень
var derivative_q_wrt_x = 1.0;
var derivative_q_wrt_y = 1.0;

// ланцюгове правило
var derivative_f_wrt_x = derivative_q_wrt_x * derivative_f_wrt_q; // -4
var derivative_f_wrt_y = derivative_q_wrt_y * derivative_f_wrt_q; // -4


От і все. Ми вирахували градієнт і тепер можемо дозволити нашим вихідним значенням трохи реагувати на нього. Давайте додамо градієнти в верхню частину вихідних значень. Вихідне значення схеми потрібно зробити більше, ніж -12!

// кінцевий градієнт, відповідно до вищевказаного: [-4, -4, 3]
var gradient_f_wrt_xyz = [derivative_f_wrt_x, derivative_f_wrt_y, derivative_f_wrt_z]

// дозволимо вихідним значенням реагувати на силу/поштовх:
var step_size = 0.01;
x = x + step_size * derivative_f_wrt_x; // -2.04
y = y + step_size * derivative_f_wrt_y; // 4.96
z = z + step_size * derivative_f_wrt_z; // -3.97

// Наша схема тепер повинна мати більш високий результат:
var q = forwardAddGate(x, y); // q стає 2.92
var f = forwardMultiplyGate(q, z); // результат дорівнює -11.59, більше, ніж -12! 


Здається, спрацювало! Давайте тепер спробуємо інтуїтивно інтерпретувати, що тільки що сталося. Схема хоче видавати в результаті більш високі значення. Останній логічний елемент побачив вихідні значення q = 3, z = -4 і визначили результат -12. Шляхом «підштовхування» вгору, це вихідне значення доклало силу на обидва значення — q і z: щоб збільшити вихідне значення, схема «хоче», щоб збільшилось значення z, як це видно з позитивного значення похідної (derivative_f_wrt_z = +3). Знову ж таки, розмір цієї похідної можна тлумачити як величину сили. З одного боку, до q була прикладена велика спадна сила, так як derivative_f_wrt_q = -4. Іншими словами, схема хоче зменшити q, приклавши до нього силу рівну 4.

Тепер ми підходимо до другого логічного елемента — "+", який видає вихідне значення q. За замовчуванням, логічний елемент + обчислює свої похідні, які повідомляють нам, як потрібно змінити x і y, щоб зробити q більше. АЛЕ! Ось важливий момент: градієнт значення q був обчислений як негативне число (derivative_f_wrt_q = -4), тому схема хоче зменшити модуль q, докладаючи силу, рівну 4! Тому, якщо логічний елемент + хоче допомогти зробити остаточне вихідне значення більше, він повинен прислухатися до сигналів градієнта, що надходять зверху. Зокрема, в цьому випадку, він повинен підштовхнути x,y в протилежному напрямку від того, куди він міг їх підштовхнути при нормальних умовах, і з силою, рівною 4, якщо можна так висловитися. Множення на -4, використовуване в ланцюговому правило, призводить до наступного: замість прикладання позитивної сили дорівнює +1 на обидва значення x та y (місцеву похідну), повний градієнт схеми по x і y стає рівним 1 x -4 = -4. В цьому є сенс: схема хоче, щоб x і y стали менше, так як це призведе до зменшення q, що, в свою чергу, призведе до збільшення f.

Якщо для вас в цьому є сенс, значить, ви розумієте зворотне поширення помилки.


Давайте знову повторимо те, що ми дізналися:

У попередньому розділі ми побачили, що у випадку з одним логічним елементом (або одним виразом), ми можемо взяти похідну аналітичного градієнта з допомогою простого розрахунку. Ми інтерпретували як градієнт сили або поштовх на вихідні значення, який штовхає їх у напрямку, який може зробити результат цього логічного елемента вище.

У випадку з декількома логічними елементами, все залишається приблизно так само: кожен логічний елемент існує самостійно, не підозрюючи про схему, у яку він включений. Надходять деякі початкові значення, і логічний елемент обчислює результат і похідну по відношенню до вихідних значень. Єдина відмінність на даному етапі полягає в тому, що раптово щось може підштовхнути цей логічний елемент зверху. Це градієнт остаточного вихідного значення схеми по відношенню до результату, який обчислив цей логічний елемент. Саме схема запитує, щоб логічний елемент видав у результаті більша або менша кількість, прикладаючи певну силу. Логічний елемент просто бере цю силу та помножує її на всі сили, які він обчислив до цього для своїх вихідних значень (ланцюгове правило). Це призвело до бажаних результатів:

1. Якщо на логічний елемент виявляється сильний позитивний поштовх зверху, він теж сильніше штовхає власні вихідні значення, диференційовані за силою, прикладеною на нього зверху
2. А якщо на нього виявляється негативний поштовх, це означає, що схема хоче, щоб його значення зменшувалося, а не збільшувалося, тому він докладе силу поштовху на свої вихідні значення, щоб зробити власне вихідне значення менше.

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


Хіба це не чудово? Єдина різниця між сценарієм з одним логічним елементом і декількома взаємодіючими логічними елементами, які довільно обчислюють складні вирази, це додаткова операція множення, яка зустрічається у кожному логічному елементі.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.