Руководство хакера по нейронным сетям. Глава 2: Машинное обучение. Обобщаем SVM до нейронной сети

http://karpathy.github.io/neuralnets/
  • Перевод
Содержание:
Глава 1: Схемы реальных значений
Часть 1:
   Введение   
      Базовый сценарий: Простой логический элемент в схеме
      Цель
         Стратегия №1: Произвольный локальный поиск

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

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

Часть 4:
      Схемы с несколькими логическими элементами
         Обратное распространение ошибки

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

Часть 6:
      Становимся мастером обратного распространения ошибки


Глава 2: Машинное обучение
Часть 7:
      Бинарная классификация

Часть 8:
      Обучение сети на основе метода опорных векторов (SVM)

Часть 9:
      Обобщаем SVM до нейронной сети

Часть 10:
      Более традиционный подход: Функции потерь



Интересен тот факт, что 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;
  // несмотря на громоздкость, это можно использовать на практике.
  // готово!
}


Вот таким образом мы обучаем нейронную сеть. Очевидно, вам захочется аккуратно разбить свой код на блоки, но я специально показал вам этот пример в надежде, что он объясняет все четко и понятно. Позже мы рассмотрим наиболее оптимальные способы применения этих сетей, и будем структурировать код намного более аккуратно, модульным и более разумным образом.

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

PAYSTO

38,00

Компания

Поделиться публикацией
Комментарии 1
    –1
    Было бы неплохо ввести нумерацию статей, и список с содержанием в конце каждой для удобства. Не знаю, как по мне — не хватает живых примеров, я всё не читал, но отсылок к каким-либо фидлам в jsfiddle или репозиториям в github не нашёл. Нужна наглядность, а так получается — грубое описание методов без привязки к каким либо реальным задачам. Можно оформить каких-то gif'ок… в общем было бы желание.

    Вы же серьёзная контора — за эти статьи наверняка кому-то платятся деньги, займитесь нормальным оформлением.

    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

    Самое читаемое