web-dev-qa-db-ja.com

PythonでSoftmax関数を実装する方法

Udacityの深層学習クラス から、y_iのsoftmaxは、単純に指数をYベクトル全体の指数の合計で割ったものです。

enter image description here

ここで、S(y_i)y_iのsoftmax関数、eは指数関数、jはnoです。入力ベクトル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)

これは、最初の実装が各列と最大値の差を明示的に取得して合計で除算した場合でも、最初の実装と同じ 出力を生成します .

誰かがなぜ数学的に示すことができますか?一方は正しいのか、もう一方は間違っているのですか。

コードと時間の複雑さという点で実装は似ていますか?どちらがより効率的ですか?

197
alvas

どちらも正しいですが、数値の安定性の観点からはあなたのものが好ましいです。

あなたは始めます

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)を任意の変数に置き換えることができ、それはキャンセルされます。

108

(まあ...質問と回答の両方で、ここでかなり混乱しています...)

まず、2つのソリューション(つまり、あなたのものと提案されたもの)はnotと同等です。それらはhappen1-Dスコア配列の特別な場合にのみ等価です。 Udacityクイズの例で2Dスコア配列も試してみたら、それを発見できたでしょう。

結果的に、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

先ほど言ったように、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)

それでも、テスト例として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]]

結果は異なります。2番目の結果は、Udacityクイズで予想されるものと実際に同じで、すべての列の合計が1になりますが、最初の(間違った)結果ではありません。

そのため、実際の実装の詳細、つまりaxis引数は大騒ぎでした。 numpy.sum documentation によると:

デフォルトのaxis = Noneは、入力配列のすべての要素を合計します

ここでは行ごとに合計したいので、axis=0です。 1次元配列の場合、(唯一の)行の合計とすべての要素の合計は偶然同じであるため、その場合の結果は同じになります...

axisの問題は別として、実装(つまり、最初に最大値を減算する選択)は、提案されたソリューションよりも実際にはbetterです!実際、これはsoftmax関数を実装する推奨方法です-正当化については here を参照してください(数値の安定性、上記のいくつかの回答でも指摘されています)。

86
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

デザートの例を見てみましょう:

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次元入力を使用するのはそのためです。次の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アクティベーションが3行目と同じになり、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)
45
ChuckFive

どちらも数学的には正しいが、実装面では最初の方が良いと思います。 softmaxを計算すると、中間値が非常に大きくなる可能性があります。 2つの大きな数を除算すると、数値的に不安定になる可能性があります。 これらのノート (Stanfordから)は、基本的にあなたがしていることである正規化トリックについて述べています。

31
Shagun Sodhani

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]]) 
23
Roman Orac

数学的な観点からは、両側は等しいです。

そして、あなたは簡単にこれを証明することができます。 m=max(x)しましょう。あなたの関数softmaxは、i番目の座標がに等しいベクトルを返します。

enter image description here

これはあらゆるmに対して機能することに注意してください。なぜなら、すべての(複雑な場合でも)数に対してe^m != 0

  • 計算の複雑さの観点からも、それらは同等であり、両方ともO(n)時間に実行されます。ここでnはベクトルのサイズです。

  • e^xは非常に速く成長し、xの非常に小さい値でもオーバーフローするので、 数値安定性 /の観点から、最初の解決策が好ましいです。最大値を引くと、このオーバーフローを回避できます。私が話していたことを実際に体験するには、両方の関数にx = np.array([1000, 5])を入れてみてください。 1つは正しい確率を返し、2つ目はnanでオーバーフローします

  • 質問とは関係ありませんが、解決策はベクトルに対してのみ有効です(Udacityクイズでは行列に対しても計算するように求めています)。それを修正するためにはsum(axis=0)を使う必要があります。

10
Salvador Dali

ここ あなたは彼らがなぜ- maxを使ったのかを知ることができます。

そこから:

「実際にSoftmax関数を計算するためのコードを書いているとき、中間項は指数のために非常に大きくなるかもしれません。大きな数を分割することは数値的に不安定になることがあるので、正規化トリックを使うことは重要です。」

9
Sadegh Salehi

_ edit _ 。バージョン1.2.0以降、scipyは特別な機能としてsoftmaxを含みます。

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

ソフトマックスを任意の軸に適用する関数を書きました。

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

他のユーザーが説明したように、最大​​値を引くことは良い習慣です。私はそれについての詳細な投稿を書きました ここ

8
Nolan Conaway

より簡潔なバージョンは:

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

別の解決策を提供するために、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))
4
PikalaxALT

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

1

みんなが自分の解決策を投稿しているようですので、私が投稿します。

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
1
Julian

私はこれをお勧めします:

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 を参照してください。

1

数値安定性を維持するために、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
1
Rahul Ahuja

テンソル流と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.]]

Scipyを使ったSoftmax:

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

Numpyを使ったSoftmax( 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.]]
0
mrgloom

目的は、NumpyとTensorflowを使用して同様の結果を達成することでした。元の答えからの唯一の変更はnp.sum apiのaxisパラメータです。

初期アプローチ axis=0 - 次元がNの場合、これは意図した結果を提供しません。

修正されたアプローチ axis=len(e_x.shape)-1 - 常に最後の次元で合計します。これはtensorflowの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)
0
kingspp

私は問題についてもう少し理解を深めたいと思います。ここでは、配列の最大値を引くことが正しいです。しかし、他の記事でコードを実行すると、配列が2次元以上の場合、正しい答えが得られないことがわかります。

ここで私はあなたにいくつかの提案をします:

  1. 最大にするには、x軸に沿ってやってみると、1次元配列になります。
  2. 最大配列を元の形に変形します。
  3. Np.expが指数値になるようにします。
  4. 軸に沿ってnp.sumを実行します。
  5. 最終結果を入手してください。

結果に従うと、ベクトル化を実行して正しい答えが得られます。それは大学の宿題に関連しているので、私はここに正確なコードを投稿することはできませんが、私があなたが理解していないならもっと提案をしたいと思います。

0
Hao Xu

上記の回答で既に詳細に回答されています。オーバーフローを避けるために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))
0
Debashish

Softmax関数の目的は、値が飽和する(すなわち、+ / - 1(tanh)または0から1(ロジスティック)になる傾向がある)ようにシグモイドで終点を押しつぶすのとは対照的に、ベクトルの比を保存することである。これは、終点での変化率に関するより多くの情報を保持しているため、1-of-N出力エンコーディングを持つニューラルネットにより適しているためです(つまり、終点を押しつぶすと、1を区別するのが難しくなります)。 -of-N出力クラスは、どれが「最大」または「最小」のどちらであるかがわからないからです。また、合計出力の合計が1になり、明確な勝者が1に近くなりますが、互いに近い他の数の合計は1/pになります。ここで、pは、同様の値を持つ出力ニューロンの数です。

ベクトルから最大値を引く目的は、指数を実行すると、浮動小数点を最大値でクリップしてタイにつながる非常に高い値になる可能性があることです。この例ではそうではありません。あなたが負の数を作るために最大値を引くならば、これはBIG問題になります、そしてあなたはポスターの質問で起こったことである比率を変える値を急速に縮める負の指数を持っています。

Udacityが提供した答えは非常に非効率的です。最初にやるべきことは、すべてのベクトル成分についてe ^ y_jを計算し、これらの値を保ち、それらを合計して除算することです。 Udacityがめちゃくちゃになったところで彼らはe ^ y_jを2回計算します!これが正しい答えです。

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)
0
user2356685
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()
0
King