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

Постановка задачи — многоклассовая классификация

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

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

В нашей задаче набор меток состоит из цифр от 0 до 9.

Модель

Так как данные MNIST достаточно просты, Perceptron будет вполне достаточно для нашей задачи. Для тех из вас, кто никогда не слышал об этом термине, Perceptron — это модель, состоящая из одного линейного слоя, за которым следует функция активации с одномерным выходом. Однако будет отличие от классического Perceptron, так как есть 10 возможных классов, нам действительно нужно 10 выходов. Итак, основная часть нашей модели будет выглядеть так:

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

Из теории вероятностей мы знаем, что вероятности всех возможных исходов в сумме должны равняться 1. Наше возможное множество меток естественным образом образует пространство для k возможных взаимоисключающих исходов. Это означает, что k выходов для каждой выборки необходимо масштабировать до 0..1, а также они должны суммироваться до 1. В противном случае эти числа нельзя интерпретировать как вероятности. Это основная причина, по которой мы не можем просто использовать сигмоид — второе правило просто не сработает.

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

Функция Softmax удовлетворит наши потребности в выходных значениях:

Алгоритмически softmax также можно интерпретировать следующим образом:

Если бы вам пришлось реализовать softmax с нуля, это бы работало так:

  1. Используйте exp() для каждого элемента в матрице Z
  2. Применить построчную нормализацию

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

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

Формулировка функции потерь

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

Чтобы понять кросс-энтропийную потерю, мы должны сначала вспомнить, что мы пытаемся сделать с вероятностной точки зрения. Наша модель представляет собой черный ящик, который пытается изучить условное распределение вероятностей целевой переменной y относительно признаков x. Применяя модель к некоторой входной функции x, она *волшебным образом* дает нам оценку того, как выглядит реальная вероятность.

Перекрестная энтропия — это классный способ из теории информации измерить, насколько наше оценочное распределение близко к реальному (знаю, это больше похоже на KL-дивергенцию, но эти два понятия вполне эквивалентны в случае задачи обучения ML [см. https: //stats.stackexchange.com/a/357974/366361 за хорошее объяснение]). Чем лучше мы аппроксимируем реальное распределение, тем лучше потеря — просто :)

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

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

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

Обучение методом градиентного спуска

Наконец, давайте сформулируем, как мы собираемся обновлять веса Персептрона. Снова будет немного математики, но давайте попробуем разобраться :)

Обновление весов нашей модели можно выполнить любым методом на основе градиента, но мы будем придерживаться простого градиентного спуска:

Поскольку наша модель — это просто персептрон, все ее параметры определяются одной матрицей W размером nx10, где n — общее количество пикселей в каждом изображении MNIST. Чтобы применить градиентный спуск, нам нужно будет вычислить производную функции потерь по всем весам в матрице W. Это делается путем применения цепного правила.

Вышеприведенное выражение может показаться трудным для понимания, но на самом деле это не так сложно. Производная логарифмической суммы показателей — это в точности вектор z, а e — умный способ написать индикатор: https://en.wikipedia.org/wiki/Indicator_function

Приведенное выше выражение вычисляет потери по одному вектору Z, чтобы обобщить его в матричной форме (поскольку мы хотим одновременно передать несколько выборок x в нашу нейронную сеть), давайте просто напишем это так:

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

Небольшой код для ясности

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

Этот код иллюстрирует полный цикл обучения со всеми предписанными вычислениями:

def softmax_regression_epoch(X, y, theta, lr = 0.1, batch=100):
    """ Runs a single training epoch over X,y dataset in a batch fashion (you better use it instead of standard GD if you have limited memory), performs training of *theta weights* inplace.
    """
    # ...some code here
    while b*batch < n:
        x_batch = X[b*batch:min(b*batch + batch, n)] # batch slice
        y_batch = y[b*batch:min(b*batch + batch, n)] # batch slice
        m = y_batch.size 
        H = x_batch @ theta # output of model before softmax
        Z = softmax(H) # applied softmax
        I_y = np.zeros((m, k)) # indicator matrix for loss computation
        I_y[np.arange(m), y_batch] = 1
        grad_l = (x_batch.T @ (Z - I_y)) / m # gradient computation, everything works same as prescribed, just added averaging over batch
        theta -= lr * grad_l # sgd weight update
        b += 1

Надеюсь, этот код прояснит, как все написанные формулы работают вместе для обучения веса модели! Не запутайтесь в пакетной обработке, это всего лишь небольшая корректировка оригинальной концепции, которая не влияет на работу всего остального — матрицы остаются матрицами :)

Ты сделал это!

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

Рекомендации

  1. Тяньци Чен, Зико Колтер, «ML Refresher / Softmax Regression», 10–414/714: Системы глубокого обучения (онлайн-версия), CMU, Питтсбург, США, 8 сентября 2022 г.
  2. Руководство пользователя Scikit-Learn — https://scikit-learn.org/stable/modules/linear_model.html#multinomial-case