[python] Python에서 Softmax 함수를 구현하는 방법

로부터 Udacity의의 깊은 학습 클래스 , y_i의 softmax를 전체 Y 벡터의 지수의 합으로 나눈 지수는 간단하다 :

여기에 이미지 설명을 입력하십시오

어디 S(y_i)의 softmax를 함수 y_ie지수 함수이며, 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)

첫 번째 구현은 명시 적으로 각 열과 최대의 차이를 취한 다음 합계로 나눠도 첫 번째 구현과 동일한 출력 을 생성합니다 .

누군가 수학적으로 이유를 보여줄 수 있습니까? 하나는 정확하고 다른 하나는 잘못입니까?

코드 및 시간 복잡성 측면에서 구현이 유사합니까? 어느 것이 더 효율적입니까?



답변

둘 다 맞지만 수치 안정성의 관점에서 당신이 선호됩니다.

당신은 시작

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)를 임의의 변수로 바꾸면 취소됩니다.


답변

(글쎄 … 질문과 대답 모두에서 많은 혼란이 있습니다 …)

우선 두 가지 솔루션 (예 : 귀하와 제안 된 솔루션)은 동일 하지 않습니다 . 그들은 일어난다 단지 1-D 점수 배열의 특별한 경우에 해당합니다. 제공된 Udacity 퀴즈 예제에서 2 차원 점수 배열을 시도한 경우이를 발견했을 것입니다.

결과적으로 두 솔루션의 실제 차이점은 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

내가 말했듯이 1D 점수 배열의 경우 결과는 실제로 동일합니다.

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)

그럼에도 불구하고 Udacity 퀴즈에서 테스트 예제로 제공된 2 차원 점수 배열의 결과는 다음과 같습니다.

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 = None은 입력 배열의 모든 요소를 ​​합산합니다.

여기서 우리는 행 단위로 합계를 원합니다 axis=0. 1-D 배열의 경우 (전용) 행의 합과 모든 요소의 합이 동일 하므로이 경우 동일한 결과가 발생합니다 …

axis문제를 제외하고, 구현 (최대 첫째을 뺄 즉, 당신의 선택은) 실제로 더 나은 제안 된 솔루션보다! 실제로, 그것은 softmax 기능을 구현하기 위해 권장되는 방법입니다 . 정당화에 대해서는 여기 를 참조 하십시오 (숫자 안정성, 여기에 다른 답변이 지적함).


답변

따라서 이것은 실제로 desertnaut의 답변에 대한 의견이지만 내 평판으로 인해 아직 댓글을 달 수 없습니다. 그가 지적했듯이 입력이 단일 샘플로 구성된 경우에만 버전이 정확합니다. 입력 값이 여러 샘플로 구성되어 있으면 잘못되었습니다. 그러나 desertnaut의 솔루션도 잘못되었습니다. 문제는 일단 1 차원 입력을받은 다음 2 차원 입력을 받는다는 것입니다. 이것을 당신에게 보여 드리겠습니다.

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

Desertnauts를 예로 들어 보겠습니다.

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]])

이 상황에서 desernauts 버전이 실패한다는 것을 알 수 있습니다. (입력이 np.array ([1, 2, 3, 6])와 같은 1 차원 인 경우에는 그렇지 않습니다.

이제 2 차원 입력을 사용하는 이유 때문에 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 개의 샘플이 포함 된 배치로 구성됩니다. 그러나 샘플 1과 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)

또한 TensorFlows softmax 구현 결과는 다음과 같습니다.

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)


답변

둘 다 수학적으로, 구현 측면에서는 정확하지만 첫 번째 방법이 더 낫습니다. softmax를 계산할 때 중간 값이 매우 커질 수 있습니다. 두 개의 큰 숫자를 나누면 수치 적으로 불안정 할 수 있습니다.(스탠포드의) 이 노트 는 본질적으로하고있는 정규화 트릭을 언급합니다.


답변

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]]) 


답변

수학적 관점에서 양측은 동일합니다.

그리고 당신은 이것을 쉽게 증명할 수 있습니다. 하자 m=max(x). 이제 함수 softmax는 i 번째 좌표가 같은 벡터를 반환합니다.

여기에 이미지 설명을 입력하십시오

이 모든 작동 것을 통보 m하기 때문에 모든 (복잡한) 숫자,e^m != 0

  • 계산 복잡성의 관점에서 볼 때 그것들은 또한 동등하며 O(n)시간 에 따라 실행됩니다 n. 벡터의 크기는 어디 입니까?

  • 에서 수치 적 안정성 때문에 관점, 제 용액, 바람직 e^x매우 빠르게 성장하고 짝수의 아주 작은 값 x것이 오버플. 최대 값을 빼면이 오버플로를 제거 할 수 있습니다. 내가 말한 것을 실제로 경험하려면 x = np.array([1000, 5])두 기능 모두에 공급 하십시오. 하나는 올바른 확률을 반환하고 두 번째는nan

  • 솔루션은 벡터에 대해서만 작동합니다 (Udacity 퀴즈에서는 행렬에 대해서도 계산하려고 함). 그것을 고치려면 사용해야합니다.sum(axis=0)


답변

편집하다 . 버전 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

다른 사용자가 설명한 것처럼 최대 값을 빼는 것이 좋습니다. 나는 여기 에 대한 자세한 게시물을 썼습니다 .