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

前言(作者的唠叨)

接触周志华《机器学习》这本书不久,书中内容对我来说都是新知识,学习新的事物过程是曲折的,但我希望自己能不断的学习,不断的进步。这是我第二篇文章,我希望自己的简书能做到“准确、生动、简洁、易懂”的水平,做到对自己、对读者负责,作为一名小白,希望大家对我多指正,也希望能和大家多交流!

神经网络概述

神经网络:是由具有适应性的简单单元组成的广泛并行互连的网络,它能够模拟生物神经系统对真实世界物体所作出的交互反应。

可简单理解为:最基本成分是简单单元(输入单元,功能单元),特性是适应性和并行互连,功能是模拟生物神经反应。


5.1 神经元模型

M-P神经元模型:神经元接收到来自n个其他神经元传递过来的输入信号,这些输入信号通过带权重的连接(连接权)进行传递,神经元接收到的总输入值与神经元的阈值进行比较,然后通过“激活函数”(activation functions)处理以产生神经元的输出。把许多个这样的神经元按一定的层次结构连接起来,就得到了神经网络。

M-P 神经元模型

激活函数:在神经元中,输入信号经过加权,求和后被应用于一个函数,这个函数就是激活函数。

下面介绍两种典型的神经元激活函数:阶跃函数 和 Sigmoid函数

图 5.2 典型的神经元激活函数

理想中的激活函数是图 5.2(a)所示的阶跃函数,它将输入值映射为输出值 “0” 或 “1” ,“1” 对应于神经元兴奋,“0”对应于神经元抑制,然而。阶跃函数具有不连续、不光滑等不太好的性质,因此实际常用Sigmoid函数作为激活函数。典型的Sigmoid函数如图 5.2(b)所示,它把可能在较大范围内变化的输入值挤压到(0,1)输出值范围内,因此有时也称为“挤压函数”。


5.2 感知机与多层网络

5.2.1 感知机

感知机(Perceptron)由两层神经元组成(输入层、输出层),输入层接收外界输入信号后传递给输出层,输出层是M-P神经元,亦称“阈值逻辑单元”(threshold logic unit)。
感知机能容易的实现逻辑与、或、非运算。实现的原理是改变连接权重和偏置




周志华 《机器学习》
感知机只有输出层神经元进行激活函数处理,即只拥有一层功能神经元,其学习能力有限,不能解决非线性可分问题(如图 5.5 异或问题)。此时需考虑使用多层功能神经元,简单的两层感知机就能解决异或问题。在输入层和输出层之间,存在隐含层(如图5.5所示),隐含层和输出层神经元都是拥有激活函数的功能神经元

5.2.2 多层前馈神经网络

多层前馈神经网络:每层神经元与下层神经元全互连,神经元之间不存在同层连接,也不存在跨层连接,这样的神经网络称为“多层前馈神经网络”。(Tip. "前馈"并不意味着网络中信号不能向后传,而是指网络拓扑结构不存在环或回路。下面介绍的前向传播和反向传播的概念可以让你更好理解“前馈”的意思。)

多层前馈神经网络的运行机理:输入层神经元接受外界输入,隐层与输出层神经元对信号进行加工,最终结果有输出层神经元输出。换言之,输入层神经元仅是接受输入,不进行函数处理,隐层与输出层包含功能神经元。


5.3 误差逆传播算法(BP算法)

误差逆传播算法(BP算法):亦称“反向传播算法”,是一种与最优化方法(如梯度下降法)结合使用的,用来训练人工神经网络的常见方法。 该方法对网络中所有权重计算损失函数的梯度。 这个梯度会反馈给最优化方法,用来更新权值以最小化损失函数。(误差的反向传播)

花了两天时间去理解BP算法的推导,个人觉得对于推导勉勉强强能理解,但是总结复述起来,堆一堆公式也没什么意思,为了给大家讲清楚BP算法的推导,上网搜了许多资料,最后决定用图解来讲述推导过程,相信大家看完之后整个人都会好起来的!

BP算法讲解(重点来了,看懂它!看懂它!)

下面拿一个简单的三层神经网络来举例,如下:


每个神经元由两部分组成,第一部分(e)是输入值和权重系数乘积的和,第二部分(f(e))是一个激活函数(非线性函数)的输出, y=f(e)即为某个神经元的输出,如下:

下面是前向传播过程:



到这里为止,神经网络的前向传播已经完成,最后输出的y就是本次前向传播神经网络计算出来的结果(预测结果),但这个预测结果不一定是正确的,要和真实的标签(z)相比较,计算预测结果和真实标签的误差(δ \deltaδ),如下:

下面开始计算每个神经元的误差(δ \deltaδ):


下面开始利用反向传播的误差,计算各个神经元的导数,开始反向传播修改权重



到此为止,整个网络的前向,反向传播和权重更新已经完成


误差E有了,怎么调整权重让误差不断减小?
E是权重w的函数,何如找到使得函数值最小的w。
解决上面问题的方法是梯度下降算法(简单图示如下),大家如有不太懂的可先行查阅别的资料,只要能达到理解线性回归梯度下降算法的水平即可,这里不再赘述。有需要的童鞋可以去B站找吴恩达机器学习视频,线性回归章节里有详细介绍梯度下降算法。


误差逆传播算法(BP算法)参考代码(Python):

  1 #coding:utf-8
  2 import random
  3 import math
  4 
  5 #
  6 #   参数解释:
  7 #   "pd_" :偏导的前缀
  8 #   "d_" :导数的前缀
  9 #   "w_ho" :隐含层到输出层的权重系数索引
 10 #   "w_ih" :输入层到隐含层的权重系数的索引
 11 
 12 class NeuralNetwork:
 13     LEARNING_RATE = 0.5
 14 
 15     def __init__(self, num_inputs, num_hidden, num_outputs, hidden_layer_weights = None, hidden_layer_bias = None, output_layer_weights = None, output_layer_bias = None):
 16         self.num_inputs = num_inputs
 17 
 18         self.hidden_layer = NeuronLayer(num_hidden, hidden_layer_bias)
 19         self.output_layer = NeuronLayer(num_outputs, output_layer_bias)
 20 
 21         self.init_weights_from_inputs_to_hidden_layer_neurons(hidden_layer_weights)
 22         self.init_weights_from_hidden_layer_neurons_to_output_layer_neurons(output_layer_weights)
 23 
 24     def init_weights_from_inputs_to_hidden_layer_neurons(self, hidden_layer_weights):
 25         weight_num = 0
 26         for h in range(len(self.hidden_layer.neurons)):
 27             for i in range(self.num_inputs):
 28                 if not hidden_layer_weights:
 29                     self.hidden_layer.neurons[h].weights.append(random.random())
 30                 else:
 31                     self.hidden_layer.neurons[h].weights.append(hidden_layer_weights[weight_num])
 32                 weight_num += 1
 33 
 34     def init_weights_from_hidden_layer_neurons_to_output_layer_neurons(self, output_layer_weights):
 35         weight_num = 0
 36         for o in range(len(self.output_layer.neurons)):
 37             for h in range(len(self.hidden_layer.neurons)):
 38                 if not output_layer_weights:
 39                     self.output_layer.neurons[o].weights.append(random.random())
 40                 else:
 41                     self.output_layer.neurons[o].weights.append(output_layer_weights[weight_num])
 42                 weight_num += 1
 43 
 44     def inspect(self):
 45         print('------')
 46         print('* Inputs: {}'.format(self.num_inputs))
 47         print('------')
 48         print('Hidden Layer')
 49         self.hidden_layer.inspect()
 50         print('------')
 51         print('* Output Layer')
 52         self.output_layer.inspect()
 53         print('------')
 54 
 55     def feed_forward(self, inputs):
 56         hidden_layer_outputs = self.hidden_layer.feed_forward(inputs)
 57         return self.output_layer.feed_forward(hidden_layer_outputs)
 58 
 59     def train(self, training_inputs, training_outputs):
 60         self.feed_forward(training_inputs)
 61 
 62         # 1. 输出神经元的值
 63         pd_errors_wrt_output_neuron_total_net_input = [0] * len(self.output_layer.neurons)
 64         for o in range(len(self.output_layer.neurons)):
 65 
 66             # ∂E/∂zⱼ
 67             pd_errors_wrt_output_neuron_total_net_input[o] = self.output_layer.neurons[o].calculate_pd_error_wrt_total_net_input(training_outputs[o])
 68 
 69         # 2. 隐含层神经元的值
 70         pd_errors_wrt_hidden_neuron_total_net_input = [0] * len(self.hidden_layer.neurons)
 71         for h in range(len(self.hidden_layer.neurons)):
 72 
 73             # dE/dyⱼ = Σ ∂E/∂zⱼ * ∂z/∂yⱼ = Σ ∂E/∂zⱼ * wᵢⱼ
 74             d_error_wrt_hidden_neuron_output = 0
 75             for o in range(len(self.output_layer.neurons)):
 76                 d_error_wrt_hidden_neuron_output += pd_errors_wrt_output_neuron_total_net_input[o] * self.output_layer.neurons[o].weights[h]
 77 
 78             # ∂E/∂zⱼ = dE/dyⱼ * ∂zⱼ/∂
 79             pd_errors_wrt_hidden_neuron_total_net_input[h] = d_error_wrt_hidden_neuron_output * self.hidden_layer.neurons[h].calculate_pd_total_net_input_wrt_input()
 80 
 81         # 3. 更新输出层权重系数
 82         for o in range(len(self.output_layer.neurons)):
 83             for w_ho in range(len(self.output_layer.neurons[o].weights)):
 84 
 85                 # ∂Eⱼ/∂wᵢⱼ = ∂E/∂zⱼ * ∂zⱼ/∂wᵢⱼ
 86                 pd_error_wrt_weight = pd_errors_wrt_output_neuron_total_net_input[o] * self.output_layer.neurons[o].calculate_pd_total_net_input_wrt_weight(w_ho)
 87 
 88                 # Δw = α * ∂Eⱼ/∂wᵢ
 89                 self.output_layer.neurons[o].weights[w_ho] -= self.LEARNING_RATE * pd_error_wrt_weight
 90 
 91         # 4. 更新隐含层的权重系数
 92         for h in range(len(self.hidden_layer.neurons)):
 93             for w_ih in range(len(self.hidden_layer.neurons[h].weights)):
 94 
 95                 # ∂Eⱼ/∂wᵢ = ∂E/∂zⱼ * ∂zⱼ/∂wᵢ
 96                 pd_error_wrt_weight = pd_errors_wrt_hidden_neuron_total_net_input[h] * self.hidden_layer.neurons[h].calculate_pd_total_net_input_wrt_weight(w_ih)
 97 
 98                 # Δw = α * ∂Eⱼ/∂wᵢ
 99                 self.hidden_layer.neurons[h].weights[w_ih] -= self.LEARNING_RATE * pd_error_wrt_weight
100 
101     def calculate_total_error(self, training_sets):
102         total_error = 0
103         for t in range(len(training_sets)):
104             training_inputs, training_outputs = training_sets[t]
105             self.feed_forward(training_inputs)
106             for o in range(len(training_outputs)):
107                 total_error += self.output_layer.neurons[o].calculate_error(training_outputs[o])
108         return total_error
109 
110 class NeuronLayer:
111     def __init__(self, num_neurons, bias):
112 
113         # 同一层的神经元共享一个截距项b
114         self.bias = bias if bias else random.random()
115 
116         self.neurons = []
117         for i in range(num_neurons):
118             self.neurons.append(Neuron(self.bias))
119 
120     def inspect(self):
121         print('Neurons:', len(self.neurons))
122         for n in range(len(self.neurons)):
123             print(' Neuron', n)
124             for w in range(len(self.neurons[n].weights)):
125                 print('  Weight:', self.neurons[n].weights[w])
126             print('  Bias:', self.bias)
127 
128     def feed_forward(self, inputs):
129         outputs = []
130         for neuron in self.neurons:
131             outputs.append(neuron.calculate_output(inputs))
132         return outputs
133 
134     def get_outputs(self):
135         outputs = []
136         for neuron in self.neurons:
137             outputs.append(neuron.output)
138         return outputs
139 
140 class Neuron:
141     def __init__(self, bias):
142         self.bias = bias
143         self.weights = []
144 
145     def calculate_output(self, inputs):
146         self.inputs = inputs
147         self.output = self.squash(self.calculate_total_net_input())
148         return self.output
149 
150     def calculate_total_net_input(self):
151         total = 0
152         for i in range(len(self.inputs)):
153             total += self.inputs[i] * self.weights[i]
154         return total + self.bias
155 
156     # 激活函数sigmoid
157     def squash(self, total_net_input):
158         return 1 / (1 + math.exp(-total_net_input))
159 
160 
161     def calculate_pd_error_wrt_total_net_input(self, target_output):
162         return self.calculate_pd_error_wrt_output(target_output) * self.calculate_pd_total_net_input_wrt_input();
163 
164     # 每一个神经元的误差是由平方差公式计算的
165     def calculate_error(self, target_output):
166         return 0.5 * (target_output - self.output) ** 2
167 
168     
169     def calculate_pd_error_wrt_output(self, target_output):
170         return -(target_output - self.output)
171 
172     
173     def calculate_pd_total_net_input_wrt_input(self):
174         return self.output * (1 - self.output)
175 
176 
177     def calculate_pd_total_net_input_wrt_weight(self, index):
178         return self.inputs[index]
179 
180 
181 # 文中的例子:
182 
183 nn = NeuralNetwork(2, 2, 2, hidden_layer_weights=[0.15, 0.2, 0.25, 0.3], hidden_layer_bias=0.35, output_layer_weights=[0.4, 0.45, 0.5, 0.55], output_layer_bias=0.6)
184 for i in range(10000):
185     nn.train([0.05, 0.1], [0.01, 0.09])
186     print(i, round(nn.calculate_total_error([[[0.05, 0.1], [0.01, 0.09]]]), 9))
187 
188 
189 #另外一个例子,可以把上面的例子注释掉再运行一下:
190 
191 # training_sets = [
192 #     [[0, 0], [0]],
193 #     [[0, 1], [1]],
194 #     [[1, 0], [1]],
195 #     [[1, 1], [0]]
196 # ]
197 
198 # nn = NeuralNetwork(len(training_sets[0][0]), 5, len(training_sets[0][1]))
199 # for i in range(10000):
200 #     training_inputs, training_outputs = random.choice(training_sets)
201 #     nn.train(training_inputs, training_outputs)
202 #     print(i, nn.calculate_total_error(training_sets))

以上部分参考资料:http://galaxy.agh.edu.pl/~vlsi/AI/backp_t_en/backprop.html
https://www.cnblogs.com/charlotte77/p/5629865.html

5.4 全局最小与局部极小

若用E表示神经网络在训练集上的误差,则它显然是关于连接权w和阈值θ的函数。此时,神经网络的训练集过程可看作一个参数寻优过程,即在参数空间中,寻找一组最有参数使得E最小。

有两个“最优”,分别是“局部最优”和“全局最优”,这可用微积分中的柯西极限存在原理来理解。请看以下分析:


如何跳出跳出局部极小值

多个出发点
以多组不同参数值初始化多个神经网络,按照标准方法训练之后,取其中误差最小的解作为最终参数,这相当于从不同的初始点开始搜索,这样就有可能陷入不同的局部最小值,从中选择有可能获得更接近全局最小的结果。

模拟退火
模拟退火,每一步都以一定的概率接受比当前更差的结果,从而有助于“跳出”局部最小,在每一步的迭代中,接受“次优解”的概率要随着时间推移而降低,从而保证算法稳定。

随机梯度下降
随机梯度下降法与标准的梯度下降法不同,随机梯度下降法在计算梯度时加入了随机因素,于是即使陷入局部极小值点,它计算出的结果仍可能不为00,这样就可能有机会跳出局部极小值点继续搜索。

代码

(参考资料://www.greatytc.com/p/5ce112881c95 《机器学习》第5章 神经网络——[andyham])
数据来自西瓜书P84

import numpy as np
dataset = pd.read_csv('watermelon_3.csv', delimiter=",")

数据预处理

# 处理数据集
attributeMap = {}
attributeMap['浅白'] = 0
attributeMap['青绿'] = 0.5
attributeMap['乌黑'] = 1
attributeMap['蜷缩'] = 0
attributeMap['稍蜷'] = 0.5
attributeMap['硬挺'] = 1
attributeMap['沉闷'] = 0
attributeMap['浊响'] = 0.5
attributeMap['清脆'] = 1
attributeMap['模糊'] = 0
attributeMap['稍糊'] = 0.5
attributeMap['清晰'] = 1
attributeMap['凹陷'] = 0
attributeMap['稍凹'] = 0.5
attributeMap['平坦'] = 1
attributeMap['硬滑'] = 0
attributeMap['软粘'] = 1
attributeMap['否'] = 0
attributeMap['是'] = 1
del dataset['编号']
dataset = np.array(dataset)
m, n = np.shape(dataset)
for i in range(m):
    for j in range(n):
        if dataset[i, j] in attributeMap:
            dataset[i, j] = attributeMap[dataset[i, j]]
        dataset[i, j] = round(dataset[i, j], 3)

trueY = dataset[:, n-1]
X = dataset[:, :n-1]
m, n = np.shape(X)

初始化参数

# P101,初始化参数
import random
d = n   # 输入向量的维数
l = 1   # 输出向量的维数
q = d+1   # 隐层节点的数量
theta = [random.random() for i in range(l)]   # 输出神经元的阈值
gamma = [random.random() for i in range(q)]   # 隐层神经元的阈值
# v size= d*q .输入和隐层神经元之间的连接权重
v = [[random.random() for i in range(q)] for j in range(d)]
# w size= q*l .隐藏和输出神经元之间的连接权重
w = [[random.random() for i in range(l)] for j in range(q)]
eta = 0.2    # 学习率,控制每一轮迭代的步长
maxIter = 5000  # 最大训练次数

sigmoid函数

import math
def sigmoid(iX,dimension):  # iX is a matrix with a dimension
    if dimension == 1:
        for i in range(len(iX)):
            iX[i] = 1 / (1 + math.exp(-iX[i]))
    else:
        for i in range(len(iX)):
            iX[i] = sigmoid(iX[i], dimension-1)
    return iX

标准的误差逆传播

# 标准BP
while(maxIter > 0):
    maxIter -= 1
    sumE = 0
    for i in range(m):
        alpha = np.dot(X[i], v)  # p101 line 2 from bottom, shape=1*q
        b = sigmoid(alpha-gamma, 1)  # b=f(alpha-gamma), shape=1*q
        beta = np.dot(b, w)  # shape=(1*q)*(q*l)=1*l
        predictY = sigmoid(beta-theta, 1)   # shape=1*l ,p102--5.3
        E = sum((predictY-trueY[i])*(predictY-trueY[i]))/2    # 5.4
        sumE += E  # 5.16
        # p104
        g = predictY*(1-predictY)*(trueY[i]-predictY)  # shape=1*l p103--5.10
        e = b*(1-b)*((np.dot(w, g.T)).T)  # shape=1*q , p104--5.15
        w += eta*np.dot(b.reshape((q, 1)), g.reshape((1, l)))  # 5.11
        theta -= eta*g  # 5.12
        v += eta*np.dot(X[i].reshape((d, 1)), e.reshape((1, q)))  # 5.13
        gamma -= eta*e  # 5.14
    # print(sumE)

累积的误差逆传播

# #累积 BP
# trueY=trueY.reshape((m,l))
# while(maxIter>0):
#     maxIter-=1
#     sumE=0
#     alpha = np.dot(X, v)#p101 line 2 from bottom, shape=m*q
#     b = sigmoid(alpha - gamma,2)  # b=f(alpha-gamma), shape=m*q
#     beta = np.dot(b, w)  # shape=(m*q)*(q*l)=m*l
#     predictY = sigmoid(beta - theta,2)  # shape=m*l ,p102--5.3
#
#     E = sum(sum((predictY - trueY) * (predictY - trueY))) / 2  # 5.4
#     # print(round(E,5))
#     g = predictY * (1 - predictY) * (trueY - predictY)  # shape=m*l p103--5.10
#     e = b * (1 - b) * ((np.dot(w, g.T)).T)  # shape=m*q , p104--5.15
#     w += eta * np.dot(b.T, g)  # 5.11 shape (q*l)=(q*m) * (m*l)
#     theta -= eta * g  # 5.12
#     v += eta * np.dot(X.T, e)  # 5.13 (d,q)=(d,m)*(m,q)
#     gamma -= eta * e  # 5.14

预测

def predict(iX):
    alpha = np.dot(iX, v)  # p101 line 2 from bottom, shape=m*q
    b = sigmoid(alpha-gamma, 2)  # b=f(alpha-gamma), shape=m*q
    beta = np.dot(b, w)  # shape=(m*q)*(q*l)=m*l
    predictY = sigmoid(beta - theta, 2)  # shape=m*l ,p102--5.3
    return predictY

print(predict(X))

输出结果
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,376评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,126评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,966评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,432评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,519评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,792评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,933评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,701评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,143评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,488评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,626评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,292评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,896评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,742评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,324评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,494评论 2 348

推荐阅读更多精彩内容