Как реализовать функцию Softmax в Python

Из класса Udacity глубокого обучения softmax of y_i является просто экспонентой, деленной на сумму экспоненты всего Y-вектора:

введите описание изображения здесь

Где S(y_i) - функция softmax от y_i, а e - экспоненциальная, а j - нет. столбцов входного вектора Y.

Я пробовал следующее:

import numpy as np

def softmax(x):
    """Compute softmax values for each sets of scores in x."""
    e_x = np.exp(x - np.max(x))
    return e_x / e_x.sum()

scores = [3.0, 1.0, 0.2]
print(softmax(scores))

который возвращает:

[ 0.8360188   0.11314284  0.05083836]

Но предлагаемое решение было:

def softmax(x):
    """Compute softmax values for each sets of scores in x."""
    return np.exp(x) / np.sum(np.exp(x), axis=0)

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

Может ли кто-нибудь математически показать почему? Правильно ли, а другое неправильно?

Являются ли реализация аналогичной с точки зрения сложности кода и времени? Что более эффективно?

Ответы

Ответ 1

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

Вы начинаете с

e ^ (x - max(x)) / sum(e^(x - max(x))

Используя тот факт, что a ^ (b - c) = (a ^ b)/(a ^ c) мы имеем

= e ^ x / (e ^ max(x) * sum(e ^ x / e ^ max(x)))

= e ^ x / sum(e ^ x)

Вот что говорит другой ответ. Вы можете заменить max (x) на любую переменную, и она будет отменена.

Ответ 2

(Ну... много путаницы здесь, как в вопросе, так и в ответах...)

Для начала два решения (т.е. ваш и предлагаемый) эквивалентны не; они происходят, чтобы быть эквивалентными только для частного случая массивов с 1-D счетом. Вы бы это обнаружили, если бы вы попробовали также массив 2-D баллов в примере с показами Udacity.

По результатам, единственной фактической разницей между двумя решениями является аргумент axis=0. Чтобы убедиться, что это так, попробуйте ваше решение (your_softmax) и одно, где единственное отличие - это аргумент axis:

import numpy as np

# your solution:
def your_softmax(x):
    """Compute softmax values for each sets of scores in x."""
    e_x = np.exp(x - np.max(x))
    return e_x / e_x.sum()

# correct solution:
def softmax(x):
    """Compute softmax values for each sets of scores in x."""
    e_x = np.exp(x - np.max(x))
    return e_x / e_x.sum(axis=0) # only difference

Как я уже сказал, для массива с 1-D счетом результаты действительно идентичны:

scores = [3.0, 1.0, 0.2]
print(your_softmax(scores))
# [ 0.8360188   0.11314284  0.05083836]
print(softmax(scores))
# [ 0.8360188   0.11314284  0.05083836]
your_softmax(scores) == softmax(scores)
# array([ True,  True,  True], dtype=bool)

Тем не менее, вот результаты для 2-D массива баллов, заданного в викторине Udacity, в качестве тестового примера:

scores2D = np.array([[1, 2, 3, 6],
                     [2, 4, 5, 6],
                     [3, 8, 7, 6]])

print(your_softmax(scores2D))
# [[  4.89907947e-04   1.33170787e-03   3.61995731e-03   7.27087861e-02]
#  [  1.33170787e-03   9.84006416e-03   2.67480676e-02   7.27087861e-02]
#  [  3.61995731e-03   5.37249300e-01   1.97642972e-01   7.27087861e-02]]

print(softmax(scores2D))
# [[ 0.09003057  0.00242826  0.01587624  0.33333333]
#  [ 0.24472847  0.01794253  0.11731043  0.33333333]
#  [ 0.66524096  0.97962921  0.86681333  0.33333333]]

Результаты разные: вторая действительно идентична той, что ожидалась в викторине Udacity, где все столбцы действительно равны 1, что не соответствует первому (неправильному) результату.

Итак, вся суета была фактически для детали реализации - аргументом axis. Согласно документации numpy.sum:

По умолчанию ось = Нет, суммирует все элементы входного массива

в то время как здесь мы хотим суммировать строки, поэтому axis=0. Для 1-D массива сумма (единственная) строка и сумма всех элементов оказываются одинаковыми, поэтому ваши идентичные результаты в этом случае...

Отказ axis в стороне, ваша реализация (т.е. ваш выбор для вычитания max сначала) на самом деле лучше, чем предлагаемое решение! На самом деле, это рекомендуемый способ реализации функции softmax - см. здесь для обоснования (числовая стабильность, также отмеченная некоторыми ответами выше).

Ответ 3

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

import numpy as np

# your solution:
def your_softmax(x):
    """Compute softmax values for each sets of scores in x."""
    e_x = np.exp(x - np.max(x))
    return e_x / e_x.sum()

# desertnaut solution (copied from his answer): 
def desertnaut_softmax(x):
    """Compute softmax values for each sets of scores in x."""
    e_x = np.exp(x - np.max(x))
    return e_x / e_x.sum(axis=0) # only difference

# my (correct) solution:
def softmax(z):
    assert len(z.shape) == 2
    s = np.max(z, axis=1)
    s = s[:, np.newaxis] # necessary step to do broadcasting
    e_x = np.exp(z - s)
    div = np.sum(e_x, axis=1)
    div = div[:, np.newaxis] # dito
    return e_x / div

Давайте рассмотрим пример пустыни:

x1 = np.array([[1, 2, 3, 6]]) # notice that we put the data into 2 dimensions(!)

Это вывод:

your_softmax(x1)
array([[ 0.00626879,  0.01704033,  0.04632042,  0.93037047]])

desertnaut_softmax(x1)
array([[ 1.,  1.,  1.,  1.]])

softmax(x1)
array([[ 0.00626879,  0.01704033,  0.04632042,  0.93037047]])

Вы можете видеть, что в этой ситуации ошибка версии deernauts не удалась. (Это не так, если бы вход был всего лишь одномерным, как np.array([1, 2, 3, 6]).

Теперь мы используем 3 выборки, так как это причина, по которой мы используем двумерный вход. Следующий x2 не совпадает с приведенным ниже примером desernauts.

x2 = np.array([[1, 2, 3, 6],  # sample 1
               [2, 4, 5, 6],  # sample 2
               [1, 2, 3, 6]]) # sample 1 again(!)

Этот вход состоит из пакета с 3 образцами. Но образец один и третий, по сути, один и тот же. Теперь мы ожидаем 3 ряда активации softmax, где первая должна быть такой же, как и третья, а также наша активация x1!

your_softmax(x2)
array([[ 0.00183535,  0.00498899,  0.01356148,  0.27238963],
       [ 0.00498899,  0.03686393,  0.10020655,  0.27238963],
       [ 0.00183535,  0.00498899,  0.01356148,  0.27238963]])


desertnaut_softmax(x2)
array([[ 0.21194156,  0.10650698,  0.10650698,  0.33333333],
       [ 0.57611688,  0.78698604,  0.78698604,  0.33333333],
       [ 0.21194156,  0.10650698,  0.10650698,  0.33333333]])

softmax(x2)
array([[ 0.00626879,  0.01704033,  0.04632042,  0.93037047],
       [ 0.01203764,  0.08894682,  0.24178252,  0.65723302],
       [ 0.00626879,  0.01704033,  0.04632042,  0.93037047]])

Надеюсь, вы увидите, что это только в случае моего решения.

softmax(x1) == softmax(x2)[0]
array([[ True,  True,  True,  True]], dtype=bool)

softmax(x1) == softmax(x2)[2]
array([[ True,  True,  True,  True]], dtype=bool)

Кроме того, вот результаты реализации softmax TensorFlows:

import tensorflow as tf
import numpy as np
batch = np.asarray([[1,2,3,6],[2,4,5,6],[1,2,3,6]])
x = tf.placeholder(tf.float32, shape=[None, 4])
y = tf.nn.softmax(x)
init = tf.initialize_all_variables()
sess = tf.Session()
sess.run(y, feed_dict={x: batch})

И результат:

array([[ 0.00626879,  0.01704033,  0.04632042,  0.93037045],
       [ 0.01203764,  0.08894681,  0.24178252,  0.657233  ],
       [ 0.00626879,  0.01704033,  0.04632042,  0.93037045]], dtype=float32)

Ответ 4

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

Ответ 5

sklearn также предлагает реализацию softmax

from sklearn.utils.extmath import softmax
import numpy as np

x = np.array([[ 0.50839931,  0.49767588,  0.51260159]])
softmax(x)

# output
array([[ 0.3340521 ,  0.33048906,  0.33545884]]) 

Ответ 6

С математической точки зрения обе стороны равны.

И вы можете легко доказать это. Пусть m=max(x). Теперь ваша функция softmax возвращает вектор, i-я координата которого равна

enter image description here

обратите внимание, что это работает для любого m, потому что для всех (даже комплексных) чисел e^m != 0

  • с точки зрения сложности вычислений они также эквивалентны и оба выполняются за время O(n), где n - размер вектора.

  • с точки зрения числовой стабильности первое решение является предпочтительным, поскольку e^x растет очень быстро и даже при довольно небольших значениях x оно будет переполнено. Вычитание максимального значения позволяет избавиться от этого переполнения. Чтобы на практике испытать то, о чем я говорил, попробуйте ввести x = np.array([1000, 5]) в обе ваши функции. Один вернет правильную вероятность, второй переполнится nan

  • ваше решение работает только для векторов (тест Udacity хочет, чтобы вы рассчитали его и для матриц). Для того, чтобы это исправить, вам нужно использовать sum(axis=0)

Ответ 7

Здесь вы можете узнать, почему они использовали - max.

Оттуда:

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

Ответ 8

РЕДАКТИРОВАТЬ. Начиная с версии 1.2.0, scipy включает softmax в качестве специальной функции:

https://scipy.github.io/devdocs/generated/scipy.special.softmax.html

Я написал функцию, применяющую softmax к любой оси:

def softmax(X, theta = 1.0, axis = None):
    """
    Compute the softmax of each element along an axis of X.

    Parameters
    ----------
    X: ND-Array. Probably should be floats. 
    theta (optional): float parameter, used as a multiplier
        prior to exponentiation. Default = 1.0
    axis (optional): axis to compute values along. Default is the 
        first non-singleton axis.

    Returns an array the same size as X. The result will sum to 1
    along the specified axis.
    """

    # make X at least 2d
    y = np.atleast_2d(X)

    # find axis
    if axis is None:
        axis = next(j[0] for j in enumerate(y.shape) if j[1] > 1)

    # multiply y against the theta parameter, 
    y = y * float(theta)

    # subtract the max for numerical stability
    y = y - np.expand_dims(np.max(y, axis = axis), axis)

    # exponentiate y
    y = np.exp(y)

    # take the sum along the specified axis
    ax_sum = np.expand_dims(np.sum(y, axis = axis), axis)

    # finally: divide elementwise
    p = y / ax_sum

    # flatten if X was 1D
    if len(X.shape) == 1: p = p.flatten()

    return p

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

Ответ 9

Более краткий вариант:

def softmax(x):
    return np.exp(x) / np.exp(x).sum(axis=0)

Ответ 10

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

import scipy.special as sc
import numpy as np

def softmax(x: np.ndarray) -> np.ndarray:
    return np.exp(x - sc.logsumexp(x))

Ответ 11

Я бы предложил это:

def softmax(z):
    z_norm=np.exp(z-np.max(z,axis=0,keepdims=True))
    return(np.divide(z_norm,np.sum(z_norm,axis=0,keepdims=True)))

Это будет работать как для стохастика, так и для пакета.
Для более подробной информации смотрите: https://medium.com/@ravish1729/analysis-of-softmax-function-ad058d6a564d

Ответ 12

import tensorflow as tf
import numpy as np

def softmax(x):
    return (np.exp(x).T / np.exp(x).sum(axis=-1)).T

logits = np.array([[1, 2, 3], [3, 10, 1], [1, 2, 5], [4, 6.5, 1.2], [3, 6, 1]])

sess = tf.Session()
print(softmax(logits))
print(sess.run(tf.nn.softmax(logits)))
sess.close()

Ответ 13

Чтобы поддерживать численную стабильность, max (x) следует вычесть. Ниже приведен код для функции softmax;

def softmax (x):

if len(x.shape) > 1:
    tmp = np.max(x, axis = 1)
    x -= tmp.reshape((x.shape[0], 1))
    x = np.exp(x)
    tmp = np.sum(x, axis = 1)
    x /= tmp.reshape((x.shape[0], 1))
else:
    tmp = np.max(x)
    x -= tmp
    x = np.exp(x)
    tmp = np.sum(x)
    x /= tmp


return x

Ответ 14

Уже более подробно ответили в ответах. max вычитается, чтобы избежать переполнения. Я добавляю здесь еще одну реализацию в python3.

import numpy as np
def softmax(x):
    mx = np.amax(x,axis=1,keepdims = True)
    x_exp = np.exp(x - mx)
    x_sum = np.sum(x_exp, axis = 1, keepdims = True)
    res = x_exp / x_sum
    return res

x = np.array([[3,2,4],[4,5,6]])
print(softmax(x))

Ответ 15

Кажется, что все публикуют свое решение, поэтому я опубликую свое:

def softmax(x):
    e_x = np.exp(x.T - np.max(x, axis = -1))
    return (e_x / e_x.sum(axis=0)).T

Я получаю те же результаты, что и импортированные из sklearn:

from sklearn.utils.extmath import softmax

Ответ 16

Мне нужно было что-то совместимое с выводом плотного слоя из Tensorflow.

Решение от @desertnaut в этом случае не работает, потому что у меня есть пакеты данных. Поэтому я пришел с другим решением, которое должно работать в обоих случаях:

def softmax(x, axis=-1):
    e_x = np.exp(x - np.max(x)) # same code
    return e_x / e_x.sum(axis=axis, keepdims=True)

Результаты:

logits = np.asarray([
    [-0.0052024,  -0.00770216,  0.01360943, -0.008921], # 1
    [-0.0052024,  -0.00770216,  0.01360943, -0.008921]  # 2
])

print(softmax(logits))

#[[0.2492037  0.24858153 0.25393605 0.24827873]
# [0.2492037  0.24858153 0.25393605 0.24827873]]

Ссылка: Tensorflow softmax

Ответ 17

Я хотел бы дополнить немного больше понимания проблемы. Здесь правильно вычесть макс массива. Но если вы запустите код в другом сообщении, вы обнаружите, что он не дает вам правильного ответа, когда массив имеет размер 2D или более высокий.

Здесь я даю вам несколько предложений:

  • Чтобы получить max, попробуйте сделать это по оси x, вы получите 1D-массив.
  • Измените свой максимальный массив до первоначальной формы.
  • Сделать np.exp получить экспоненциальное значение.
  • Сделайте np.sum вдоль оси.
  • Получите окончательные результаты.

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

Ответ 18

Цель функции softmax состоит в том, чтобы сохранить соотношение векторов, в отличие от сдавливания конечных точек сигмовидной кишкой, когда значения насыщаются (т.е. стремятся к + / - 1 (tanh) или от 0 до 1 (логистический)). Это связано с тем, что он сохраняет больше информации о скорости изменения в конечных точках и, таким образом, более применим к нейронным сетям с выходным кодированием 1 из N (т.е. Если мы раздавим конечные точки, было бы сложнее дифференцировать 1). выходного класса -N, потому что мы не можем сказать, какой из них является "самым большим" или "самым маленьким", потому что они были сжаты.); также это делает общую выходную сумму равной 1, и чистый победитель будет ближе к 1, в то время как другие числа, которые близки друг к другу, будут суммироваться в 1/p, где p - число выходных нейронов с аналогичными значениями.

Цель вычитания максимального значения из вектора состоит в том, что когда вы выполняете e ^ y экспоненты, вы можете получить очень высокое значение, которое обрезает число с плавающей точкой при максимальном значении, приводящем к привязке, что не имеет место в этом примере. Это становится БОЛЬШОЙ проблемой, если вы вычитаете максимальное значение, чтобы получить отрицательное число, тогда у вас есть отрицательный показатель, который быстро сжимает значения, меняющие соотношение, что и произошло в вопросе плаката и дало неправильный ответ.

Ответ от Udacity УЖАСНО неэффективен. Первое, что нам нужно сделать, это вычислить e ^ y_j для всех компонент вектора, СОХРАНЯЙТЕ ЭТИ ЗНАЧЕНИЯ, затем суммируйте их и делите. Где Udacity испортил это они вычисляют e ^ y_j ДВАЖДЫ !!! Вот правильный ответ:

def softmax(y):
    e_to_the_y_j = np.exp(y)
    return e_to_the_y_j / np.sum(e_to_the_y_j, axis=0)

Ответ 19

Цель состояла в том, чтобы достигнуть подобных результатов, используя Numpy и Tensorflow. Единственное отличие от исходного ответа - параметр axis для np.sum api.

Начальный подход: axis=0 - это, однако, не дает ожидаемых результатов, когда измерения равны N.

Модифицированный подход: axis=len(e_x.shape)-1 - Всегда суммировать по последнему измерению. Это дает результаты, аналогичные функции tenmax flow softmax.

def softmax_fn(input_array):
    """
    | **@author**: Prathyush SP
    |
    | Calculate Softmax for a given array
    :param input_array: Input Array
    :return: Softmax Score
    """
    e_x = np.exp(input_array - np.max(input_array))
    return e_x / e_x.sum(axis=len(e_x.shape)-1)

Ответ 20

Вот обобщенное решение, использующее numpy и сравнение для корректности с tenorflow и scipy:

Подготовка данных:

import numpy as np

np.random.seed(2019)

batch_size = 1
n_items = 3
n_classes = 2
logits_np = np.random.rand(batch_size,n_items,n_classes).astype(np.float32)
print('logits_np.shape', logits_np.shape)
print('logits_np:')
print(logits_np)

Выход:

logits_np.shape (1, 3, 2)
logits_np:
[[[0.9034822  0.3930805 ]
  [0.62397    0.6378774 ]
  [0.88049906 0.299172  ]]]

Softmax с использованием тензор потока:

import tensorflow as tf

logits_tf = tf.convert_to_tensor(logits_np, np.float32)
scores_tf = tf.nn.softmax(logits_np, axis=-1)

print('logits_tf.shape', logits_tf.shape)
print('scores_tf.shape', scores_tf.shape)

with tf.Session() as sess:
    scores_np = sess.run(scores_tf)

print('scores_np.shape', scores_np.shape)
print('scores_np:')
print(scores_np)

print('np.sum(scores_np, axis=-1).shape', np.sum(scores_np,axis=-1).shape)
print('np.sum(scores_np, axis=-1):')
print(np.sum(scores_np, axis=-1))

Выход:

logits_tf.shape (1, 3, 2)
scores_tf.shape (1, 3, 2)
scores_np.shape (1, 3, 2)
scores_np:
[[[0.62490064 0.37509936]
  [0.4965232  0.5034768 ]
  [0.64137274 0.3586273 ]]]
np.sum(scores_np, axis=-1).shape (1, 3)
np.sum(scores_np, axis=-1):
[[1. 1. 1.]]

Softmax с использованием scipy:

from scipy.special import softmax

scores_np = softmax(logits_np, axis=-1)

print('scores_np.shape', scores_np.shape)
print('scores_np:')
print(scores_np)

print('np.sum(scores_np, axis=-1).shape', np.sum(scores_np, axis=-1).shape)
print('np.sum(scores_np, axis=-1):')
print(np.sum(scores_np, axis=-1))

Выход:

scores_np.shape (1, 3, 2)
scores_np:
[[[0.62490064 0.37509936]
  [0.4965232  0.5034768 ]
  [0.6413727  0.35862732]]]
np.sum(scores_np, axis=-1).shape (1, 3)
np.sum(scores_np, axis=-1):
[[1. 1. 1.]]

Softmax с использованием numpy (https://nolanbconaway.github.io/blog/2017/softmax-numpy):

def softmax(X, theta = 1.0, axis = None):
    """
    Compute the softmax of each element along an axis of X.

    Parameters
    ----------
    X: ND-Array. Probably should be floats.
    theta (optional): float parameter, used as a multiplier
        prior to exponentiation. Default = 1.0
    axis (optional): axis to compute values along. Default is the
        first non-singleton axis.

    Returns an array the same size as X. The result will sum to 1
    along the specified axis.
    """

    # make X at least 2d
    y = np.atleast_2d(X)

    # find axis
    if axis is None:
        axis = next(j[0] for j in enumerate(y.shape) if j[1] > 1)

    # multiply y against the theta parameter,
    y = y * float(theta)

    # subtract the max for numerical stability
    y = y - np.expand_dims(np.max(y, axis = axis), axis)

    # exponentiate y
    y = np.exp(y)

    # take the sum along the specified axis
    ax_sum = np.expand_dims(np.sum(y, axis = axis), axis)

    # finally: divide elementwise
    p = y / ax_sum

    # flatten if X was 1D
    if len(X.shape) == 1: p = p.flatten()

    return p


scores_np = softmax(logits_np, axis=-1)

print('scores_np.shape', scores_np.shape)
print('scores_np:')
print(scores_np)

print('np.sum(scores_np, axis=-1).shape', np.sum(scores_np, axis=-1).shape)
print('np.sum(scores_np, axis=-1):')
print(np.sum(scores_np, axis=-1))

Выход:

scores_np.shape (1, 3, 2)
scores_np:
[[[0.62490064 0.37509936]
  [0.49652317 0.5034768 ]
  [0.64137274 0.3586273 ]]]
np.sum(scores_np, axis=-1).shape (1, 3)
np.sum(scores_np, axis=-1):
[[1. 1. 1.]]

Ответ 21

Основываясь на всех ответах и CS231n заметках, позвольте мне подвести итог:

def softmax(x, axis):
    x -= np.max(x, axis=axis, keepdims=True)
    return np.exp(x) / np.exp(x).sum(axis=axis, keepdims=True)

Использование:

x = np.array([[1, 0, 2,-1],
              [2, 4, 6, 8], 
              [3, 2, 1, 0]])
softmax(x, axis=1).round(2)

Выход:

array([[0.24, 0.09, 0.64, 0.03],
       [0.  , 0.02, 0.12, 0.86],
       [0.64, 0.24, 0.09, 0.03]])