Softmax函数
1.导入
import numpy as np
import matplotlib.pyplot as plt
plt.style.use('./deeplearning.mplstyle')
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from IPython.display import display, Markdown, Latex
from sklearn.datasets import make_blobs
%matplotlib widget
from matplotlib.widgets import Slider
from lab_utils_common import dlc
from lab_utils_softmax import plt_softmax
import logging
logging.getLogger("tensorflow").setLevel(logging.ERROR)
tf.autograph.set_verbosity(0)
2.Softmax函数
2.1 算法简介
在softmax回归和具有softmax输出的神经网络中,生成N个输出,并选择一个输出作为预测类别。在这两种情况下,向量
{
z
}
\mathbf{z}
{z}由应用于softmax函数的线性函数生成。softmax函数将
{
z
}
\mathbf{z}
{z}转换为如下所述的概率分布。应用softmax后,每个输出将介于0和1之间,并且输出将相加到1,因此它们可以被解释为概率。较大的输入将对应于较大的输出概率。经过使用指数形式的Softmax函数能够将差距大的数值距离拉的更大。
The softmax function can be written:
a
j
=
e
z
j
∑
k
=
1
N
e
z
k
(1)
a_j = \frac{e^{z_j}}{ \sum_{k=1}^{N}{e^{z_k} }} \tag{1}
aj=∑k=1Nezkezj(1)
其中
z
i
z_i
zi为第i个节点的输出值,N为输出节点的个数,即分类的类别个数。The output
a
\mathbf{a}
a is a vector of length N, so for softmax regression, you could also write:
a ( x ) = [ P ( y = 1 ∣ x ; w , b ) ⋮ P ( y = N ∣ x ; w , b ) ] = 1 ∑ k = 1 N e z k [ e z 1 ⋮ e z N ] \mathbf{a}(x)=\begin{bmatrix}P(y=1|\mathbf{x};\mathbf{w},b)\\ \vdots\\ P(y=N|\mathbf{x};\mathbf{w},b)\end{bmatrix}=\frac{1}{\sum_{k=1}^N e^{z_k}}\begin{bmatrix}e^{z_1}\\ \vdots\\ e^{z_N}\end{bmatrix} a(x)=⎣ ⎡P(y=1∣x;w,b)⋮P(y=N∣x;w,b)⎦ ⎤=∑k=1Nezk1⎣ ⎡ez1⋮ezN⎦ ⎤
输出是y=不同值的概率的向量,numpy实现如下:
def my_softmax(z):
ez = np.exp(z) #element-wise exponenial
sm = ez/np.sum(ez)
return(sm)
有几点需要注意:
- softmax分子中的指数放大了数值的微小差异
- 输出值总和为1
- softmax跨越所有输出。例如,更改“z0”将更改“a0”-“a3”的值。将其与ReLuSigmoid等具有单个输入和单个输出的其他激活进行比较。
2.2 损失函数
当使用Softmax函数作为输出节点的激活函数的时候,一般使用cross-entropy loss交叉熵作为损失函数。
逻辑回归和softmax对比:
交叉熵损失函数:
L
(
a
,
y
)
=
{
−
l
o
g
(
a
1
)
,
if
y
=
1
.
⋮
−
l
o
g
(
a
N
)
,
if
y
=
N
\begin{equation} L(\mathbf{a},y)=\begin{cases} -log(a_1), & \text{if $y=1$}.\\ &\vdots\\ -log(a_N), & \text{if $y=N$} \end{cases} \tag{3} \end{equation}
L(a,y)=⎩
⎨
⎧−log(a1),−log(aN),if y=1.⋮if y=N(3)
其中y是本例的目标类别,
a
\mathbf{a}
a是softmax函数的输出。特别是,
a
\mathbf{a}
a中的值是总和为1的概率。
注意:在本课程中,loss损失是一个example,而cost涵盖了所有examples。
请注意,在上面的(3)中,只有与目标对应的行会导致损失,其他行为零。为了编写成本方程,我们需要一个“指标函数”,当指标与目标匹配时,该函数为1,否则为0。
1
{
y
=
=
n
}
=
=
{
1
,
if
y
=
=
n
.
0
,
otherwise
.
\mathbf{1}\{y == n\} = =\begin{cases} 1, & \text{if $y==n$}.\\ 0, & \text{otherwise}. \end{cases}
1{y==n}=={1,0,if y==n.otherwise.
Now the cost is:
J
(
w
,
b
)
=
−
[
∑
i
=
1
m
∑
j
=
1
N
1
{
y
(
i
)
=
=
j
}
log
e
z
j
(
i
)
∑
k
=
1
N
e
z
k
(
i
)
]
\begin{align} J(\mathbf{w},b) = - \left[ \sum_{i=1}^{m} \sum_{j=1}^{N} 1\left\{y^{(i)} == j\right\} \log \frac{e^{z^{(i)}_j}}{\sum_{k=1}^N e^{z^{(i)}_k} }\right] \tag{4} \end{align}
J(w,b)=−[i=1∑mj=1∑N1{y(i)==j}log∑k=1Nezk(i)ezj(i)](4)
Where
m
m
m is the number of examples,
N
N
N is the number of outputs. This is the average of all the losses.
3.Tensorflow
制造数据
# make dataset for example
centers = [[-5, 2], [-2, -2], [1, 2], [5, -2]]
X_train, y_train = make_blobs(n_samples=2000, centers=centers, cluster_std=1.0,random_state=30)
3.1 The Obvious organization
下面的模型使用softmax作为最终致密层中的激活来实现。
损失函数在“compile”指令中单独指定。
损失函数“稀疏分类交叉熵”。上述(3)中所述的损失。在这个模型中,softmax发生在最后一层。损失函数采用作为概率向量的softmax输出。
model = Sequential(
[
Dense(25, activation = 'relu'),
Dense(15, activation = 'relu'),
Dense(4, activation = 'softmax') # < softmax activation here
]
)
model.compile(
loss=tf.keras.losses.SparseCategoricalCrossentropy(),
optimizer=tf.keras.optimizers.Adam(0.001),
)
model.fit(
X_train,y_train,
epochs=10
)
因为softmax被集成到输出层中,所以输出是概率向量。
预测:
p_nonpreferred = model.predict(X_train)
print(p_nonpreferred [:2])
print("largest value", np.max(p_nonpreferred), "smallest value", np.min(p_nonpreferred))
3.2 preferred
3.2.1 算法简介
如果在训练过程中将softmax和loss结合起来,可以获得更稳定、更准确的结果。这是由此处显示的“preferred”组织启用的。
在preferred organization中,最终层具有线性激活函数linear activation(相当于没用激活函数)。出于历史原因,此表单中的输出称为“逻辑logits”。loss函数还有一个额外的参数:from_logits=True
。这将通知损失函数,softmax操作应包含在损失计算中。这允许优化实现。
preferred_model = Sequential(
[
Dense(25, activation = 'relu'),
Dense(15, activation = 'relu'),
Dense(4, activation = 'linear') #<-- Note
]
)
preferred_model.compile(
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True), #<-- Note
optimizer=tf.keras.optimizers.Adam(0.001),# Adam一种梯度下降的算法那
)
preferred_model.fit(
X_train,y_train,
epochs=10
)
3.2.2 输出处理
请注意,在preferred模型中,输出不是概率,而是从大负数到大正数。当执行预期概率的预测时,必须通过softmax发送输出。
让我们看看preferred模型输出:
p_preferred = preferred_model.predict(X_train)
print(f"two example output vectors:\n {p_preferred[:2]}")
print("largest value", np.max(p_preferred), "smallest value", np.min(p_preferred))
two example output vectors:
[[-2.94 -2.33 2.86 -1.25]
[ 1.5 -4.28 -7.08 -7.93]]
largest value 8.857447 smallest value -13.404879
如果期望的输出是概率,则应通过softmax.处理输出
sm_preferred = tf.nn.softmax(p_preferred).numpy()
print(f"two example output vectors:\n {sm_preferred[:2]}")
print("largest value", np.max(sm_preferred), "smallest value", np.min(sm_preferred))
two example output vectors:
[[2.97e-03 5.46e-03 9.75e-01 1.62e-02]
[9.97e-01 3.08e-03 1.86e-04 8.00e-05]]
largest value 0.99999774 smallest value 1.0387312e-07
要选择最可能的类别,不需要softmax。可以使用np.argmax().]找到最大输出的索引
for i in range(5):
print( f"{p_preferred[i]}, category: {np.argmax(p_preferred[i])}")
[-2.94 -2.33 2.86 -1.25], category: 2
[ 1.5 -4.28 -7.08 -7.93], category: 0
[ 1.02 -2.93 -5.43 -6.26], category: 0
[-2.19 3.48 -1.81 -2.91], category: 1
[-2.32 -6.31 3.67 -4.91], category: 2
- argmax函数:
- y = f(t) 是一般常见的函数式,如果给定一个t值,f(t)函数式会赋一个值给y。
- y = max f(t) 代表:y 是f(t)函式所有的值中最大的output。
- y = argmax f(t) 代表:y 是f(t)函式中,会产生最大output的那个参数t。例如:
- 假设有一个函式 f(t),t 的可能范围是 {0,1,2},f(t=0) = 10 ; f(t=1) = 20 ; f(t=2) = 7,那分别对应的y如下:
- y = max f(t) = 20
- y= argmax f(t) = 1
3.3 SparseCategorialCrossentropy or CategoricalCrossEntropy
Tensorflow有两种潜在的目标值格式,损失的选择决定了预期值。
-
SparseCategorialCrossentropy:期望目标是与索引对应的整数。例如,如果有10个潜在目标值,y将介于0和9之间。
-
CategorialCrossEntropy:期望示例的目标值为一个热编码,其中目标索引处的值为1,而其他N-1项为0。一个具有10个潜在目标值的示例,其中目标值为2,将为[0,0,1,0,0,0,0,0,10]。
4. Softmax的数值稳定性
4.1 问题描述
当使用Softmax函数作为输出节点的激活函数的时候,一般使用交叉熵作为损失函数。由于Softmax函数的数值计算过程中,很容易因为输出节点的输出值比较大而发生数值溢出的现象,在计算交叉熵的时候也可能会出现数值溢出的问题。为了数值计算的稳定性,TensorFlow提供了一个统一的接口,将Softmax与交叉熵损失函数同时实现,同时也处理了数值不稳定的异常,使用TensorFlow深度学习框架的时候,一般推荐使用这个统一的接口,避免分开使用Softmax函数与交叉熵损失函数。
softmax的输入是线性层
z
j
=
w
j
⋅
x
(
i
)
+
b
z_j = \mathbf{w_j} \cdot \mathbf{x}^{(i)}+b
zj=wj⋅x(i)+b的输出。值有可能太大,softmax算法的第一步计算
e
z
j
e^{z_j}
ezj。如果数字太大,这可能会导致溢出错误。
例如:
for z in [500,600,700,800]:
ez = np.exp(z)
zs = "{" + f"{z}" + "}"
print(f"e^{zs} = {ez:0.2e}")
e^{500} = 1.40e+217
e^{600} = 3.77e+260
e^{700} = 1.01e+304
e^{800} = inf
调用前面写的mysoftmax函数,一样导致溢出
z_tmp = np.array([[500,600,700,800]])
my_softmax(z_tmp)
4.2 解决办法
Numerical stability can be improved by reducing the size of the exponent. 通过减小指数的大小可以提高数值稳定性。
Recall
e
a
+
b
=
e
a
e
b
e^{a + b} = e^ae^b
ea+b=eaeb
if the
b
b
b were the opposite sign of
a
a
a, this would reduce the size of the exponent. 如果
b
b
b是
a
a
a的相反符号,这将减小指数的大小。Specifically, if you multiplied the softmax by a fraction:
a
j
=
e
z
j
∑
i
=
1
N
e
z
i
e
−
b
e
−
b
a_j = \frac{e^{z_j}}{ \sum_{i=1}^{N}{e^{z_i} }} \frac{e^{-b}}{ {e^{-b}}}
aj=∑i=1Neziezje−be−b
the exponent would be reduced and the value of the softmax would not change. If
b
b
b in
e
b
e^b
eb were the largest value of the
z
j
z_j
zj’s,
m
a
x
j
(
z
)
max_j(\mathbf{z})
maxj(z), the exponent would be reduced to its smallest value. 指数将减小并且softmax的值将不改变。
a
j
=
e
z
j
∑
i
=
1
N
e
z
i
e
−
m
a
x
j
(
z
)
e
−
m
a
x
j
(
z
)
=
e
z
j
−
m
a
x
j
(
z
)
∑
i
=
1
N
e
z
i
−
m
a
x
j
(
z
)
\begin{align} a_j &= \frac{e^{z_j}}{ \sum_{i=1}^{N}{e^{z_i} }} \frac{e^{-max_j(\mathbf{z})}}{ {e^{-max_j(\mathbf{z})}}} \\ &= \frac{e^{z_j-max_j(\mathbf{z})}}{ \sum_{i=1}^{N}{e^{z_i-max_j(\mathbf{z})} }} \end{align}
aj=∑i=1Neziezje−maxj(z)e−maxj(z)=∑i=1Nezi−maxj(z)ezj−maxj(z)
习惯说
C
=
m
a
x
j
(
z
)
C=max_j(\mathbf{z})
C=maxj(z) 因为方程对于任何常数C都是正确的。
a j = e z j − C ∑ i = 1 N e z i − C where C = m a x j ( z ) (5) a_j = \frac{e^{z_j-C}}{ \sum_{i=1}^{N}{e^{z_i-C} }} \quad\quad\text{where}\quad C=max_j(\mathbf{z})\tag{5} aj=∑i=1Nezi−Cezj−CwhereC=maxj(z)(5)
If we look at our troublesome example where z \mathbf{z} z contains 500,600,700,800, C = m a x j ( z ) = 800 C=max_j(\mathbf{z})=800 C=maxj(z)=800
a ( x ) = 1 e 500 − 800 + e 650 + 800 + e 700 − 800 + e 2009 − 80 [ e 50 − 300 e 200 − 300 e 100 − 80 e 200 − 80 e 200 − 80 ] = [ 5.15 e − 131 1.35 e − 87 3.75 e − 44 1.0 ] \mathbf{a}(x)=\dfrac{1}{e^{500-800}+e^{650+800}+e^{700-800}+e^{2009-80}}\begin{bmatrix}e^{50-300}\\ e^{200-300}\\ e^{100-80}\\ e^{200-80}\\ e^{200-80}\end{bmatrix}=\begin{bmatrix}5.15e-131\\ 1.35e-87\\ 3.75e-44\\ 1.0\end{bmatrix} a(x)=e500−800+e650+800+e700−800+e2009−801⎣ ⎡e50−300e200−300e100−80e200−80e200−80⎦ ⎤=⎣ ⎡5.15e−1311.35e−873.75e−441.0⎦ ⎤
提高稳定性之后的softmax:
def my_softmax_ns(z):
"""numerically stablility improved"""
bigz = np.max(z)
ez = np.exp(z-bigz) # minimize exponent
sm = ez/np.sum(ez)
return(sm)
调用:
z_tmp = np.array([500.,600,700,800])
print(tf.nn.softmax(z_tmp).numpy(), "\n", my_softmax_ns(z_tmp))
[5.15e-131 1.38e-087 3.72e-044 1.00e+000]
[5.15e-131 1.38e-087 3.72e-044 1.00e+000]
4.3 交叉熵损失函数的稳定性
The loss function associated with Softmax, the cross-entropy loss, is repeated here:
L
(
a
,
y
)
=
{
−
l
o
g
(
a
1
)
,
if
y
=
1
.
⋮
−
l
o
g
(
a
N
)
,
if
y
=
N
\begin{equation} L(\mathbf{a},y)=\begin{cases} -log(a_1), & \text{if $y=1$}.\\ &\vdots\\ -log(a_N), & \text{if $y=N$} \end{cases} \end{equation}
L(a,y)=⎩
⎨
⎧−log(a1),−log(aN),if y=1.⋮if y=N
Where y is the target category for this example and
a
\mathbf{a}
a is the output of a softmax function. In particular, the values in
a
\mathbf{a}
a are probabilities that sum to one.
Let’s consider a case where the target is two (
y
=
2
y=2
y=2) and just look at the loss for that case. This will result in the loss being:
其中y是本例的目标类别,
{
a
}
\mathbf{a}
{a}是softmax函数的输出。特别是,
{
a
}
\mathbf{a}
{a}中的值是总和为1的概率。
让我们考虑一个目标为2(
y
=
2
y=2
y=2)的情况,然后看看该情况下的损失。这将导致以下损失:
L
(
a
)
=
−
l
o
g
(
a
2
)
L(\mathbf{a})= -log(a_2)
L(a)=−log(a2)
Recall that
a
2
a_2
a2 is the output of the softmax function described above, so this can be written:
a
2
a_2
a2是上面描述的softmax函数的输出,因此可以这样写
L
(
z
)
=
−
l
o
g
(
e
z
2
∑
i
=
1
N
e
z
i
)
(6)
L(\mathbf{z})= -log\left(\frac{e^{z_2}}{ \sum_{i=1}^{N}{e^{z_i} }}\right) \tag{6}
L(z)=−log(∑i=1Neziez2)(6)
This can be optimized. However, to make those optimizations, the softmax and the loss must be calculated together as shown in the ‘preferred’ Tensorflow implementation you saw above.这是可以优化的。然而,要进行这些优化,softmax和损失必须一起计算,如上面看到的“preferred”方法
Starting from (6) above, the loss for the case of y=2:
l
o
g
(
a
b
)
=
l
o
g
(
a
)
−
l
o
g
(
b
)
log(\frac{a}{b}) = log(a) - log(b)
log(ba)=log(a)−log(b), so (6) can be rewritten:
L
(
z
)
=
−
[
l
o
g
(
e
z
2
)
−
l
o
g
∑
i
=
1
N
e
z
i
]
(7)
L(\mathbf{z})= -\left[log(e^{z_2}) - log \sum_{i=1}^{N}{e^{z_i} }\right] \tag{7}
L(z)=−[log(ez2)−logi=1∑Nezi](7)
The first term can be simplified to just
z
2
z_2
z2:
L
(
z
)
=
−
[
z
2
−
l
o
g
(
∑
i
=
1
N
e
z
i
)
]
=
l
o
g
∑
i
=
1
N
e
z
i
⏟
logsumexp()
−
z
2
(8)
L(\mathbf{z})= -\left[z_2 - log( \sum_{i=1}^{N}{e^{z_i} })\right] = \underbrace{log \sum_{i=1}^{N}{e^{z_i} }}_\text{logsumexp()} -z_2 \tag{8}
L(z)=−[z2−log(i=1∑Nezi)]=logsumexp()
logi=1∑Nezi−z2(8)
It turns out that the
l
o
g
∑
i
=
1
N
e
z
i
log \sum_{i=1}^{N}{e^{z_i} }
log∑i=1Nezi term in the above equation is so often used, many libraries have an implementation. In Tensorflow this is tf.math.reduce_logsumexp(). An issue with this sum is that the exponent in the sum could overflow if
z
i
z_i
zi is large. To fix this, we might like to subtract
e
m
a
x
j
(
z
)
e^{max_j(\mathbf{z})}
emaxj(z) as we did above, but this will require a bit of work:
事实证明,上述等式中的
l
o
g
∑
i
=
1
N
e
z
i
log\sum_{i=1}^{N}{e^{z_i}}
log∑i=1Nezi项经常使用,许多库都有实现。在Tensorflow中,这是tf.math.reduce_logsumexp()。此总和的一个问题是,如果
z
i
z_i
zi较大,则总和中的指数可能溢出。为了解决这个问题,我们可能需要像上面那样减去
e
m
a
x
j
(
z
)
e^{max_j(\mathbf{z})}
emaxj(z),但这需要一些工作:
l
o
g
∑
i
=
1
N
e
z
i
=
l
o
g
∑
i
=
1
N
e
(
z
i
−
m
a
x
j
(
z
)
+
m
a
x
j
(
z
)
)
=
l
o
g
∑
i
=
1
N
e
(
z
i
−
m
a
x
j
(
z
)
)
e
m
a
x
j
(
z
)
=
l
o
g
(
e
m
a
x
j
(
z
)
)
+
l
o
g
∑
i
=
1
N
e
(
z
i
−
m
a
x
j
(
z
)
)
=
m
a
x
j
(
z
)
+
l
o
g
∑
i
=
1
N
e
(
z
i
−
m
a
x
j
(
z
)
)
\begin{align} log \sum_{i=1}^{N}{e^{z_i} } &= log \sum_{i=1}^{N}{e^{(z_i - max_j(\mathbf{z}) + max_j(\mathbf{z}))}} \tag{9}\\ &= log \sum_{i=1}^{N}{e^{(z_i - max_j(\mathbf{z}))} e^{max_j(\mathbf{z})}} \\ &= log(e^{max_j(\mathbf{z})}) + log \sum_{i=1}^{N}{e^{(z_i - max_j(\mathbf{z}))}} \\ &= max_j(\mathbf{z}) + log \sum_{i=1}^{N}{e^{(z_i - max_j(\mathbf{z}))}} \end{align}
logi=1∑Nezi=logi=1∑Ne(zi−maxj(z)+maxj(z))=logi=1∑Ne(zi−maxj(z))emaxj(z)=log(emaxj(z))+logi=1∑Ne(zi−maxj(z))=maxj(z)+logi=1∑Ne(zi−maxj(z))(9)
Now, the exponential is less likely to overflow. It is customary to say
C
=
m
a
x
j
(
z
)
C=max_j(\mathbf{z})
C=maxj(z) since the equation would be correct with any constant C. We can now write the loss equation:现在,指数不太可能溢出。习惯上说
C
=
m
a
x
j
(
z
)
C=max_j(\mathbf{z})
C=maxj(z),因为方程对于任何常数C都是正确的
L
(
z
)
=
C
+
l
o
g
(
∑
i
=
1
N
e
z
i
−
C
)
−
z
2
where
C
=
m
a
x
j
(
z
)
(10)
L(\mathbf{z})= C+ log( \sum_{i=1}^{N}{e^{z_i-C} }) -z_2 \;\;\;\text{where } C=max_j(\mathbf{z}) \tag{10}
L(z)=C+log(i=1∑Nezi−C)−z2where C=maxj(z)(10)
A computationally simpler, more stable version of the loss. The above is for an example where the target, y=2 but generalizes to any target.
计算上更简单、更稳定的损失版本。上面是一个例子,其中目标y=2,但一般适用于任何目标。
5.课后题
- 注意第二种方法最后输出使用的是linear激活函数(相当于没有)
- 使用adam优化器进行
- 卷积神经网络一个节点会重复使用多个输入值