【问题标题】:Softmax derivative in NumPy approaches 0 (implementation)NumPy 中的 Softmax 导数接近 0(实现)
【发布时间】:2016-03-29 09:07:56
【问题描述】:

我正在尝试为用 Numpy 编写的神经网络实现 softmax 函数。令 h 为给定信号 i 的 softmax 值。

我一直在努力实现 softmax 激活函数的偏导数。

我目前遇到的问题是,随着训练的进行,所有偏导数都接近 0。我用this excellent answer 交叉引用了我的数学,但我的数学似乎没有成功。

import numpy as np
def softmax_function( signal, derivative=False ):
    # Calculate activation signal
    e_x = np.exp( signal )
    signal = e_x / np.sum( e_x, axis = 1, keepdims = True )

    if derivative:
        # Return the partial derivation of the activation function
        return np.multiply( signal, 1 - signal ) + sum(
            # handle the off-diagonal values
            - signal * np.roll( signal, i, axis = 1 )
            for i in xrange(1, signal.shape[1] )
        )
    else:
        # Return the activation signal
        return signal
#end activation function

signal 参数包含发送到激活函数的输入信号,形状为 (n_samples, n_features)

# sample signal (3 samples, 3 features)
signal = [[0.3394572666491664, 0.3089068053925853, 0.3516359279582483], [0.33932706934615525, 0.3094755563319447, 0.3511973743219001], [0.3394407172182317, 0.30889042266755573, 0.35166886011421256]]

以下代码片段是一个完全有效的激活函数,仅作为参考和证明(主要是为了我自己)该概念性想法确实有效。

from scipy.special import expit
import numpy as np
def sigmoid_function( signal, derivative=False ):
    # Prevent overflow.
    signal = np.clip( signal, -500, 500 )

    # Calculate activation signal
    signal = expit( signal )

    if derivative:
        # Return the partial derivation of the activation function
        return np.multiply(signal, 1 - signal)
    else:
        # Return the activation signal
        return signal
#end activation function

编辑

  • 简单的单层网络直观地存在问题。 softmax(及其导数)应用于最后一层。

【问题讨论】:

    标签: python numpy neural-network softmax


    【解决方案1】:

    这是关于如何以更矢量化的 numpy 方式计算 softmax 函数的导数的答案。然而,偏导数趋近于零的事实可能不是数学问题,而只是学习率问题或复杂深度神经网络的已知垂死权重问题。像ReLU 这样的层有助于防止后一个问题。


    首先,我使用了以下信号(只是复制了您的最后一个条目)使其成为4 samples x 3 features,以便更容易看到尺寸发生了什么。

    >>> signal = [[0.3394572666491664, 0.3089068053925853, 0.3516359279582483], [0.33932706934615525, 0.3094755563319447, 0.3511973743219001], [0.3394407172182317, 0.30889042266755573, 0.35166886011421256], [0.3394407172182317, 0.30889042266755573, 0.35166886011421256]]
    >>> signal.shape
    (4, 3)
    

    接下来,您要计算 softmax 函数的雅可比矩阵。根据引用的页面,它被定义为非对角线条目的-hi * hjn_features > 2 的大部分矩阵),所以让我们从那里开始。在 numpy 中,您可以使用 broadcasting 有效地计算雅可比矩阵:

    >>> J = - signal[..., None] * signal[:, None, :]
    >>> J.shape
    (4, 3, 3)
    

    第一个signal[..., None](相当于signal[:, :, None])将信号整形为(4, 3, 1),而第二个signal[:, None, :]将信号整形为(4, 1, 3)。然后,* 只是将两个矩阵元素相乘。 Numpy 的内部广播重复这两个矩阵,为每个样本形成n_features x n_features 矩阵。

    然后,我们需要修复对角线元素:

    >>> iy, ix = np.diag_indices_from(J[0])
    >>> J[:, iy, ix] = signal * (1. - signal)
    

    以上行提取n_features x n_features 矩阵的对角线索引。相当于做iy = np.arange(n_features); ix = np.arange(n_features)。然后,用您的定义 hi * (1 - hi) 替换对角线条目。

    最后,根据链接来源,您需要对每个样本的行求和。可以这样做:

    >>> J = J.sum(axis=1)
    >>> J.shape
    (4, 3)
    

    在下面找到一个总结版本:

    if derivative:
        J = - signal[..., None] * signal[:, None, :] # off-diagonal Jacobian
        iy, ix = np.diag_indices_from(J[0])
        J[:, iy, ix] = signal * (1. - signal) # diagonal
        return J.sum(axis=1) # sum across-rows for each sample
    

    导数比较:

    >>> signal = [[0.3394572666491664, 0.3089068053925853, 0.3516359279582483], [0.33932706934615525, 0.3094755563319447, 0.3511973743219001], [0.3394407172182317, 0.30889042266755573, 0.35166886011421256], [0.3394407172182317, 0.30889042266755573, 0.35166886011421256]]
    >>> e_x = np.exp( signal )
    >>> signal = e_x / np.sum( e_x, axis = 1, keepdims = True )
    

    你的:

    >>> np.multiply( signal, 1 - signal ) + sum(
            # handle the off-diagonal values
            - signal * np.roll( signal, i, axis = 1 )
            for i in xrange(1, signal.shape[1] )
        )
    array([[  2.77555756e-17,  -2.77555756e-17,   0.00000000e+00],
           [ -2.77555756e-17,  -2.77555756e-17,  -2.77555756e-17],
           [  2.77555756e-17,   0.00000000e+00,   2.77555756e-17],
           [  2.77555756e-17,   0.00000000e+00,   2.77555756e-17]])
    

    我的:

    >>> J = signal[..., None] * signal[:, None, :]
    >>> iy, ix = np.diag_indices_from(J[0])
    >>> J[:, iy, ix] = signal * (1. - signal)
    >>> J.sum(axis=1)
    array([[  4.16333634e-17,  -1.38777878e-17,   0.00000000e+00],
           [ -2.77555756e-17,  -2.77555756e-17,  -2.77555756e-17],
           [  2.77555756e-17,   1.38777878e-17,   2.77555756e-17],
           [  2.77555756e-17,   1.38777878e-17,   2.77555756e-17]])
    

    【讨论】:

    • 首先,为超级直观的答案致敬!计时代码时:如果数据集具有大量特征,您的代码似乎在高样本量下效率最高,而我提供的 sn-p 效率更高。无论如何,计算出的导数“总是”在 10^-17 的范围内——换句话说,在 0 左右。
    • @jorgenkg 但是,我们似乎没有得到相同的衍生物。请注意,最后一个样本的结果(最后 2 个,它们是相同的)与第二个特征无关,而我的则不会(参见编辑)。试试那个版本,看看它是否具有更好的数值稳定性并且不会完全消除你的导数。
    • 您绝对正确,NumPy 在您的实现中显示出更好的精度——我的代码中的精度问题可能来自内置的“sum()”调用。我会投票给你两次
    • @jorgenkg 尝试将 sum 替换为 np.sum,因为它将使用 numpy,这样您可能不会失去精度。
    • @jorgenkg 嗯.. 试过了。由于某些奇怪的原因,我无法使用您的方法获得相同的精度,尽管我已经验证这两种方法执行完全相同的操作。
    猜你喜欢
    • 2018-10-29
    • 2017-03-27
    • 1970-01-01
    • 1970-01-01
    • 2018-03-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多