Pull to refresh

Введение в нейросети

Reading time 9 min
Views 47K
Original author: Ben Gorman
image

Искусственные нейронные сети сейчас находятся на пике популярности. Можно задаться вопросом, сыграло ли громкое название свою роль в маркетинге и применении этой модели. Я знаю некоторых бизнес-менеджеров, радостно упоминающих об использовании в их продуктах «искусственных нейронных сетей» и «глубокого обучения». Так ли рады были бы они, если бы их продукты использовали «модели с соединёнными кругами» или «машины „совершишь ошибку — будешь наказан“»? Но, вне всяких сомнений, искусственные нейросети — стоящая вещь, и это очевидно благодаря их успеху во множестве областей применения: распознавание изображений, обработка естественных языков, автоматизированный трейдинг и автономные автомобили. Я специалист по обработке и анализу данных, но раньше не понимал их, поэтому чувствовал себя мастером, не освоившим свой инструмент. Но наконец я выполнил своё «домашнее задание» и написал эту статью, чтобы помочь другим преодолеть те же самые препятствия, которые встретились мне в процессе моего (всё ещё продолжающегося) обучения.

Код на R для примеров, представленных в этой статье, можно найти здесь в Библии задач машинного обучения. Кроме того, после прочтения этой статьи стоит изучить часть 2, Neural Networks – A Worked Example, в которой приведены подробности создания и программирования нейросети с нуля.

Мы начнём с мотивирующей задачи. У нас есть набор изображений в градациях серого, каждое из которых является сеткой пикселей 2×2, в которой каждый пиксель имеет значение яркости от 0 (белый) до 255 (чёрный). Наша цель — создать модель, которая будет находить изображения с паттерном «лестницы».



На этом этапе нас интересует только нахождение модели, которая сможет логично подбирать данные. Методология подбора нам будет интересна позже.

Предварительная обработка


В каждом изображении мы помечаем пиксели $x_{1}$, $x_{2}$, $x_{3}$, $x_{4}$ и генерируем входной вектор $x=\begin{bmatrix}x_{1}&x_{2}&x_{3}&x_{4}\end{bmatrix}$, который будет являться входными данными нашей модели. Мы ожидаем, что наша модель будет прогнозировать True (изображение содержит паттерн лестницы) или False (изображение не содержит паттерна лестницы).



ImageId x1 x2 x3 x4 IsStairs
1 252 4 155 175 TRUE
2 175 10 186 200 TRUE
3 82 131 230 100 FALSE
498 36 187 43 249 FALSE
499 1 160 169 242 TRUE
500 198 134 22 188 FALSE

Однослойный перцептрон (итерация модели 0)


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

$f(x)={\begin{cases} 1 &{\text{if }}\ w_1x_1 + w_2x_2 + w_3x_3 + w_4x_4 > threshold\\ 0 & {\text{otherwise}} \end{cases}}$



Давайте выразим это иначе

$\widehat y = \mathbf w \cdot \mathbf x + b$



$f(x)={\begin{cases} 1 &{\text{if }}\ \widehat{y} > 0\\ 0 & {\text{otherwise}} \end{cases}}$


Здесь $\hat{y}$ — наша оценка прогноза.

Графически мы можем представить перцептрон как входные узлы, передающие данные выходному узлу.

image


Для нашего примера мы построим следующий перцептрон:

$\hat{y}=-0.0019x_{1}+0.0016x_{2}+0.0020x_{3}+0.0023x_{4}+0.0003$


Вот как будет работать перцептрон на некоторых из обучающих изображений.

image


Это определённо лучше случайных догадок и имеет здравый смысл. У всех паттернов лестниц есть в нижнем ряду тёмные пиксели, что создаёт большие положительные коэффициенты $x_{3}$ и $x_{4}$. Тем не менее, в этой модели есть очевидные проблемы.

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

Случай A

Начнём с изображения x = [100, 0, 0, 125]. Увеличим $x_{3}$ с 0 до 60.

image


Случай B

Начнём с предыдущего изображения, x = [100, 0, 60, 125]. Увеличим $x_{3}$ с 60 до 120.

image


Интуитивно понятно, что случай A должен гораздо сильнее увеличить $\hat{y}$, чем случай B. Однако поскольку наша модель перцептрона является линейным уравнением, то прирост +60 $x_{3}$ в обоих случаях приведёт к приросту +0.12 $\hat{y}$.

У нашего линейного перцептрона есть и другие проблемы, но давайте сначала решим эти две.

Однослойный перцептрон с сигмоидной функцией активации (итерация модели 1)


Мы можем решить проблемы 1 и 2, обернув наш перцептрон в сигмоиду (с последующим выбором других весов). Стоит напомнить, что функция «сигмоида» — это S-образная кривая, ограниченная по вертикальной оси между 0 и 1, благодаря чему она часто используется для моделирования вероятности двоичного события.

$sigmoid(z) = \frac{1}{1 + e^{-z}}$


image


В соответствии с этой мыслью, мы можем дополнить нашу модель следующим изображением и уравнением.

image


$z = w \cdot x = w_1x_1 + w_2x_2 + w_3x_3 + w_4x_4$


$\widehat y = sigmoid(z) = \frac{1}{1 + e^{-z}}$


Выглядит знакомо? Да, это наша старая подруга, логистическая регрессия. Однако она хорошо послужит нам для интерпретации модели как линейного перцептрона с сигмоидной функцией активации, потому что это даёт нам больше возможностей для более общего подхода. Кроме того, поскольку мы теперь можем интерпретировать $\hat{y}$ как вероятность, то нам нужно соответствующим образом изменить правило принятия решений.

$f(x)={\begin{cases} 1 &{\text{if }}\ \widehat{y} > 0.5\\ 0 & {\text{otherwise}} \end{cases}} $



Продолжим с нашим примером задачи и будем считать, что у нас получилась следующая подобранная модель:

$\begin{bmatrix} w_1 & w_2 & w_3 & w_4 \end{bmatrix} = \begin{bmatrix} -0.140 & -0.145 & 0.121 & 0.092 \end{bmatrix}$


$b = -0.008$


$\widehat y = \frac{1}{1 + e^{-(-0.140x_1 -0.145x_2 + 0.121x_3 + 0.092x_4 -0.008)}}$


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

image


Нам определённо удалось решить проблему 1. Посмотрите, как она решает и проблему 2.

Случай A

Начнём с изображения [100, 0, 0, 100]. Увеличим $x_3$" с 0 до 50.

image


Случай B

Начнём с изображения [100, 0, 50, 100]. Увеличим $x_3$" с 50 до 100.

image


Заметьте, как кривизна сигмоиды заставляет случай A «сработать» (быстро увеличиться) с увеличением $z = w \cdot x$, но темп замедляется при продолжении увеличения $z$. Это соответствует нашему интуитивному пониманию, что случай A должен отражать бОльшее увеличение вероятности паттерна лестницы, чем случай B.

image

К сожалению, эта модель по-прежнему имеет проблемы.

  1. $\widehat y$ имеет монотонную связь с каждой переменной. А что если нам нужно распознавать лестницы более светлого оттенка?
  2. Модель не учитывает взаимодействие переменных. Предположим, что нижний ряд изображения чёрный. Если верхний левый пиксель белый, то затемнение верхнего правого пикселя должно увеличить вероятность паттерна лестницы. Если верхний левый пиксель чёрный, то затенение верхнего правого пикселя должно снижать вероятность лестницы. Другими словами, увеличение $x_3$ должно потенциально приводить к увеличению или уменьшению $\widehat y$, в зависимости от значений других переменных. В нашей текущей модели этого никак не достичь.

Многослойный перцептрон с сигмоидной функцией активации (итерация модели 2)


Мы можем решить обе вышеуказанные проблемы, добавив в нашу модель перцептрона ещё один слой. Мы создадим несколько базовых моделей, похожих на представленные выше, но мы будем передавать выходные данные каждой базовой модели на вход другого перцептрона. Эта модель на самом деле является «ванильной» нейронной сетью. Давайте посмотрим, как она может работать в разных примерах.

Пример 1: распознавание паттерна лестницы

  1. Построим модель, которая срабатывает при распознавании «левых лестниц», $\widehat y_{left}$
  2. Построим модель, которая срабатывает при распознавании «правых лестниц», $\widehat y_{right}$
  3. Добавим базовым моделям оценку, чтобы конечная сигмоида срабатывала только если оба значения ($\widehat y_{left}$, $\widehat y_{right}$) велики

image


image


Другой вариант

  1. Построим модель, срабатывающую, когда нижний ряд тёмный, $\widehat y_1$
  2. Построим модель, срабатывающую, когда верхний левый пиксель тёмный и верхний правый пиксель светлый, $\widehat y_2$
  3. Построим модель, срабатывающую, когда верхний левый пиксель светлый и верхний правый пиксель тёмный, $\widehat y_3$
  4. Добавим базовые модели так, что конечная сигмоидная функция срабатывала только когда $\widehat y_1$ и $\widehat y_2$ велики, или когда $\widehat y_1$ и $\widehat y_3$ велики. (Заметьте, что $\widehat y_2$ и $\widehat y_3$ не могут быть большими одновременно.)


image


image


Пример 2: распознать лестницы светлого оттенка

  1. Построим модели, срабатывающие при «затенённом нижнем ряде», «затенённом x1 и белом x2», «затенённом x2 и белом x1», $\widehat y_1$, $\widehat y_2$ и $\widehat y_3$
  2. Построим модели, срабатывающие при «тёмном нижнем ряде», «тёмном x1 и белом x2», «тёмном x2 и белом x», $\widehat y_4$, $\widehat y_5$ и $\widehat y_6$
  3. Соединим модели таким образом, чтобы «тёмные» идентификаторы вычитались из «затенённых» идентификаторов перед сжиманием результата сигмоидой


image

image

Примечание о терминологии


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



Альтернативные функции активации


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

Многоклассовая классификация


Мы с лёгкостью можем расширить нашу модель, чтобы она работала в многоклассовой классификации, с помощью использования нескольких узлов в конечном выходном слое. Идея здесь заключается в том, что каждый выходной узел соответствует одному из классов $C$, которые мы стремимся спрогнозировать. Вместо сужения выхода с помощью сигмоиды, которая отражает элемент из $\mathbb{R}$ в элемент из интервала [0, 1] мы можем использовать функцию softmax, которая отражает вектор в $\mathbb{R}^n$ в вектор в $\mathbb{R}^n$ таким образом, что сумма элементов получившегося вектора равна 1. Иными словами, мы можем создать такую сеть, которая даёт на выходе вектор [$prob(class_1)$, $prob(class_2)$, …, $prob(class_C)$].



Использование трёх и более слоёв (глубокое обучение)


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



Подбор модели под размеченные обучающие образцы (обратное распространение ошибки обучения)


Увы, но мы добрались и до процедуры подбора. До этого мы говорили о том, что нейросети могут работать эффективно, но не обсуждали то, как нейросеть подгоняется под размеченные обучающие образцы. Аналогом этого вопроса может быть такой: «Как можно выбрать наилучшие веса для сети на основании нескольких размеченных обучающих образцов?». Обычным ответом является градиентный спуск (хотя может подойти и ММП). Если продолжить работу над нашим примером задачи, то процедура градиентного спуска может выглядеть примерно так:

  1. Начинаем с каких-то размеченных обучающих данных
  2. Выберем для минимизации дифференцируемую функцию потерь, $L(\mathbf{\widehat Y}, \mathbf{Y})$
  3. Выберем структуру сети. Особенно чётко нужно определить количество слоёв и узлов на каждом слое.
  4. Инициализируем сеть со случайными весами
  5. Пропускаем сквозь сеть обучающие данные, чтобы сгенерировать прогноз для каждого образца. Измерим общую погрешность согласно функции потерь, $L(\mathbf{\widehat Y}, \mathbf{Y})$. (Это называется прямым распространением.)
  6. Определяем, насколько меняются текущие потери относительно небольших изменений каждого из весов. Другими словами, вычисляем градиент $L$ с учётом каждого веса в сети. (Это называется обратным распространением.)
  7. Делаем небольшой «шаг» в направлении отрицательного градиента. Например, если $w_{23} = 1.5$, а $\frac{\partial L}{\partial w_{23}} = 2.2$, то уменьшение $w_{23}$ на небольшую величину должно привести к небольшому уменьшению текущих потерь. Поэтому мы изменяем $w_3 := w_3 - 2.2 \times 0.001$ (где 0.001 — заданный «размер шага»).
  8. Повторяем этот процесс (с шага 5) фиксированное количество раз или пока потери не сойдутся

По крайней мере, такова основная идея. При реализации на практике возникает множество затруднений.

Затруднение 1 – вычислительная сложность


В процессе подбора среди прочего нам нужно вычислять градиент $L$ с учётом каждого веса. Это сложно, потому что $L$ зависит от каждого узла в выходном слое, и каждый из этих узлов зависит от каждого узла в слое перед ним, и так далее. Это значит, что вычисление $\frac{\partial L}{\partial w_{ab}}$ превращается в настоящий кошмар с формулами сложных производных. (Не забывайте, что многие нейронные сети в реальном мире содержат тысячи узлов в десятках слоёв.) Решить эту задачу можно, заметив, что при применении формулы сложной производной большинство $\frac{\partial L}{\partial w_{ab}}$ повторно использует одинаковые промежуточные производные. Если вы будете внимательно это отслеживать, то сможете избежать одних и тех же повторных вычислений тысячи раз.

Ещё одна хитрость заключается в использовании специальных функций активации, производные которых можно записать как функцию их значения. Например, производная $sigmoid(x)$ = $sigmoid(x)(1 - sigmoid(x))$. Это удобно, потому что во время прямого прохода при вычислении $\widehat y$ для каждого обучающего образца нам нужно вычислять $sigmoid(\mathbf{x})$ поэлементно для некоторого вектора $\mathbf{x}$. Во время обратного распространения мы можем повторно использовать эти значения для вычисления градиента $L$ с учётом весов, что позволит сэкономить время и память.

Третья хитрость заключается в разделении обучающих образцов на «минигруппы» и в изменении весов с учётом каждой группы, одной за другой. Например, если мы разделим обучающие данные на {batch1, batch2, batch3}, то первый проход по обучающим данным будет

  1. Изменять веса на основе batch1
  2. Изменять веса на основе batch2
  3. Изменять веса на основе batch3

где градиент $L$ повторно вычисляется после каждого изменения.

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

Затруднение 2 – у градиентного спуска могут возникать проблемы с поиском абсолютного минимума


Это проблема не столько нейросетей, сколько градиентного спуска. Существует вероятность, что во время градиентного спуска веса могут застрять в локальном минимуме. Также возможно, что веса «перепрыгнут» минимум. Один из способов справиться с этим — использовать различные размеры шагов. Ещё один способ — увеличить количество узлов и/или слоёв в сети. (Но стоит опасаться чрезмерно близкой подгонки). Кроме того, могут быть эффективными некоторые эвристические техники, например, использование момента.

Затруднение 3 – как выработать общий подход?


Как написать генетическую программу, которая сможет подбирать значения для любой нейросети с любым количеством узлов и слоёв? Правильный ответ — никак, нужно использовать Tensorflow. Но если вы хотите попробовать, то самой сложной частью будет вычисление градиента функции потерь. Хитрость здесь в том, чтобы определить, что градиент можно представить как рекурсивную функцию. Нейронная сеть с пятью слоями — это просто нейронная сеть с четырьмя слоями, передающая данные в какие-то перцептроны. Но нейросеть с четырьмя слоями — это просто нейросеть с тремя слоями, передающая данные в какие-то перцептроны и так далее. Более формально это называется автоматическим дифференцированием.
Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
+19
Comments 12
Comments Comments 12

Articles