Поиск по сайту Поиск

Стэнфордский курс: лекция 4. Введение в нейронные сети

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

Предыдущие лекции:

Лекция 1. Введение
Лекция 2. Классификация изображений
Лекция 3. Функция потерь и оптимизация

Вспомним определение классификатора, на котором мы остановились. Функция f принимает данные x и параметры W на вход и выдаёт вектор оценок s для каждой из категорий, которые вы хотите классифицировать. Также у нас есть функция потерь L (например, SVM), определяющая, насколько правильные получились оценки. С её помощью мы можем вычислить потери данных. Узнать «простоту» модели помогает регуляризация. 

Наша цель — найти параметры W, соответствующие наименьшим потерям. Для этого мы используем отрицательное направление градиента функции L, который указывает путь к её минимуму.

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

Введение в теорию графов и backpropagation

Вычислительный граф — это иллюстрированная запись какой-либо функции, состоящая из вершин и рёбер. Вершины (иногда их ещё называют узлы) — вычислительные операции, которые необходимо выполнить, а рёбра связывают их в определённую последовательность. 

На рисунке выше изображён пример графа с нашим классификатором. Узел с операцией (*) означает умножение матриц параметров W и данных x, результатом которого является вектор весов s. Следующая вершина зависимых потерь (hinge loss) определяет потери данных L. Узел R вычисляет регуляризацию. И, наконец, в самом конце мы получаем общие потери, суммируя регуляризацию и потери данных.

Преимущество графов в том, что они позволяют использовать так называемый метод обратного распространения ошибки (backpropagation). Этот алгоритм рекурсивно использует правило дифференцирования сложной функции для вычисления градиента каждой переменной в графе. Метод становится очень полезным для действительно сложных функций, которые применяются в свёрточных нейросетях. Рассмотрим, как он работает.

Начнём с простого примера:

У нас есть функция f(x,y,z) = (x+y)z и мы хотим найти её градиенты по отношению к каждой из переменных. На вычислительном графе отражены операции x+y и (x+y)z в виде вершин сложения (+) и умножения (*). Для примера взяты значения x = 2, y = 5, z = 4, поэтому: −2 + 5 = 3 (промежуточный результат после узла сложения), а 3 * (4) = 12 (результат умножения).

Обозначим какими-нибудь буквами наши промежуточные значения. Пусть переменная после (+) называется q (q = x+y), тогда функция f будет равна qz. Выпишем градиенты (частные производные): градиент q зависит от переменных x и y, а градиент f от q и z. Поскольку мы ищем градиент исходной функции f, которая зависит от всех трёх переменных x, y и z, то нам хотелось бы найти df/dx, df/dy и df/dz.

Частные производные dq/dx и dq/dy равны единице — это означает, что при любом изменении x или y q изменится точно так же.

Попробуем рекурсивно воспользоваться правилом дифференцирования сложной функции. Начнём с конца вычислительного графа и будем двигаться к началу, по пути считая все градиенты. Самое последнее полученное нами значение — функция f и её промежуточный результат −12, частная производная по которому равна единице. Далее идёт переменная z, и мы знаем, что df/dz = q = 3. Переходим к вершине (+) и переменной q: производная df/dq равна z, то есть −4

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

Теперь мы приблизились к производной df/dy. Правило дифференцирования сложной функции говорит, что она может быть записана как df/dq * dq/dy. Значения df/dq и dq/dy нам известны: они равны −4 и 1 соответственно, поэтому производная dfdy равна −4. Точно так же это работает и для df/dx:

Здесь мы нашли ещё одну закономерность: в вершине сложения оказались равными все три градиента: и самой вершины, и входящие в неё. Обратите внимание: значение производной для узла, который следует за текущим, называется восходящий градиент, а для предыдущего узла — локальный градиент. Например, для (+) восходящим градиентом является производная df/df, а локальным — производные df/dx и df/dy.

Мы справились с задачей и нашли градиенты каждой переменной. Метод обратного распространения ошибки может сильно упрощать вычисление градиентов для громоздких функций. Убедимся в этом на более сложном примере.

Обратное распространение ошибки: продвинутый уровень

Возьмём функцию f, вычислительный граф которой выглядит следующим образом:

Здесь сходу посчитать производные уже не так просто, как в предыдущем случае. Нам нужно найти градиенты df/dw0, df/dw1, df/dw2, df/dx0 и df/dx1. Для вычислений возьмём тестовые значения w0 = 2, w1 =3, w2 =3, x0 =1 и x1 =2. Если подставить их в функцию, то на выходе получим f = 0.73.

Снова начнём с конца и посчитаем производную функции df/df, которая, очевидно, равна единице. Движемся дальше — первая вершина содержит сложную функцию 1/x. Можно воспользоваться таблицей производных и вспомнить, что 1/x = x-1. В следующем узле находится константа +1, производная которой, как вы помните, равна нулю. Третья вершина — это экспонента в степени x, а четвёртая — умножение на −1

На рисунке выше расписаны вычисления градиентов для первых четырёх узлов. Заметьте, что на каждом шаге мы умножаем локальный градиент на восходящий, чтобы выполнялось правило дифференцирования. Локальные градиенты считаются по формулам в нижней части рисунка.

Мы подошли к узлу сложения с двумя входящими в него рёбрами, для которого известен восходящий градиент 0.2. В предыдущем простом примере мы выяснили, что в вершине сложения градиент по отношению к каждому из входов будет равен 1. Поэтому мы просто берём единицу (локальный градиент) и умножаем её на восходящий градиент для обоих рёбер:

Точно так же выполняем и со следующим узлом сложения. 

Продвинемся к первым двум переменным: w0 и x0. Перед ними находится вершина умножения. Ранее мы обнаружили, что в этом случае градиент по отношению к одному из входов просто является значением другого входа, умноженным на восходящий градиент. Поэтому df/dw0 = 2 * 0.2 = 0.4, а df/dw0 = (1) * 0.2 = 0.2. Таким же образом разбираемся со второй вершиной напротив w1 и x1:

Вот и всё! Было непросто, но мы справились.

Прокачайте свой граф

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

Возьмём, например, сигмоиду. Если присмотреться, то она похожа на функцию из предыдущего примера. У сигмоиды есть очень полезное свойство: её производная легко выражается через саму функцию.

Переменные степени экспоненты w0, w1, w2, x0 и x1 складываются в вершине сложения, которая следует сразу за w2. Поэтому мы можем взять ту часть графа, которая полностью совпадает с сигмоидой, и объединить несколько узлов в один. Проверим, что локальные градиенты в этом случае окажутся одинаковыми: просто подставим значение функции f в выражение производной сигмоиды:

Видим, что градиенты совпадают. Теперь наш граф выглядит следующим образом:

Всегда ли необходимо группировать вершины? Это зависит от того, что вам нужно больше: короткий граф со сложными вычислениями, или объёмный с более простыми.

Мы рассмотрели пример со скалярными величинами, но точно так же можно работать с векторами и матрицами: в этом случае на каждом шаге придётся считать матрицу производных, которая также называется матрица Якоби.

Переносим вычислительный граф в код

Все наши предыдущие действия над вычислительными графами делятся на два больших этапа:

  1. Проходим по графу в прямом направлении, подставляя исходные значения переменных и считая все промежуточные результаты;
  2. Идём в обратном направлении, вычисляя градиенты и пользуясь правилом дифференцирования.

В коде их можно описать функциями forward и backward, которые будут выглядеть примерно так:

При этом мы можем установить механику forward и backward для каждой вершины. Вот пример функции, которая обрабатывает узел умножения по установленным правилам:

Рассмотрим популярный фреймворк для глубокого обучения Caffe. Зайдём в его репозиторий и откроем папку Layers:

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

Таким образом, почти любую нейросеть можно представить в виде графа. Теперь мы плавно приблизились к тому, как устроены архитектуры настоящих нейронных сетей.

Нейронные сети: начинаем с малого

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

Вспомним функцию линейного классификатора, о которой мы так много говорили: f = Wx. Если мы захотим «превратить» её в нейросеть, то нам надо будет разделить параметры W на две части: W1 и W2 и применить одно линейное преобразование поверх другого:

Мы получили простую двухслойную нейросеть с двумя линейными слоями. На рисунке выше x — входные данные, h — промежуточная нелинейность (мы поговорим о ней чуть позже) и s — выходной вектор оценок. В более широком смысле, нейронные сети — это сложные функции, состоящие из простых. 

В одной из предыдущих лекций мы упоминали, что каждая строка весовой матрицы W является шаблоном одного из классов. Эти шаблоны выглядели, как некий усреднённый объект:

Также мы говорили о том, что с такими единичными шаблонами возникает проблема: например, в классе “car” машина окрашена в красный цвет, в то время как в реальных данных могут встречаться автомобили и других цветов. Многослойные сети решают этот вопрос: W1 содержит те же единичные шаблоны, но теперь оценки для них хранятся в промежуточной нелинейной переменной h. И следующий слой W2 объединит шаблоны с помощью взвешенной суммы, что позволит дать более точные оценки для машин других цветов и прочих разнообразных объектов.

Кстати, ничто не мешает нам добавить ещё один слой, чтобы повысить точность распознавания:

Именно так появляются глубокие нейросети.

Для наглядности посмотрим на код простой двухслойной сети, которую можно реализовать всего в 20 строк:

Artificial vs Biological

На самом деле искусственные и биологические нейронные сети имеют мало общего. Тем не менее, именно изучение человеческого мышления вдохновило учёных на создание AI. 

В нашем мозге присутствует огромное число нейронов, которые соединены друг с другом специальными «ветвями» — аксонами. Когда происходит какая-либо мыслительная деятельность, по нейронам проходят электрические импульсы, передающиеся от одних клеток к другим. У нейронов есть дендриты — это отростки, к которым приходят импульсы. Тело клетки объединяет в себе все поступающие сигналы и посылает их в следующие нейроны через свой аксон. Место, где происходит контакт двух нейронов, называется синапс.

Если мы посмотрим на вершины вычислительных графов, то сможем провести параллели: например, данные xi — это исходные импульсы, поступающие в узел через дендрит, а веса wi — синапсы. Тело клетки обрабатывает сигналы и отправляет их к следующим вершинам через выходной аксон с помощью функции активации.

Функция активации вычисляет выходное значение текущего нейрона в зависимости от результата взвешенной суммы. Сигмоида, кстати, — одна из её разновидностей. Это и есть та самая нелинейность, которая вводится между линейными слоями нейросети. В биологическом смысле она служит для приведения нейронов в активное состояние.

Хотя искусственные и настоящие нейроны кажутся очень похожими, всё-таки нужно учесть несколько важных моментов:

— биологические нейроны делятся на множество разновидностей;

— реальные дендриты могут выполнять сложные нелинейные вычисления;

— синапсы — это не просто веса, а сложная нелинейная динамическая система;

— обычной функции активации на практике может быть недостаточно.

Поэтому будьте осторожны с биологическими аналогиями.

⌘⌘⌘

В следующий раз мы поговорим о свёрточных нейросетях, обсудим их историю, особенности архитектуры и области применения. Не забывайте задавать вопросы в комментариях, если что-то кажется вам сложным или непонятным — мы всегда поможем разобраться.

Следующие лекции (список будет дополняться по мере появления материалов):

Лекция 5. Свёрточные нейронные сети
Лекция 6. Обучение нейросетей, часть 1
Лекция 7. Обучение нейросетей, часть 2
Лекция 8. ПО для глубокого обучения
Лекция 9. Архитектуры CNN
Лекция 10. Рекуррентные нейронные сети

С оригинальной лекцией можно ознакомиться на YouTube.

10 лучших IDE и редакторов кода для веб-разработчиков

10 лучших IDE и редакторов кода для веб-разработчиков

Писать код при желании можно и в текстовом редакторе — ничто не мешает вам создать простейший сайт в «Блокноте», сохранив...
Read More
Domains weekly: .РФ на страже русского языка, рост new gTLDs и пассивный доход от PORNO.COM

Domains weekly: .РФ на страже русского языка, рост new gTLDs и пассивный доход от PORNO.COM

В новой подборке новостей мы расскажем, как развивался русский язык вместе с зоной .РФ, что за риски таит в себе...
Read More
VPS нового поколения, ИИ, юникодные домены и мини‑сериал об админах: всё, что вы знали и чего могли не знать о REG.RU

VPS нового поколения, ИИ, юникодные домены и мини‑сериал об админах: всё, что вы знали и чего могли не знать о REG.RU

Ура-ура! 22 мая нам исполнилось 14 лет, и мы по-прежнему двигаемся только вперёд и становимся лучше. Мы решили поделиться с...
Read More
Domains weekly: старт .MEET от Google, годовой рост .RU и .РФ, вирусная реклама рэп‑альбома с new gTLDs

Domains weekly: старт .MEET от Google, годовой рост .RU и .РФ, вирусная реклама рэп‑альбома с new gTLDs

В новой еженедельной подборке новостей расскажем о старте регистраций в зоне  .MEET от Google, вирусной рекламной кампании нового рэп-альбома Future...
Read More
Как скорость загрузки страниц на мобильных устройствах влияет на посещаемость сайта

Как скорость загрузки страниц на мобильных устройствах влияет на посещаемость сайта

Поисковые системы уделяют большое внимание скорости загрузки сайтов с мобильных устройств, и этот фактор ранжирования становится всё более важным. В...
Read More
Популярные уязвимости сайтов: чем опасны и как их избежать

Популярные уязвимости сайтов: чем опасны и как их избежать

Для любого, кто управляет веб-сайтом, на первом месте должен стоять вопрос безопасности. Критические угрозы и уязвимости могут сильно ударить как...
Read More
Domains weekly: 10 лет .РФ, новый топ регистраторов в .COM и спор за ягодный домен

Domains weekly: 10 лет .РФ, новый топ регистраторов в .COM и спор за ягодный домен

В свежей подборке новостей расскажем о юбилее .РФ, отчёте ICANN о динамике регистраций в зоне .COM и неудачной попытке канадской...
Read More
С днём рождения, .РФ!

С днём рождения, .РФ!

В этом году кириллической национальной российской доменной зоне исполняется 10 лет. Мы решили вспомнить, как всё начиналось: в этом материале...
Read More
Domains weekly: стагнация ccTLD, конец страстей по .ORG и взлом клиентов GoDaddy

Domains weekly: стагнация ccTLD, конец страстей по .ORG и взлом клиентов GoDaddy

В сегодняшней подборке новостей расскажем, как изменилось число регистраций национальных доменов по сравнению с прошлым годом, чем закончилась сделка с...
Read More
Как поменять домен, чтобы сайт не просел в поисковой выдаче

Как поменять домен, чтобы сайт не просел в поисковой выдаче

Итак, вы решили изменить имя своего сайта после ребрендинга или просто выбрали более короткий домен. Но как при этом сохранить...
Read More