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

Стэнфордский курс: лекция 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.

Domains weekly: самые опасные зоны, проблемы доменов для взрослых и недоступный Whois

Domains weekly: самые опасные зоны, проблемы доменов для взрослых и недоступный Whois

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

Поведенческие факторы ранжирования и их влияние на SEO: взгляд изнутри

Редакция блога продолжает цикл образовательных SEO-статей. Сегодня вместе с SEO-специалистом REG.RU Евгением Сметаниным мы расскажем, что такое поведенческие факторы ранжирования,...
Read More
Безвозмездно, то есть даром: что можно получить бесплатно в REG.RU

Безвозмездно, то есть даром: что можно получить бесплатно в REG.RU

В REG.RU мы постоянно работаем над развитием и улучшением сервисов, и на первое место всегда ставим заботу о клиентах. У...
Read More
Domains weekly: неудавшийся захват Domovoy.ru, гранты от ICANN и домен, приносящий богатство

Domains weekly: неудавшийся захват Domovoy.ru, гранты от ICANN и домен, приносящий богатство

Сегодня поделимся новостями о том, как сеть супермаркетов не смогла заполучить желаемый домен, почему в Китае ценятся числовые адреса и...
Read More
Шпаргалка по Python для Django

Шпаргалка по Python для Django

В Python очень много полезных функций, библиотек и других элементов, перечислить которые в одном материале очень сложно. Мы поделимся базовой...
Read More
Domains weekly: безопасное инвестирование, открытие зоны .NEW и блокчейн‑домены

Domains weekly: безопасное инвестирование, открытие зоны .NEW и блокчейн‑домены

Дайджест домейнера с новостями о безопасном способе инвестирования в домены, политике ICANN в отношении доменных споров, открытии общедоступной регистрации .NEW...
Read More
Как подготовить и провести вебинар на любую тему: стратегия из 8 шагов от REG.RU

Как подготовить и провести вебинар на любую тему: стратегия из 8 шагов от REG.RU

Харизматичный спикер, интересная тема, качественная презентация, внимательные слушатели — что же ещё нужно для хорошего вебинара? В этом материале мы...
Read More
Domains weekly: популярные ccTLDs в России, 17‑летняя ошибка Microsoft и уязвимости аукционных доменов

Domains weekly: популярные ccTLDs в России, 17‑летняя ошибка Microsoft и уязвимости аукционных доменов

Сегодня расскажем о том, как изменился рынок доменных имён в 2019 году, какие национальные домены кроме .RU и .РФ используют...
Read More
10 фишек Облачных серверов REG.RU

10 фишек Облачных серверов REG.RU

Если вы выбрали для своего проекта VPS, то наверняка знаете об их особенностях. Но что, если мы скажем, что Облачные...
Read More
Настраиваем шифрование жесткого диска, чтобы избежать утечек данных

Настраиваем шифрование жесткого диска, чтобы избежать утечек данных

В каждой компании есть сотрудники, которые хранят на рабочем компьютере конфиденциальную информацию, и её утечка может оказаться катастрофой. Среди таких...
Read More