Основы нейронных сетей

Все курсы > Вводный курс > Занятие 21

На завершающей лекции вводного курса мы изучим основы нейронных сетей (neural network), более сложных алгоритмов машинного обучения.

Алгоритмы нейронных сетей принято относить к области глубокого обучения (deep learning). Все изученные нами ранее алгоритмы относятся к так называемому традиционному машинному обучению (traditional machine learning).

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

Смысл, структура и принцип работы

Смысл алгоритма нейронной сети такой же, как и у классических алгоритмов. Мы также имеем набор данных и цель, которой хотим добиться, обучив наш алгоритм (например, предсказать число или отнести объект к определенному классу).

Отличие нейросети от других алгоритмов заключается в ее структуре.

три типа слоев нейронной сети: входной слой, скрытые слои и выходной слой

Как мы видим, нейронная сеть состоит из нейронов, сгруппированных в слои (layers), у нее есть входной слой (input layer), один или несколько скрытых слоев (hidden layers) и выходной слой (output layer). Каждый нейрон связан с нейронами предыдущего слоя через определенные веса.

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

Функционирует нейросеть следующим образом.

На первом этапе данные подаются в нейроны входного слоя ($x$ и $y$) и умножаются на соответствующие веса ($ w_1, w_2, w_3, w_4 $). Полученные произведения складываются. К результату прибавляется смещение (bias, в данном случае $b_1$ и $b_2$).

$$ w_{1}\cdot x + w_{3}\cdot y + b_{1} $$

$$ w_{2}\cdot x + w_{4}\cdot y + b_{2} $$

Получившаяся сумма подаётся в функцию активации (activation function) для ограничения диапазона и стабилизации результата. Этот результат записывается в нейроны скрытого слоя ($h_1$ и $h_2$).

$$ h_{1} = actfun(w_{1}\cdot x + w_{3}\cdot y + b_{1}) $$

$$ h_{2} = actfun(w_{2}\cdot x + w_{4}\cdot y + b_{2}) $$

На втором этапе процесс повторяется для нейронов скрытого слоя ($h_1$ и $h_2$), весов ($w_5$ и $w_6$) и смещения ($b_3$) до получения конечного результата ($r$).

$$ r = actfun(w_{5}\cdot h_{1} + w_{6}\cdot h_{2} + b_{3}) $$

Описанная выше нейронная сеть называется персептроном (perceptron). Эта модель стремится повторить восприятие информации человеческим мозгом и учитывает три этапа такого процесса:

  • Восприятие информации через сенсоры (входной слой)
  • Создание ассоциаций (скрытый слой)
  • Реакцию (выходной слой)

Основы нейронных сетей на простом примере

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

основы нейронных сетей на простом примере

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

$$ f(x) = \frac{\mathrm{1} }{\mathrm{1} + e^{-x}} $$

График сигмоиды выглядит следующим образом.

основы нейронных сетей: применение сигмоиды для бинарной классификации

Эта функция преобразует любые значения в диапазон (или вероятность) от 0 до 1. В случае задачи классификации, если результат (вероятность) близок к нулю, мы отнесем наблюдение к одному классу, если к единице, то к другому. Граница двух классов пройдет на уровне 0,5.

Общее уравнение нейросети выглядит следующим образом.

$$ r = sigmoid(w_{1}\cdot weight + w_{2}\cdot height + bias) $$

Теперь предположим, что у нас есть следующие данные и параметры нейросети.

Откроем ноутбук к этому занятию

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

Теперь к полученному результату (r) применим сигмоиду.

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

Как мы видим, модель отработала верно.

Обучение нейронной сети

В примере выше был описан первый этап работы нейронной сети, называемый прямым распространением (forward propagation).

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

В реальности начальные веса выбираются случайно и отклонение истинного результата от расчетного (т.е. ошибка) довольно велико.

Как и с обычными алгоритмами ML, для построения модели, нам нужно подобрать идеальные веса или заняться оптимизацией. Применительно к нейронным сетям этот процесс называется обратным распространением (back propagation).

основы нейронных сетей: forward propagation и back propagation

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

Для того чтобы математически описать процесс оптимизации, нам не хватает знаний математического анализа (calculus) и, если говорить более точно, понятия производной (derivative).

Затем, уже с новыми весами, мы снова повторяем весь процесс forward propagation слева направо и снова рассчитываем ошибку. После этого мы вновь меняем веса в ходе back propagation.

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

Создание нейросети в библиотеке Keras

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

и импортируем их.

1. Подготовка данных

Как вы вероятно уже поняли, сегодня мы снова будем использовать уже знакомый нам набор написанных от руки цифр MNIST (только на этот раз воспользуемся не библиотекой sklearn, а возьмем отдельный модуль).

В модуле MNIST содержатся чёрно-белые изображения цифр от 0 до 9 размером 28 х 28 пикселей. Каждый пиксель может принимать значения от 0 (черный) до 255 (белый).

Данные в этом модуле уже разбиты на тестовую и обучающую выборки, а также на признаки и целевую переменную. Подгрузим их.

Посмотрим на размерность обучающей выборки.

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

Таких изображений и целевых значений 10000.

Посмотрим на сами изображения.

первые четыре изображения датасета MNIST

Нейросети любят, когда диапазон входных значений ограничен (нормализован). В частности, мы можем преобразовать диапазон [0, 255] в диапазон от [–1, 1]. Сделать это можно по следующей формуле.

$$ x’ = 2 \cdot \frac {x-min(x)}{max(x)-min(x)}-1 $$

Применим эту формулу к нашим данным.

Посмотрим на новый диапазон.

Теперь нам необходимо «вытянуть» изображения и превратить массивы, содержащие три измерения, в двумерные матрицы. Мы уже делали это на занятии по компьютерному зрению.

Применим этот метод к нашим данным.

Посмотрим на получившиеся значения пикселей.

Наши данные готовы. Теперь нужно задать конфигурацию модели.

2. Конфигурация нейронной сети

Существует множество различных архитектур нейронных сетей. Пока что мы познакомились с персептроном или в более общем смысле нейросетями прямого распространения (Feed Forward Neural Network, FFNN), в которых данные (сигнал) поступают строго от входного слоя к выходному.

Такую же сеть мы и будем использовать для решения поставленной задачи. В частности, на входе мы будем одновременно подавать 784 значения, которые затем будут проходить через два скрытых слоя по 64 нейрона каждый и поступать в выходной слой из 10 нейронов (по одному для каждой из цифр или классов).

схема нейронной сети для классификации изображений датасета MNIST

В первую очередь воспользуемся классом Sequential библиотеки Keras, который укажет, что мы задаём последовательно связанные между собой слои.

Далее нам нужно прописать сами слои и связи между нейронами.

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

Выходной слой будет состоять из 10 нейронов, по одному для каждого из классов (цифры от 0 до 9). В качестве функции активации будет использована новая для нас функция softmax (softmax function).

Если сигмоида подходит для бинарной классификации, то softmax применяется для задач многоклассовой классификации. Приведем формулу.

$$ \text{softmax}(\mathbf{z})_{i} = \frac{e^{z_i}}{\sum_{j=1}^K e^{z_i}} $$

Функция softmax на входе принимает вектор действительных чисел ($\mathbf{z}$), применяет к каждому из элементов $ z_i $ экспоненциальную функцию и нормализует результат через деление на сумму экспоненциальных значений каждого из элементов.

На выходе получается вероятностное распределение любого количества классов ($K$), причем каждое значение находится в диапазоне от 0 до 1, а сумма всех значений равна единице. Приведем пример для трех классов.

схема классификации изображений с помощью функции softmax

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


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

Впрочем, алгоритм по-прежнему уверен, что речь идет о кошке.


3. Настройки

Настроек будет три:

  • тип функции потерь (loss function) определяет, как мы будем считать отклонение прогнозного значения от истинного
  • способ или алгоритм оптимизации этой функции (optimizer) поможет снизить потерю или ошибку и подобрать правильные веса в процессе back propagation
  • метрика (metric) покажет, насколько точна наша модель
Функция потерь

В первую очередь, определимся с функцией потерь. Раньше, например, в задаче регрессии, мы использовали среднеквадратическую ошибку (MSE). Для задач классификации мы будем использовать функцию потерь, называемую перекрестной или кросс-энтропией (cross-entropy). Продолжим пример с собакой, кошкой и попугаем.

функция перекрестной или кросс-энтропии (cross-entropy)

Функция перекрестной энтропии (D) показывает степень отличия прогнозного вероятностного распределения (которое мы получили на выходе функции softmax (S)) от истинного (наша целевая переменная (L)). Чем больше отличие, тем выше ошибка.

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

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

пример кодировки цифры 5 с помощью one-hot encoding

В дополнение замечу, что функция кросс-энтропии, в которой применяется one-hot encoding, называется категориальной кросс-энтропией (categorical cross-entropy).

Отлично! С тем как мы будем измерять уровень ошибки (качество обучения) нашей модели, мы определились. Теперь нужно понять, как мы эту ошибку будем минимизировать. Для этого существует несколько алгоритмов оптимизации.

Алгоритм оптимизации

Классическим алгоритмом является, так называемый, метод стохастического градиентного спуска (Stochastic Gradient Descent или SGD).

Если предположить для простоты, что наша функция потерь оптимизирует один вес исходной модели, и мы находимся изначально в точке А (с неидеальным случайным весом), то наша задача — оказаться в точке B, где ошибка (L) минимальна, а вес (w) оптимален.

общая схема метода градиентного спуска

Спускаться мы будем вдоль градиента, то есть по кратчайшему пути. Идею градиента проще увидеть на функции с двумя весами. Такая функция имеет уже три измерения (две независимых переменных, $w_1$ и $w_2$, и одну зависимую, L) и графически похожа на «холмистую местность», по которой мы будем спускаться по наиболее оптимальному маршруту.

метод градиентного спуска для функции с двумя независимыми переменными

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

Метрика

Остается определиться с метрикой качества. Здесь мы просто возьмём знакомую нам метрику accuracy, которая посчитает долю правильно сделанных прогнозов.

Посмотрим на используемый код.

4. Обучение модели

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

  1. Значения пикселей каждого изображения поступают в 784 нейрона входного слоя
  2. Далее они проходят через скрытые слои, где они умножаются на веса, складываются, смещаются и поступают в соответствующую функцию активации
  3. На выходе из функции softmax мы получаем вероятности для каждой из цифр
  4. После этого результат сравнивается с целевой переменной с помощью функции перекрестной энтропии (функции потерь); делается расчет ошибки
  5. На следующем шаге алгоритм оптимизации стремится уменьшить ошибку и соответствующим образом изменяет веса
  6. После этого процесс повторяется, но уже с новыми весами.

Давайте выполним все эти операции в библиотеке Keras.

На обучающей выборке мы добились неплохого результата, 91,49%.

5. Оценка качества модели

На этом шаге нам нужно оценить качество модели на тестовых данных.

Результат «на тесте» оказался даже чуть выше, 92,07%.

6. Прогноз

Теперь давайте в качестве упражнения сделаем прогноз.


Работа над ошибками. На видео я говорю про первые десять изображений. Разумеется, это неверно. Срез [-10:] выводит последние десять изображений.


В переменной pred содержится массив Numpy с десятью вероятностями для каждого из десяти наблюдений. Нам нужно выбрать максимальную вероятность для каждого изображения и определить ее индекс (индекс и будет искомой цифрой). Все это можно сделать с помощью функции np.argmax(). Посмотрим на примере.

пример работы функции np.argmax() библиотеки Numpy

Теперь применим к нашим данным.

Для первых десяти цифр модель сделала верный прогноз.

7. Пример улучшения алгоритма

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

Посмотрим на результат на обучающей и тестовой выборке.

Как вы видите, с помощью одного изменения мы повысили долю правильных прогнозов до 97,82% и 96,62% на обучающей и тестовой выборках соответственно.

Более подходящие для работы с изображениями сверточные нейронные сети (convolutional neural network, CNN) достигают свыше 99% точности на этом наборе данных, как это видно в примере⧉ на официальном сайте библиотеки Keras.

Подведем итог

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

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

Вопросы для закрепления

Перечислите типы слоев нейронной сети

Посмотреть правильный ответ

Из каких двух этапов состоит обучение нейронной сети?

Посмотреть правильный ответ

Для чего используются сигмоида и функция softmax в выходном слое нейронной сети в задачах классификации?

Посмотреть правильный ответ


Ответы на вопросы

Вопрос. Что означает число 1875 в результате работы модели?

Ответ. Я планировал рассказать об этом на курсе по оптимизации, но попробую дать общие определения уже сейчас. Как я уже сказал, при оптимизации методом градиентного спуска мы можем использовать (1) все данные, (2) часть данных или (3) одно наблюдение для каждого обновления весов. Это регулируется параметром batch_size (размер партии):

  • в первом случае, количество наблюдений (batch, партия) равно размеру датасета, веса не обновляются пока мы не пройдемся по всем наблюдениям, это full-batch градиентный спуск;
  • во втором случае, мы берем часть наблюдений (mini-batch, мини-партию), и когда обработаем их, то обновляем веса; после этого мы обрабатываем следующую партию; и наконец
  • мы можем взять только одно наблюдение и сразу после его анализа обновить веса, это стохастический градиентный спуск (stochastic gradient descent), параметр batch_size = 1.

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

Если берем только одно наблюдение, то считаем все быстро, но расчет минимума функции потерь менее точен.

В библиотеке Keras (и нашей нейросети) по умолчанию используется второй подход и размер партии равный 32 наблюдениям (batch_size = 32). С учетом того, что в обучающей выборке 60000 наблюдений, разделив 60000 на 32 мы получим 1875 итераций или обновлений весов в рамках одной эпохи. Отсюда и число 1875.

Повторим, алгоритм обрабатывает 32 наблюдения, обновляет веса и после этого переходит к следующей партии (batch) из 32-х наблюдений. Обработав таким образом 60000 изображений, алгоритм заканчивает первую эпоху и начинает вторую. Размер партии и количество эпох регулируется параметрами batch_size и epochs соответственно.