Мы начинаем публиковать перевод книги (как называет ее сам автор) «Руководство хакера по нейронным сетям». Книга состоит из четырех частей, две из которых уже закончены. Мы постараемся разбить текст на логически завершенные части, размер которых позволит не перегружать читателя. Также мы будем следить за обновлением книги и опубликуем перевод новых частей после их появления в блоге автора.
Часть 1:
Введение
Глава 1: Схемы реальных значений
Базовый сценарий: Простой логический элемент в схеме
Цель
Стратегия №1: Произвольный локальный поиск
Всем привет, я аспирант по вычислительной технике в Стэнфорде. Несколько лет, как часть моего исследования, я работал над глубинным обучением, и среди нескольких связанных с ним моих любимых проектов — ConvNetJS – Javascript-библиотека для обучения нейронных сетей. Javascript позволяет хорошо визуализировать то, что происходит, и дает возможность разобраться с различными настройками гиперпараметров, но я все равно регулярно слышу от людей пожелания рассмотреть эту тему более подробно. Эта статья (которую я планирую постепенно расширить до объема нескольких глав) – это моя скромная попытка сделать это. Я размещаю ее в сети, вместо того, чтобы создать в формате PDF, как и следует поступать с книгами, потому что она должна (и я надеюсь, будет) в конечном итоге содержать анимации/демонстрации и пр.
Мой личный опыт работы над нейронными сетями показывает, что все становится намного понятнее, когда я начинаю игнорировать полностраничность, плотные производные уравнений обратного распространения ошибки, и просто начинаю писать код. Поэтому, это руководство будет содержать очень немного математики (мне не кажется, что это необходимо, и это может местами даже усложнить понимание простых концепций). Так как моя специальность – вычислительная техника и физика, я, вместо этого, буду развивать эту тему с точки зрения хакера. Мое изложение будет сосредоточено вокруг кода и физической интуиции, вместо математических производных. В основном я буду пытаться представить алгоритмы таким образом, какой я хотел бы использовать, когда я только начинал.
"… все стало намного понятнее, когда я начал писать код"
Наверняка вы захотите перескочить и сразу начать изучать нейронные сети, как их можно использовать на практике и пр. Но прежде чем мы возьмемся за это, я бы хотел, чтобы мы обо всем этом забыли. Давайте сделаем шаг назад и разберемся, что, по сути, происходит. Давайте сначала поговорим о схемах реальных значений.
Глава 1: Схемы реальных значений
По моему мнению, лучший способ представить себе нейронные сети – это в качестве схем реальных значений, в которых реальные значения (вместо булевых {0,1}) «протекают» вдоль границ и взаимодействуют в логических элементах. Однако, вместо таких логических элементов, как AND, OR,NOT и пр., у нас есть двоичные логические элементы, такие как * (умножить), + (прибавить), max, или унарные логические элементы, такие как exp и пр. Однако, в отличие от обычных булевых схем, у нас в конечном итоге также будут градиенты, протекающие по тем же границам схемы, но в обратном направлении. Но мы забегаем вперед. Давайте сконцентрируемся и начнем с простого.
Базовый сценарий: Простой логический элемент в схеме
Давайте сначала рассмотрим единичную простую схему с одним логическим элементом. Вот пример:
Схема берет два исходных реальных значения x и y и перемножает x * y с помощью логического элемента *.
Javascript-версия этого уравнения будет выглядеть очень просто, например, так:
var forwardMultiplyGate = function(x, y) {
return x * y;
};
forwardMultiplyGate(-2, 3); // returns -6.
А в математическом виде мы можем рассмотреть этот логический элемент в качестве воспроизведения функции с реальными значениями:
f(x,y)=xy
Как и в этом примере, все наши логические элементы будут брать одно или два исходных значения и выдавать одно выходное значение.
Цель
Проблема, изучение которой нас интересует, выглядит следующим образом:
1. Мы вводим в приведенную схему определенные исходные значения (например, x = -2, y = 3)
2. Схема вычисляет выходное значение (например, -6)
3. В результате этого возникает ключевой вопрос: Как нужно незначительно изменить исходящее значение, чтобы увеличить результат?
В данном случае, в какую сторону нам следует изменить x,y, чтобы получить число больше, чем -6? Следует учесть, что, например, x = -1.99 и y = 2.99 дает в результате x * y = -5.95, что уже больше, чем -6.0. Не пугайтесь:-5.95 лучше (выше), чем -6.0. Это улучшение на 0.05, даже несмотря на то, что величина -5.95 (расстояние от нуля) иногда ниже.
Стратегия №1: Произвольный локальный поиск
Хорошо. У нас есть схема, у нас есть несколько исходных значений и нам просто нужно немного их изменить, чтобы увеличить выходное значение? В чем сложность? Мы можем легко «помочь» схеме вычислить результат для любого заданного значения x и y. Разве это не просто? Почему бы не изменить x и y произвольно и не отследить, какое из изменений дает наилучший результат:
// схема, содержащая пока один логический элемент
var forwardMultiplyGate = function(x, y) { return x * y; };
var x = -2, y = 3; // some input values
// пытаемся произвольно изменить x,y на небольшую величину и отследить, что дает наилучший результат
var tweak_amount = 0.01;
var best_out = -Infinity;
var best_x = x, best_y = y;
for(var k = 0; k < 100; k++) {
var x_try = x + tweak_amount * (Math.random() * 2 - 1); // немного изменяем x
var y_try = y + tweak_amount * (Math.random() * 2 - 1); // немного изменяем y
var out = forwardMultiplyGate(x_try, y_try);
if(out > best_out) {
// вот оптимальное улучшение! Следим за изменением x и y
best_out = out;
best_x = x_try, best_y = y_try;
}
}
Когда я это запустил, я получил best_x = -1.9928, best_y = 2.9901, и best_out = -5.9588. Опять же, -5.9588 выше, чем -6.0. Ну, мы закончили, правильно? Не совсем: Это идеальная стратегия для маленьких проблем с небольшим количеством логических элементов, если в вашем распоряжении есть время на вычисление, но это не сработает, если мы захотим разобраться с большими схемами с миллионами исходных значений. Оказывается, мы можем больше, но об этом в следующей части.
Автор: Irina_Ua