sigmoid函数的特性及硬件实现方法--含matlab代码实现及讲解

  • 1. 简介
  • 2. sigmoid函数的特性介绍
  • 2.1 sigmoid(x)与sigmoid(-x)的关系
  • 2.2 sigmoid函数与tanh函数的关系
  • 2.3 sigmoid函数的n阶导数
  • 2.4 当x=n*ln2时的数值
  • 2.5 其他关系式
  • 3. 硬件实现方案
  • 4. matlab代码实现及讲解


1. 简介

sigmoid是神经网络中常用的一种激活函数,在机械学习和很多降噪滤波算法中也常常会用到这个函数。sigmoid函数的表达式如下:
sigmoid函数文章 sigmoid函数实现_硬件实现
本文主要会介绍一些sigmoid函数常用的特性。并且基于sigmoid函数特有的性质,我会给出一种硬件实现sigmoid函数的方法(不是多项式拟合法),为了方便读者理解,我还用matlab模拟了sigmoid函数的实现。有兴趣的小伙伴可以直接到我下面的链接中下载代码。

2. sigmoid函数的特性介绍

2.1 sigmoid(x)与sigmoid(-x)的关系

sigmoid(x)与sigmoid(-x)的具有下面的关系:
sigmoid函数文章 sigmoid函数实现_matlab代码_02
推导过程如下:
sigmoid函数文章 sigmoid函数实现_matlab代码_03

2.2 sigmoid函数与tanh函数的关系

tanh(x)为双曲余弦函数,其表达式如下
sigmoid函数文章 sigmoid函数实现_硬件实现_04
sigmoid(x)与tanh(x)函数的关系如下:
sigmoid函数文章 sigmoid函数实现_硬件实现_05
推导过程如下:
sigmoid函数文章 sigmoid函数实现_matlab_06

2.3 sigmoid函数的n阶导数

sigmoid函数求导有一个非常好的特殊性质,即导数的结果是可以完全表达成y的函数,与x无关。 假设sigmoid函数文章 sigmoid函数实现_matlab_07
sigmoid函数文章 sigmoid函数实现_sigmoid_08
sigmoid函数文章 sigmoid函数实现_sigmoid_09只是y的函数。如果要求二阶导数可以直接对sigmoid函数文章 sigmoid函数实现_sigmoid_09再次求导
sigmoid函数文章 sigmoid函数实现_matlab代码_11
根据上述方法,sigmoid函数的前n-1阶导数一定一个只含有y的函数,对y求导再乘以y(1-y)就是sigmoid函数的n阶导数。这里我没有推导出sigmoid函数的n阶导数的紧凑表达式。有兴趣的同学可以自己推导试一下,也欢迎大神留言给出正解。
另一种求解sigmoid函数n阶导数的方法,就是利用它与tanh(x)的关系。由于sigmoid函数文章 sigmoid函数实现_硬件实现_12,因此sigmoid(x)的n阶导数其实就是sigmoid函数文章 sigmoid函数实现_matlab代码_13的n阶导。tanh(x)的n阶导数有一些复杂,国内的高等数学教材中并没有提到,最后我还是在一篇NASA的技术笔记[1]中找到的,这里我直接给出tanh(x)函数的n阶导数结果:
sigmoid函数文章 sigmoid函数实现_sigmoid函数文章_14
sigmoid函数文章 sigmoid函数实现_sigmoid_15
上式中的sigmoid函数文章 sigmoid函数实现_matlab_16可以通过下面的递推公式得到
sigmoid函数文章 sigmoid函数实现_硬件实现_17
sigmoid函数文章 sigmoid函数实现_硬件实现_18
sigmoid函数文章 sigmoid函数实现_硬件实现_19
因此根据tanh(x)的n阶导数可以得到sigmoid(x)的n阶导数,具体形式如下
sigmoid函数文章 sigmoid函数实现_matlab代码_20
sigmoid函数文章 sigmoid函数实现_matlab代码_21

2.4 当x=n*ln2时的数值

这个特性其实非常有趣,当sigmoid函数文章 sigmoid函数实现_sigmoid函数文章_22(n可以为任意非0的整数,可正可负)时,sigmoid函数可以变成
sigmoid函数文章 sigmoid函数实现_sigmoid函数文章_23
sigmoid函数文章 sigmoid函数实现_matlab_24的机械数是非常有规律的.
下面给一个几个例子
sigmoid函数文章 sigmoid函数实现_硬件实现_25时,
sigmoid函数文章 sigmoid函数实现_sigmoid函数文章_26
上式中sigmoid函数文章 sigmoid函数实现_sigmoid函数文章_27代表2进制表示。
sigmoid函数文章 sigmoid函数实现_matlab_28时,
sigmoid函数文章 sigmoid函数实现_matlab_29
sigmoid函数文章 sigmoid函数实现_sigmoid函数文章_30时,
sigmoid函数文章 sigmoid函数实现_matlab_31
以此类推,当n大于0时,sigmoid函数的结果的机器数为n个1和0交替。
sigmoid函数文章 sigmoid函数实现_matlab代码_32时,
sigmoid函数文章 sigmoid函数实现_matlab_33
sigmoid函数文章 sigmoid函数实现_硬件实现_34时,
sigmoid函数文章 sigmoid函数实现_matlab代码_35
sigmoid函数文章 sigmoid函数实现_硬件实现_36时,
sigmoid函数文章 sigmoid函数实现_sigmoid函数文章_37
当n小于0时,sigmoid函数的结果的机器数为n个0和1交替。
根据上述性质,我们可以很容易通过硬件实现sigmoid函数文章 sigmoid函数实现_sigmoid函数文章_22时的sigmoid函数值。

2.5 其他关系式

sigmoid函数有一个非常类似柯西中值定理的关系式:
sigmoid函数文章 sigmoid函数实现_matlab代码_39
sigmoid函数文章 sigmoid函数实现_sigmoid_40
公式的左边与柯西中值定理很像
sigmoid函数文章 sigmoid函数实现_matlab代码_41

3. 硬件实现方案

sigmoid函数的硬件实现很多是通过分区多项式拟合法得到的。这里我给大家介绍一种基于泰勒级数展开的硬件实现方法。并且在下文中我会给出matlab版本的实现
首先泰勒级数展开的方法如下:
sigmoid函数文章 sigmoid函数实现_matlab代码_42
需要指出的是泰勒级数展开其实只能适用于一个sigmoid函数文章 sigmoid函数实现_sigmoid_43很小的区域范围。对于从负无穷到正无穷范围取值的sigmoid函数,我们很难仅通过零点展开对其进行近似。
如果可以找到在任意sigmoid函数文章 sigmoid函数实现_sigmoid_44附近的一个sigmoid函数文章 sigmoid函数实现_sigmoid_45,并且我们可以得到sigmoid函数文章 sigmoid函数实现_matlab代码_46,sigmoid函数文章 sigmoid函数实现_matlab_47,…,sigmoid函数文章 sigmoid函数实现_sigmoid_48,那么就可以计算泰勒级数的展开结果。对于硬件实现其实sigmoid函数文章 sigmoid函数实现_matlab代码_49可以认为是一个预先算好的系数。
回忆第2节中我们提到两个sigmoid的性质。当sigmoid函数文章 sigmoid函数实现_sigmoid函数文章_22时,sigmoid函数的机械数是很容易计算的,其次如果sigmoid函数的任意n阶导数只是sigmoid函数值的函数,即我们可以很容易得到sigmoid函数文章 sigmoid函数实现_sigmoid函数文章_22时,sigmoid函数的任意n阶导数。根据上述方法我们可以得到一种基于泰勒级数展开的硬件实现方案。其基本思想如下:
1. 输入任意x,找到距离x最近的sigmoid函数文章 sigmoid函数实现_matlab_52
2. 计算sigmoid函数文章 sigmoid函数实现_matlab_52时的函数值,以及n阶导数,sigmoid函数文章 sigmoid函数实现_matlab代码_54可以预先计算好输入。
3. 利用泰勒级数展开公式对sigmoid函数进行计算。

4. matlab代码实现及讲解

下面我会给出泰勒级数展开法实现sigmoid函数的matlab代码。可以点击下面的链接下载我的代码:


sigmoid函数文章 sigmoid函数实现_sigmoid_55


图 1 matlab代码

解压sigmoid_function.rar里面有四个.m文件。test.m是一个测试案例。sigmoid_hw.m是主要的实现函数。buildRefVal.m是用来计算sigmoid函数文章 sigmoid函数实现_硬件实现_56时的结果。sigmoidTaylor.m是sigmoid的泰勒展系数的计算方法。下面我会依次介绍这几个文件。

1. test.m
代码如下

clear
% 计算x0 = nln2时,f(x0)的机械数的最大长度
byteNum = 64;
% 泰勒级数展开的阶数
taylorNum = 2;

% 生成x
dx = 0.01;
x = 0:dx:5;

% 计算golden数值
golden = 1 ./ (1 + exp(-x));

% 遍历每一个x 用泰勒级数展开法计算sigmoid函数
for i = 1:length(x)
    [result_hw(i),~] = sigmoid_hw(x(i),byteNum,taylorNum);
end

% 显示结果
disp('the max error is ')
disp(max(abs(result_hw-golden)))

plot(x,golden)
hold on
plot(x,result_hw,'--r')

byteNum是当sigmoid函数文章 sigmoid函数实现_matlab代码_57的最大机械数的长度,taylorNum是泰勒级数展开的阶数。计算中的golden直接通过matlab的函数直接实现。byteNum是当sigmoid函数文章 sigmoid函数实现_matlab代码_57的最大机械数的长度,taylorNum是泰勒级数展开的阶数。计算中的golden直接通过matlab的函数直接实现。sigmoid_hw是实现泰勒级数展开发的核心函数。

2. sigmoid_hw.m
代码如下

% 这个函数采用泰勒级数展开法计算函数sigmoid
% 输入:
% x 需要计算的sigmoid(x)的结果
% byteNum 为计算x0 = nln2时,f(x0)的机械数的最大长度
% taylorNum 为泰勒级数展开阶数
% 输出:
% val为最后结果
% taylorSeries 是一个n阶向量,它代表x0 = nln2时的n阶泰勒级数计算结果

function [val,taylorSeries] = sigmoid_hw(x,byteNum,taylorNum)
% 计算ln2 
ln2 =  log(2);
% 得到距离x最近的x0,refX就是x0
n = round(-x/ln2);
refX = -n * ln2;

% 计算x0 = nln2时的机器数,并且转化为10进制数
refVal = buildRefVal(n,byteNum);
% 计算dx
dx = x - refX;

% 计算x0 = nln2时的n阶泰勒级数计算结果
taylorSeries = sigmoidTaylor(refVal,taylorNum);

% 计算dx^n
for i = 1:length(taylorSeries)
    if i == 1
       dxn(i) =  dx;
    else
       dxn(i) =  dxn(i-1)*dx;
    end
end

% 泰勒级数n阶求和
val  = dot(taylorSeries,dxn) + refVal;
end

函数sigmoid_hw输入的是刚才提到的byteNum,taylorNum还有需要计算的点x。输出的结果是最后泰勒级数展开法计算得到的结果val以及泰勒级数的系数taylorSeries 。首先先找到里x最近的sigmoid函数文章 sigmoid函数实现_matlab代码_57。这里sigmoid函数文章 sigmoid函数实现_matlab代码_60。buildRefVal函数是根据得到的n计算sigmoid函数文章 sigmoid函数实现_matlab_61,也就是上文提到的一串循环重复的0,1序列。得到sigmoid函数文章 sigmoid函数实现_matlab_61之后,根据sigmoidTaylor函数计算n阶泰勒级数展开的系数。buildRefVal和sigmoidTaylor会在下面讲解。

3. buildRefVal.m
代码如下

% 本函数用于产生 1 / (1 + 2^n)的结果
% 当 n = 1, 1 / (1 + 2^n) = 01010101...
% 当 n = 2, 1 / (1 + 2^n) = 001100110011...
% 当 n = -1, 1 / (1 + 2^n) = 101010101...
% 当 n = -2, 1 / (1 + 2^n) = 110011001100...
% 输入
% n 是1 / (1 + 2^n)中的n
% threshold 最大的二进制长度
% 输出
% val 为1/(1 + 2^n)的十进制结果

function val = buildRefVal(n,threshold)
if n == 0
    val = 0.5;
    return
end

val = 0;
counter = 1;
if n > 0 
   while(counter <= threshold)
        binary = mod(fix((counter-1)/n),2);
        val = val + binary*1/2^counter;
        counter = counter + 1;
   end
else
    while(counter <= threshold)
        binary = 1-mod(fix((counter-1)/-n),2);
        val = val + binary*1/2^counter;
        counter = counter + 1;
    end
end
end

sigmoid函数文章 sigmoid函数实现_matlab代码_57时,sigmoid函数等于
sigmoid函数文章 sigmoid函数实现_硬件实现_64
buildRefVal函数则是计算sigmoid函数文章 sigmoid函数实现_sigmoid_65的结果。如上文提到的sigmoid函数文章 sigmoid函数实现_sigmoid_65是一串n个连续循环的01序列,当n>0时,先0后1;当n<0时先1后0。buildRefVal函数中输出的结果是以十进制形式表示的,其基本实现是设置一个counter,当counter-1整除n为奇数时输出0(或者1),反之则输出1(或者0)。同事counter数有可以表示2进制数小数点后的位数,因此当结果为1时,在结果val中加上1/2^counter。

4. sigmoidTaylor
代码如下

% 计算sigmoid的n阶泰勒级数展开项
% 输入
% valY 是f(x0)
% n 代表泰勒级数展开项
% 输出
% z 是一个n维向量, 代表[df/dx, d2f/dx2, ...,dnf/dxn]

function [z] = sigmoidTaylor(valY,n)
% 这里通过matlab的符号运算直接求导计算,在硬件实现过程中,可以实现知道函数的形式
syms t
for i = 1:n
    if i == 1
        % y0 代表df/dx
        y0 = t*(1-t);
        y = y0;
        % 将y值带入方程
        z(i) = double(subs(y,t,valY));
    else
        %对上一次的结果求导
        y = diff(y,t)*y0;
        z(i) = double(subs(y,t,valY));
        %除以n的阶乘
        z(i) = z(i)/factorial(n);
    end
end
end

sigmoidTaylor函数用于求解n阶导数系数。这里为了提升函数的通用性我直接用符号运算中的求导来计算。事实上在硬件实现过程中这些函数,包括1/n!可以预先算好。求导的方法根据上文2.3节中提到的sigmoid函数n阶导数的求解方法即可。

最后给读者一张2阶精度拟合下的结果图。可以看到泰勒级数展开法的拟合结果与matlab里直接计算的golden结果还是比较贴近的,最大的误差为8.0948e-04。

sigmoid函数文章 sigmoid函数实现_sigmoid_67

[1] formulas for nth order derivatives of hyperbolic and trigonometric functions, Edwin G. Wintucky, 1971.