AI学习——自动微分算法

前情回顾

在上一次的博文中,学习了感知机和BPNN算法,我们可以做一个简单的知识点总结:

  1. 激活函数非线性
  2. 深层网络比浅层网络拥有更强的表达能力
  3. 三层神经网络可以表达任意函数(足够多的隐藏单元)
  4. 神经网络与图灵机(现代计算机)等价
  5. 计算性能的发展带动神经网络的发展
  6. 深层神经网络可以充分利用计算机的性能
  7. 多层感知机就是神经网络

激活函数

  • sigmoid函数

AI学习——自动微分算法_第1张图片
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+ex1:(0,1)=sigmoid(x)(1sigmoid(x))(0,41)

特性:

  1. 由于该函数的取值范围为(0,1),所以我们可以很轻易地将其与概率对应起来
  2. 事实上,该函数常用于解决或反映二分类问题
  3. 该函数在反向传播的计算中会比较简单,因为其导数为
    f ′ ( x ) = f ( x ) ( 1 − f ( x ) ) f'(x)=f(x)(1-f(x)) f(x)=f(x)(1f(x))
  4. 但是该函数会存在梯度消失现象
  • tanh函数
    AI学习——自动微分算法_第2张图片
    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)=1tanh2(x):(0,1)

特性:

  1. 跟sigmoid函数非常像,可以看成sigmoid函数在竖直方向拉伸了两倍
  2. 解决了sigmoid函数收敛较慢的问题,提高了收敛速度
  3. 由于其导数的取值范围为(0,1),所以不用担心梯度爆炸的问题
  • ReLu函数
    (PS:画不出来,随便搜了一个)
    AI学习——自动微分算法_第3张图片
    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)={x0if x > 0if x0x>01

特性:

  1. 虽然该函数的构造非常简单,但是在梯度下降里,该算法可以说是一个里程碑式的算法,对于深度学习性能的提升非常巨大
  2. 由于它的有效导数是1,所以ReLu函数可以完美避免梯度爆炸和梯度消失的问题
  3. 同时它的计算也非常的简单,只需要使用简单的判断就可以了,导数几乎不用计算
  4. ReLu函数的收敛速度相较于sigmoid函数和tanh函数要快很多
  5. 缺点是x<0的部分导数为0,这会导致一些节点失效,为计算带来影响

实验问题:自动微分算法的应用

  • 计算图:简单来理解,就是将计算通过图的形式表现出来。
  • 自动微分:是一种数值计算的方式,用来计算因变量对某个自变量的导数。

模型构建:

  • 计算图
    先计算出整体的局部微分,再将局部的微分根据我们的需要整合起来得到我们想要的结果。
    AI学习——自动微分算法_第4张图片

① ∂ 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} w1u=1w2u=1uL=w3=1L=uw3=(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} ac=bcaba1an=an1ana2a3a1a2

  • 自动微分

设 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))+11sigmoid(x,w,b)=(1,0,0)f(1,0,0)=21
AI学习——自动微分算法_第5张图片

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=1h2=0h4=exp(h3)=1h5=h4+1=2h6=1/h5=21xh1=w=0wh1=x=1h1h2=1bh2=1h2h3=1h3h4=1h4h5=1h5h6=h521=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} wf(x;w,b)=h6f(x;w,b)h5h6h4h5h3h4h2h3h1h2wh1=0.25

由此可知,反向传播算法只是自动微分的一种特殊形式
优点: 计算复用,提高计算效率

  • 梯度消失:梯度消失是传统的神经网络训练中非常致命的一个问题,其本质是由于链式法则的乘法特性导致的。

例如一个只有三层的隐藏层的神经网络,当梯度消失发生时,靠近输出层的隐藏层的权值更新相对而言比较正常,但对于靠近输入层的隐藏层而言,其权值几乎不变或者变得很慢,仍十分接近初始化的权值,这就导致了靠近输入层的部分隐藏层其实只是一个映射层,只是对所有的输入起到了映射的作用。

AI学习——自动微分算法_第6张图片

实验过程

本次实验是为了更好地研究自动微分,我们做了不同的实验:

  1. 利用计算图和自动微分解决简单的逻辑问题,如异或问题
  2. 利用自动微分观察sigmoid函数存在的梯度消失问题
  3. 用ReLu函数代替sigmoid函数,看看是否能解决梯度消失的问题
  • 利用计算图和自动微分解决简单的异或问题
    在做第一个实验之前,我们需要研究异或问题的计算图
    关于感知机的一些理解可以参考我之前的文章:
    AI学习——感知机和BPNN算法
    AI学习——自动微分算法_第7张图片
    计算图的计算思路是:同路径的梯度作乘法,不同路径的梯度作加法
    分析如下:
    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=h11+h211.5h2=x(1)+y(1)+1.5h1=x1+y10.5xh3=h1h3xh1+h2h3xh2yh3=h1h3yh1+h2h3yh2

从计算图和分析的结果可知,除了输入x,Y和我们最终所需要的预测值y,我们至少还需要 w 1 … … w 6 和 h 1 、 h 2 、 h 3 w_1……w_6和h_1、h_2、h_3 w1w6h1h2h3共计9个参数。

那么,实验开始

  1. 利用自动微分的框架,我们首先需要定义一个激活函数,这里我们选用的是sigmoid函数。 因为我们前面提到过,sigmoid函数在解决二分类问题非常有效,且计算简单
    在这里插入图片描述

  2. 接下来我们开始定义输入的值,以及我们预测的值和另外9个参数,其中预测值需要根据我们之前分析的结果,代入9个参数进行迭代。 由于当时的能力不足,在最初做实验的时候我只能将值一个一个的输入,无法像之前的实验那样一次输入一个数组进行判断,这样的计算效率无疑是非常低的
    AI学习——自动微分算法_第8张图片

  3. 调用梯度下降函数,放入循环,不断地训练、迭代,并计算出其中的损失函数,直到预测值不断地接近我们想要的结果
    AI学习——自动微分算法_第9张图片
    AI学习——自动微分算法_第10张图片

  • 自动微分解决异或问题的优化
    在上面的实验中,我们提到过缺点,就是一次只能输入一个值进行计算,效率低下且笨拙,这次在老师的指导下,成功改良,能够输入一个数组了(其实就是抄的老师的方法)
    AI学习——自动微分算法_第11张图片
    AI学习——自动微分算法_第12张图片

核心思想依然没有改变,但是老师的代码看起来明显更加简单、清爽

  • 利用自动微分观察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} yw1c=w5h4=w5f(w4h3)=w5f(w4f(w3h2))=w5f(w4f(w3f(w2h1)))=w5f(w4f(w3f(w2f(w1x))))=w121(y3)2=(y3)w5w1h4=(y3)w5w4w3w2f(h4)f(h3)f(h2)f(h1)

根据梯度的值我们可以做一个简单的判断,判断其结果的取值范围,将其结果分为三个部分:

  1. ( y − 3 ) (y-3) (y3),由于不断的迭代,最后的预测值y一定会无限接近于3,所以这个值应该是无限接近于0
  2. w 5 w 4 w 3 w 2 w_5w_4w_3w_2 w5w4w3w2,这部分的权值是我们给定的,一般来说不会太大,通常情况下都为1
  3. 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层隐藏层
AI学习——自动微分算法_第13张图片
AI学习——自动微分算法_第14张图片

  • 移花接木,ReLu函数登场!
    之前我们就提到,ReLu函数可以完美地解决梯度爆炸和梯度消失的问题。
    AI学习——自动微分算法_第15张图片
    这种感觉,好似满腔热情扑了空。
    为什么带入了ReLu函数之后结果反而没有了。

经过反复的研究观察,我得出了结论:
由于我这次设计的神经网络过于简单,每层都只有一个节点,而ReLu函数的值在遇到<0的输入时,其导数是0,这也就意味之,只要其中一个节点的导数值为0,在链式法则求解出后续值的过程中,结果都为0。

解决办法:

  1. 将神经网络复杂化,每一层隐藏层多设置几个神经单元
  2. ReLu函数的改造,给<0的部分的函数同样设置一个系数,让其不等于0
  • 卷土重来,进化吧!ReLu函数!
  1. 这次我们将ReLu函数稍作修改,将其进化成leaky ReLu函数,x<0时,给其带上一个系数0.01
    AI学习——自动微分算法_第16张图片
  2. 接着按照之前的分析,我们让我们的神经网络复杂化,这里由于能力问题,我只能手动写了一个两层隐藏层,三个节点的神经网络

AI学习——自动微分算法_第17张图片
这个时候我们会发现,我们得到了一个杂乱无章的梯度
AI学习——自动微分算法_第18张图片
是的,虽然还会有个别的零值出现,但是同样是因为神经网络不够负复杂,训练次数不够多产生的个别影响,起码我们已经能够成功解决梯度消失的问题了

实验结果

  • 利用计算图和自动微分解决简单的异或问题
    AI学习——自动微分算法_第19张图片
  • 自动微分解决异或问题的优化
    AI学习——自动微分算法_第20张图片
  • 利用自动微分观察sigmoid函数存在的梯度消失问题
    AI学习——自动微分算法_第21张图片
  • 进化的ReLu函数(leaky ReLu)解决梯度消失问题
    AI学习——自动微分算法_第22张图片

小结

在本次对自动微分算法的学习过程中,我们学到了以下几个知识:

  1. 神经网络的本质——一个又一个的计算表达式
  2. 计算图和自动微分的原理
  3. Python编程能力的提升
  4. 逐渐学会使用计算机的思维来思考问题

作为一个AI小白,通过这次学习我能够感觉到自己的学识有了很大的提升,但是AI领域深似海,一望无际,我在其中飘荡,好似一叶扁舟,越是往深处走,越是发觉自己的无知,越要谦虚谨慎,我的代码仍有许多不足,我的理解也会有很多欠缺的、没有考虑到地方。

有新的想法,欢迎交流和指正!

AI学习——自动微分算法_第23张图片

代码

# 自动微分框架源码
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]]))

你可能感兴趣的:(python,机器学习,深度学习,神经网络)