前情回顾
在上一次的博文中,学习了感知机和BPNN算法,我们可以做一个简单的知识点总结:
- 激活函数非线性
- 深层网络比浅层网络拥有更强的表达能力
- 三层神经网络可以表达任意函数(足够多的隐藏单元)
- 神经网络与图灵机(现代计算机)等价
- 计算性能的发展带动神经网络的发展
- 深层神经网络可以充分利用计算机的性能
- 多层感知机就是神经网络
激活函数
s
i
g
m
o
i
d
(
x
)
=
1
1
+
e
?
x
函
数
的
取
值
范
围
:
(
0
,
1
)
导
数
=
s
i
g
m
o
i
d
(
x
)
(
1
?
s
i
g
m
o
i
d
(
x
)
)
导
数
的
取
值
范
围
(
0
,
1
4
)
\begin{aligned} &sigmoid(x)=\frac{1}{1+e^{-x}} \\ &函数的取值范围:(0,1) \\ &导数=sigmoid(x)(1-sigmoid(x)) \\ &导数的取值范围(0,\frac{1}{4}) \end{aligned}
?sigmoid(x)=1+e?x1?函数的取值范围:(0,1)导数=sigmoid(x)(1?sigmoid(x))导数的取值范围(0,41?)?
特性:
- 由于该函数的取值范围为(0,1),所以我们可以很轻易地将其与概率对应起来
- 事实上,该函数常用于解决或反映二分类问题
- 该函数在反向传播的计算中会比较简单,因为其导数为
f
′
(
x
)
=
f
(
x
)
(
1
?
f
(
x
)
)
f'(x)=f(x)(1-f(x))
f′(x)=f(x)(1?f(x)) - 但是该函数会存在梯度消失现象
- tanh函数
t
a
n
h
(
x
)
=
e
(
x
)
?
e
(
?
x
)
e
(
x
)
+
e
(
?
x
)
函
数
的
取
值
范
围
:
(
?
1
,
1
)
导
数
=
1
?
t
a
n
h
2
(
x
)
导
数
的
取
值
范
围
:
(
0
,
1
)
\begin{aligned} &tanh(x)=\frac{e^{(x)}-e^{(-x)}}{e^{(x)}+e^{(-x)}} \\ &函数的取值范围:(-1,1) \\ &导数=1-tanh^2(x) \\ &导数的取值范围:(0,1) \end{aligned}
?tanh(x)=e(x)+e(?x)e(x)?e(?x)?函数的取值范围:(?1,1)导数=1?tanh2(x)导数的取值范围:(0,1)?
特性:
- 跟sigmoid函数非常像,可以看成sigmoid函数在竖直方向拉伸了两倍
- 解决了sigmoid函数收敛较慢的问题,提高了收敛速度
- 由于其导数的取值范围为(0,1),所以不用担心梯度爆炸的问题
- ReLu函数
(PS:画不出来,随便搜了一个)
R
e
L
u
(
x
)
=
{
x
if?x?>?0
0
if?x
≤
0
x
>
0
时
,
导
数
为
1
\begin{aligned} &ReLu(x)= \begin{cases} x& \text{if x > 0}\\ 0& \text{if x$\le$0} \end{cases} \\ &x>0时,导数为1 \end{aligned}
?ReLu(x)={x0?if?x?>?0if?x≤0?x>0时,导数为1?
特性:
- 虽然该函数的构造非常简单,但是在梯度下降里,该算法可以说是一个里程碑式的算法,对于深度学习性能的提升非常巨大
- 由于它的有效导数是1,所以ReLu函数可以完美避免梯度爆炸和梯度消失的问题
- 同时它的计算也非常的简单,只需要使用简单的判断就可以了,导数几乎不用计算
- ReLu函数的收敛速度相较于sigmoid函数和tanh函数要快很多
- 缺点是x<0的部分导数为0,这会导致一些节点失效,为计算带来影响
实验问题:自动微分算法的应用
- 计算图:简单来理解,就是将计算通过图的形式表现出来。
- 自动微分:是一种数值计算的方式,用来计算因变量对某个自变量的导数。
模型构建:
- 计算图:
先计算出整体的局部微分,再将局部的微分根据我们的需要整合起来得到我们想要的结果。
①
?
u
?
w
1
=
1
②
?
u
?
w
2
=
1
③
?
L
?
u
=
w
3
=
1
④
L
=
u
?
w
3
=
(
w
1
+
w
2
)
?
w
3
\begin{aligned} &①\frac{\partial u}{\partial w_1}=1 \\ &②\frac{\partial u}{\partial w2}=1 \\ &③\frac{\partial L}{\partial u} =w3=1 \\ &④L=u*w3=(w_1+w_2)*w_3 \end{aligned}
?①?w1??u?=1②?w2?u?=1③?u?L?=w3=1④L=u?w3=(w1?+w2?)?w3??
?
c
?
a
=
?
c
?
b
?
?
b
?
a
?
a
n
?
a
1
=
?
a
n
?
a
n
?
1
?
…
…
?
?
a
3
?
a
2
?
?
a
2
?
a
1
\begin{aligned} &\frac{\partial c}{\partial a}=\frac{\partial c}{\partial b}*\frac{\partial b}{\partial a} \\ & \frac{\partial a_n}{\partial a_1}=\frac{\partial a_n}{\partial a_{n-1}}*……*\frac{\partial a_3}{\partial a_{2}}*\frac{\partial a_2}{\partial a_{1}} \end{aligned}
??a?c?=?b?c???a?b??a1??an??=?an?1??an???……??a2??a3????a1??a2???
设
f
(
x
,
w
,
b
)
=
1
e
x
p
(
?
(
w
x
+
b
)
)
+
1
相
当
于
一
个
s
i
g
m
o
i
d
函
数
当
(
x
,
w
,
b
)
=
(
1
,
0
,
0
)
时
,
f
(
1
,
0
,
0
)
=
1
2
\begin{aligned} &设f(x,w,b)=\frac{1}{exp(-(wx+b))+1} \\ &相当于一个sigmoid函数 \\ &当(x,w,b)=(1,0,0)时,f(1,0,0)=\frac{1}{2} \end{aligned}
?设f(x,w,b)=exp(?(wx+b))+11?相当于一个sigmoid函数当(x,w,b)=(1,0,0)时,f(1,0,0)=21??
h
1
=
w
x
=
0
h
2
=
h
1
+
b
=
0
h
3
=
?
1
?
h
2
=
0
h
4
=
e
x
p
(
h
3
)
=
1
h
5
=
h
4
+
1
=
2
h
6
=
1
/
h
5
=
1
2
①
?
h
1
?
x
=
w
=
0
②
?
h
1
?
w
=
x
=
1
③
?
h
2
?
h
1
=
1
④
?
h
2
?
b
=
1
⑤
?
h
3
?
h
2
=
?
1
⑥
?
h
4
?
h
3
=
1
⑦
?
h
5
?
h
4
=
1
⑧
?
h
6
?
h
5
=
?
1
h
5
2
=
?
1
4
\begin{aligned} &h_1=wx=0 \\ &h_2=h_1+b=0 \\ &h_3=-1*h_2=0 \\ &h_4=exp(h_3)=1 \\ &h_5=h_4+1=2 \\ &h_6=1/h_5=\frac{1}{2} \\ &①\frac{\partial h_1}{\partial x}=w=0 \\ &②\frac{\partial h_1}{\partial w}=x=1 \\ &③\frac{\partial h_2}{\partial h_1}=1 \\ &④\frac{\partial h_2}{\partial b}=1 \\ &⑤\frac{\partial h_3}{\partial h_2}=-1 \\ &⑥\frac{\partial h_4}{\partial h_3}=1 \\ &⑦\frac{\partial h_5}{\partial h_4}=1 \\ &⑧\frac{\partial h_6}{\partial h_5}=-\frac{1}{h_5^2}=-\frac{1}{4} \\ \end{aligned}
?h1?=wx=0h2?=h1?+b=0h3?=?1?h2?=0h4?=exp(h3?)=1h5?=h4?+1=2h6?=1/h5?=21?①?x?h1??=w=0②?w?h1??=x=1③?h1??h2??=1④?b?h2??=1⑤?h2??h3??=?1⑥?h3??h4??=1⑦?h4??h5??=1⑧?h5??h6??=?h52?1?=?41??
?
f
(
x
;
w
,
b
)
?
w
=
?
f
(
x
;
w
,
b
)
?
h
6
?
?
h
6
?
h
5
?
?
h
5
?
h
4
?
?
h
4
?
h
3
?
?
h
3
?
h
2
?
?
h
2
?
h
1
?
?
h
1
?
w
=
0.25
\begin{aligned} \frac{\partial f(x;w,b)}{\partial w}=\frac{\partial f(x;w,b)}{\partial h_6}*\frac{\partial h_6}{\partial h_5}*\frac{\partial h_5}{\partial h_4}*\frac{\partial h_4}{\partial h_3}*\frac{\partial h_3}{\partial h_2}*\frac{\partial h_2}{\partial h_1}*\frac{\partial h_1}{\partial w} \\ =0.25 \end{aligned}
?w?f(x;w,b)?=?h6??f(x;w,b)???h5??h6????h4??h5????h3??h4????h2??h3????h1??h2????w?h1??=0.25?
由此可知,反向传播算法只是自动微分的一种特殊形式 优点: 计算复用,提高计算效率
- 梯度消失:梯度消失是传统的神经网络训练中非常致命的一个问题,其本质是由于链式法则的乘法特性导致的。
例如一个只有三层的隐藏层的神经网络,当梯度消失发生时,靠近输出层的隐藏层的权值更新相对而言比较正常,但对于靠近输入层的隐藏层而言,其权值几乎不变或者变得很慢,仍十分接近初始化的权值,这就导致了靠近输入层的部分隐藏层其实只是一个映射层,只是对所有的输入起到了映射的作用。
实验过程
本次实验是为了更好地研究自动微分,我们做了不同的实验:
- 利用计算图和自动微分解决简单的逻辑问题,如异或问题
- 利用自动微分观察sigmoid函数存在的梯度消失问题
- 用ReLu函数代替sigmoid函数,看看是否能解决梯度消失的问题
- 利用计算图和自动微分解决简单的异或问题
在做第一个实验之前,我们需要研究异或问题的计算图 关于感知机的一些理解可以参考我之前的文章: AI学习——感知机和BPNN算法 计算图的计算思路是:同路径的梯度作乘法,不同路径的梯度作加法 分析如下:
h
3
=
h
1
?
1
+
h
2
?
1
?
1.5
h
2
=
x
?
(
?
1
)
+
y
?
(
?
1
)
+
1.5
h
1
=
x
?
1
+
y
?
1
?
0.5
?
h
3
?
x
=
?
h
3
?
h
1
?
?
h
1
?
x
+
?
h
3
?
h
2
?
?
h
2
?
x
?
h
3
?
y
=
?
h
3
?
h
1
?
?
h
1
?
y
+
?
h
3
?
h
2
?
?
h
2
?
y
\begin{aligned} &h_3=h_1*1+h_2*1-1.5 \\ &h_2=x*(-1)+y*(-1)+1.5 \\ &h_1=x*1+y*1-0.5 \\ &\frac{\partial h_3}{\partial x}=\frac{\partial h_3}{\partial h_1}*\frac{\partial h_1}{\partial x}+\frac{\partial h_3}{\partial h_2}*\frac{\partial h_2}{\partial x} \\ &\frac{\partial h_3}{\partial y}=\frac{\partial h_3}{\partial h_1}*\frac{\partial h_1}{\partial y}+\frac{\partial h_3}{\partial h_2}*\frac{\partial h_2}{\partial y} \end{aligned}
?h3?=h1??1+h2??1?1.5h2?=x?(?1)+y?(?1)+1.5h1?=x?1+y?1?0.5?x?h3??=?h1??h3????x?h1??+?h2??h3????x?h2???y?h3??=?h1??h3????y?h1??+?h2??h3????y?h2???
从计算图和分析的结果可知,除了输入x,Y和我们最终所需要的预测值y,我们至少还需要
w
1
…
…
w
6
和
h
1
、
h
2
、
h
3
w_1……w_6和h_1、h_2、h_3
w1?……w6?和h1?、h2?、h3?共计9个参数。
那么,实验开始
-
利用自动微分的框架,我们首先需要定义一个激活函数,这里我们选用的是sigmoid函数。 因为我们前面提到过,sigmoid函数在解决二分类问题非常有效,且计算简单 -
接下来我们开始定义输入的值,以及我们预测的值和另外9个参数,其中预测值需要根据我们之前分析的结果,代入9个参数进行迭代。 由于当时的能力不足,在最初做实验的时候我只能将值一个一个的输入,无法像之前的实验那样一次输入一个数组进行判断,这样的计算效率无疑是非常低的 -
调用梯度下降函数,放入循环,不断地训练、迭代,并计算出其中的损失函数,直到预测值不断地接近我们想要的结果
- 自动微分解决异或问题的优化
在上面的实验中,我们提到过缺点,就是一次只能输入一个值进行计算,效率低下且笨拙,这次在老师的指导下,成功改良,能够输入一个数组了(其实就是抄的老师的方法)
核心思想依然没有改变,但是老师的代码看起来明显更加简单、清爽
- 利用自动微分观察sigmoid函数存在的梯度消失问题
为了更好地研究梯度消失,我们同样需要设计一个简单的神经网络,下面这个神经网络总共有四个隐藏层,但是每一层隐藏层都只含有一个单元节点,这就导致了这个神经网络尽管有较多的隐藏层,但依旧十分简单
y
=
w
5
h
4
=
w
5
f
(
w
4
h
3
)
=
w
5
f
(
w
4
f
(
w
3
h
2
)
)
=
w
5
f
(
w
4
f
(
w
3
f
(
w
2
h
1
)
)
)
=
w
5
f
(
w
4
f
(
w
3
f
(
w
2
f
(
w
1
x
)
)
)
)
?
c
?
w
1
=
?
1
2
(
y
?
3
)
2
?
w
1
=
(
y
?
3
)
w
5
?
h
4
?
w
1
=
(
y
?
3
)
w
5
w
4
w
3
w
2
f
′
(
h
4
)
f
′
(
h
3
)
f
′
(
h
2
)
f
′
(
h
1
)
\begin{aligned} y&=w_5h_4=w_5f(w_4h_3)=w_5f(w_4f(w_3h_2)) \\ &=w_5f(w_4f(w_3f(w_2h_1)))=w_5f(w_4f(w_3f(w_2f(w_1x)))) \\ \frac{\partial c}{\partial w_1}&=\frac{\partial \frac{1}{2}(y-3)^2}{\partial w_1}=(y-3)w_5\frac{\partial h_4}{\partial w_1} \\ &=(y-3)w_5w_4w_3w_2f'(h_4)f'(h_3)f'(h_2)f'(h_1) \end{aligned}
y?w1??c??=w5?h4?=w5?f(w4?h3?)=w5?f(w4?f(w3?h2?))=w5?f(w4?f(w3?f(w2?h1?)))=w5?f(w4?f(w3?f(w2?f(w1?x))))=?w1??21?(y?3)2?=(y?3)w5??w1??h4??=(y?3)w5?w4?w3?w2?f′(h4?)f′(h3?)f′(h2?)f′(h1?)?
根据梯度的值我们可以做一个简单的判断,判断其结果的取值范围,将其结果分为三个部分:
-
(
y
?
3
)
(y-3)
(y?3),由于不断的迭代,最后的预测值y一定会无限接近于3,所以这个值应该是无限接近于0
-
w
5
w
4
w
3
w
2
w_5w_4w_3w_2
w5?w4?w3?w2?,这部分的权值是我们给定的,一般来说不会太大,通常情况下都为1
-
f
′
(
h
4
)
f
′
(
h
3
)
f
′
(
h
2
)
f
′
(
h
1
)
f'(h_4)f'(h_3)f'(h_2)f'(h_1)
f′(h4?)f′(h3?)f′(h2?)f′(h1?),由于我们使用的激活函数是sigmoid函数,我们知道其导数的取值范围是
(
0
,
1
4
)
(0,\frac{1}{4})
(0,41?),所以该部分的取值范围应该是
(
0
,
1
4
4
)
(0,\frac{1}{4^4})
(0,441?)
综上可知,其求导的结果的取值应该是小于0的,而我们的神经网络自动微分,是通过每一层的梯度自动运算,来求解其他的参数或权值,由于每一层的梯度太小,通过链式法则求到的值自然也是非常小的,这种细微的变化可以忽略不计,这就是我们之前提到的梯度消失,神经网络最靠近输入层的那部分隐藏层也就成了映射层
为了更好地观察到梯度消失的现象,在实验过程中我将神经网络设置了10层隐藏层
- 移花接木,ReLu函数登场!
之前我们就提到,ReLu函数可以完美地解决梯度爆炸和梯度消失的问题。 这种感觉,好似满腔热情扑了空。 为什么带入了ReLu函数之后结果反而没有了。
经过反复的研究观察,我得出了结论: 由于我这次设计的神经网络过于简单,每层都只有一个节点,而ReLu函数的值在遇到<0的输入时,其导数是0,这也就意味之,只要其中一个节点的导数值为0,在链式法则求解出后续值的过程中,结果都为0。
解决办法:
- 将神经网络复杂化,每一层隐藏层多设置几个神经单元
- ReLu函数的改造,给<0的部分的函数同样设置一个系数,让其不等于0
- 这次我们将ReLu函数稍作修改,将其进化成leaky ReLu函数,x<0时,给其带上一个系数0.01
- 接着按照之前的分析,我们让我们的神经网络复杂化,这里由于能力问题,我只能手动写了一个两层隐藏层,三个节点的神经网络
这个时候我们会发现,我们得到了一个杂乱无章的梯度 是的,虽然还会有个别的零值出现,但是同样是因为神经网络不够负复杂,训练次数不够多产生的个别影响,起码我们已经能够成功解决梯度消失的问题了
实验结果
- 利用计算图和自动微分解决简单的异或问题
- 自动微分解决异或问题的优化
- 利用自动微分观察sigmoid函数存在的梯度消失问题
- 进化的ReLu函数(leaky ReLu)解决梯度消失问题
小结
在本次对自动微分算法的学习过程中,我们学到了以下几个知识:
- 神经网络的本质——一个又一个的计算表达式
- 计算图和自动微分的原理
- Python编程能力的提升
- 逐渐学会使用计算机的思维来思考问题
作为一个AI小白,通过这次学习我能够感觉到自己的学识有了很大的提升,但是AI领域深似海,一望无际,我在其中飘荡,好似一叶扁舟,越是往深处走,越是发觉自己的无知,越要谦虚谨慎,我的代码仍有许多不足,我的理解也会有很多欠缺的、没有考虑到地方。
有新的想法,欢迎交流和指正!
代码
# 自动微分框架源码
from collections import defaultdict
import numpy as np
class Variable:
def __init__(self, value, local_gradients=[]):
self.value = value
self.local_gradients = local_gradients
def __add__(self, other):
return add(self, other)
def __mul__(self, other):
return mul(self, other)
def __sub__(self, other):
return add(self, neg(other))
def __neg__(self):
return neg(self)
def __truediv__(self, other):
return mul(self, inv(other))
def add(a, b):
value = a.value + b.value
local_gradients = ((a, 1), (b, 1))
return Variable(value, local_gradients)
def mul(a, b):
value = a.value * b.value
local_gradients = ((a, b.value), (b, a.value))
return Variable(value, local_gradients)
def neg(a):
value = -1 * a.value
local_gradients = ((a, -1),)
return Variable(value, local_gradients)
def inv(a):
value = 1. / a.value
local_gradients = ((a, -1 / a.value ** 2),)
return Variable(value, local_gradients)
def exp(a):
value = np.exp(a.value)
local_gradients = ((a, value),)
return Variable(value, local_gradients)
def get_gradients(variable):
gradients = defaultdict(lambda: 0) # 可以根据Variable变量地址索引对应的梯度
def compute_gradients(variable, path_value):
for child_variable, local_gradient in variable.local_gradients: # 两条路径循环2次
value_of_path_to_child = path_value * local_gradient # 从后往前,乘以每条边的梯度
gradients[child_variable] += value_of_path_to_child # 不同路径的梯度相加,算的是局部偏微分
compute_gradients(child_variable, value_of_path_to_child) # 递归整个计算图
compute_gradients(variable, path_value=1) # path_value=1,输出对自己的偏微分为1
return gradients
def sigmoid(z):
ONE = Variable(1)
return ONE / (ONE + exp(-z))
x = Variable(1)
w = Variable(0)
b = Variable(0)
Y = Variable(1)
y = sigmoid(w * x + b)
gradients = get_gradients(y)
print('dy/dw=%f, dy/db=%f' % (gradients[w], gradients[b]))
for i in range(1000):
y = sigmoid(w * x + b)
C = (y - Y) * (y - Y)
print('Cost=%f,y=%f' % (C.value, y.value))
gradients = get_gradients(C)
w.value = w.value - 0.1 * gradients[w]
b.value = b.value - 0.1 * gradients[b]
# 感知机初始代码,但是由于Variable类型,最开始只能一次输入一个值进行运算
from collections import defaultdict
import numpy as np
class Variable:
def __init__(self, value, local_gradients=[]):
self.value = value
self.local_gradients = local_gradients
def __add__(self, other):
return add(self, other)
def __mul__(self, other):
return mul(self, other)
def __sub__(self, other):
return add(self, neg(other))
def __neg__(self):
return neg(self)
def __truediv__(self, other):
return mul(self, inv(other))
def add(a, b):
value = a.value + b.value
local_gradients = ((a, 1), (b, 1))
return Variable(value, local_gradients)
def mul(a, b):
value = a.value * b.value
local_gradients = ((a, b.value), (b, a.value))
return Variable(value, local_gradients)
def neg(a):
value = -1 * a.value
local_gradients = ((a, -1),)
return Variable(value, local_gradients)
def inv(a):
value = 1. / a.value
local_gradients = ((a, -1 / a.value ** 2),)
return Variable(value, local_gradients)
def exp(a):
value = np.exp(a.value)
local_gradients = ((a, value),)
return Variable(value, local_gradients)
def get_gradients(variable):
gradients = defaultdict(lambda: 0) # 可以根据Variable变量地址索引对应的梯度
def compute_gradients(variable, path_value):
for child_variable, local_gradient in variable.local_gradients: # 两条路径循环2次
value_of_path_to_child = path_value * local_gradient # 从后往前,乘以每条边的梯度
gradients[child_variable] += value_of_path_to_child # 不同路径的梯度相加,算的是局部偏微分
compute_gradients(child_variable, value_of_path_to_child) # 递归整个计算图
compute_gradients(variable, path_value=1) # path_value=1,输出对自己的偏微分为1
return gradients
def sigmoid(z):
ONE = Variable(1)
return ONE / (ONE + exp(-z))
x1=Variable(1)
x2=Variable(0)
Y=Variable(1)
w1= Variable(0)
w2= Variable(0)
w3= Variable(0)
w4= Variable(0)
w5= Variable(0)
w6= Variable(0)
b1= Variable(0)
b2= Variable(0)
b3= Variable(0)
y = sigmoid(w5*sigmoid(x1*w1+x2*w2+b1)+w6*sigmoid(x1*w3+x2*w4+b2)+b3)
gradients = get_gradients(y)
#print('dy/dw1=%f, dy/dw2=%f,dy/dw3=%f,dy/dw3=%f,dy/dw3=%f' % (gradients[w1], gradients[w2],gradients[w3]))
for i in range(1000):
y = sigmoid(w5*sigmoid(x1*w1+x2*w2+b1)+w6*sigmoid(x1*w3+x2*w4+b2)+b3)
C = (y - Y) * (y - Y)
print('Cost=%f,Y=%f,y=%f' % (C.value,Y.value, y.value))
gradients = get_gradients(C)
w1.value = w1.value - 0.1 * gradients[w1]
w2.value = w2.value - 0.1 * gradients[w2]
w3.value = w3.value - 0.1 * gradients[w3]
w4.value = w4.value - 0.1 * gradients[w4]
w5.value = w5.value - 0.1 * gradients[w5]
w6.value = w6.value - 0.1 * gradients[w6]
b1.value = b1.value - 0.1 * gradients[b1]
b2.value = b2.value - 0.1 * gradients[b2]
b3.value = b3.value - 0.1 * gradients[b3]
print('x1=%f,x2=%f,w1=%f.w2=%f,w3=%f,w4=%f,w5=%f,w6=%f,b1=%f,b2=%f,b3=%f,y=%f' % (x1.value,x2.value,w1.value,w4.value,w3.value,w4.value,w5.value,w6.value,b1.value,b2.value,b3.value,y.value))
#感知机改良算法,成功的输入了数组,让计算更加简单便捷
from collections import defaultdict
import numpy as np
class Variable:
def __init__(self, value, local_gradients=[]):
self.value = value
self.local_gradients = local_gradients
def __add__(self, other):
return add(self, other)
def __mul__(self, other):
return mul(self, other)
def __sub__(self, other):
return add(self, neg(other))
def __neg__(self):
return neg(self)
def __truediv__(self, other):
return mul(self, inv(other))
def add(a, b):
value = a.value + b.value
local_gradients = ((a, 1), (b, 1))
return Variable(value, local_gradients)
def mul(a, b):
value = a.value * b.value
local_gradients = ((a, b.value), (b, a.value))
return Variable(value, local_gradients)
def neg(a):
value = -1 * a.value
local_gradients = ((a, -1),)
return Variable(value, local_gradients)
def inv(a):
value = 1. / a.value
local_gradients = ((a, -1 / a.value ** 2),)
return Variable(value, local_gradients)
def exp(a):
value = np.exp(a.value)
local_gradients = ((a, value),)
return Variable(value, local_gradients)
def get_gradients(variable):
gradients = defaultdict(lambda: 0) # 可以根据Variable变量地址索引对应的梯度
def compute_gradients(variable, path_value):
for child_variable, local_gradient in variable.local_gradients: # 两条路径循环2次
value_of_path_to_child = path_value * local_gradient # 从后往前,乘以每条边的梯度
gradients[child_variable] += value_of_path_to_child # 不同路径的梯度相加,算的是局部偏微分
compute_gradients(child_variable, value_of_path_to_child) # 递归整个计算图
compute_gradients(variable, path_value=1) # path_value=1,输出对自己的偏微分为1
return gradients
def sigmoid(z):
ONE = Variable(1)
return ONE / (ONE + exp(-z))
#自动微分实现异或
w = [Variable(np.random.randn()) for i in range(9)]
def f(x1,x2):
x1 = Variable(x1)
x2 = Variable(x2)
h1 = sigmoid(x1*w[0]+x2*w[1]+w[2])
h2 = sigmoid(x1*w[3]+x2*w[4]+w[5])
ho = sigmoid(w[6]*h1+w[7]*h2+w[8])
return ho
for i in range(30000):
C = (f(0,0) - Variable(0)) * (f(0,0) - Variable(0))
C = (f(0,1) - Variable(1)) * (f(0,1) - Variable(1)) + C
C = (f(1,0) - Variable(1)) * (f(1,0) - Variable(1)) + C
C = (f(1,1) - Variable(0)) * (f(1,1) - Variable(0)) + C
print('Epoch %d, Cost=%f' % (i,C.value))
gradients = get_gradients(C)
for j in range(len(w)):
w[j].value = w[j].value - 0.1 *gradients[w[j]]
print("f(0,0)=%f" % f(0,0).value)
print("f(0,1)=%f" % f(0,1).value)
print("f(1,0)=%f" % f(1,0).value)
print("f(1,1)=%f" % f(1,1).value)
# 研究我们之前提到的sigmoid函数存在的梯度消失现象
from collections import defaultdict
import numpy as np
class Variable:
def __init__(self, value, local_gradients=[]):
self.value = value
self.local_gradients = local_gradients
def __add__(self, other):
return add(self, other)
def __mul__(self, other):
return mul(self, other)
def __sub__(self, other):
return add(self, neg(other))
def __neg__(self):
return neg(self)
def __truediv__(self, other):
return mul(self, inv(other))
def add(a, b):
value = a.value + b.value
local_gradients = ((a, 1), (b, 1))
return Variable(value, local_gradients)
def mul(a, b):
value = a.value * b.value
local_gradients = ((a, b.value), (b, a.value))
return Variable(value, local_gradients)
def neg(a):
value = -1 * a.value
local_gradients = ((a, -1),)
return Variable(value, local_gradients)
def inv(a):
value = 1. / a.value
local_gradients = ((a, -1 / a.value ** 2),)
return Variable(value, local_gradients)
def exp(a):
value = np.exp(a.value)
local_gradients = ((a, value),)
return Variable(value, local_gradients)
def get_gradients(variable):
gradients = defaultdict(lambda: 0) # 可以根据Variable变量地址索引对应的梯度
def compute_gradients(variable, path_value):
for child_variable, local_gradient in variable.local_gradients: # 两条路径循环2次
value_of_path_to_child = path_value * local_gradient # 从后往前,乘以每条边的梯度
gradients[child_variable] += value_of_path_to_child # 不同路径的梯度相加,算的是局部偏微分
compute_gradients(child_variable, value_of_path_to_child) # 递归整个计算图
compute_gradients(variable, path_value=1) # path_value=1,输出对自己的偏微分为1
return gradients
def sigmoid(z):
ONE = Variable(1)
return ONE / (ONE + exp(-z))
#自动微分,sigmoid函数的梯度消失现象
w = [Variable(np.random.randn()) for i in range(10)]
x = Variable(1)
Y = Variable(3)
y = sigmoid(w[1]*x)
for i in range(10):
y = sigmoid(w[i]*y)
C = (Y-y)*(Y-y)
gradients = get_gradients(C)
for i in range(10):
print('dC/dw%d=%f' % (i,gradients[w[i]]))
# 使用leaky ReLu函数避免梯度下降
from collections import defaultdict
import numpy as np
class Variable:
def __init__(self, value, local_gradients=[]):
self.value = value
self.local_gradients = local_gradients
def __add__(self, other):
return add(self, other)
def __mul__(self, other):
return mul(self, other)
def __sub__(self, other):
return add(self, neg(other))
def __neg__(self):
return neg(self)
def __truediv__(self, other):
return mul(self, inv(other))
def add(a, b):
value = a.value + b.value
local_gradients = ((a, 1), (b, 1))
return Variable(value, local_gradients)
def mul(a, b):
value = a.value * b.value
local_gradients = ((a, b.value), (b, a.value))
return Variable(value, local_gradients)
def neg(a):
value = -1 * a.value
local_gradients = ((a, -1),)
return Variable(value, local_gradients)
def inv(a):
value = 1. / a.value
local_gradients = ((a, -1 / a.value ** 2),)
return Variable(value, local_gradients)
def exp(a):
value = np.exp(a.value)
local_gradients = ((a, value),)
return Variable(value, local_gradients)
def get_gradients(variable):
gradients = defaultdict(lambda: 0) # 可以根据Variable变量地址索引对应的梯度
def compute_gradients(variable, path_value):
for child_variable, local_gradient in variable.local_gradients: # 两条路径循环2次
value_of_path_to_child = path_value * local_gradient # 从后往前,乘以每条边的梯度
gradients[child_variable] += value_of_path_to_child # 不同路径的梯度相加,算的是局部偏微分
compute_gradients(child_variable, value_of_path_to_child) # 递归整个计算图
compute_gradients(variable, path_value=1) # path_value=1,输出对自己的偏微分为1
return gradients
def LReLu(x):
if x.value > 0:
return x
else:
x.value=0.01*x.value
return x
k=10
print("leaky ReLu函数")
w = [Variable(np.random.randn()) for i in range(k)]
x = Variable(1)
Y = Variable(3)
y = LReLu(w[7]*LReLu(w[4]*LReLu(w[1]*x))+w[6]*ReLu(w[8]*LReLu(w[2]*x))+w[9]*ReLu(w[6]*LReLu(w[3]*x)))
for i in range(k):
#print("y=%f" % y.value)
y = LReLu(w[7]*LReLu(w[4]*LReLu(w[1]*y))+w[6]*ReLu(w[8]*LReLu(w[2]*y))+w[9]*ReLu(w[6]*LReLu(w[3]*y)))
C = (Y - y) * (Y - y)
gradients = get_gradients(C)
for i in range(k):
print('dC/dw%d=%f' % (i, gradients[w[i]]))
|