Введение в архитектуры нейронных сетей

3c18dfad31738c1b68a9c612f338d468.png

Григорий Сапунов (Intento)


Меня зовут Григорий Сапунов, я СТО компании Intento. Занимаюсь я нейросетями довольно давно и machine learning«ом, в частности, занимался построением нейросетевых распознавателей дорожных знаков и номеров. Участвую в проекте по нейросетевой стилизации изображений, помогаю многим компаниям.

Давайте перейдем сразу к делу. Моя цель — дать вам базовую терминологию и понимание, что к чему в этой области, из каких кирпичиков собираются нейросети, и как это использовать.

План доклада такой. Сначала небольшое введение про то, что такое нейрон, нейросеть, глубокая нейросеть, чтобы мы с вами общались на одном языке.

Дальше я расскажу про важные тренды, что происходит в этой области. Затем мы углубимся в архитектуру нейросетей, рассмотрим 3 основных их класса. Это будет самая содержательная часть.

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


На конференции Наталья Ефремова из компании NTechLab рассказывала о практических кейсах. Я же расскажу, как нейросети устроены внутри, из каких кирпичиков они внутри состоят.

Краткое содержание


719ef840a52f666efe25a773d487359e.png

Recap: нейрон, нейросеть, глубокая нейросеть


Краткое напоминание

355148987ed68fe7592f074b1e746625.png

Искусственный нейрон — это очень отдаленное подобие биологического нейрона.

Что такое искусственный нейрон? Это простая функция на самом деле. У нее есть входы. Каждый вход умножается на некие веса, дальше все суммируется, прогоняется через какую-то нелинейную функцию, результат выдается на выход — все, это один нейрон.

Если вы знакомы с логистической регрессией, под которой понимаем нелинейную функцию SIGMOID, то один нейрон — это полный аналог логистической регрессии, простого линейного классификатора.

На самом деле существует много разных функций активации, в том числе приведенные на рисунке гиперболический тангенс (TANH), SIGMOID, RELU.

8f85a4771f737614646b5bcd5aaf9ee6.png

В реальности все сильно сложнее. Этой темы касаться не будем.

Я привел совсем базовое представление искусственного нейрона, как некое подобие биологического нейрона.

7d888d66cda6787af984befdc025ef78.png

Искусственная нейросеть — это способ собрать нейроны в сеть, чтобы она решала определенную задачу, например, задачу классификации. Нейроны собираются по слоям. Есть входной слой, куда подается входной сигнал, есть выходной слой, откуда снимается результат работы нейросети, и между ними есть скрытые слои. Их может быть 1, 2, 3, много. Если скрытых слоев больше, чем 1, нейросеть считается глубокой, если 1, то неглубокой.

3c18dfad31738c1b68a9c612f338d468.png

Существует огромное разнообразие различных архитектур, основные из которых мы рассмотрим. Но имейте в виду, что их очень много. Если интересно, перейдите потом по ссылке — посмотрите, почитайте.

5969d4395ea17419793401ec4183b4a6.png

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

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

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

Recap: важные тренды


Что сейчас происходит с качеством и сложностью моделей

f9d5c2aa8367586fc0c9ad1571a45473.png

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

На диаграмме фиолетовым цветом выделен Deep Learning, голубым — классический алгоритм компьютерного зрения. Видно, что появился Deep Learning, ошибка уменьшилась и продолжает уменьшаться дальше. Именно поэтому Deep Learning целиком вытесняет все, условно, классические алгоритмы.

Другая важная веха — то, что мы начинаем обгонять по качеству человека. На соревновании ImageNet это впервые произошло в 2015 году. Но на самом деле нейросетевые системы, которые по качеству превосходят человека, появились раньше. Первый задокументированный внятный случай — это 2011 год, когда была построена система, которая распознавала немецкие дорожные знаки и делала это в 2 раза лучше человека.

9a444d3ccb09d4a3cb088bfa74feda49.png

Второй важный тренд — сложность нейросетей растет. В терминах глубины растет глубина. Если победитель 2012 года на ImageNet — сеть AlexNet — там было меньше 10 слоев, то в 2014 году их было уже больше 20, в 2015 — под 150. В этом году, кажется, уже за 200. Что будет дальше — непонятно, возможно, будет еще больше.

86a596ec3fa0b9da421e871813c44895.png

http://cs.unc.edu/~wliu/papers/GoogLeNet.pdf

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

dbe4454395cda02e033dba012a4d40be.png

https://culurciello.github.io/tech/2016/06/04/nets.html

Это график точности различных нейросетей. Здесь указано время, которое требуется на выполнение, на просчет этой сети, то есть некая вычислительная нагрузка. Размер кружка — это количество параметров, которые описываются нейросетью. Интересно сравнить классическую сеть AlexNet — победителя 2012 года и более поздние сети. Они лучше по точности, но, как правило, содержат меньше параметров. Это тоже важный тренд, что нейросети усложняются очень умно. То есть архитектура изменяется так, что даже несмотря на то, что число слоев 150, общее количество параметров оказывается меньше, чем в 6–7-слойной сети, которая в 2012 году была. Архитектура как-то усложняется очень интересным способом.

ced7e7e63e27edc26f9c98f2eb5cba55.png

Еще один тренд — рост объемов данных. В 1998 году для обучения сверточной
нейросети, которая распознавала рукописные чеки, было использовано 10 7 пикселей, в 2012 году (IMAGENET) — 10 14.

7 порядков за 14 лет — это безумная разница и огромный сдвиг!

396a3b7a6a528ef1ad74851257ff6663.png

При этом количество транзитов на процессоре тоже растет, растут вычислительные мощности — закон Мура действует. За эти 14 лет процессоры стали условно в 1000 раз быстрее. Это видно на примере GPUs, которые сейчас доминируют в области Deep Learning. Практически все считается на графических ускорителях.

Компания NVIDIA перепрофилировалась из игровой фактически в компанию для искусственного интеллекта. Ее экспоненты оставили далеко позади экспоненты Intel, которые на этом фоне вообще не смотрятся.

Это картинка 2013 года, когда топовая видеокарта была 4,5 TFLOPS. Сейчас новый TITAN X — это уже 11 TFLOPS. В общем, экспонента продолжается!

На самом деле можно ожидать, что в ближайшее время появится FPGA«сики, которые частично потеснят GPU, и, может быть, со временем появятся даже нейроморфные процессоры. Следите за этим — там тоже много интересного происходит.

Архитектуры нейросетей. Нейросети прямого распространения


Fully Connected Feed-Forward Neural Networks, FNN

Первая классическая архитектура — полносвязные нейросети прямого распространения, или Fully Connected Feed-Forward Neural Network, FNN.

442b7cbb29128bd5f02c8cdcc78eb655.png

Многослойный Perceptron — это вообще классика нейросетей. Та картинка нейросетей, которую вы видели, это он и есть — многослойная полносвязная сеть. Полносвязная — это значит, что каждый нейрон связан со всеми нейронами предыдущего слоя. Хорошая сеть, работает, для классификации годится, многие задачи классификации успешно решаются.

Однако у нее есть 2 проблемы:

  • Много параметров


Например, если взять нейросеть из 3 скрытых слоев, которой нужно обрабатывать картинки 100×100 ps, это значит, что на входе будет 10 000 ps, и они заводятся на 3 слоя. В общем, если честно посчитать все параметры, у такой сети их будет порядка миллиона. Это на самом деле много. Чтобы обучить нейросеть с миллионом параметров, нужно очень много обучающих примеров, которые не всегда есть. На самом деле сейчас примеры есть, а раньше их не было — поэтому, в частности, сети не могли обучать, как следует.

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

Плюс есть другая проблема под названием:

  • Затухающие градиенты


Помните ту историю про Backpropagation, когда ошибка с выходов отправляется на вход, распределяется по всем весам и отправляется дальше по сети? Далее эти производные — то есть градиент (производная ошибки) — прогоняются через нейросеть обратно. Когда в нейросети много слоев, от этого градиента в самом конце может остаться очень-очень маленькая часть. В этом случае веса на входе будет практически невозможно изменить потому, что этот градиент практически «сдох», его там нет.

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

c38d28cd634b4ad3e0bfb6935ab10a13.png

Есть различные вариации FNN-сетей. Например, очень интересная вариация Автоэнкодер. Это сеть прямого распространения с так называемым бутылочным горлышком в середине. Это очень маленький слой, допустим, всего на 10 нейронов.

В чем преимущества такой нейросети?

Цель этой нейросети взять какой-то вход, прогнать через себя и на выходе сгенерировать тот же самый вход, то есть чтобы они совпадали. В чем смысл? Если мы сможем обучить такую сеть, которая берет вход, прогоняет через себя и генерирует точно такой же выход, это значит, что этих 10 нейронов в середине достаточно для описания этого входа. То есть можно очень сильно уменьшить пространство, сократить объём данных, экономно закодировать любые входные данные в новых терминах 10 векторов.

Это удобно, и это работает. Такие сети могут помочь вам, например, уменьшить размерность вашей задачи или найти какие-то интересные фичи, которые можно использовать.

ed70c7deca8bdee99ef2e4dfd29bfc0d.png

Есть еще интересная модель RBM. Я ее написал в вариации FNN, но на самом деле это не правда. Во-первых, она не глубокая, во-вторых, она не Feed-Forward. Но она часто связана с FNN-сетями.

Что это такое?

Это неглубокая модель (на слайде она в уголке нарисована), у которой есть вход и есть какой-то скрытый слой. Вы подаете сигнал на вход и пытаетесь обучить скрытый слой так, чтобы он генерил этот вход.

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

d5bdbaf5717f1b555ac248fbb5e606c1.png

Чем хороши RBM — тем, что их можно использовать для обучения глубоких сетей. Есть такой термин — Deep Belief Networks (DBN) — фактически это способ обучения глубоких сетей, когда берутся отдельно 2 нижних слоя глубокой сети, подается вход и RBM обучается на этих первых двух слоях. После этого фиксируются эти веса. Далее берется второй слой, рассматривается как отдельная RBM и точно также обучается. И так по всей сети. Потом эти RBM стыкуются, объединяются в одну нейросеть. Получается глубокая нейросеть, какая и должна была бы быть.

Но теперь есть огромное преимущество — если бы раньше вы ее обучали просто с какого-то рандомного (случайного) состояния, то теперь оно не рандомное — сеть обучена восстанавливать или генерить данные предыдущего слоя. То есть у нее веса разумные, и на практике это приводит к тому, что такие нейросети действительно уже довольно неплохо обучены. Их потом можно слегка дообучить какими-то примерами, и качество такой сети будет хорошим.

Плюс есть дополнительное преимущество. Когда вы используете RBM, вы, по сути, работаете на неразмеченных данных, что называется Un supervised learning. У вас есть просто картинки, вы не знаете их классов. Вы прогнали миллионы, миллиарды картинок, которые вы скачали с Flickr«а или еще откуда-то, и у вас есть какая-то структура в самой сети, которая описывает эти картинки.

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

Дальше вы увидите, что вся эта история на самом деле — про Lego. То есть у вас есть отдельные сети — рекуррентные нейросети, еще какие-то сети — это все блоки, которые можно совмещать. Они хорошо совмещаются на разных задачах.

Это были классические нейросети прямого распространения. Далее перейдем к сверточным нейросетям.

Архитектуры нейросетей: Сверточные нейросети


Convolutional Neural Networks, CNN

7e11db737d47a8add0827df12505469b.png

https://research.facebook.com/blog/learning-to-segment/

Сверточные нейросети решают 3 основные задачи:

  1. Классификация. Вы подаете картинку, и нейросеть просто говорит — у вас картинка про собаку, про лошадь, еще про что-то, и выдает класс.
  2. Детекция — это более продвинутая задачка, когда нейросеть не просто говорит, что на картинке есть собака или лошадь, но находит еще Bounding box — где это находится на картинке.
  3. Сегментация. На мой взгляд, это самая крутая задача. По сути, это попиксельная классификация. Здесь мы говорим про каждый пиксель изображения: этот пиксель относится к собаке, этот — к лошади, а этот еще к чему-то. На самом деле, если вы умеете решать задачу сегментации, то остальные 2 задачи уже автоматически даны.


4730c47f8ed2a86daa40356e88cc7dbd.png

Что такое сверточная нейросеть? На самом деле сверточная нейросеть — это обычная Feed-Forward сеть, просто она немножко специального вида. Вот уже начинается Lego.

Что есть в сверточной сети? У нее есть:

  • Сверточные слои — я дальше расскажу, что это такое;
  • Subsampling, или Pooling-слои, которые уменьшают размер изображения;
  • Обычные полносвязные слои, тот самый многослойный персептрон, который просто сверху навешен на эти первые 2 хитрых слоя.


Немного более подробно про все эти слои.

4628ddbdea420895e5b0e426fae3c1b5.png

  • Сверточные слои обычно рисуются в виде набора плоскостей или объемов. Каждая плоскость на таком рисунке или каждый срез в этом объеме — это, по сути, один нейрон, который реализует операцию свертки. Опять же, дальше я расскажу, что это такое. По сути, это матричный фильтр, который трансформирует исходное изображение в какое-то другое, и это можно делать много раз.
  • Слои субдискретизации (буду называть Subsampling, так проще) просто уменьшают размер изображения: было 200×200 ps, после Subsampling стало 100×100 ps. По сути, усреднение, чуть более хитрое.
  • Полносвязные слои обычно персептрон использует для классификации. Ничего специального в них нет.


65644e038835331869e3938fc8cde463.png

http://intellabs.github.io/RiverTrail/tutorial/

Что такое операция свертки? Это всех пугает, но на самом деле это очень простая вещь. Если вы работали в Photoshop и делали Gaussian Blur, Emboss, Sharpen и кучу других фильтров, это все матричные фильтры. Матричные фильтры — это на самом деле и есть операция свертки.

Как она реализована? Есть матрица, которая называется ядром фильтра (на рисунке kernel). Для Blur это будут все единицы. Есть изображение. Эта матрица накладывается на кусочек изображения, соответствующие элементы просто перемножаются, результаты складываются и записываются в центральную точку.

09e08ec5407bec4e6b78e2b0303ce336.png

http://intellabs.github.io/RiverTrail/tutorial/

Так это выглядит более наглядно. Есть изображение Input, есть фильтр. Вы пробегаете фильтром по всему изображению, честно перемножаете соответствующие элементы, складываете, записываете в центр. Пробегаете, пробегаете — построили новое изображение. Все, это операция свертки.

То есть, по сути, свертка в сверточных нейросетях — это хитрый цифровой фильтр (Blur, Emboss, что угодно еще), который сам обучается.

12a61ead639a0036f27b770925f3e653.png

http://cs231n.github.io/convolutional-networks/

На самом деле сверточные слои все работают на объемах. То есть если даже взять обычное изображение RGB, там уже 3 канала — это, по сути, не плоскость, а объем из 3-х, условно, кубиков.

Свертка в этом случае уже представляется не матрицей, а тензором — кубиком на самом деле.

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

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

Что делают такие нейроны? Они фактически учатся искать какие-то фичи, какие-то локальные признаки в той небольшой части, которую они видят — и все. Прогон одного такого фильтра — это построение некой карты нахождения этих признаков в изображении.

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

43761ca43da0f4f6e5920e56cf7185cf.png

http://vaaaaaanquish.hatenablog.com/entry/2015/01/26/060622

Операция Pooling — еще более простая операция. Это просто усреднение либо взятие максимума. Она тоже работает на каких-то небольших квадратиках, например, 2×2. Вы накладываете на изображение и, например, выбираете максимальный элемент из этого квадратика 2×2, отправляете на выход.

Таким образом вы уменьшили изображение, но не хитрым Average, а чуть более продвинутой штукой — взяли максимум. Это дает небольшую инвариантность к смещениям. То есть вам не важно, какой-то признак нашелся в этой позиции или на 2 ps вправо. Эта штука позволяет нейросети быть чуть более устойчивой к сдвигам изображения.

536cceebbbfe79cc9ab897a713e04ee6.png

http://cs231n.github.io/convolutional-networks/

Так работает Pooling-слой. Есть кубик какого-то объема — 3 канала, 10, или 100 каналов, которые вы насчитали свертками. Он просто уменьшает его по ширине и по высоте, остальные размерности не трогает. Все — примитивная вещь.

57f0ac26615a8c131d8dca161733ad63.png

Чем хороши сверточные сети?

Они хороши тем, что у них сильно меньше параметров, чем у обычной полносвязной сети. Вспомните пример с полносвязной сетью, который мы рассматривали, где получился миллион весов. Если взять аналогичную, точнее, похожую — аналогичной ее нельзя назвать, но близкую сверточную нейросеть, у которой будет такой же вход, такой же выход, тоже будет один полносвязный слой на выходе и еще 2 сверточных слоя, где тоже будет по 100 нейронов, как в базовой сети, то выяснится, что число параметров в такой нейросети уменьшилось больше, чем на порядок.

Это здорово, если параметров настолько меньше — сеть проще обучать. Мы это видим, ее действительно проще обучать.

1885855e00c81842e46eab62013aa624.png

Что делает сверточная нейросеть?

По сути, она автоматически учит какие-то иерархические признаки для изображений: сначала базовые детекторы, линии разного наклона, градиенты и т.д. Из них она собирает какие-то более сложные объекты, потом еще более сложные.

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

40ed8d18d16fce12d39945fae8c4cc80.png

Современные архитектуры сверточных нейросетей сильно усложнились. Те нейросети, которые побеждали на последних соревнованиях ImageNet — это уже не просто какие-то сверточные слои, Pooling-слои. Это прямо законченные блоки. На рисунке приведены примеры из сети Inception (Google) и ResNet (Microsoft).

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

Это была совсем классика сверточных нейросетей. Да, там есть разные типы слоев, которые можно использовать для классификации. Но есть более интересные применения.

4ef1a56d92647176c1f03f93448b60f3.png

https://arxiv.org/abs/1411.4038

Например, есть такая разновидность сверточных нейросетей, называется Fully-convolutional networks (FCN). Про них редко говорят, но это классная вещь. Можно взять и оторвать последний многослойный персептрон, он не нужен — и выкинуть его. И тогда нейросеть магическим образом может работать на изображениях произвольного размера.

То есть она научилась, допустим, определять 1000 классов в изображениях кошечек, собачек, чего-то еще, а потом мы последний слой взяли и не оторвали, но трансформировали в сверточный слой. Там нет проблем — можно веса пересчитать. Тогда выясняется, что эта нейросеть как бы работает тем же самым окном, на которое она была обучена, 100×100 ps, но теперь она может пробежаться этим окном по всему изображению и построить как бы тепловую карту на выходе — где в этом конкретном изображении находится конкретный класс.

Вы можете построить, например, 1000 этих Heatmap для всех своих классов и потом использовать это для определения места нахождения объекта на картинке.

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

a61bda9d8388ed5e231da9b84d336c1c.png

http://cvlab.postech.ac.kr/research/deconvnet/

Более продвинутый пример — Deconvolution networks. Про них тоже редко говорят, но это еще более классная штука.

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

Что это такое? По сути, это обучаемый Upsampling. То есть вы уменьшили в какой-то момент ваше изображение до какого-то небольшого размера, может, даже до 1 ps. Скорее, не до пикселя, а до какого-то небольшого вектора. Потом можно взять этот вектор и раскрыть.

Или, если в какой-то момент получилось изображение 10×10 ps, теперь можно делать Upsampling этого изображения, но каким-то хитрым способом, в котором веса Upsampling также обучаются.

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

На самом деле много задач можно свести к генерации картинок. Классификация — классная задача, но она все-таки не всеобъемлющая. Есть много задач, где надо картинки генерить. Сегментация — это в принципе классическая задача, где надо на выходе картинку иметь.

Более того, если вы научились делать так, то можно сделать еще по-другому, более интересно.

f6a257b280d9a4c01fb71833c35fa929.png

https://arxiv.org/abs/1411.5928

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

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

0fc5f12af36fe13ed0b35a5cc017f2cf.png

https://arxiv.org/abs/1508.06576

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

be18964da703d44c30825ae87f0c4502.png

https://arxiv.org/abs/1508.06576

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

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

Единственная проблема этого метода в том, что он долгий. Этот итеративный прогон картинки туда-сюда — это долго. Кто игрался с этим кодом по генерации стиля, знает, что классический код долгий, и надо помучиться. Все сервисы типа Призмы и так далее, которые генерят более-менее быстро, работают по-другому.

8721ad43970ad085378350cfe9ddcaf6.png

https://arxiv.org/abs/1603.03417

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

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

Это такие хакерские методы использования нейросетей, но оказалось, что они работают, и это приводит к классным результатам.

Далее перейдем к рекуррентным нейросетям.

Архитектуры нейросетей: Рекуррентные нейросети


Recurrent Neural Networks, RNN

7129572b2675e11655d950078ce18493.png

Рекуррентная нейросеть на самом деле очень крутая штука. На первый взгляд, главное отличие их от обычных FNN-сетей в том, что просто появляется какая-то циклическая связь. То есть скрытый слой свои же значения отправляет сам на себя на следующем шаге. Казалось бы, вроде бы минорная вещь, но есть принципиальная разница.

daa0359837994cefd1c7f31025815be0.png

Про обычную нейросеть Feed-Forward известно, что это универсальный аппроксиматор. Они могут аппроксимировать более-менее любую непрерывную функцию (есть такая теорема Цыбенко). Это здорово, но рекуррентные нейросети — тьюринг полный. Они могут вычислить любое вычислимое.

По сути рекуррентные нейросети — это обычный компьютер. Задача — его правильно обучить. Потенциально он может считать любой алгоритм. Другое дело, что научить его сложно.

Кроме того, обычные Feed-Forward-нейросети никакой возможности не имеют учесть порядок во времени — нет в них этого, не представлено. Рекуррентные сети это делают явным образом, в них зашито понятие времени.

Обычные Feed-Forward сети не обладают никакой памятью, кроме той, которая была получена на этапе обучения, а рекуррентные обладают. За счет того, что содержимое слоя передается нейросети обратно, это как бы ее память. Она хранится во время работы нейросети. Это тоже очень многое добавляет.

b04271af2ae279f8db2ac11de666e50e.png

http://colah.github.io/posts/2015–08-Understanding-LSTMs/

Как обучаются рекуррентные нейросети? На самом деле почти так же. Кроме Backpropagation, конечно, существует много других алгоритмов, но в данный момент Backpropagation работает лучше всего.

Для рекуррентных нейросетей есть вариация этого алгоритма — Backpropagation through time. Идея очень простая — вы берете рекуррентную нейросеть и цикл просто разворачиваете на сколько-то шагов, например, на 10, 20 или 100, и получается обычная глубокая нейросеть, которую после этого вы обучаете обычным Backpropagation.

Но есть проблема. Как только мы начинаем говорить про глубокие нейросети — где 10, 20, 100 слоев — от градиентов, которые с конца должны пройти в самое начало, за 100 слоев ничего не остается. С этим надо что-то делать. В этом месте придумали некий хак, красивое инженерное решение под названием LSTM или GRU- это ячейки памяти.

8a4517731c88d9d177c1e1db00849c51.png

https://deeplearning4j.org/lstm

Их идея заключается в том, что визуализация обычного нейрона заменяется на некую хитрую штуку, у которой есть память и есть gate, которые контролируют то, когда эту память нужно сбросить, перезаписать или сохранить и т.д. Эти gate тоже обучаются так же, как и все остальное. Фактически эта ячейка, когда она обучилась, может сказать нейросети, что сейчас мы держим это внутреннее состояние долго, например, 100 шагов. Потом, когда нейросеть это состояние для чего-то использовала, его можно обнулить. Оно стало не нужно, мы пошли новое считать.

Эти нейросети на всех более-менее серьезных тестах сильно по качеству уделывают обычные классические рекуррентные, которые просто на нейронах. Почти все рекуррентные сети в данный момент построены либо на LSTM, либо на GRU.

e4e604956ad069370718269501d0ad83.png

http://kvitajakub.github.io/2016/04/14/rnn-diagrams

Не буду углубляться, что это такое внутри, но это такие хитрые блоки, сильно сложнее, чем обычные нейроны, но, по сути, они похожи. Там есть некие gate, которые контролируют это самое «запомнить — не запомнить», «передать дальше — не передать дальше».

Это были классические рекуррентные нейросети. Дальше начинается тема, о которой часто умалчивается, но она тоже важная.

1c59c681aa1521c9d317dc0f51027b83.png

Когда мы работаем с последовательностью в рекуррентной сети, мы обычно подаем один элемент, потом следующий, и заводим предыдущее состояние сети на вход, возникает такое естественное направление — слева направо. Но оно же не единственное! Если у нас есть, например, предложение, мы начинаем подавать его слова в обычном порядке в нейросеть — да, это нормальный способ, но почему бы с конца не подать?

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

Это и называется Bidirectional — двунаправленная рекуррентная нейросеть. Их качество еще выше, чем обычных рекуррентных с

© Habrahabr.ru