赞
踩
在上一次的博文中,学习了感知机和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
)
特性:
特性:
特性:
①
∂
u
∂
w
1
=
1
②
∂
u
∂
w
2
=
1
③
∂
L
∂
u
=
w
3
=
1
④
L
=
u
∗
w
3
=
(
w
1
+
w
2
)
∗
w
3
∂
c
∂
a
=
∂
c
∂
b
∗
∂
b
∂
a
∂
a
n
∂
a
1
=
∂
a
n
∂
a
n
−
1
∗
…
…
∗
∂
a
3
∂
a
2
∗
∂
a
2
∂
a
1
设
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
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
∂
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
由此可知,反向传播算法只是自动微分的一种特殊形式
优点: 计算复用,提高计算效率
例如一个只有三层的隐藏层的神经网络,当梯度消失发生时,靠近输出层的隐藏层的权值更新相对而言比较正常,但对于靠近输入层的隐藏层而言,其权值几乎不变或者变得很慢,仍十分接近初始化的权值,这就导致了靠近输入层的部分隐藏层其实只是一个映射层,只是对所有的输入起到了映射的作用。
本次实验是为了更好地研究自动微分,我们做了不同的实验:
从计算图和分析的结果可知,除了输入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个参数进行迭代。 由于当时的能力不足,在最初做实验的时候我只能将值一个一个的输入,无法像之前的实验那样一次输入一个数组进行判断,这样的计算效率无疑是非常低的
调用梯度下降函数,放入循环,不断地训练、迭代,并计算出其中的损失函数,直到预测值不断地接近我们想要的结果
核心思想依然没有改变,但是老师的代码看起来明显更加简单、清爽
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
)
根据梯度的值我们可以做一个简单的判断,判断其结果的取值范围,将其结果分为三个部分:
综上可知,其求导的结果的取值应该是小于0的,而我们的神经网络自动微分,是通过每一层的梯度自动运算,来求解其他的参数或权值,由于每一层的梯度太小,通过链式法则求到的值自然也是非常小的,这种细微的变化可以忽略不计,这就是我们之前提到的梯度消失,神经网络最靠近输入层的那部分隐藏层也就成了映射层
为了更好地观察到梯度消失的现象,在实验过程中我将神经网络设置了10层隐藏层
经过反复的研究观察,我得出了结论:
由于我这次设计的神经网络过于简单,每层都只有一个节点,而ReLu函数的值在遇到<0的输入时,其导数是0,这也就意味之,只要其中一个节点的导数值为0,在链式法则求解出后续值的过程中,结果都为0。
解决办法:
这个时候我们会发现,我们得到了一个杂乱无章的梯度
是的,虽然还会有个别的零值出现,但是同样是因为神经网络不够负复杂,训练次数不够多产生的个别影响,起码我们已经能够成功解决梯度消失的问题了
在本次对自动微分算法的学习过程中,我们学到了以下几个知识:
作为一个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]]))
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。