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

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

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

Определения и обозначения

Многослойный персептрон (MLP) – это нейронная сеть, имеющая как минимум три слоя: входной, скрытый и выходной. Каждый уровень работает с выводами предыдущего уровня:

Мы будем использовать следующие обозначения:

  • aᵢˡ — активация (выход) нейрона i в слое l
  • wᵢⱼˡ — это вес связи между нейроном j в слое l-1 и нейроном i в слое л
  • bᵢˡ — член смещения нейрона i в слое l

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

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

Прямое распространение

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

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

Например, давайте посмотрим на нейрон i в слое l. Активация этого нейрона вычисляется в два этапа:

  1. Сначала мы вычисляем чистый вход нейрона как взвешенную сумму его входящих входов плюс его смещение:

2. Теперь мы применяем функцию активации к сетевому входу, чтобы получить активацию нейрона:

По определению, активации нейронов во входном слое равны значениям признаков примера, представленного в данный момент в сети, т. е.

где m — количество объектов в наборе данных.

Векторизованная форма

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

Сначала мы определяем вектор aˡ как вектор, содержащий активации всех нейронов в слое l, и вектор bˡ как вектор со смещениями всех нейронов в слое l.

Мы также определяем как матрицу весов связей от всех нейронов в слое l — 1 ко всем нейронам в слое l. Например, W¹₂₃ — это вес связи между нейроном № 2 в слое 0 (входной слой) и нейрон №. 3 в слое 1 (первый скрытый слой).

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

Решение проблемы XOR

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

Однако MLP с одним скрытым слоем может легко решить эту проблему:

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

Верхний скрытый нейрон подключен только к первому входу x₁ с весом соединения 1 и смещением -1. Следовательно, этот нейрон срабатывает только тогда, когда x₁ = 1 (в этом случае его чистый вход равен 1 × 1 + (-1) = 0 и f(0) = 1, где f — ступенчатая функция).

Средний скрытый нейрон подключен к обоим входам с весами соединения 1 и имеет смещение -2. Следовательно, этот нейрон срабатывает только тогда, когда оба входа равны 1.

Нижний скрытый нейрон подключен только ко второму входу x₂ с весом соединения 1 и смещением -1. Следовательно, этот нейрон срабатывает только тогда, когда x₂ = 1.

Выходной нейрон соединен с верхним и нижним скрытыми нейронами с весом 1 и со средним скрытым нейроном с весом -2 и имеет смещение -1. Следовательно, он срабатывает только тогда, когда срабатывает верхний или нижний скрытый нейрон, но не когда они оба срабатывают вместе. Другими словами, он срабатывает только тогда, когда x₁ = 1 или x₂ = 1, но не когда оба входа равны 1, что является именно тем, что мы ожидаем от вывода XOR. функция быть.

Например, давайте вычислим прямое распространение этого MLP для входных параметров x₁ = 1 и x₂ = 0. Активации скрытых нейронов в этом случае:

Мы видим, что в этом случае срабатывает только верхний скрытый нейрон.

Таким образом, активация выходного нейрона:

В этом случае срабатывает выходной нейрон, и мы ожидаем, что результат XOR будет для входов x₁ = 1 и x₂ = 0.

Убедитесь, что вы понимаете, как MLP вычисляет и другие три случая функции XOR!

Упражнение по построению MLP

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

Создайте MLP, который правильно классифицирует все точки в этом наборе данных.
Совет. Используйте скрытые нейроны, чтобы определить три области классификации.

Решение можно найти внизу этой статьи.

Теорема универсального приближения

Одним из замечательных фактов о MLP является то, что они могут вычислять любую произвольную функцию (даже несмотря на то, что каждый нейрон в сети вычисляет очень простую функцию, такую ​​как ступенчатая функция).

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

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

Обучение в MLP: обратное распространение

Хотя МЛП доказали свою вычислительную мощность, долгое время было неясно, как их обучать на конкретном наборе данных. В то время как одиночные персептроны имеют простое правило обновления веса, было неясно, как применить это правило к весам скрытых слоев, поскольку они не влияют напрямую на выход сети (и, следовательно, на ее потери при обучении).

Сообществу ИИ потребовалось более 30 лет, чтобы решить эту проблему, когда в 1986 году Rumelhart et al. представили свой новаторский алгоритм обратного распространения для обучения MLP.

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

Алгоритм обратного распространения подробно описан в этой статье.

Функции активации

В однослойных персептронах мы использовали либо ступенчатую, либо знаковую функции для активации нейрона. Проблема с этими функциями заключается в том, что их градиент почти везде равен 0 (поскольку они равны постоянному значению для x > 0 и для x ‹ 0). Это означает, что мы не можем использовать их в градиентном спуске, чтобы найти минимальную ошибку сети.

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

Для скрытых слоев наиболее распространены три функции активации:

  1. Сигмовидная функция

2. Функция гиперполитического тангенса

3. Функция ReLU (выпрямленная линейная единица)

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

  1. Для задач регрессии мы используем функцию тождества f(x) = x.
  2. Для задач бинарной классификации мы используем сигмовидную функцию (показана выше).
  3. Для задач многоклассовой классификации мы используем функции softmax. Эта функция преобразует вектор из K действительных чисел в распределение вероятностей K возможных результатов:

MLP в Scikit-Learn

Scikit-Learn предоставляет два класса, которые реализуют MLP в модуле sklearn.neural_network:

  1. MLPClassifier используется для задач классификации.
  2. MLPRegressor используется для задач регрессии.

Важными гиперпараметрами в этих классах являются:

  • hidden_layer_sizes — кортеж, определяющий количество нейронов в каждом скрытом слое. Значение по умолчанию — (100), т. е. один скрытый слой со 100 нейронами. Для решения многих задач достаточно использовать всего один или два скрытых слоя. Для более сложных задач вы можете постепенно увеличивать количество скрытых слоев, пока сеть не начнет переобучать обучающую выборку.
  • активация — функция активации для использования в скрытых слоях. Возможные варианты: «идентификация», «логистика», «танх» и «релу» (по умолчанию).
  • решатель — решатель, используемый для оптимизации веса. По умолчанию используется «адам», который хорошо работает с большинством наборов данных. Поведение различных оптимизаторов будет объяснено в следующей статье.
  • альфа — коэффициент регуляризации L2 (по умолчанию 0,0001).
  • batch_size — размер мини-пакетов, используемых для обучения (по умолчанию 200).
  • learning_rate — график скорости обучения для обновлений веса (по умолчанию «постоянный»).
  • learning_rate_init — используемая начальная скорость обучения (по умолчанию 0,001).
  • early_stopping — следует ли останавливать обучение, когда оценка проверки не улучшается (по умолчанию False).
  • validation_fraction — доля обучающего набора, отведенная для проверки (по умолчанию 0,1).

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

Обучение MLP на MNIST

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

Набор данных содержит 60 000 обучающих изображений и 10 000 тестовых изображений рукописных цифр. Каждое изображение имеет размер 28 × 28 пикселей и обычно представляется вектором из 784 чисел в диапазоне [0, 255]. Задача состоит в том, чтобы классифицировать эти изображения по одной из десяти цифр (0–9).

Сначала мы получаем набор данных MNIST с помощью функции fetch_openml():

from sklearn.datasets import fetch_openml

X, y = fetch_openml('mnist_784', return_X_y=True, as_frame=False)

Параметр as_frame указывает, что мы хотим получать данные и метки в виде массивов NumPy, а не DataFrames (значение по умолчанию для этого параметра изменено в Scikit-Learn 0.24 с False на «auto»).

Давайте рассмотрим форму X:

print(X.shape)
(70000, 784)

То есть X состоит из 70 000 плоских векторов размером 784 пикселя.

Давайте отобразим первые 50 цифр в наборе данных:

fig, axes = plt.subplots(5, 10, figsize=(10, 5))
i = 0
for ax in axes.flat:
    ax.imshow(X[i].reshape(28, 28), cmap='binary')
    ax.axis('off')    
    i += 1

Давайте проверим, сколько образцов у нас есть из каждой цифры:

np.unique(y, return_counts=True)
(array(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], dtype=object),
 array([6903, 7877, 6990, 7141, 6824, 6313, 6876, 7293, 6825, 6958],
       dtype=int64))

Набор данных достаточно сбалансирован между 10 классами.

Теперь мы масштабируем входные данные, чтобы они находились в диапазоне [0, 1] вместо [0, 255]:

X = X / 255

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

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

train_size = 60000
X_train, y_train = X[:train_size], y[:train_size]
X_test, y_test = X[train_size:], y[train_size:]

Теперь мы создаем классификатор MLP с одним скрытым слоем с 300 нейронами. Мы сохраним все остальные гиперпараметры со значениями по умолчанию, за исключением параметра early_stopping, который мы изменим на True. Мы также установим verbose=True, чтобы отслеживать ход обучения:

from sklearn.neural_network import MLPClassifier

mlp = MLPClassifier(hidden_layer_sizes=(300,), early_stopping=True, 
                    verbose=True)

Подгоним классификатор к обучающей выборке:

mlp.fit(X_train, y_train)

Результат, который мы получаем во время обучения:

Iteration 1, loss = 0.35415292
Validation score: 0.950167
Iteration 2, loss = 0.15504686
Validation score: 0.964833
Iteration 3, loss = 0.10840875
Validation score: 0.969833
Iteration 4, loss = 0.08041958
Validation score: 0.972333
Iteration 5, loss = 0.06253450
Validation score: 0.973167
...
Iteration 31, loss = 0.00285821
Validation score: 0.980500
Validation score did not improve more than tol=0.000100 for 10 consecutive epochs. Stopping.

Обучение было остановлено после 31 итерации, так как за предыдущие 10 итераций оценка проверки не улучшилась.

Проверим точность МЛП на обучающей и тестовой выборках:

print('Accuracy on training set:', mlp.score(X_train, y_train))
print('Accuracy on test set:', mlp.score(X_test, y_test))
Accuracy on training set: 0.998
Accuracy on test set: 0.9795

Это отличные результаты, но сети с более сложной архитектурой, такие как сверточные нейронные сети (CNN), могут достичь еще лучших результатов на этом наборе данных (до 99,91% точности в тесте!). Вы можете найти современные результаты на MNIST со ссылками на соответствующие статьи здесь.

Чтобы лучше понять ошибки нашей модели, давайте отобразим ее матрицу путаницы:

from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay

y_pred = mlp.predict(X_test)
cm = confusion_matrix(y_test, y_pred)
disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=mlp.classes_)
disp.plot(cmap='Blues')

Мы видим, что основная путаница в модели возникает между цифрами 4⇔9, 7⇔9 и 2⇔8. Это имеет смысл, поскольку эти цифры часто напоминают друг друга, когда они написаны от руки. Чтобы помочь нашей модели различать эти цифры, мы можем добавить больше примеров из этих цифр (например, с помощью увеличения данных) или извлечь дополнительные функции из изображений (например, количество замкнутых петель в цифре).

Визуализация весов MLP

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

Например, давайте нанесем веса между входными и скрытыми слоями нашего классификатора MLP. Матрица весов имеет форму (784, 300) и хранится в переменной с именем mlp.coefs_[0]:

print(mlp.coefs_[0].shape)
(784, 300)

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

Следующий график отображает веса первых 20 скрытых нейронов:

fig, axes = plt.subplots(4, 5)

for coef, ax in zip(mlp.coefs_[0].T, axes.flat):
    im = ax.imshow(coef.reshape(28, 28), cmap='gray')
    ax.axis('off')
    
fig.colorbar(im, ax=axes.flat)

Мы видим, что каждый скрытый нейрон фокусируется на разных сегментах изображения.

MLP в других библиотеках

Хотя классификатор MLP в Scikit-Learn прост в использовании, в практических приложениях вы, скорее всего, будете использовать библиотеку глубокого обучения, такую ​​как TensorFlow или PyTorch, для создания MLP. Эти библиотеки могут использовать преимущества более быстрой обработки графическим процессором, а также предоставляют множество дополнительных опций, таких как дополнительные функции активации и оптимизаторы. Пример использования этих библиотек вы можете найти в этом посте.

Решение упражнения по построению MLP

Следующий MLP правильно классифицирует все точки в наборе данных:

Объяснение:

Левый скрытый нейрон срабатывает, только когда x₁ ≤ 3, средний скрытый нейрон срабатывает, только когда x₂ ≥ 4, а правый скрытый нейрон срабатывает, только когда x ₂ ≤ 0.

Левый выходной нейрон выполняет операцию ИЛИ между левым и средним скрытыми нейронами, поэтому он срабатывает, только если x₁ ≤ 3 ИЛИ x₂ ≥ 4, т. е. только когда точка синяя.

Средний выходной нейрон выполняет НЕ-ИЛИ (не ИЛИ) между всеми скрытыми нейронами, поэтому он срабатывает только тогда, когда НЕ (x₁ ≤ 3 ИЛИ x₂ ≥ 4 ИЛИ x₂ ≤ 0). Другими словами, он срабатывает только тогда, когда x₁ › 3 AND 0 ‹ x₂ ‹ 4, т. е. только когда точка красная.

Правый выходной нейрон срабатывает только тогда, когда срабатывает правый скрытый нейрон, т. е. только когда x₂ ≤ 0, что справедливо только для фиолетовых точек.

Заключительные примечания

Вы можете найти примеры кода этой статьи на моем github: https://github.com/roiyeho/medium/tree/main/mlp

Все изображения, если не указано иное, принадлежат автору.

Информация о наборе данных MNIST:

  • Цитирование:Дэн, Л., 2012. База данных mnist рукописных цифровых изображений для исследований в области машинного обучения. Журнал IEEE Signal Processing, 29(6), стр. 141–142.
  • Лицензия. Янн ЛеКун и Коринна Кортес владеют авторскими правами на набор данных MNIST, который доступен в соответствии с Международной лицензией Creative Commons Attribution-ShareAlike 4.0 (CC BY-SA).

Спасибо за прочтение!