【darknet源码解析-07】activations.h和activations.c 解析

本系列为darknet源码解析,本次解析src/activations.h 与 src/activations.c 两个。在本文中,activations主要完成激活函数的前向计算以及激活函数的求导,激活函数的误差反向传播。

ACTIVATION的定义在include/darknet.h中,是枚举类型。

typedef enum{
    LOGISTIC, RELU, RELIE, LINEAR, RAMP, TANH, PLSE, LEAKY, ELU, LOGGY, STAIR, HARDTAN, LHTAN
} ACTIVATION;
activations.h的声明定义详解如下:

#ifndef ACTIVATIONS_H
#define ACTIVATIONS_H
#include "darknet.h"
#include "cuda.h"
#include "math.h"
 
// 获得定义的枚举类型的激活函数类别
ACTIVATION get_activation(char *s);
 
// 获得激活函数对应的字符串描述
char *get_activation_string(ACTIVATION a);
 
// 根据不同的技术函数类型,调用不同的激活函数处理输入元素
float activate(float x, ACTIVATION a);
 
// 根据不同的激活函数求取对输入的梯度
float gradient(float x, ACTIVATION a);
 
// 计算激活函数对加权输入的导数, 并乘以delta,得到当前层最终的delta(误差项)
void gradient_array(const float *x, const int n, const ACTIVATION a, float *delta);
 
// 用激活函数处理输入x中的每一个元素
void activate_array(float *x, const int n, const ACTIVATION a);
#ifdef GPU
void activate_array_gpu(float *x, int n, ACTIVATION a);
void gradient_array_gpu(float *x, int n, ACTIVATION a, float *delta);
#endif
 
 
/*
 * 内联函数可以加快调用的速度,但是调用多次的话,会使执行文件变大,这样会降低速度。
 * static 修饰的内联函数,一般情况下不会产生函数本身的代码,而是全部嵌入在被调用的地方。
 * 如果不加 static,则表示该函数有可能被其他编译单元所调用,所以一定会产生函数本身的代码;
 *
 * gcc的 static inline相对于static函数来说只是在调用时建议编译器进行内联展开;
 * gcc不会特意为 static inline 函数生成独立的汇编码,除非出现了必须生成不可的情况(如通过函数指针和递归调用);
 * gcc 的static inline 函数仅能作用于文件范围内。
 */
 
static inline float stair_activate(float x)
{
    int n = floor(x);
    if (n%2 == 0) return floor(x/2.);
    else return (x - n) + floor(x/2.);
}
 
static inline float hardtan_activate(float x)
{
    if (x < -1) return -1;
    if (x > 1) return 1;
    return x;
}
// 返回线性激活函数(就是f(x)=x)值
static inline float linear_activate(float x){return x;}
 
// 返回logistic(sigmoid)函数值
static inline float logistic_activate(float x){return 1./(1. + exp(-x));}
 
//
static inline float loggy_activate(float x){return 2./(1. + exp(-x)) - 1;}
 
// 返回ReLU非线性激活函数值
static inline float relu_activate(float x){return x*(x>0);}
 
// 返回指数线性单元(Exponential Linear Unit, ELU)值
static inline float elu_activate(float x){return (x >= 0)*x + (x < 0)*(exp(x)-1);}
static inline float selu_activate(float x){return (x >= 0)*1.0507*x + (x < 0)*1.0507*1.6732*(exp(x)-1);}
static inline float relie_activate(float x){return (x>0) ? x : .01*x;}
static inline float ramp_activate(float x){return x*(x>0)+.1*x;}
 
// 返回leaky ReLU非线性激活函数值
static inline float leaky_activate(float x){return (x>0) ? x : .1*x;}
 
// 返回tanh非线性技激活函数值
static inline float tanh_activate(float x){return (exp(2*x)-1)/(exp(2*x)+1);}
 
static inline float plse_activate(float x)
{
    if(x < -4) return .01 * (x + 4);
    if(x > 4)  return .01 * (x - 4) + 1;
    return .125*x + .5;
}
 
static inline float lhtan_activate(float x)
{
    if(x < 0) return .001*x;
    if(x > 1) return .001*(x-1) + 1;
    return x;
}
static inline float lhtan_gradient(float x)
{
    if(x > 0 && x < 1) return 1;
    return .001;
}
 
static inline float hardtan_gradient(float x)
{
    if (x > -1 && x < 1) return 1;
    return 0;
}
 
// 返回线性激活函数(就是f(x)=x) 关于输入x的导数值
static inline float linear_gradient(float x){return 1;}
 
// 返回logistics (sigmoid)函数关于输入x的导数值
// 说明:这里直接利用输出值求激活函数关于输入的导数值是因为神经网络中所使用的绝大部分激活函数,其关于输入的导数值都可以描述为输出值的表达式
//      比如对于sigmoid激活函数(记作f(x)),其导数值为f'(x) = f(x)*(1-f(x)),因此如果给出f(x),那么f'(x) = y*(1-y),只需要输出值y就可以了
// 不需要输入x的值
static inline float logistic_gradient(float x){return (1-x)*x;}
static inline float loggy_gradient(float x)
{
    float y = (x+1.)/2.;
    return 2*(1-y)*y;
}
static inline float stair_gradient(float x)
{
    if (floor(x) == x) return 0;
    return 1;
}
// 返回ReLU非线性激活函数关于输入x的导数值
static inline float relu_gradient(float x){return (x>0);}
 
// 返回指数性单元(Exponential Linear Unit,ELU)非线性激活函数关于输入x的导数值
static inline float elu_gradient(float x){return (x >= 0) + (x < 0)*(x + 1);}
 
static inline float selu_gradient(float x){return (x >= 0)*1.0507 + (x < 0)*(x + 1.0507*1.6732);}
static inline float relie_gradient(float x){return (x>0) ? 1 : .01;}
static inline float ramp_gradient(float x){return (x>0)+.1;}
 
// 返回leaky ReLU非线性激活函数关于输入x的导数值
static inline float leaky_gradient(float x){return (x>0) ? 1 : .1;}
 
// 返回 tanh 非线性激活函数关于输入x的导数值
static inline float tanh_gradient(float x){return 1-x*x;}
 
static inline float plse_gradient(float x){return (x < 0 || x > 1) ? .01 : .125;}
 
#endif
 
activations.c 代码详解如下:

#include "activations.h"
 
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
// 获得定义的枚举类型的激活函数类别
char *get_activation_string(ACTIVATION a)
{
    switch(a){
        case LOGISTIC:
            return "logistic";
        case LOGGY:
            return "loggy";
        case RELU:
            return "relu";
        case ELU:
            return "elu";
        case SELU:
            return "selu";
        case RELIE:
            return "relie";
        case RAMP:
            return "ramp";
        case LINEAR:
            return "linear";
        case TANH:
            return "tanh";
        case PLSE:
            return "plse";
        case LEAKY:
            return "leaky";
        case STAIR:
            return "stair";
        case HARDTAN:
            return "hardtan";
        case LHTAN:
            return "lhtan";
        default:
            break;
    }
    return "relu";
}
 
/**
 * 根据输入的激活函数名称,返回定义的枚举类型的激活函数类别
 * @param s 激活函数名称
 * @return ACTIVATION ,激活函数类别:枚举类型
 * 说明: 如果不匹配,默认采用ReLU
 */
ACTIVATION get_activation(char *s)
{
    if (strcmp(s, "logistic")==0) return LOGISTIC;
    if (strcmp(s, "loggy")==0) return LOGGY;
    if (strcmp(s, "relu")==0) return RELU;
    if (strcmp(s, "elu")==0) return ELU;
    if (strcmp(s, "selu")==0) return SELU;
    if (strcmp(s, "relie")==0) return RELIE;
    if (strcmp(s, "plse")==0) return PLSE;
    if (strcmp(s, "hardtan")==0) return HARDTAN;
    if (strcmp(s, "lhtan")==0) return LHTAN;
    if (strcmp(s, "linear")==0) return LINEAR;
    if (strcmp(s, "ramp")==0) return RAMP;
    if (strcmp(s, "leaky")==0) return LEAKY;
    if (strcmp(s, "tanh")==0) return TANH;
    if (strcmp(s, "stair")==0) return STAIR;
    fprintf(stderr, "Couldn't find activation function %s, going with ReLU\n", s);
    return RELU;
}
 
/**
 * 根据不同的激活函数类型,调用不同的激活函数处理输入元素x
 * @param x
 * @param a
 * @return 激活后的结果
 */
float activate(float x, ACTIVATION a)
{
    switch(a){
        case LINEAR:
            return linear_activate(x);
        case LOGISTIC:
            return logistic_activate(x);
        case LOGGY:
            return loggy_activate(x);
        case RELU:
            return relu_activate(x);
        case ELU:
            return elu_activate(x);
        case SELU:
            return selu_activate(x);
        case RELIE:
            return relie_activate(x);
        case RAMP:
            return ramp_activate(x);
        case LEAKY:
            return leaky_activate(x);
        case TANH:
            return tanh_activate(x);
        case PLSE:
            return plse_activate(x);
        case STAIR:
            return stair_activate(x);
        case HARDTAN:
            return hardtan_activate(x);
        case LHTAN:
            return lhtan_activate(x);
    }
    return 0;
}
 
/**
 * 用激活函数去处理每一个输入
 * @param x 待处理的数组:一般为网络层每个神经元的加权输入Wx+b,在本函数中也是输出
 * @param n x中含有多少元素
 * @param a 激活函数类型
 * 说明: 该函数会逐个处理x中的元素;该函数一般用于每一层网络的前向传播网络中;
 *
 */
void activate_array(float *x, const int n, const ACTIVATION a)
{
    int i;
    for(i = 0; i < n; ++i){
        x[i] = activate(x[i], a);
    }
}
 
/**
 * 根据不用的激活函数求取对输入的梯度
 * @param x 激活函数接收的输入值
 * @param a 激活函数类型
 * @return 激活函数关于输入x的导数值
 */
float gradient(float x, ACTIVATION a)
{
    switch(a){
        case LINEAR:
            return linear_gradient(x);
        case LOGISTIC:
            return logistic_gradient(x);
        case LOGGY:
            return loggy_gradient(x);
        case RELU:
            return relu_gradient(x);
        case ELU:
            return elu_gradient(x);
        case SELU:
            return selu_gradient(x);
        case RELIE:
            return relie_gradient(x);
        case RAMP:
            return ramp_gradient(x);
        case LEAKY:
            return leaky_gradient(x);
        case TANH:
            return tanh_gradient(x);
        case PLSE:
            return plse_gradient(x);
        case STAIR:
            return stair_gradient(x);
        case HARDTAN:
            return hardtan_gradient(x);
        case LHTAN:
            return lhtan_gradient(x);
    }
    return 0;
}
 
/**
 * 计算激活函数对加权输入的导数,并乘以delta,得到当前层最终的误差项delta
 * @param x 当前层的所有输出(维度 l.batch * l.out_c * l.out_w * l.out_h)
 * @param n  l.output维度,即为l.batch * l.out_c * l.out_w * l.out_h (包含整个batch)
 * @param a 激活函数类型
 * @param delta 当前层误差(与当前输入的x维度一样)
 *
 * 说明:该函数不但计算了激活函数对于加权输入的导数,还将该导数乘以了之前完成计算的误差项delta(对应元素相乘),因此调用该函数之后,
 *      将得到最终的误差项
 *
 *      这里直接利用输出值求激活函数关于输入的导数值是因为神经网络中所使用的绝大部分激活函数,其关于输入的导数值都可以描述为输出值的函数表达式,
 *      比如对于Sigmoid激活函数(记作f(x)),其导数值为 f'(x)=f(x) * (1 - f(x)), 因此如果给出 y = f(x), 那么 f'(x)=y*(1-y),只需要输出值y就可以了,
 *      不需要输如x的值。
 *
 *      关于l.delta的初值,比如卷积层的backward_convolutional_layer()函数,并没有对l.delta赋初值,
 *      只是用calloc为其动态分配了内存。    但是整个网络会以COST或者REGION为最后一层,这些层中会对l.delta赋初值,
 *      又由于l.delta是由后向前逐层传播。因此,当反向执行到某一层时,l.delta的值都不会为0.
 */
void gradient_array(const float *x, const int n, const ACTIVATION a, float *delta)
{
    int i;
    for(i = 0; i < n; ++i){
        delta[i] *= gradient(x[i], a);
    }

 
完,
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/caicaiatnbu/article/details/100934808

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值