Numpy:计算softmax函数的导数

20

我试图了解在一个简单的三层神经网络中使用MNIST数据集的backpropagation。

第一层是带有权重和偏差的输入层。标签是MNIST,因此它是一个10类向量。

第二层是一个线性变换。第三层是softmax激活函数,以获得输出作为概率。

Backpropagation在每个步骤计算导数,并将其称为梯度。

前面的层将全局或上一步的梯度附加到局部梯度中。我很难计算softmax的局部梯度。

在线资源中有许多关于softmax及其导数的说明,甚至提供了softmax本身的代码示例

def softmax(x):
    """Compute the softmax of vector x."""
    exps = np.exp(x)
    return exps / np.sum(exps)

i = j时,以i为自变量解释导数;当i != j时,以j为自变量解释导数。下面是我写的一个简单的代码片段,希望您能帮我验证一下我的理解:

def softmax(self, x):
    """Compute the softmax of vector x."""
    exps = np.exp(x)
    return exps / np.sum(exps)

def forward(self):
    # self.input is a vector of length 10
    # and is the output of 
    # (w * x) + b
    self.value = self.softmax(self.input)

def backward(self):
    for i in range(len(self.value)):
        for j in range(len(self.input)):
            if i == j:
                self.gradient[i] = self.value[i] * (1-self.input[i))
            else: 
                 self.gradient[i] = -self.value[i]*self.input[j]

那么self.gradient就是一个向量,它代表了局部梯度。这样写正确吗?有更好的写法吗?


2
这太不清楚了...你实际上要计算什么梯度?SM是从R^n到R^n的映射,因此您可以定义n^2个偏导数dSM[i]/dx[k]... - Julien
1
@JulienBernu 我已经更新了问题。有什么想法吗? - Sam Hammamy
这两个链接帮助我理解了以下内容:https://eli.thegreenplace.net/2016/the-softmax-function-and-its-derivative/ + https://dev59.com/CFwX5IYBdhLWcg3wrA96#46028029 (它们在多个地方被引用,例如 https://e2eml.school/softmax.html)。 - Alex Punnen
3个回答

28
我假设你有一个三层的神经网络,其中W1b1与从输入层到隐藏层的线性变换相关联,W2b2与从隐藏层到输出层的线性变换相关联。Z1Z2是隐藏层和输出层的输入向量。a1a2表示隐藏层和输出层的输出。a2是你的预测输出。delta3delta2是误差(反向传播),你可以看到损失函数相对于模型参数的梯度。

enter image description here enter image description here

这是一个三层神经网络(输入层、仅有一层隐藏层和一个输出层)的一般情况。您可以按照上述过程计算梯度,这应该很容易计算!由于此帖子的另一个答案已经指出了您代码中的问题,我不会重复。


再澄清一个问题,如果我们从z2开始,即z1不存在,这是否会使它成为一个2层神经网络?线性变换发生两次会使其成为3层神经网络? - Sam Hammamy
你能解释一下方程中层的名称吗?在你的情况下,输入层是z1吗?有多少隐藏层,它们是什么? - Sam Hammamy
我打算更新我正在整理的这个库,以匹配上述链接。一旦完成,我会编辑问题。 - Sam Hammamy
这里的损失函数是什么? - sidmontu
当a1 = z1时,将隐藏层中的节点称为“激活单元”是否真的正确?该层中没有激活函数,它是线性的。 - Oortone
显示剩余2条评论

16

就像我之前所说的,你有 n^2 个偏导数。

如果你计算一下会发现,dSM[i]/dx[k] 可以表示为 SM[i] * (dx[i]/dx[k] - SM[i]) 所以你应该得到:

if i == j:
    self.gradient[i,j] = self.value[i] * (1-self.value[i])
else: 
    self.gradient[i,j] = -self.value[i] * self.value[j]
代替
if i == j:
    self.gradient[i] = self.value[i] * (1-self.input[i])
else: 
     self.gradient[i] = -self.value[i]*self.input[j]

顺便说一句,这可以通过向量化更简洁地计算:

SM = self.value.reshape((-1,1))
jac = np.diagflat(self.value) - np.dot(SM, SM.T)

1
那就是雅各比矩阵了? - Sam Hammamy
我想我又出现了断开连接。@wasi答案中的线性变换是隐藏层吗? - Sam Hammamy
1
我想是这样的。需要注意的是,大多数人认为最后的线性变换加上SM只算一个层。通常,一个层包括一个线性变换和一个非线性激活函数(sigmoid、tanh、SM、relu或其他...)。 - Julien
在一些我看到的实现中,前向传播中的softmax输出值也被使用了。但在你的版本中,情况不同,只使用了来自损失函数梯度的输入。我是否漏掉了什么,还是这就是完整的公式? - Ilknur Mustafa

12

np.exp不稳定,因为它具有无穷大值。因此,您应该从x中减去最大值。

def softmax(x):
    """Compute the softmax of vector x."""
    exps = np.exp(x - x.max())
    return exps / np.sum(exps)

如果x是矩阵,请查看此笔记本中的softmax函数


减去最大值会改变softmax导数吗?如果不会,为什么? - K Bazan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接