欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

softmax及交叉熵损失的反向传递推导及实现

程序员文章站 2024-02-11 12:49:46
...

一般网络最后一个线性层后面都会跟一个输出层,比如sigmoid,softmax等,其基本做法是将输出Zi取指数再归一化。下面记录一下softmax和交叉熵的反向传递推导。

参考博文:https://blog.csdn.net/u014313009/article/details/51045303

下面做一个摘录:

1. softmax函数及其求导

        softmax的函数公式如下:

softmax及交叉熵损失的反向传递推导及实现


        中,softmax及交叉熵损失的反向传递推导及实现表示第L层(通常是最后一层)第j个神经元的输入,softmax及交叉熵损失的反向传递推导及实现表示第L层第j个神经元的输出,softmax及交叉熵损失的反向传递推导及实现表示自然常数。注意看,softmax及交叉熵损失的反向传递推导及实现表示了第L层所有神经元的输入之和。

        softmax函数最明显的特点在于:它把每个神经元的输入占当前层所有神经元输入之和的比值,当作该神经元的输出。这使得输出更容易被解释:神经元的输出值越大,则该神经元对应的类别是真实类别的可能性更高。

        另外,softmax不仅把神经元输出构造成概率分布,而且还起到了归一化的作用,适用于很多需要进行归一化处理的分类问题。

        由于softmax在ANN算法中的求导结果比较特别,分为两种情况。希望能帮助到正在学习此类算法的朋友们。求导过程如下所示:

softmax及交叉熵损失的反向传递推导及实现



2. softmax配合log似然代价函数训练ANN


        在上一篇博文“交叉熵代价函数”中讲到,二次代价函数在训练ANN时可能会导致训练速度变慢的问题。那就是,初始的输出值离真实值越远,训练速度就越慢。这个问题可以通过采用交叉熵代价函数来解决。其实,这个问题也可以采用另外一种方法解决,那就是采用softmax**函数,并采用log似然代价函数(log-likelihood cost function)来解决。

        log似然代价函数的公式为:

softmax及交叉熵损失的反向传递推导及实现

        其中,softmax及交叉熵损失的反向传递推导及实现表示第k个神经元的输出值,softmax及交叉熵损失的反向传递推导及实现表示第k个神经元对应的真实值,取值为0或1。

        我们来简单理解一下这个代价函数的含义。在ANN中输入一个样本,那么只有一个神经元对应了该样本的正确类别;若这个神经元输出的概率值越高,则按照以上的代价函数公式,其产生的代价就越小;反之,则产生的代价就越高。

        为了检验softmax和这个代价函数也可以解决上述所说的训练速度变慢问题,接下来的重点就是推导ANN的权重w和偏置b的梯度公式。以偏置b为例:

softmax及交叉熵损失的反向传递推导及实现


        同理可得:

softmax及交叉熵损失的反向传递推导及实现


        从上述梯度公式可知,softmax函数配合log似然代价函数可以很好地训练ANN,不存在学习速度变慢的问题。

总体上推导没什么问题,看看网友评论说最后结果应该是yj(aj-1),yj=1,结果难度不是一样吗?推论上,softmax层前面是一个线性层,实际使用时,只需要将求C对z的梯度就可以了,z对w和b的梯度由fc层实现就可以啦,这里进行一个纸质推导:

softmax及交叉熵损失的反向传递推导及实现

下面看一下dlib中对该层的实现:

class loss_multiclass_log_ 
    {
    public:

        typedef unsigned long training_label_type;
        typedef unsigned long output_label_type;

        template <
            typename SUB_TYPE,
            typename label_iterator
            >
        void to_label (
            const tensor& input_tensor,
            const SUB_TYPE& sub,
            label_iterator iter
        ) const
        {
            const tensor& output_tensor = sub.get_output();
            DLIB_CASSERT(sub.sample_expansion_factor() == 1);
            DLIB_CASSERT(output_tensor.nr() == 1 && 
                         output_tensor.nc() == 1 );
            DLIB_CASSERT(input_tensor.num_samples() == output_tensor.num_samples());


            // Note that output_tensor.k() should match the number of labels.

            for (long i = 0; i < output_tensor.num_samples(); ++i)
            {
                // The index of the largest output for this sample is the label.
                *iter++ = index_of_max(rowm(mat(output_tensor),i));
            }
        }


        template <
            typename const_label_iterator,
            typename SUBNET
            >
        double compute_loss_value_and_gradient (//这个是主要方法,输入tensor,通过sub计算得到output,计算同真实标签truth的loss,
            const tensor& input_tensor,         //计算loss对output的梯度,并写入到sub中
            const_label_iterator truth, 
            SUBNET& sub
        ) const
        {
            const tensor& output_tensor = sub.get_output();
            tensor& grad = sub.get_gradient_input();

            DLIB_CASSERT(sub.sample_expansion_factor() == 1);
            DLIB_CASSERT(input_tensor.num_samples() != 0);
            DLIB_CASSERT(input_tensor.num_samples()%sub.sample_expansion_factor() == 0);
            DLIB_CASSERT(input_tensor.num_samples() == grad.num_samples());
            DLIB_CASSERT(input_tensor.num_samples() == output_tensor.num_samples());
            DLIB_CASSERT(output_tensor.nr() == 1 && 
                         output_tensor.nc() == 1);
            DLIB_CASSERT(grad.nr() == 1 && 
                         grad.nc() == 1);

            tt::softmax(grad, output_tensor);//计算a

            // The loss we output is the average loss over the mini-batch.
            const double scale = 1.0/output_tensor.num_samples();
            double loss = 0;
            float* g = grad.host();//sub的误差,也就是loss对output的梯度
            for (long i = 0; i < output_tensor.num_samples(); ++i)
            {
                const long y = (long)*truth++;
                // The network must produce a number of outputs that is equal to the number
                // of labels when using this type of loss.
                DLIB_CASSERT(y < output_tensor.k(), "y: " << y << ", output_tensor.k(): " << output_tensor.k());
                for (long k = 0; k < output_tensor.k(); ++k)
                {
                    const unsigned long idx = i*output_tensor.k()+k;
                    if (k == y)
                    {
                        loss += scale*-std::log(g[idx]);//-log(a)
                        g[idx] = scale*(g[idx]-1);//计算梯度,详见推导
                    }
                    else
                    {
                        g[idx] = scale*g[idx];//计算梯度
                    }
                }
            }
            return loss;
        }

        friend void serialize(const loss_multiclass_log_& , std::ostream& out)
        {
            serialize("loss_multiclass_log_", out);
        }

        friend void deserialize(loss_multiclass_log_& , std::istream& in)
        {
            std::string version;
            deserialize(version, in);
            if (version != "loss_multiclass_log_")
                throw serialization_error("Unexpected version found while deserializing dlib::loss_multiclass_log_.");
        }

        friend std::ostream& operator<<(std::ostream& out, const loss_multiclass_log_& )
        {
            out << "loss_multiclass_log";
            return out;
        }

        friend void to_xml(const loss_multiclass_log_& /*item*/, std::ostream& out)
        {
            out << "<loss_multiclass_log/>";
        }

    };

相关标签: softmax 交叉熵