专栏名称: 给自己封个神
目录
相关文章推荐
今天看啥  ›  专栏  ›  给自己封个神

softmax_layer的实现

给自己封个神  · 简书  ·  · 2019-08-08 21:43

文章预览

Softmax层概述

Softmax是将神经网络得到的多个值,进行归一化处理,使得到的值在[0,1]之间,让结果变得可解释。Softmax可以将结果看作是概率,某个类别概率越大,将样本归为该类别的可能性也就越高,因此通常用于多分类。

Softmax输出计算

下图参考李宏毅老师课件中的Softmax的计算图。图中\hat{y}_{i}=e^{z_{i}}/\sum_{k=1}^{n}e^{z_{k}}是神经元的输出也可以作为第i个类别预测的概率结果,y_{i}是第i个类别的真实值,只能取0或者1,且\sum_{i=1}^{n}\hat{y}_{i}=1

image.png

Softmax反向传播

在神经网络后面添加Softmax,真实的标签(或者是类别)就相当于真实的分布,经过Softmax计算出的值就是预测的结果,因此可以使用交叉熵函数来作为损失函数。Softmax的损失函数为L=-\sum_{i}y_{i}ln\hat{y}_{i}
反向传播的过程其实就是计算 \frac{\partial{L}}{\partial{z_{i}}}的过程,根据导数的链式法则:
\frac{\partial{L}}{\partial{z_{i}}} = -\sum_{j}\frac{\partial{L}}{\partial{\hat{y}_{j}}}\frac{\partial{\hat{y}_{j}}}{\partial{z_{i}}}
根据前向计算过程得出:
\hat{y}_{j}=\frac{e^{z_{j}}}{\sum_{k=1}^{n}e^{z_{k}}}
i=j的时候
\frac{\partial{\hat{y}_{j}}}{\partial{z_{i}}}=\hat{y}_{i}(1-\hat{y}_{i})
{i}\ne{j}的时候
\frac{\partial{\hat{y}_{j}}}{\partial{z_{i}}}=-\hat{y}_{j}\hat{y}_{i}
根据L=\sum_{i}y_{i}ln\hat{y}_{i}求导得出:
\frac{\partial{L}}{\partial{\hat{y}_{j}}}=\frac{y_{j}}{\hat{y}_{j}}
综合两项偏导数,推出:
\begin{equation}\begin{split} \frac{\partial{L}}{\partial{z_{i}}}&=-\sum_{j}\frac{y_{j}}{\hat{y}_{j}}\frac{\partial{\hat{y}_{j}}}{\partial{z_{u}}}\\ &=-\sum_{j=i}\frac{y_{j}}{\hat{y}_{j}}[\hat{y}_{i}(1-\hat{y}_{i})]+\sum_{j\ne{i}}\frac{y_{j}}{\hat{y}_{j}}\hat{y}_{j}\hat{y}_{i}\\ &=y_{i}\hat{y}_{i}-y_{i} + \sum_{j\ne{i}}y_{j}\hat{y}_{i}\\ & =-y_{i}+\hat{y}_{i}\sum_{j}y_{j}\\ &=\hat{y}_{i}-y_{i} \end{split}\end{equation}
注:因为如果给定一个样本x,那么他对应的真实标签只有一个值为1,其余为0,故\sum_j{y_{j}}=1

代码实现

前向计算实现
先调用softmax_cpu实现前向计算,再调用softmax_x_ent_cpu计算整个网络的损失函数值和当前层的sensitivity即l.delta的值。

void forward_softmax_layer(const softmax_layer l, network_state net)
{
    if(l.softmax_tree){
        int i;
        int count = 0;
        for (i = 0; i < l.softmax_tree->groups; ++i) {
            int group_size = l.softmax_tree->group_size[i];
            softmax_cpu(net.input + count, group_size, l.batch, l.inputs, 1, 0, 1, l.temperature, l.output + count);
            count += group_size;
        }
    } else {
        softmax_cpu(net.input, l.inputs/l.groups, l.batch, l.inputs, l.groups, l.inputs/l.groups, 1, l.temperature, l.output);
    }

    if(net.truth && !l.noloss){
        softmax_x_ent_cpu(l.batch*l.inputs, l.output, net.truth, l.delta, l.loss);//计算softmax的sensitivity
        l.cost[0] = sum_array(l.loss, l.batch*l.inputs);//计算网络的总损失
    }
}

这个实现很简单,就是一步步按照前面的公式,计算指数值,再求和,最后归一化。

void softmax(float *input, int n, float temp, float *output, int stride)
{
    int i;
    float sum = 0;
    // 赋初始最大值为float中的最小值-FLT_MAX(定义在float.h中)
    float largest = -FLT_MAX;
    // 寻找输入中的最大值,至于为什么要找出最大值,是为了数值计算上的稳定,详细请戳:http://freemind.pluskid.org/machine-learning/softmax-vs-softmax-loss-numerical-stability/
    // 这篇博客写的不错,博客在接近尾声的时候,提到了为什么要减去输入中的最大值。
    for(i = 0; i < n; ++i){
        if(input[i*stride] > largest) largest = input[i*stride];
    }
    for(i = 0; i < n; ++i){
        // 在进行指数运算之间,如上面博客所说,首先减去最大值(当然温度参数也要除)
        float e = exp(input[i*stride]/temp - largest/temp);
        sum += e;//求和
        output[i*stride] = e;// 并将每一个输入的结果保存在相应的输出中
    }
    // 最后一步:归一化转换为概率(就是softmax函数的原型~),最后的输出结果保存在output中
    for(i = 0; i < n; ++i){
        output[i*stride] /= sum;
    }
}

下面的实现计算delta[i]的值的时候是真实值减去预测值,而上面推导的结果是预测值减去真实值。为什么也可以???

void softmax_x_ent_cpu(int n, float *pred, float *truth, float *delta, float *error)
{
    int i;
    for (i = 0; i < n; ++i) {//n相当于类别数
        float t = truth[i];//真值
        float p = pred[i];//预测值
        error[i] = (t) ? -log(p) : 0;//计算交叉熵损失
        delta[i] = t - p;//根据公式计算softmax层的梯度
    }
}

反向传播实现
softmax的敏感梯度图在softmax前向inference的时候已经计算完成,所以在反向的时候,这里只要调用axpy_cpu将对应位置的值传回去就行了。

void backward_softmax_layer(const softmax_layer l, network_state net)
{
    axpy_cpu(l.inputs*l.batch, 1, l.delta, 1, net.delta, 1);
}

参考资料

【1】http://freemind.pluskid.org/machine-learning/softmax-vs-softmax-loss-numerical-stability/
【2】图示Softmax及交叉熵损失函数
【3】Softmax函数与交叉熵

………………………………

原文地址:访问原文地址
快照地址: 访问文章快照
总结与预览地址:访问总结与预览
推荐文章