第五章节:神经网络_周志华《机器学习》

本部分重点是对5.3章节的梳理和公式推导。

1.1神经网络

先简单介绍下神经网络,它是现在用的比较多的,深度学习也是基于此。简单看下最初的M-P神经元。

M-P神经元:神经元收到n个其他神经元传递过来的信号Xi,通过权重Wi连接,超过阈值θ就用f函数激活。

 

这里的f,是激活函数,可以是Sigmoid函数,也可以是其他函数。分类的话用S型函数,是他的健壮性比较好,也易于求导,故选用他来gank neural network。

1.2感知机

也简单介绍下感知机(Perceptron),他是有两层神经元组成。可以解决简单线性的与或非问题。但关于非线性的问题他就只能哭了。

 

 感知机就是比M-P神经元多了一层输入层,第二层还是M-P神经元。

1.3误差逆传播算法(Error BackPropagation)

这里的误差逆传播算法,是指标准BP算法:每次只基于一个样例进行一次参数的调整。还有一个叫累积BP:每次基于整个训练集进行一次参数调整。

1.3.1符号介绍

好了,上面的爸爸解决不了的非线性问题,只能留给大孙子了--------->具有多层的神经网络。他是家族里面的皇长孙。皇长孙的老师就是BP算法,目的很明显,就是把皇长孙给教育好,好继承王位。==好了,开始吧。

BP算法可以训练单层神经网络,也可以是多层神经网络。

 

简单说下符号:这里只看有三层的神经网络,第一层是输入层,Xi是各个属性值,中间是一层隐层结点bh,隐层结点和输入层结点每个之间有连接权重Vih,隐层每个结点有一个阈值γh,隐层和输出层之间也有连接权重Whj,输出层每个结点的阈值是θj。

所以有这样的四个关系式:

(1)第h个隐层神经元的输入:

(2)第h个隐层神经元的输出:

 (3)第j个输出神经元的输入:

(4)第j个输出神经元的输出:

 好,一会链式求导会用到这四个式子,其中f是S型函数。

 1.3.2优化思路

 本BP算法还是考虑要使得输出结果的误差最小,比如分类要和原类标一致,分类错误的越多模型就越不好。那怎么衡量好与不好?于是就有了对误差的考虑:

先考虑一个样本的输入,比如一个西瓜的各个属性值Xi都输进去后,假设得到的输出是Y*,原始类标是Y,那么Y*-Y就是一个预测样本的类标向量的误差Ek,这样的误差有L个,把每个训练例的L个误差加起来最小化不就达到我们优化误差的目的了。这里使用均方误差最小化来优化误差,就是最小二乘法。其实还是误差和,只不过是误差子项的平方和。下面公式中前面1/2是为了求导时消去平方而添加的,不影响我们的优化目标。

所以在第k个训练样例(Xk,Yk)上面的均方误差是:

其中输出的预测值y^j 就是1.3.1中的公式(4)。

而真正的目标是最小化训练集D上的累积误差:

 

即为公式(5.16),m代表共有m个训练样本,Ek是每一个样本的误差,E是总的积累误差。除以m是归一化。这里的E是代码中用于判别收敛的。

但是下面求偏导用的是Ek,因为是针对每个样本误差逆调整参数的。

1.3.3参数调整

 和感知机类似,对原来的参数+误差的大小作为增量(不一定是增加)进行迭代:

V = V + △V

(1)对Whj进行调整

BP算法的△V是基于梯度下降策略给出,下面以Whj为例子。

 

 

  

 

好,开始我不懂为什么图片里面公式(5.7)为什么不用求和,而上面eh里面要求和。

因为Ek和Whj是1对1对应的,不懂?念一下咒语:误差逆传播算法,关键就是这个逆字,链式求导时候,变量之间的关系是:Whj - βj - yjk - Ejk ,就是周志华讲的Whj先影响到第j个βj ,再影响到预测值yjk,最后影响到Ejk,怎么看?看上面1.3.1节里面的4个公式即可,Whj可以影响的是βj,因为Whj=∑Whj*bh,同理βj先影响到预测值yjk,是因为1.3.1里面的第4个公式:yjk=f(βj - θj),Ek同理。

公式(5.7)链式求导的顺序和上面前馈神经网络的变量之间的顺序:Whj - βj - yjk - Ej是相反的,是因为BP是把误差 Ek 先逆传播给预测值  yjk ,再由 yjk 逆传播给 βj , βj 最后逆传播给 Whj 。这样不就是误差可以逆向调整参数了嘛,这是公式(5.7)的链式求导部分理解。至于为什么没有求和,是因为误差Ek到Whj都是一一对应的,前后都有j,有木有发现! 就是Ek动一下,Whj也要动一下。

(2)对参数θ的调整

先分析下链路:θj -  yjk - Ejk,那么求偏导时候要相反:

(3)对参数Vih的调整

先分析下链路:Vih -  αh - bh - βj - yjk - Ejk ,虽然这里有点长,但可以解释为什么要有求和符号:因为从Vih -  αh - bh都是一一对应的,但是到了 bh - βj - yjk - Ejk,有没有发现已经没有纽带了,直接从 h 跨越到了 j ,这里就差了个求和符号∑,为什么是l个,因为前馈是从 bh - βj,误差逆向传播的时候是 βj -  bh,从图5.7来看,是l个结点误差汇总到bh,所以要把l个误差都加起来反馈回参数。求导公式如下:

 

 (4)对γh进行调整

 先分析链路:γh先影响bh,bh影响βj,βj影响 yjk ,最后影响Ejkγh - bh - βj - yjEjk,同样 bh - βj 时候,是l个误差汇总到bh,求偏导如下:

注意:有j的都要放到∑符号里面;第三个等号中gj里面有一个负号,把上一步的负号消了;bh=f(αh-γh),就是f函数,而f是S函数有特性:f'(x) = f(x)*( 1 - f(x) ),即b'h = bh(1-bh);

bh对γh求偏导,就是对γh求复合导数:f ' *(αh-γh)'=bh(1-bh)*(-1)。其中η是学习率,η∈(0,1),相当于调整误差步长的大小。上面几个推导也是类似。

至此:5.3节的大头已经gank掉了,爽!

1.3.4算法伪代码

 

好了,思考:怎么达到停止条件?因为这个是采用的梯度下降,你可以指定循环轮数就停止,比如循环100000次,反正是机器跑,你怕什么,但是这样遇到大数据量的时候收敛的反而比牛顿法和最小二乘法快,因为他们要求逆矩阵,大的数据量的逆矩阵不好求,这也是梯度下降的一个优点。或者根据误差小于阈值就停止循环比如0.000001,我们就认为它足够小,而符合我们的原始类标。

之前提到的我们计算的是标准BP算法,只基于一个样本来调整误差,还有一种累积BP算法,两者的差别类似于随机梯度下降和批量梯度下降的区别。关于梯度下降有一个很好的博客,推荐参考:

 https://www.cnblogs.com/pinard/p/5970503.html

 1.4代码参考

先说明这个代码不是我写的,网上的,在哪找的忘了。先贴一个累积BP算法的代码,标准BP的好像有错,我再看看,虽然我贴的和我上面单个样本调整误差的是不同的,但其实差不多,只是一个是整体训练样本矩阵放入计算,一个是每个样本进行迭代。

调用的时候输入2,选择累积BP算法即可。用的是pycharm+python3.5,安装个numpy包就好了。也欢迎有志者修改标准BP。

 

  1 import numpy as np
  2 import math
  3 
  4 x = np.mat( '2,3,3,2,1,2,3,3,3,2,1,1,2,1,3,1,2;\
  5              1,1,1,1,1,2,2,2,2,3,3,1,2,2,2,1,1;\
  6              2,3,2,3,2,2,2,2,3,1,1,2,2,3,2,2,3;\
  7              3,3,3,3,3,3,2,3,2,3,1,1,2,2,3,1,2;\
  8              1,1,1,1,1,2,2,2,2,3,3,3,1,1,2,3,2;\
  9              1,1,1,1,1,2,2,1,1,2,1,2,1,1,2,1,1;\
 10              0.697,0.774,0.634,0.668,0.556,0.403,0.481,0.437,0.666,0.243,0.245,0.343,0.639,0.657,0.360,0.593,0.719;\
 11              0.460,0.376,0.264,0.318,0.215,0.237,0.149,0.211,0.091,0.267,0.057,0.099,0.161,0.198,0.370,0.042,0.103\
 12         ').T
 13 x = np.array(x)  #以数组列表的形式一行一个样本赋值给x
 14 y = np.mat('1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0')
 15 # print(y)
 16 y = np.array(y).T  #对应类别标记,竖的一排list
 17 '''
 18 x = np.mat( '1,1,2,2;\
 19         1,2,1,2\
 20         ').T
 21 x = np.array(x)
 22 y=np.mat('0,1,1,0')
 23 y = np.array(y).T
 24 '''
 25 xrow, xcol = x.shape    #x矩阵行列xrow:17,xcol:8
 26 yrow, ycol = y.shape    #17行1列
 27 
 28 np.random.seed(0)   #当我们设置相同的seed,每次生成的随机数相同
 29 print('x: \n', x)
 30 print ('y: \n', y)
 31 
 32 def sigmoid(x):
 33     return 1.0 / (1.0 + np.exp(-x))
 34 
 35 def printParam(v, w, t0, t1):
 36     print ('v:', v)
 37     print ('w: ', w)
 38     print ('t0: ', t0)
 39     print ('t1: ', t1)
 40 
 41 def bpa(x, y, n_hidden_layer, r, error, n_max_train):
 42   #  print(  )
 43     print ('all bp algorithm')
 44     print ('------------------------------------')
 45     print ('init param')
 46     [xrow, xcol] = x.shape
 47     [yrow, ycol] = y.shape
 48     v = np.random.random((xcol, n_hidden_layer))  #初始化隐层和输入层之间的权重v, 8行5列随机数。5个隐层结点,每个结点是8个不同属性权值和5个阈值的Sigmoid函数输出值。
 49 
 50     w = np.random.random((n_hidden_layer, ycol))  #也是8行5列随机数输出层和隐层的权值
 51 
 52     t0 = np.random.random((1, n_hidden_layer)) #1行5列的随机值,隐层的阈值。
 53 
 54     t1 = np.random.random((1, ycol))    #应该是输出层的阈值
 55 
 56 
 57     print ('---------- train begins ----------')
 58     n_train = 0
 59     yo = 0
 60     loss = 0
 61     while 1:
 62         b = sigmoid(x.dot(v) - t0)      # bh=f(αh-t0)
 63         yo = sigmoid(b.dot(w) - t1)
 64         loss = sum((yo - y)**2) / xrow
 65         if loss < error or n_train > n_max_train:
 66             break
 67         n_train += 1
 68         # update param
 69         g = yo * (1 - yo) * (y - yo)
 70 
 71         w += r * b.T.dot(g)     #r 是学习步长
 72         t1 -= r * g.sum(axis = 0)
 73         e = b * (1 - b) * g * w.T
 74         v += r * x.T.dot(e)
 75         t0 -= r * e.sum(axis = 0)
 76         if n_train % 10000 == 0:
 77             print('train count: ', n_train)
 78             print (np.hstack((y, yo)))
 79     print ('---------- train ends ----------')
 80     print ('train count = ', n_train)
 81     yo = yo.tolist()
 82     print ('---------- learned param: ----------')
 83     printParam(v, w, t0, t1)
 84     print ('---------- result: ----------')
 85     print (np.hstack((y, yo)))
 86     print ('loss: ', loss)
 87 
 88 r = 0.1
 89 error = 0.001    #误差阈值
 90 n_max_train = 1000000 #最大训练量100万。
 91 n_hidden_layer = 5  #隐藏层五个结点
 92 
 93 #bpa(x, y, n_hidden_layer, r, error, n_max_train)
 94 
 95 
 96 def bps(x, y, n_hidden_layer, r, error, n_max_train):
 97 
 98     print ('standard bp algorithm')
 99     print ('------------------------------------')
100     print ('init param')
101     [xrow, xcol] = x.shape
102     [yrow, ycol] = y.shape
103     v = np.random.random((xcol, n_hidden_layer))
104     w = np.random.random((n_hidden_layer, ycol))
105     t0 = np.random.random((1, n_hidden_layer))
106     t1 = np.random.random((1, ycol))
107     print ('---------- train begins ----------')
108     n_train = 0
109     tag = 0
110     yo = 0
111     loss = 0
112     while 1:
113         for k in range(len(x)):
114             b = sigmoid(x.dot(v) - t0)
115             yo = sigmoid(b.dot(w) - t1)
116             loss = sum((yo - y)**2) / xrow
117             if loss < error or n_train > n_max_train:
118                 tag = 1
119                 break
120             b = b[k]
121             b = b.reshape(1,b.size)
122             n_train += 1
123             g = yo[k] * (1 - yo[k]) * (y[k] - yo[k])
124             g = g.reshape(1,g.size)
125             w += r * b.T.dot(g)
126             t1 -= r * g
127             e = b * (1 - b) * g * w.T
128             v += r * x[k].reshape(1, x[k].size).T.dot(e)
129             t0 -= r * e
130             if n_train % 10000 == 0:
131                 print ("train count: ", n_train)
132                 print (np.hstack((y, yo)))
133 
134         if tag:
135             break
136 
137     print( '---------- train ends ----------')
138     print ('train count = ', n_train)
139     yo = yo.tolist()
140     print ('---------- learned param: ----------')
141     printParam(v, w, t0, t1)
142     print ('---------- result: ----------')
143     print (np.hstack((y, yo)))
144     print ('loss: ', loss)
145 
146 n  = int(input('标准BP请输入1,累积BP输入2:'))
147 if n == 1:
148     bps(x, y, n_hidden_layer, r, error, n_max_train)
149 elif n == 2:
150     bpa(x, y, n_hidden_layer, r, error, n_max_train)
151 else:
152     print ('命令行参数错误')

 

1.5总结

其实这章我看了2次了,总是没想通公式5.7没求和,但是公式5.15求和了,今天终于开悟了,故用一天时间整理和记录下来,希望可以给和我一样的初学者一点启发。也通过这个记录发现了自己对最小二乘法还有点不理解,以后再看看。

我是知识分享和开源的爱好者,想转载的可以,在评论里面和我说下就好了,如果有闲钱的大佬,欢迎扫描下面二维码转账,谢谢!

1.6参考

1.周志华《机器学习》

2.梯度下降: https://www.cnblogs.com/pinard/p/5970503.html

 

 

 

 

   

posted @ 2018-06-03 15:53  Pancho  阅读(1245)  评论(0编辑  收藏  举报