吴恩达机器学习-C2W2-Softmax函数

Softmax Function

在这个实验中,我们将探讨softmax函数。该函数用于Softmax回归和神经网络中解决多类分类问题。
在这里插入图片描述

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)

通常,在本课程中,笔记本使用的惯例是计数从0开始,以N-1结束, ∑ i = 0 N − 1 \sum_{i=0}^{N-1} i=0N1,而讲座从1开始,以N结束, ∑ i = 1 N \sum_{i=1}^{N} i=1N。这是因为代码通常从0开始迭代,而在课堂上,从1数到N会得到更清晰、更简洁的方程。这个笔记本有更多的方程,而不是典型的实验室,因此将打破惯例,将从1到N计数。

Softmax Function

在softmax回归和具有softmax输出的神经网络中,都产生N个输出,并选择一个输出作为预测类别。在这两种情况下,向量 z \mathbf{z} z都是由应用于softmax函数的线性函数生成的。softmax函数将 z \mathbf{z} z转换为如下所述的概率分布。应用softmax后,每个输出将在0到1之间,输出将加1,因此它们可以被解释为概率。更大的输入将对应更大的输出概率。

在这里插入图片描述softmax函数可以写成:

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)
输出a是一个长度为N的向量,所以对于softmax回归,你也可以这样写:
在这里插入图片描述这表明输出是一个概率向量。第一个条目是给定输入 x \mathbf{x} x和参数 w \mathbf{w} w b \mathbf{b} b,输入是第一个类别的概率。
让我们创建一个NumPy实现:

def my_softmax(z):
    ez = np.exp(z)              #element-wise exponenial
    sm = ez/np.sum(ez)
    return(sm)

下面,使用滑块改变“z”输入的值

plt.close("all")
plt_softmax(my_softmax)

当你改变上面z的值时,有几件事需要注意:

  • softmax分子中的指数放大了数值中的微小差异
  • 输出值之和为1
  • softmax跨越所有输出。例如,’ z0 ‘的变化将改变’ a0 ’ - ’ a3 '的值。将此与具有单个输入和单个输出的其他激活(如ReLu或Sigmoid)进行比较。

开销

在这里插入图片描述与Softmax相关的损失函数,即交叉熵损失为:
在这里插入图片描述其中y是本例的目标类别, a \mathbf{a} a是softmax函数的输出。特别地, a \mathbf{a} a中的值是求和为1的概率。

在本课程中,损失是一个例子,而成本涵盖了所有的例子。

注意在上面(3)中,只有目标对应的线对损失有贡献,其他线为零。为了编写成本方程,我们需要一个“指示函数”,当指标与目标匹配时,它将为1,否则为零。
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.
在这里插入图片描述其中 m m m为示例数, N N N为输出数。这是所有损失的平均值。

Tensorflow

本实验将讨论两种实现softmax的方法,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)

明显的组织

下面的模型是用softmax作为最终Dense层的激活来实现的。损失函数在compile指令中单独指定。
损失函数SparseCategoricalCrossentropy。上述(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))

Preferred

回顾讲座,如果在训练中结合softmax和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),
)

preferred_model.fit(
    X_train,y_train,
    epochs=10
)
        

Output Handling

注意,在首选模型中,输出不是概率,而是从大的负数到大的正数的范围。当执行期望概率的预测时,必须通过softmax发送输出。
让我们看看首选模型输出:

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

输出预测不是概率!如果期望的输出是概率,输出应该由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))

要选择最可能的类别,不需要softmax。可以使用np.argmax()找到最大输出的索引。

for i in range(5):
    print( f"{p_preferred[i]}, category: {np.argmax(p_preferred[i])}")

SparseCategorialCrossentropy或CategoricalCrossEntropyTensorflow有两种可能的目标值格式,损失的选择定义了预期的格式。

  • SparseCategorialCrossentropy:期望目标是与索引对应的整数。例如,如果有10个潜在的目标值,y将在0到9之间。
  • CategoricalCrossEntropy:期望示例的目标值为one-hot编码,其中目标索引处的值为1,而其他N-1项为零。例如,有10个可能的目标值,其中目标为2,则为[0,0,1,0,0,0,0,0,0,0]。

祝贺

在这个实验中

  • 更加熟悉softmax函数及其在softmax回归和神经网络中softmax激活中的应用。
  • 学习在Tensorflow中首选的模型构建:
    最后一层没有激活(和线性激活一样)
    • SparseCategoricalCrossentropy损失函数
    • 使用from_logits=True
    • 认识到与ReLu和Sigmoid不同,softmax跨越多个输出。

数值稳定性(可选)

本节讨论一些用来提高数值稳定性的方法。这是为感兴趣的读者准备的,完全不是必需的。

Softmax数值稳定性

softmax的输入是线性层 z j = w j ⋅ x ( i ) + b z_j = \mathbf{w_j} \cdot \mathbf{x}^{(i)}+b zj=wjx(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}")

如果指数太大,操作将产生溢出。当然,my_softmax()也会产生同样的错误:

z_tmp = np.array([[500,600,700,800]])
my_softmax(z_tmp)

通过减小指数的大小可以提高数值稳定性。
回忆
e a + b = e a e b e^{a + b} = e^ae^b ea+b=eaeb
如果 b b b a a a的相反符号,这将减小指数的大小。具体来说,如果你将softmax乘以一个分数:
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=1Neziezjebeb
指数会减少,softmax的值不会改变。如果 e b e^b eb中的 b b b z j z_j zj s, m a x j ( z ) max_j(\mathbf{z}) maxj(z)的最大值,则指数将减少到其最小值。
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=1Neziezjemaxj(z)emaxj(z)=i=1Nezimaxj(z)ezjmaxj(z)

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=1NeziCezjCwhereC=maxj(z)(5)

在这里插入图片描述

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)

让我们尝试一下,并将其与tensorflow的实现进行比较:

z_tmp = np.array([500.,600,700,800])
print(tf.nn.softmax(z_tmp).numpy(), "\n", my_softmax_ns(z_tmp))

交叉熵损失数值稳定性

与Softmax相关的损失函数,即交叉熵损失,在这里重复:
在这里插入图片描述其中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)

回想一下, 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)
这是可以优化的。然而,为了进行这些优化,softmax和损失必须一起计算,如您在上面看到的“首选”Tensorflow实现所示。

由上式(6)出发,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),所以(6)可以改写为:
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=1Nezi](7)
第一项可以简化为 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)=[z2log(i=1Nezi)]=logsumexp() logi=1Neziz2(8)
事实证明,上述等式中的 l o g ∑ i = 1 N e z i log \sum_{i=1}^{N}{e^{z_i} } logi=1Nezi项经常被使用,许多库都有实现。在Tensorflow中,这是tf.math.reduce_logsumexp()。关于t的问题

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=1Nezi=logi=1Ne(zimaxj(z)+maxj(z))=logi=1Ne(zimaxj(z))emaxj(z)=log(emaxj(z))+logi=1Ne(zimaxj(z))=maxj(z)+logi=1Ne(zimaxj(z))(9)
现在,指数不太可能溢出。习惯上说 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=1NeziC)z2where C=maxj(z)(10)
一个计算上更简单,更稳定的损失版本。上面是一个例子,其中目标y=2,但推广到任何目标。

  • 16
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值