Керівництво хакера по нейронних мереж. Глава 2: Машинне навчання. Узагальнюємо SVM до нейронної мережі

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

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

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

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

Частина 5:
Шаблони в «зворотному» потоці 
Приклад "Один нейрон"

Частина 6:
Стаємо майстром зворотного поширення помилки


Розділ 2: Машинне навчанняЧастина 7:
Бінарна класифікація

Частина 8:
Навчання мережі на основі методу опорних векторів (SVM)

Частина 9:
Узагальнюємо SVM до нейронної мережі



Цікавий той факт, що SVM є всього лише окремим видом дуже простої схеми (схеми, яка обчислює score = a*x + b*y + c, де a,b,c є ваговими функціями, а x,y являють собою точки введення даних). Його можна легко розширити до більш складних функцій. Наприклад, давайте запишемо двошарову нейронну мережу, яка виконує бінарну класифікацію. Прохід вперед буде виглядати наступним чином:

// задамо початкові значення x,y
var n1 = Math.max(0, a1*x + b1*y + c1); // активація 1-го нейрона прихованого
var n2 = Math.max(0, a2*x + b2*y + c2); // 2-го нейрона
var n3 = Math.max(0, a3*x + b3*y + c3); // 3-го нейрона
var score = a4*n1 + b4*n2 + c4*n3 + d4; // результат


Вищевказане визначення є двошарової нейронної мережею з 3 прихованими нейронами (n1, n2, n3), в якій використовується нелінійність випрямленої лінійного елемента (Rectified Linear Unit або ReLU) для кожного нейрона прихованого. Як ви бачите, тепер у нас є кілька параметрів, що означає, що наш класифікатор став більш складним і може ускладнювати межі прийняття рішень, а не тільки використовувати просте лінійне правило прийняття рішень як, наприклад, SVM. Ще один спосіб подання — що кожен з трьох прихованих нейронів є лінійним класифікатором, і в даний момент ми створюємо додатковий лінійний класифікатор поверх них. Тепер почнемо заглиблюватися :). Так, давайте навчимо цю двошарову нейронну мережу. Код виглядає дуже схоже на приклад коду SVM, наведений вище, нам потрібно змінити тільки прохід вперед і назад:

// довільні початкові параметри
var a1 = Math.random() - 0.5; // довільне число між -0,5 0,5
// ... таким же чином ініціалізуємо всі інші параметри 
for(var iter = 0; iter < 400; iter++) {
// обираємо довільну точку введення даних
var i = Math.floor(Math.random() * data.length);
var x = data[i][0];
var y = data[i][1];
var label = labels[i];

// обчислюємо прохід вперед
var n1 = Math.max(0, a1*x + b1*y + c1); // активація 1-го нейрона прихованого
var n2 = Math.max(0, a2*x + b2*y + c2); // 2-го нейрона
var n3 = Math.max(0, a3*x + b3*y + c3); // 3-го нейрона
var score = a4*n1 + b4*n2 + c4*n3 + d4; // результат

// обчислюємо натяг зверху 
var pull = 0.0;
if(label === 1 && score < 1) pull = 1; // нам потрібен більший результат! Тягнемо вгору.
if(label === -1 && score > -1) pull = -1; // нам потрібен менший результат! Тягнемо вниз.

// тепер розраховуємо зворотний прохід для всіх параметром моделі

// зворотне поширення помилки через останній «результативний» нейрон
var dscore = pull;
var da4 = n1 * dscore;
var dn1 = a4 * dscore;
var db4 = n2 * dscore;
var dn2 = b4 * dscore;
var dc4 = n3 * dscore;
var dn3 = c4 * dscore;
var dd4 = 1.0 * dscore; // фух

// зворотне поширення помилки нелінійностей ReLU, на місці
// тобто просто встановлюємо градієнти в нульове значення, якщо нейрони не «вистрілюють»
var dn3 = n3 === 0 ? 0 : dn3;
var dn2 = n2 === 0 ? 0 : dn2;
var dn1 = n1 === 0 ? 0 : dn1;

// зворотне поширення до параметрів нейрона 1
var da1 = x * dn1;
var db1 = y * dn1;
var dc1 = 1.0 * dn1;

// зворотне поширення до параметрів нейрона 2
var da2 = x * dn2;
var db2 = y * dn2;
var dc2 = 1.0 * dn2;

// зворотне поширення до параметрів нейрона 3
var da3 = x * dn3;
var db3 = y * dn3;
var dc3 = 1.0 * dn3;

// фух! Кінець зворотного поширення помилки!
// зверніть увагу, що ми могли виконати зворотне поширення на x,y
// але нам не потрібні ці градієнти. Ми використовуємо тільки градієнти
// за нашими параметрами при їх оновленні, і опускаємо x,y

// додаємо натяг з боку регуляризації, підштовхуючи всі розмножувальні
// параметри (тобто не систематичні помилки) вниз, пропорційно їх значень
da1 += -a1; da2 += -a2; da3 += -a3;
db1 += -b1; db2 += -b2; db3 += -b3;
da4 += -a4; db4 += -b4; dc4 += -c4;

// нарешті, виконуємо оновлення параметра
var step_size = 0.01;
a1 += step_size * da1; 
b1 += step_size * db1; 
c1 += step_size * dc1;
a2 += step_size * da2; 
b2 += step_size * db2;
c2 += step_size * dc2;
a3 += step_size * da3; 
b3 += step_size * db3; 
c3 += step_size * dc3;
a4 += step_size * da4; 
b4 += step_size * db4; 
c4 += step_size * dc4; 
d4 += step_size * dd4;
// незважаючи на громіздкість, це можна використовувати на практиці.
// готово!
}


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

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

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

0 коментарів

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