├── .gitattributes ├── iris.csv ├── bp_iris.py └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | *.md linguist-language=python 2 | -------------------------------------------------------------------------------- /iris.csv: -------------------------------------------------------------------------------- 1 | 5.1,3.5,1.4,0.2,Iris-setosa 2 | 4.9,3.0,1.4,0.2,Iris-setosa 3 | 4.7,3.2,1.3,0.2,Iris-setosa 4 | 4.6,3.1,1.5,0.2,Iris-setosa 5 | 5.0,3.6,1.4,0.2,Iris-setosa 6 | 5.4,3.9,1.7,0.4,Iris-setosa 7 | 4.6,3.4,1.4,0.3,Iris-setosa 8 | 5.0,3.4,1.5,0.2,Iris-setosa 9 | 4.4,2.9,1.4,0.2,Iris-setosa 10 | 4.9,3.1,1.5,0.1,Iris-setosa 11 | 5.4,3.7,1.5,0.2,Iris-setosa 12 | 4.8,3.4,1.6,0.2,Iris-setosa 13 | 4.8,3.0,1.4,0.1,Iris-setosa 14 | 4.3,3.0,1.1,0.1,Iris-setosa 15 | 5.8,4.0,1.2,0.2,Iris-setosa 16 | 5.7,4.4,1.5,0.4,Iris-setosa 17 | 5.4,3.9,1.3,0.4,Iris-setosa 18 | 5.1,3.5,1.4,0.3,Iris-setosa 19 | 5.7,3.8,1.7,0.3,Iris-setosa 20 | 5.1,3.8,1.5,0.3,Iris-setosa 21 | 5.4,3.4,1.7,0.2,Iris-setosa 22 | 5.1,3.7,1.5,0.4,Iris-setosa 23 | 4.6,3.6,1.0,0.2,Iris-setosa 24 | 5.1,3.3,1.7,0.5,Iris-setosa 25 | 4.8,3.4,1.9,0.2,Iris-setosa 26 | 5.0,3.0,1.6,0.2,Iris-setosa 27 | 5.0,3.4,1.6,0.4,Iris-setosa 28 | 5.2,3.5,1.5,0.2,Iris-setosa 29 | 5.2,3.4,1.4,0.2,Iris-setosa 30 | 4.7,3.2,1.6,0.2,Iris-setosa 31 | 4.8,3.1,1.6,0.2,Iris-setosa 32 | 5.4,3.4,1.5,0.4,Iris-setosa 33 | 5.2,4.1,1.5,0.1,Iris-setosa 34 | 5.5,4.2,1.4,0.2,Iris-setosa 35 | 4.9,3.1,1.5,0.1,Iris-setosa 36 | 5.0,3.2,1.2,0.2,Iris-setosa 37 | 5.5,3.5,1.3,0.2,Iris-setosa 38 | 4.9,3.1,1.5,0.1,Iris-setosa 39 | 4.4,3.0,1.3,0.2,Iris-setosa 40 | 5.1,3.4,1.5,0.2,Iris-setosa 41 | 5.0,3.5,1.3,0.3,Iris-setosa 42 | 4.5,2.3,1.3,0.3,Iris-setosa 43 | 4.4,3.2,1.3,0.2,Iris-setosa 44 | 5.0,3.5,1.6,0.6,Iris-setosa 45 | 5.1,3.8,1.9,0.4,Iris-setosa 46 | 4.8,3.0,1.4,0.3,Iris-setosa 47 | 5.1,3.8,1.6,0.2,Iris-setosa 48 | 4.6,3.2,1.4,0.2,Iris-setosa 49 | 5.3,3.7,1.5,0.2,Iris-setosa 50 | 5.0,3.3,1.4,0.2,Iris-setosa 51 | 7.0,3.2,4.7,1.4,Iris-versicolor 52 | 6.4,3.2,4.5,1.5,Iris-versicolor 53 | 6.9,3.1,4.9,1.5,Iris-versicolor 54 | 5.5,2.3,4.0,1.3,Iris-versicolor 55 | 6.5,2.8,4.6,1.5,Iris-versicolor 56 | 5.7,2.8,4.5,1.3,Iris-versicolor 57 | 6.3,3.3,4.7,1.6,Iris-versicolor 58 | 4.9,2.4,3.3,1.0,Iris-versicolor 59 | 6.6,2.9,4.6,1.3,Iris-versicolor 60 | 5.2,2.7,3.9,1.4,Iris-versicolor 61 | 5.0,2.0,3.5,1.0,Iris-versicolor 62 | 5.9,3.0,4.2,1.5,Iris-versicolor 63 | 6.0,2.2,4.0,1.0,Iris-versicolor 64 | 6.1,2.9,4.7,1.4,Iris-versicolor 65 | 5.6,2.9,3.6,1.3,Iris-versicolor 66 | 6.7,3.1,4.4,1.4,Iris-versicolor 67 | 5.6,3.0,4.5,1.5,Iris-versicolor 68 | 5.8,2.7,4.1,1.0,Iris-versicolor 69 | 6.2,2.2,4.5,1.5,Iris-versicolor 70 | 5.6,2.5,3.9,1.1,Iris-versicolor 71 | 5.9,3.2,4.8,1.8,Iris-versicolor 72 | 6.1,2.8,4.0,1.3,Iris-versicolor 73 | 6.3,2.5,4.9,1.5,Iris-versicolor 74 | 6.1,2.8,4.7,1.2,Iris-versicolor 75 | 6.4,2.9,4.3,1.3,Iris-versicolor 76 | 6.6,3.0,4.4,1.4,Iris-versicolor 77 | 6.8,2.8,4.8,1.4,Iris-versicolor 78 | 6.7,3.0,5.0,1.7,Iris-versicolor 79 | 6.0,2.9,4.5,1.5,Iris-versicolor 80 | 5.7,2.6,3.5,1.0,Iris-versicolor 81 | 5.5,2.4,3.8,1.1,Iris-versicolor 82 | 5.5,2.4,3.7,1.0,Iris-versicolor 83 | 5.8,2.7,3.9,1.2,Iris-versicolor 84 | 6.0,2.7,5.1,1.6,Iris-versicolor 85 | 5.4,3.0,4.5,1.5,Iris-versicolor 86 | 6.0,3.4,4.5,1.6,Iris-versicolor 87 | 6.7,3.1,4.7,1.5,Iris-versicolor 88 | 6.3,2.3,4.4,1.3,Iris-versicolor 89 | 5.6,3.0,4.1,1.3,Iris-versicolor 90 | 5.5,2.5,4.0,1.3,Iris-versicolor 91 | 5.5,2.6,4.4,1.2,Iris-versicolor 92 | 6.1,3.0,4.6,1.4,Iris-versicolor 93 | 5.8,2.6,4.0,1.2,Iris-versicolor 94 | 5.0,2.3,3.3,1.0,Iris-versicolor 95 | 5.6,2.7,4.2,1.3,Iris-versicolor 96 | 5.7,3.0,4.2,1.2,Iris-versicolor 97 | 5.7,2.9,4.2,1.3,Iris-versicolor 98 | 6.2,2.9,4.3,1.3,Iris-versicolor 99 | 5.1,2.5,3.0,1.1,Iris-versicolor 100 | 5.7,2.8,4.1,1.3,Iris-versicolor 101 | 6.3,3.3,6.0,2.5,Iris-virginica 102 | 5.8,2.7,5.1,1.9,Iris-virginica 103 | 7.1,3.0,5.9,2.1,Iris-virginica 104 | 6.3,2.9,5.6,1.8,Iris-virginica 105 | 6.5,3.0,5.8,2.2,Iris-virginica 106 | 7.6,3.0,6.6,2.1,Iris-virginica 107 | 4.9,2.5,4.5,1.7,Iris-virginica 108 | 7.3,2.9,6.3,1.8,Iris-virginica 109 | 6.7,2.5,5.8,1.8,Iris-virginica 110 | 7.2,3.6,6.1,2.5,Iris-virginica 111 | 6.5,3.2,5.1,2.0,Iris-virginica 112 | 6.4,2.7,5.3,1.9,Iris-virginica 113 | 6.8,3.0,5.5,2.1,Iris-virginica 114 | 5.7,2.5,5.0,2.0,Iris-virginica 115 | 5.8,2.8,5.1,2.4,Iris-virginica 116 | 6.4,3.2,5.3,2.3,Iris-virginica 117 | 6.5,3.0,5.5,1.8,Iris-virginica 118 | 7.7,3.8,6.7,2.2,Iris-virginica 119 | 7.7,2.6,6.9,2.3,Iris-virginica 120 | 6.0,2.2,5.0,1.5,Iris-virginica 121 | 6.9,3.2,5.7,2.3,Iris-virginica 122 | 5.6,2.8,4.9,2.0,Iris-virginica 123 | 7.7,2.8,6.7,2.0,Iris-virginica 124 | 6.3,2.7,4.9,1.8,Iris-virginica 125 | 6.7,3.3,5.7,2.1,Iris-virginica 126 | 7.2,3.2,6.0,1.8,Iris-virginica 127 | 6.2,2.8,4.8,1.8,Iris-virginica 128 | 6.1,3.0,4.9,1.8,Iris-virginica 129 | 6.4,2.8,5.6,2.1,Iris-virginica 130 | 7.2,3.0,5.8,1.6,Iris-virginica 131 | 7.4,2.8,6.1,1.9,Iris-virginica 132 | 7.9,3.8,6.4,2.0,Iris-virginica 133 | 6.4,2.8,5.6,2.2,Iris-virginica 134 | 6.3,2.8,5.1,1.5,Iris-virginica 135 | 6.1,2.6,5.6,1.4,Iris-virginica 136 | 7.7,3.0,6.1,2.3,Iris-virginica 137 | 6.3,3.4,5.6,2.4,Iris-virginica 138 | 6.4,3.1,5.5,1.8,Iris-virginica 139 | 6.0,3.0,4.8,1.8,Iris-virginica 140 | 6.9,3.1,5.4,2.1,Iris-virginica 141 | 6.7,3.1,5.6,2.4,Iris-virginica 142 | 6.9,3.1,5.1,2.3,Iris-virginica 143 | 5.8,2.7,5.1,1.9,Iris-virginica 144 | 6.8,3.2,5.9,2.3,Iris-virginica 145 | 6.7,3.3,5.7,2.5,Iris-virginica 146 | 6.7,3.0,5.2,2.3,Iris-virginica 147 | 6.3,2.5,5.0,1.9,Iris-virginica 148 | 6.5,3.0,5.2,2.0,Iris-virginica 149 | 6.2,3.4,5.4,2.3,Iris-virginica 150 | 5.9,3.0,5.1,1.8,Iris-virginica -------------------------------------------------------------------------------- /bp_iris.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | @author: lqhou 5 | @file: bp_iris.py 6 | @time: 2019/06/21 7 | """ 8 | 9 | from csv import reader 10 | import numpy as np 11 | from sklearn.preprocessing import MinMaxScaler 12 | import random 13 | import matplotlib.pyplot as plt 14 | import math 15 | 16 | 17 | def load_dataset(dataset_path, n_train_data): 18 | """加载数据集,对数据进行预处理,并划分训练集和验证集 19 | :param dataset_path: 数据集文件路径 20 | :param n_train_data: 训练集的数据量 21 | :return: 划分好的训练集和验证集 22 | """ 23 | dataset = [] 24 | label_dict = {'Iris-setosa': 0, 'Iris-versicolor': 1, 'Iris-virginica': 2} 25 | with open(dataset_path, 'r') as file: 26 | # 读取CSV文件,以逗号为分隔符 27 | csv_reader = reader(file, delimiter=',') 28 | for row in csv_reader: 29 | # 将字符串类型的特征值转换为浮点型 30 | row[0:4] = list(map(float, row[0:4])) 31 | # 将标签替换为整型 32 | row[4] = label_dict[row[4]] 33 | # 将处理好的数据加入数据集中 34 | dataset.append(row) 35 | 36 | # 对数据进行归一化处理 37 | dataset = np.array(dataset) 38 | mms = MinMaxScaler() 39 | for i in range(dataset.shape[1] - 1): 40 | dataset[:, i] = mms.fit_transform(dataset[:, i].reshape(-1, 1)).flatten() 41 | 42 | # 将类标转为整型 43 | dataset = dataset.tolist() 44 | for row in dataset: 45 | row[4] = int(row[4]) 46 | # 打乱数据集 47 | random.shuffle(dataset) 48 | 49 | # 划分训练集和验证集 50 | train_data = dataset[0:n_train_data] 51 | val_data = dataset[n_train_data:] 52 | 53 | return train_data, val_data 54 | 55 | 56 | def fun_z(weights, inputs): 57 | """计算神经元的输入:z = weight * inputs + b 58 | :param weights: 网络参数(权重矩阵和偏置项) 59 | :param inputs: 上一层神经元的输出 60 | :return: 当前层神经元的输入 61 | """ 62 | bias_term = weights[-1] 63 | z = 0 64 | for i in range(len(weights)-1): 65 | z += weights[i] * inputs[i] 66 | z += bias_term 67 | return z 68 | 69 | 70 | def sigmoid(z): 71 | """激活函数(Sigmoid):f(z) = Sigmoid(z) 72 | :param z: 神经元的输入 73 | :return: 神经元的输出 74 | """ 75 | return 1.0 / (1.0 + math.exp(-z)) 76 | 77 | 78 | def sigmoid_derivative(output): 79 | """Sigmoid激活函数求导 80 | :param output: 激活函数的输出值 81 | :return: 求导计算结果 82 | """ 83 | return output * (1.0 - output) 84 | 85 | 86 | def forward_propagate(network, inputs): 87 | """前向传播计算 88 | :param network: 神经网络 89 | :param inputs: 一个样本数据 90 | :return: 前向传播计算的结果 91 | """ 92 | for layer in network: # 循环计算每一层 93 | new_inputs = [] 94 | for neuron in layer: # 循环计算每一层的每一个神经元 95 | z = fun_z(neuron['weights'], inputs) 96 | neuron['output'] = sigmoid(z) 97 | new_inputs.append(neuron['output']) 98 | inputs = new_inputs 99 | return inputs 100 | 101 | 102 | def backward_propagate_error(network, actual_label): 103 | """误差进行反向传播 104 | :param network: 神经网络 105 | :param actual_label: 真实的标签值 106 | :return: 107 | """ 108 | for i in reversed(range(len(network))): # 从最后一层开始计算误差 109 | layer = network[i] 110 | errors = list() 111 | if i != len(network)-1: # 不是输出层 112 | for j in range(len(layer)): # 计算每一个神经元的误差 113 | error = 0.0 114 | for neuron in network[i + 1]: 115 | error += (neuron['weights'][j] * neuron['delta']) 116 | errors.append(error) 117 | else: # 输出层 118 | for j in range(len(layer)): # 计算每一个神经元的误差 119 | neuron = layer[j] 120 | errors.append(actual_label[j] - neuron['output']) 121 | # 计算误差项 delta 122 | for j in range(len(layer)): 123 | neuron = layer[j] 124 | neuron['delta'] = errors[j] * sigmoid_derivative(neuron['output']) 125 | 126 | 127 | def update_parameters(network, row, l_rate): 128 | """利用误差更新神经网络的参数(权重矩阵和偏置项) 129 | :param network: 神经网络 130 | :param row: 一个样本数据 131 | :param l_rate: 学习率 132 | :return: 133 | """ 134 | for i in range(len(network)): 135 | inputs = row[:-1] 136 | if i != 0: # 获取上一层网络的输出 137 | inputs = [neuron['output'] for neuron in network[i - 1]] 138 | for neuron in network[i]: 139 | # 更新权重矩阵 140 | for j in range(len(inputs)): 141 | neuron['weights'][j] += l_rate * neuron['delta'] * inputs[j] 142 | # 更新偏置项 143 | neuron['weights'][-1] += l_rate * neuron['delta'] 144 | 145 | 146 | def initialize_network(n_inputs, n_hidden, n_outputs): 147 | """初始化BP网络(初始化隐藏层和输出层的参数:权重矩阵和偏置项) 148 | :param n_inputs: 特征列数 149 | :param n_hidden: 隐藏层神经元个数 150 | :param n_outputs: 输出层神经元个数,即分类的总类别数 151 | :return: 初始化后的神经网络 152 | """ 153 | network = list() 154 | # 隐藏层 155 | hidden_layer = [{'weights': [random.random() for i in range(n_inputs + 1)]} for i in range(n_hidden)] 156 | network.append(hidden_layer) 157 | # 输出层 158 | output_layer = [{'weights': [random.random() for i in range(n_hidden + 1)]} for i in range(n_outputs)] 159 | network.append(output_layer) 160 | return network 161 | 162 | 163 | def train(train_data, l_rate, epochs, n_hidden, val_data): 164 | """训练神经网络(迭代n_epoch个回合) 165 | :param train_data: 训练集 166 | :param l_rate: 学习率 167 | :param epochs: 迭代的回合数 168 | :param n_hidden: 隐藏层神经元个数 169 | :param val_data: 验证集 170 | :return: 训练好的网络 171 | """ 172 | # 获取特征列数 173 | n_inputs = len(train_data[0]) - 1 174 | # 获取分类的总类别数 175 | n_outputs = len(set([row[-1] for row in train_data])) 176 | # 初始化网络 177 | network = initialize_network(n_inputs, n_hidden, n_outputs) 178 | 179 | acc = [] 180 | for epoch in range(epochs): # 训练epochs个回合 181 | for row in train_data: 182 | # 前馈计算 183 | _ = forward_propagate(network, row) 184 | # 处理一下类标,用于计算误差 185 | actual_label = [0 for i in range(n_outputs)] 186 | actual_label[row[-1]] = 1 187 | # 误差反向传播计算 188 | backward_propagate_error(network, actual_label) 189 | # 更新参数 190 | update_parameters(network, row, l_rate) 191 | # 保存当前epoch模型在验证集上的准确率 192 | acc.append(validation(network, val_data)) 193 | # 绘制出训练过程中模型在验证集上的准确率变化 194 | plt.xlabel('epochs') 195 | plt.ylabel('accuracy') 196 | plt.plot(acc) 197 | plt.show() 198 | 199 | return network 200 | 201 | 202 | def validation(network, val_data): 203 | """测试模型在验证集上的效果 204 | :param network: 神经网络 205 | :param val_data: 验证集 206 | :return: 模型在验证集上的准确率 207 | """ 208 | # 获取预测类标 209 | predicted_label = [] 210 | for row in val_data: 211 | prediction = predict(network, row) 212 | predicted_label.append(prediction) 213 | # 获取真实类标 214 | actual_label = [row[-1] for row in val_data] 215 | # 计算准确率 216 | accuracy = accuracy_calculation(actual_label, predicted_label) 217 | # print("测试集实际类标:", actual_label) 218 | # print("测试集上的预测类标:", predicted_label) 219 | return accuracy 220 | 221 | 222 | def accuracy_calculation(actual_label, predicted_label): 223 | """计算准确率 224 | :param actual_label: 真实类标 225 | :param predicted_label: 模型预测的类标 226 | :return: 准确率(百分制) 227 | """ 228 | correct_count = 0 229 | for i in range(len(actual_label)): 230 | if actual_label[i] == predicted_label[i]: 231 | correct_count += 1 232 | return correct_count / float(len(actual_label)) * 100.0 233 | 234 | 235 | def predict(network, row): 236 | """使用模型对当前输入的数据进行预测 237 | :param network: 神经网络 238 | :param row: 一个数据样本 239 | :return: 预测结果 240 | """ 241 | outputs = forward_propagate(network, row) 242 | return outputs.index(max(outputs)) 243 | 244 | 245 | if __name__ == "__main__": 246 | file_path = './iris.csv' 247 | 248 | # 参数设置 249 | l_rate = 0.2 # 学习率 250 | epochs = 300 # 迭代训练的次数 251 | n_hidden = 5 # 隐藏层神经元个数 252 | n_train_data = 130 # 训练集的大小(总共150条数据,训练集130条,验证集20条) 253 | 254 | # 加载数据并划分训练集和验证集 255 | train_data, val_data = load_dataset(file_path, n_train_data) 256 | # 训练模型 257 | network = train(train_data, l_rate, epochs, n_hidden, val_data) 258 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 上篇 2 | 3 |
反向传播算法(Backpropagation Algorithm,简称BP算法)是深度学习的重要思想基础,对于初学者来说也是必须要掌握的基础知识!本文希望以一个清晰的脉络和详细的说明,来让读者彻底明白BP算法的原理和计算过程。
4 |全文分为上下两篇,上篇主要介绍BP算法的原理(即公式的推导),介绍完原理之后,我们会将一些具体的数据带入一个简单的三层神经网络中,去完整的体验一遍BP算法的计算过程;下篇是一个项目实战,我们将带着读者一起亲手实现一个BP神经网络(不使用任何第三方的深度学习框架)来解决一个具体的问题。
5 |图 1 所示是一个简单的三层(两个隐藏层,一个输出层)神经网络结构,假设我们使用这个神经网络来解决二分类问题,我们给这个网络一个输入样本,通过前向运算得到输出
。输出值
的值域为
,例如
的值越接近0,代表该样本是"0"类的可能性越大,反之是"1"类的可能性大。
为了便于理解后续的内容,我们需要先搞清楚前向传播的计算过程,以图1所示的内容为例:
10 |输入的样本为:
11 |第一层网络的参数为:
13 |第二层网络的参数为:
15 |第三层网络的参数为:
17 |第一层隐藏层有三个神经元:、
和
。该层的输入为:
以神经元为例,则其输入为:
同理有:
25 |假设我们选择函数作为该层的激活函数(图1中的激活函数都标了一个下标,一般情况下,同一层的激活函数都是一样的,不同层可以选择不同的激活函数),那么该层的输出为:
、
和
。
第二层隐藏层有两个神经元:和
。该层的输入为:
即第二层的输入是第一层的输出乘以第二层的权重,再加上第二层的偏置。因此得到和的输入分别为:
32 |该层的输出分别为:和
。
输出层只有一个神经元:。该层的输入为:
即:
39 |因为该网络要解决的是一个二分类问题,所以输出层的激活函数也可以使用一个Sigmoid型函数,神经网络最后的输出为:。
在1.1节里,我们已经了解了数据沿着神经网络前向传播的过程,这一节我们来介绍更重要的反向传播的计算过程。假设我们使用随机梯度下降的方式来学习神经网络的参数,损失函数定义为,其中
是该样本的真实类标。使用梯度下降进行参数的学习,我们必须计算出损失函数关于神经网络中各层参数(权重
和偏置
)的偏导数。
假设我们要对第层隐藏层的参数
和
求偏导数,即求
和
。假设
代表第
层神经元的输入,即
,其中
为前一层神经元的输出,则根据链式法则有:
因此,我们只需要计算偏导数、
和
。
前面说过,第k层神经元的输入为:,因此可以得到:
上式中,代表第
层神经元的权重矩阵
的第
行,
代表第
层神经元的权重矩阵
的第
行中的第
列。
我们以1.1节中的简单神经网络为例,假设我们要计算第一层隐藏层的神经元关于权重矩阵的导数,则有:
53 |因为偏置b是一个常数项,因此偏导数的计算也很简单:
56 |依然以第一层隐藏层的神经元为例,则有:
58 |偏导数又称为误差项(error term,也称为“灵敏度”),一般用
表示,例如
是第一层神经元的误差项,其值的大小代表了第一层神经元对于最终总误差的影响大小。
根据第一节的前向计算,我们知道第层的输入与第
层的输出之间的关系为:
又因为,根据链式法则,我们可以得到
为:
由上式我们可以看到,第层神经元的误差项
是由第
层的误差项乘以第
层的权重,再乘以第
层激活函数的导数(梯度)得到的。这就是误差的反向传播。
现在我们已经计算出了偏导数 、
和
,则
和
可分别表示为:
下面是基于随机梯度下降更新参数的反向传播算法:
69 |单纯的公式推导看起来有些枯燥,下面我们将实际的数据带入图1所示的神经网络中,完整的计算一遍。
71 |我们依然使用如图5所示的简单的神经网络,其中所有参数的初始值如下:
74 |输入的样本为(假设其真实类标为"1"):
75 |第一层网络的参数为:
77 |第二层网络的参数为:
79 |第三层网络的参数为:
81 |假设所有的激活函数均为Logistic函数:。使用均方误差函数作为损失函数:
为了方便求导,我们将损失函数简化为:
85 |我们首先初始化神经网络的参数,计算第一层神经元:
88 |上图中我们计算出了第一层隐藏层的第一个神经元的输入和输出
,同理可以计算第二个和第三个神经元的输入和输出:
接下来是第二层隐藏层的计算,首先我们计算第二层的第一个神经元的输入z₄和输出f₄(z₄):
98 |同样方法可以计算该层的第二个神经元的输入和输出
:
最后计算输出层的输入和输出
:
首先计算输出层的误差项,我们的误差函数为
,由于该样本的类标为“1”,而预测值为
,因此误差为
,输出层的误差项为:
接着计算第二层隐藏层的误差项,根据误差项的计算公式有:
114 |最后是计算第一层隐藏层的误差项:
116 |上一小节中我们已经计算出了每一层的误差项,现在我们要利用每一层的误差项和梯度来更新每一层的参数,权重W和偏置b的更新公式如下:
119 |通常权重的更新会加上一个正则化项来避免过拟合,这里为了简化计算,我们省去了正则化项。上式中
的是学习率,我们设其值为0.1。参数更新的计算相对简单,每一层的计算方式都相同,因此本文仅演示第一层隐藏层的参数更新:
至此,我们已经完整介绍了BP算法的原理,并使用具体的数值做了计算。在下篇中,我们将带着读者一起亲手实现一个BP神经网络(不使用任何第三方的深度学习框架)。