在本实验中,我们将探索 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 结束的约定,,虽然讲座以 1 开始,以 N 结束,这是因为在讲课时,代码通常会从 0 开始迭代,将 1 数到 N 会导致方程式更清晰、更简洁。这个笔记本包含的方程式比实验室的典型方程式多,因此将打破惯例,将 1 计算到 N。
Softmax Function
在 softmax 回归和具有 Softmax 输出的神经网络中,都会生成 N 个输出,并选择一个输出作为预测类别。在这两种情况下,向量z由应用于 SoftMax 函数的线性函数生成。softmax 函数转换z转换为如下所述的概率分布。应用 softmax 后,每个输出将介于 0 和 1 之间,输出将加到 1,因此它们可以被解释为概率。较大的输入将对应于较大的输出概率。
softmax函数可以写成
输出 a是长度为 N 的向量,因此对于 softmax 回归,您也可以编写:
这表明输出是一个概率向量。第一个条目是给定输入 x 的第一个类别的输入概率和参数 w和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)进行比较,这些激活具有单一输入和单一输出。
成本(cost)
与 Softmax 相关的损失函数,即交叉熵损失,为:
其中 y 是此示例的目标类别,而 y 是是 SoftMAX 函数的输出。具体而言,a 中的值是总和等于 1 的概率。 回想一下:在本课程中,“损失”只是一个示例,而“成本”则涵盖了所有示例。
请注意,在上述(3)中,只有对应于目标的线会造成损失,其他线为零。为了编写成本方程式,我们需要一个“指标函数”,当指数与目标匹配时,该函数将为 1,否则为 0。
这时成本为:
其中 m是示例的数量,N是输出的数量。这是所有损失的平均值。
Tensorflow
本实验将讨论在 Tensorflow 中实现 softmax 交叉熵损失的两种方法,即“明显”方法和“首选”方法。前者是最直接的,而后者在数字上更稳定。
让我们从创建一个数据集来训练多类分类模型开始。
# 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)
The Obvious organization
下面的模型是使用 softmax 作为最终 Dense 层中的激活来实现的。损失函数在编译指令中单独指定。
损失函数 SparseCategoricalCrossentropy。上述(3)所述的损失。在此模型中,softmax 发生在最后一层。loss 函数接受 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))
偏差
回想一下讲座,如果在训练过程中将softmax和loss结合起来,可以获得更稳定和准确的结果。这是由此处显示的“首选”组织启用的.
在首选组织中,最后一层具有线性激活。由于历史原因,这种形式的输出称为 logits。损失函数有一个额外的参数: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
)
输出处理
请注意,在首选模型中,输出不是概率,而是范围可以从大负数到大正数。在执行预期概率的预测时,必须通过 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 或 CategoricalCrossEntropy
Tensorflow 有两种用于目标值的可能格式,损失的选择定义了预期的格式。
SparseCategorialCrossentropy:期望目标为与索引对应的整数。例如,如果有 10 个可能的目标值,则 y 将介于 0 和 9 之间。
CategoricalCrossEntropy:期望示例的目标值是 one-hot 编码,其中目标索引处的值为 1,而其他 N-1 个条目为零。具有 10 个潜在目标值的示例,其中目标值为 2,则为 [0,0,1,0,0,0,0,0,0,0]。