Цель этого поста — не мотивировать и объяснить softmax, не предоставить лучшую функцию softmax для Julia и не представить язык Julia. Я воздержусь от философских рассуждений о математическом идеале и о том, почему я считаю математику поэтическим занятием. Но, придерживаясь темы моего первого сообщения об этом, я буду неявно иллюстрировать, почему акт занятия математикой сегодня означает принятие формы абстракции в сторону простоты, т.е. постоянно растущая деконструкция элегантности математической функции, доказательства или уравнения.

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

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

Softmax также является хорошим выбором, потому что это часто используемая функция в некоторых из самых передовых программ в мире. Приложения машинного обучения уже много лет используют softmax в качестве дифференцируемого argmax для многоклассовой классификации. авангардные фреймворки глубокого обучения, Tensorflow, PyTorch, MXNet, Flux, Gorgonia и т. д. — все используют softmax (обычно для функции потерь на последнем уровне сети, где нужна многоклассовая классификация). По этой причине на softmax можно найти множество ресурсов, в том числе программные реализации, встроенные в передовые программные проекты, о которых я упоминал. Обратите внимание, что в проектах Python (Tensorflow, Pytorch, MXNext) все они зависят от низкоуровневого выполнения C++, что означает, что вы, вероятно, найдете реализации как Python, так и C++, и это будет довольно шумно. Если вы хотите увидеть реализацию в передовом программном проекте, я бы посоветовал либо FluxML (написано на Julia), либо Gorgonia (написано на Go).

Юлия Реализация

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

f(x) = exp.(x) ./    
sum(exp.(x))

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

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

Что не работает в f(x) = exp.(x) ./ sum(exp.(x))? Реализация Julia в конечном итоге переполнит свою числовую емкость, если заданы слишком большие числа.

Python, Go и Rust тоже будут. Любой язык программирования будет. Нужно понимать числовые пределы как языков программирования, так и устройств. В этом случае для Джулии мы видим, что (typemin(Float64), typemax(Float64) чисел с плавающей запятой равно -Inf, Inf, что удобно для представления машинного эпсилон eps, вычисляемого как 2,220446049250313e-16; это наш верхний предел, и функция softmax выше превышает этот числовой предел, если наши числа слишком высоки.

julia> (typemin(Float64), typemax(Float64))
(-Inf, Inf)
julia> (typemin(Inf), typemax(Inf))
(-Inf, Inf)
julia> eps(Inf)
NaN
julia> eps(Float64)
2.220446049250313e-16

Чтобы обеспечить числовую стабильность, вычтите наибольшее число из всех остальных. Теперь [1.1, 5.0, 2.8, 7.3 это [1.1-7.3, 5.0-7.3, 2.8-7.3, 7.3-7.3].

И последнее, что мы делаем, это предоставляем параметр масштабирования θ(theta), который позволяет нам увеличивать числа. Увеличение расстояния между маленькими и большими числами позволяет легче увидеть относительные границы между большими и маленькими. Мы определили θ = 2,5. Помнить о вычислениях тоже хорошо. То есть мы получим те же результаты, если умножим все наши числа на θ и вычтем на max [два скалярно-векторных умножения, (scores4 * θ .-maximum(scores4 * θ)) ] или, если мы вычтем на max, а затем умножим на θ [одно умножение на скаляр-вектор, (scores4 .- maximum(scores4)) * θ ].

Мы перешли от исходных значений [1.1, 5.0, 2.8, 7.3] к вычтенным максимальным значениям [-6.1999999999999999, -2.3, -4.5, 0.0], к масштабированным и вычтенным максимальным значениям [-15.499999999999998, -5.75, -11.25, 0,0]. Вы можете видеть, что мы немного увеличили относительные расстояния.

Поскольку мы делаем это в Джулии, мы можем помнить о наших типах. Julia предоставляет очень интересные наборы числовых типов, а также векторных и матричных типов. Я могу использовать тип Real для всех действительных чисел или различные виды абстрактных типов, которые позволяют мне создавать функции, которые могут принимать самые разные типы векторов, матриц, чисел с плавающей запятой и т. д.

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

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

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

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

Другие ресурсы для softmax

Честно говоря, Softmax в наши дни повсюду. Просто пройдите курс Udacity или Coursera по науке о данных или глубокому обучению, возьмите любую недавнюю книгу по глубокому обучению или даже более старые книги по машинному обучению (случайно взял две книги в своем офисе, опубликованные в 2009 и 2004 годах соответственно, обе имеют записи в приложение для softmax).

Но если вы хотите быстро прочитать и/или освежить в памяти… некоторые ресурсы я рекомендую; две записи в блоге:





Функция Softmax и ее производная — веб-сайт Эли Бендерски
В литературе по машинному обучению термин «градиент
обычно используется для обозначения производной. Строго говоря, градиенты — это…eli.thegreenplace.net»



подборка из онлайн-книги: