反向传播算法用于经典的前馈人工神经网络。

它仍然是训练大型深度学习网络的技术。

在这个教程中,你将学习如何用Python从头开始实现神经网络的反向传播算法。

完成本教程后,您将了解:

  • 如何将输入前向传播以计算输出。
  • 如何反向传播错误和训练网络。
  • 如何将反向传播算法应用于实际的预测建模问题。

描述

本节将简要介绍反向传播算法和我们将在本教程中使用的 wheat seeds 数据集。

反向传播算法

反向传播算法是人工神经网络领域多层前馈网络的监督学习方法。

前馈神经网络受到一个或多个神经细胞(称为神经元)的信息处理的启发。神经元通过其树突接受输入信号,树突将电信号传递到细胞体。轴突将信号传递到突触,突触是神经元的轴突与其他细胞的树突之间的连接。

反向传播方法的原理是通过修改输入信号的内部权重来模拟给定函数,以产生预期的输出信号。该系统使用监督学习方法进行训练,其中系统输出与已知预期输出之间的误差被呈现给系统,并用于修改其内部状态。

从技术上讲,反向传播算法是一种用于训练多层前馈神经网络中权重的方法。因此,它需要定义一个由一个或多个层组成的网络结构,其中一层完全连接到下一层。标准的网络结构是一个输入层、一个隐藏层和一个输出层。

反向传播可以用于分类和回归问题,但在本教程中我们将重点讨论分类问题。

在分类问题中,当网络的输出层有一个神经元对应每个类值时,可以取得最佳结果。例如,一个具有类值A和B的二类或二进制分类问题。这些预期输出需要转换为二进制向量,每列对应一个类值。比如A对应[1, 0],B对应[0, 1]。这称为独热编码(one hot encoding)。

小麦种子数据集

种子数据集涉及根据不同小麦品种的种子测量值预测物种。

有201条记录和7个数值输入变量。这是一个具有3个输出类别的分类问题。每个数值输入值的尺度不同,因此在使用像反向传播算法这样的对输入加权的算法时,可能需要对一些数据进行标准化。

以下是数据集的前5行样本。

15.26,14.84,0.871,5.763,3.312,2.221,5.22,1
14.88,14.57,0.8811,5.554,3.333,1.018,4.956,1
14.29,14.09,0.905,5.291,3.337,2.699,4.825,1
13.84,13.94,0.8955,5.324,3.379,2.259,4.805,1
16.14,14.99,0.9034,5.658,3.562,1.355,5.175,1

使用预测最常见的类值的零规则算法,该问题的基准准确率是28.095%。

您可以从 UCI 机器学习数据仓库了解更多并下载种子数据集。

下载种子数据集,并将其放入您当前的工作目录中,文件名seeds_dataset.csv

数据集是用制表符分隔的格式,因此您必须使用文本编辑器或电子表格程序将其转换为CSV。

教程

本教程分为6个部分:

  1. 初始化网络。
  2. 前向传播。
  3. 反向传播误差。
  4. 训练网络。
  5. 预测。
  6. 种子数据集案例研究。

这些步骤将为你提供从头开始实现反向传播算法并应用于你自己的预测建模问题所需的基石。

1. 初始化网络

让我们从一个简单的事情开始,创建一个用于训练的新网络。

每个神经元都有一个需要维护的权重集。每个输入连接一个权重,还有一个用于偏置的额外权重。在训练期间,我们需要为神经元存储额外的属性,因此我们将使用字典来表示每个神经元,并通过诸如‘weights‘这样的名称来存储属性。

一个网络被组织成多层结构。输入层实际上只是我们训练数据集中的一个行。第一个真正的层是隐藏层。紧随其后的是输出层,该层为每个类值有一个神经元。

我们将把层组织成字典数组,并将整个网络视为层的数组。

将网络权重初始化为小的随机数是良好的实践。在这种情况下,我们将使用0到1范围内的随机数。

下面是一个名为initialize_network()的函数,该函数创建一个新的神经网络,准备进行训练。它接受三个参数,输入的数量、隐藏层的神经元数量和输出的数量。

你可以看到对于隐藏层我们创建了n_hidden个神经元,每个隐藏层中的神经元有n_inputs + 1个权重,一个对应于数据集中每个输入列,并且还有一个偏置。

你还可以看到,连接到隐藏层的输出层有n_outputs个神经元,每个神经元有n_hidden + 1个权重。这意味着输出层中的每个神经元都连接到(对隐藏层中的每个神经元都有权重)隐藏层中的每个神经元。

# Initialize a network
def initialize_network(n_inputs, n_hidden, n_outputs):network = list()hidden_layer = [{'weights':[random() for i in range(n_inputs + 1)]} for i in range(n_hidden)]network.append(hidden_layer)output_layer = [{'weights':[random() for i in range(n_hidden + 1)]} for i in range(n_outputs)]network.append(output_layer)return network

让我们测试一下这个功能。下面是一个创建小型网络的完整示例。

from random import seed
from random import random# Initialize a network
def initialize_network(n_inputs, n_hidden, n_outputs):network = list()hidden_layer = [{'weights':[random() for i in range(n_inputs + 1)]} for i in range(n_hidden)]network.append(hidden_layer)output_layer = [{'weights':[random() for i in range(n_hidden + 1)]} for i in range(n_outputs)]network.append(output_layer)return networkseed(1)
network = initialize_network(2, 1, 2)
for layer in network:print(layer)

运行示例,你可以看到代码逐个打印每一层。你可以看到隐藏层有一个神经元,有2个输入权重加偏置。输出层有2个神经元,每个神经元有1个权重加偏置。

[{'weights': [0.13436424411240122, 0.8474337369372327, 0.763774618976614]}]
[{'weights': [0.2550690257394217, 0.49543508709194095]}, {'weights': [0.4494910647887381, 0.651592972722763]}]

既然我们已经知道如何创建和初始化一个网络,让我们看看如何使用它来计算输出。

2. 前向传播

我们可以通过将输入信号传播通过每层神经网络,直到输出层输出其值,来计算神经网络的输出。

我们把这个过程称为前向传播。

这是我们在训练过程中生成预测时需要的技术,这些预测需要进行修正,并且这是在训练网络后需要的方法,以对新数据进行预测。

我们可以将前向传播分为三个部分:

  1. 神经元激活。
  2. 神经元转移。
  3. 前向传播。
2.1. 神经元激活

第一步是计算给定输入时一个神经元的激活。

输入可以是我们训练数据集的一行,就像隐藏层的情况一样。在输出层的情况下,输入也可能是隐藏层中每个神经元的输出。

神经元激活是通过输入的加权和计算得出的。这类似于线性回归。

activation = sum(weight_i * input_i) + bias

其中 weight 是一个网络权重,input 是一个输入,i 是权重或输入的索引,bias 是一个特殊的权重,它没有输入来相乘(或者你可以认为输入总是为1.0)。

下面是一个在名为activate()函数中实现这一功能的代码。你可以看到,该函数假定偏置是权重列表中的最后一个权重。这在这里和以后有助于使代码更易阅读。

# Calculate neuron activation for an input
def activate(weights, inputs):activation = weights[-1]for i in range(len(weights)-1):activation += weights[i] * inputs[i]return activation

现在,让我们看看如何使用神经元激活。

2.2. 神经元转移

一旦一个神经元被激活,我们需要将激活传递出去,以查看神经元的实际输出。

可以使用不同的传输函数。传统的做法是使用sigmoid激活函数,但你也可以使用tanh(双曲正切)函数来传输输出。最近,整流器传输函数在大型深度学习网络中很受欢迎。

S形激活函数看起来像字母S,也称为逻辑斯蒂克函数。它可以接受任何输入值,并在S形曲线上产生一个0到1之间的数字。它还是一个我们可以轻松计算其导数(斜率)的函数,当我们反向传播错误时,稍后会用到这个导数。

我们可以使用Sigmoid函数来传输激活函数,如下所示:

output = 1 / (1 + e^(-activation))

其中,e 是自然对数的底数 (欧拉数)。

下面是一个名为transfer()的函数,它实现了Sigmoid方程。

# Transfer neuron activation
def transfer(activation):return 1.0 / (1.0 + exp(-activation))

既然我们已经得到了这些碎片,让我们来看看它们是如何使用的。

2.3. 前向传播

将输入前向传播是 straightforward 的。

我们逐层计算网络中每个神经元的输出。一层的所有输出将成为下一层神经元的输入。

下面是一个名为forward_propagate()的函数,该函数使用我们的神经网络对数据集中的某一行数据进行前向传播。

你可以看到一个神经元的输出值存储在名为‘output‘的神经元中。你还可以看到我们收集了一层的输出,并将它们存储在名为new_inputs的数组中,这个数组变成了inputs数组,并作为下一层的输入使用。

该函数返回最后一层(也称为输出层)的输出。

# Forward propagate input to a network output
def forward_propagate(network, row):inputs = rowfor layer in network:new_inputs = []for neuron in layer:activation = activate(neuron['weights'], inputs)neuron['output'] = transfer(activation)new_inputs.append(neuron['output'])inputs = new_inputsreturn inputs

让我们将所有这些部分放在一起,测试一下我们网络的前向传播。

我们定义我们的网络具有一个隐藏神经元,该神经元期望2个输入值,并且输出层有2个神经元。

from math import exp# Calculate neuron activation for an input
def activate(weights, inputs):activation = weights[-1]for i in range(len(weights)-1):activation += weights[i] * inputs[i]return activation# Transfer neuron activation
def transfer(activation):return 1.0 / (1.0 + exp(-activation))# Forward propagate input to a network output
def forward_propagate(network, row):inputs = rowfor layer in network:new_inputs = []for neuron in layer:activation = activate(neuron['weights'], inputs)neuron['output'] = transfer(activation)new_inputs.append(neuron['output'])inputs = new_inputsreturn inputs# test forward propagation
network = [[{'weights': [0.13436424411240122, 0.8474337369372327, 0.763774618976614]}],[{'weights': [0.2550690257394217, 0.49543508709194095]}, {'weights': [0.4494910647887381, 0.651592972722763]}]]
row = [1, 0, None]
output = forward_propagate(network, row)
print(output)

运行示例将输入模式 [1, 0] 并产生一个输出值,该值被打印出来。由于输出层有两个神经元,我们得到一个包含两个数字的列表作为输出。

目前的实际输出值只是些无意义的数字,但接下来,我们将开始学习如何使神经元中的权重更加有用。

[0.6629970129852887, 0.7253160725279748]

3. 反向传播误差

反向传播算法的命名方式是根据权重的训练方法。

误差是预期输出和网络正向传播的输出之间的差异。然后这些误差从输出层反向传播通过网络到隐藏层,分配错误责任并更新权重。

用于反向传播错误的数学源于微积分,但在本节中我们将保持高级概述,重点讨论计算的内容和方法,而不是为什么计算采用这种特定形式。

这一部分被分为两个部分。

  1. 转移导数。
  2. 误差反向传播。
3.1. 转移导数

给定一个神经元的输出值,我们需要计算它的斜率。

我们使用了S形转移函数,其导数可以如下计算:

derivative = output * (1.0 - output)

下面是一个名为transfer_derivative()的函数,实现了这个方程。

# Calculate the derivative of an neuron output
def transfer_derivative(output):return output * (1.0 - output)

现在,让我们看看这将如何使用。

3.2. 误差反向传播

第一步是计算每个输出神经元的误差,这将为我们提供用于向后传播的误差信号(输入)。

对于给定的神经元,其误差可以按照以下方式计算:

1	error = (output - expected) * transfer_derivative(output)

预期输出是神经元的预期输出值,输出是神经元的实际输出值,转移导数 计算神经元输出值的斜率,如上所述。

这个误差计算用于输出层的神经元。期望值是类值本身。在隐藏层中,情况稍微复杂一些。

隐藏层中神经元的误差信号是输出层中每个神经元的加权误差。可以将误差沿着输出层的权重反向传播到隐藏层中的神经元。

反向传播的误差信号被累积,然后用于确定隐藏层神经元的误差,如下所示:

error = (weight_k * error_j) * transfer_derivative(output)

其中 error_j 是输出层第 j 个神经元的误差信号,weight_k 是连接第 k 个神经元到当前神经元的权重,output 是当前神经元的输出。

下面是一个名为backward_propagate_error()的函数,它实现了这个过程。

你可以看到,为每个神经元计算的误差信号以‘delta’这个名字存储。你可以看到,网络的层是逆序迭代的,从输出层开始向后推进。这确保了输出层的神经元先计算出‘delta’值,隐藏层的神经元在后续迭代中可以使用这些值。我选择‘delta’这个名字是为了反映误差对神经元所表示的变化(例如,权重变化)。

你可以看到隐藏层神经元的误差信号是通过输出层神经元积累的,其中隐藏神经元编号j也是输出层神经元权重的索引neuron[‘weights’][j]

# Backpropagate error and store in neurons
def backward_propagate_error(network, expected):for i in reversed(range(len(network))):layer = network[i]errors = list()if i != len(network)-1:for j in range(len(layer)):error = 0.0for neuron in network[i + 1]:error += (neuron['weights'][j] * neuron['delta'])errors.append(error)else:for j in range(len(layer)):neuron = layer[j]errors.append(neuron['output'] - expected[j])for j in range(len(layer)):neuron = layer[j]neuron['delta'] = errors[j] * transfer_derivative(neuron['output'])

让我们把所有拼图拼在一起,看看它是如何工作的。

我们定义了一个具有输出值的固定神经网络,并反向传播一个期望的输出模式。完整的示例如下所示。

# Calculate the derivative of an neuron output
def transfer_derivative(output):return output * (1.0 - output)# Backpropagate error and store in neurons
def backward_propagate_error(network, expected):for i in reversed(range(len(network))):layer = network[i]errors = list()if i != len(network)-1:for j in range(len(layer)):error = 0.0for neuron in network[i + 1]:error += (neuron['weights'][j] * neuron['delta'])errors.append(error)else:for j in range(len(layer)):neuron = layer[j]errors.append(neuron['output'] - expected[j])for j in range(len(layer)):neuron = layer[j]neuron['delta'] = errors[j] * transfer_derivative(neuron['output'])# test backpropagation of error
network = [[{'output': 0.7105668883115941, 'weights': [0.13436424411240122, 0.8474337369372327, 0.763774618976614]}],[{'output': 0.6213859615555266, 'weights': [0.2550690257394217, 0.49543508709194095]}, {'output': 0.6573693455986976, 'weights': [0.4494910647887381, 0.651592972722763]}]]
expected = [0, 1]
backward_propagate_error(network, expected)
for layer in network:print(layer)

运行示例会在误差反向传播完成后打印网络。你可以看到,误差值被计算并存储在输出层和隐藏层的神经元中。

[{'output': 0.7105668883115941, 'weights': [0.13436424411240122, 0.8474337369372327, 0.763774618976614], 'delta': 0.0005348048046610517}]
[{'output': 0.6213859615555266, 'weights': [0.2550690257394217, 0.49543508709194095], 'delta': 0.14619064683582808}, {'output': 0.6573693455986976, 'weights': [0.4494910647887381, 0.651592972722763], 'delta': -0.0771723774346327}]

现在让我们使用误差反向传播来训练网络。

4. 训练网络

网络使用随机梯度下降进行训练。

这涉及多次迭代:将训练数据集暴露给网络,对每行数据进行前向传播输入、反向传播误差并更新网络权重。

本部分分为两个部分:

  1. 更新权重。
  2. 训练网络。
4.1. 更新权重

一旦通过上述反向传播方法计算出网络中每个神经元的误差,这些误差就可以用于更新权重。

网络权重更新如下:

weight = weight - learning_rate * error * input

其中 weight 是一个给定的权重,learning_rate 是一个你必须指定的参数,error 是通过反向传播程序计算出的神经元的误差,input 是导致该误差的输入值。

同样的程序可以用于更新偏置权重,只是没有输入项,或者输入是固定值1.0。

学习率控制了为了修正错误而更改权重的幅度。例如,值为0.1将使权重更新为可能更新幅度的10%。通常更喜欢较小的学习率,这会导致在大量训练迭代中较慢的学习。这增加了网络在所有层中找到一组良好权重的可能性,而不是最快使错误最小的权重(称为过早收敛)。

下面是一个名为update_weights()的函数,该函数在给定输入数据行、学习率的情况下更新网络的权重,并假设已经进行了前向和反向传播。

记住,输出层的输入是隐藏层的输出集合。

# Update network weights with error
def update_weights(network, row, l_rate):for i in range(len(network)):inputs = row[:-1]if i != 0:inputs = [neuron['output'] for neuron in network[i - 1]]for neuron in network[i]:for j in range(len(inputs)):neuron['weights'][j] -= l_rate * neuron['delta'] * inputs[j]neuron['weights'][-1] -= l_rate * neuron['delta']

现在我们知道如何更新网络权重,让我们看看如何重复进行。

4.2. 训练网络

如上所述,网络使用随机梯度下降进行更新。

这包括首先循环固定次数的轮次,并在每个轮次中更新网络,使其适应训练数据集中的每一行。

由于每次训练模式都会进行更新,因此这种学习被称为在线学习。如果在更新权重之前将错误累积在一个时期内,这被称为批量学习或批量梯度下降。

以下是用于根据给定的训练数据集、学习率、固定轮数和期望的输出值数量来训练已初始化神经网络的函数。

预期的输出值数量用于将训练数据中的类别值转换为 one hot 编码。即为每个类别值创建一个二进制向量,以匹配网络的输出。这是计算输出层错误所必需的。

您还可以看到,每个时期之间预期输出和网络输出之间的平方误差之和被累积并打印出来。这有助于跟踪每个时期网络学习和改进的程度。

# Train a network for a fixed number of epochs
def train_network(network, train, l_rate, n_epoch, n_outputs):for epoch in range(n_epoch):sum_error = 0for row in train:outputs = forward_propagate(network, row)expected = [0 for i in range(n_outputs)]expected[row[-1]] = 1sum_error += sum([(expected[i]-outputs[i])**2 for i in range(len(expected))])backward_propagate_error(network, expected)update_weights(network, row, l_rate)print('>epoch=%d, lrate=%.3f, error=%.3f' % (epoch, l_rate, sum_error))

我们现在拥有了训练网络的所有拼图。我们可以组装一个例子,包括到目前为止我们看到的所有内容,包括网络初始化和在小数据集上训练网络。

以下是一个小型的构造数据集,我们可以用它来测试训练我们的神经网络。

    X1			    X2			    Y
2.7810836		  2.550537003		0
1.465489372		2.362125076		0
3.396561688		4.400293529		0
1.38807019		1.850220317		0
3.06407232		3.005305973		0
7.627531214		2.759262235		1
5.332441248		2.088626775		1
6.922596716		1.77106367		1
8.675418651		-0.242068655		1
7.673756466		3.508563011		1

以下是完整的示例。我们将使用2个隐藏层神经元。这是一个二元分类问题(2个类别),因此输出层将有两个神经元。网络将训练20个时期,学习率为0.5,因为我们的迭代次数很少,所以这个学习率较高。

from math import exp
from random import seed
from random import random# Initialize a network
def initialize_network(n_inputs, n_hidden, n_outputs):network = list()hidden_layer = [{'weights':[random() for i in range(n_inputs + 1)]} for i in range(n_hidden)]network.append(hidden_layer)output_layer = [{'weights':[random() for i in range(n_hidden + 1)]} for i in range(n_outputs)]network.append(output_layer)return network# Calculate neuron activation for an input
def activate(weights, inputs):activation = weights[-1]for i in range(len(weights)-1):activation += weights[i] * inputs[i]return activation# Transfer neuron activation
def transfer(activation):return 1.0 / (1.0 + exp(-activation))# Forward propagate input to a network output
def forward_propagate(network, row):inputs = rowfor layer in network:new_inputs = []for neuron in layer:activation = activate(neuron['weights'], inputs)neuron['output'] = transfer(activation)new_inputs.append(neuron['output'])inputs = new_inputsreturn inputs# Calculate the derivative of an neuron output
def transfer_derivative(output):return output * (1.0 - output)# Backpropagate error and store in neurons
def backward_propagate_error(network, expected):for i in reversed(range(len(network))):layer = network[i]errors = list()if i != len(network)-1:for j in range(len(layer)):error = 0.0for neuron in network[i + 1]:error += (neuron['weights'][j] * neuron['delta'])errors.append(error)else:for j in range(len(layer)):neuron = layer[j]errors.append(neuron['output'] - expected[j])for j in range(len(layer)):neuron = layer[j]neuron['delta'] = errors[j] * transfer_derivative(neuron['output'])# Update network weights with error
def update_weights(network, row, l_rate):for i in range(len(network)):inputs = row[:-1]if i != 0:inputs = [neuron['output'] for neuron in network[i - 1]]for neuron in network[i]:for j in range(len(inputs)):neuron['weights'][j] -= l_rate * neuron['delta'] * inputs[j]neuron['weights'][-1] -= l_rate * neuron['delta']# Train a network for a fixed number of epochs
def train_network(network, train, l_rate, n_epoch, n_outputs):for epoch in range(n_epoch):sum_error = 0for row in train:outputs = forward_propagate(network, row)expected = [0 for i in range(n_outputs)]expected[row[-1]] = 1sum_error += sum([(expected[i]-outputs[i])**2 for i in range(len(expected))])backward_propagate_error(network, expected)update_weights(network, row, l_rate)print('>epoch=%d, lrate=%.3f, error=%.3f' % (epoch, l_rate, sum_error))# Test training backprop algorithm
seed(1)
dataset = [[2.7810836,2.550537003,0],[1.465489372,2.362125076,0],[3.396561688,4.400293529,0],[1.38807019,1.850220317,0],[3.06407232,3.005305973,0],[7.627531214,2.759262235,1],[5.332441248,2.088626775,1],[6.922596716,1.77106367,1],[8.675418651,-0.242068655,1],[7.673756466,3.508563011,1]]
n_inputs = len(dataset[0]) - 1
n_outputs = len(set([row[-1] for row in dataset]))
network = initialize_network(n_inputs, 2, n_outputs)
train_network(network, dataset, 0.5, 20, n_outputs)
for layer in network:print(layer)

运行示例首先会打印每个训练周期的平方误差。我们可以看到这个误差在每个周期中逐渐减少。

训练完成后,网络将被打印出来,显示所学习的权重。网络中还包括输出和增量值,这些值可以被忽略。如果我们愿意,可以更新我们的训练函数来删除这些数据。

>epoch=0, lrate=0.500, error=6.350
>epoch=1, lrate=0.500, error=5.531
>epoch=2, lrate=0.500, error=5.221
>epoch=3, lrate=0.500, error=4.951
>epoch=4, lrate=0.500, error=4.519
>epoch=5, lrate=0.500, error=4.173
>epoch=6, lrate=0.500, error=3.835
>epoch=7, lrate=0.500, error=3.506
>epoch=8, lrate=0.500, error=3.192
>epoch=9, lrate=0.500, error=2.898
>epoch=10, lrate=0.500, error=2.626
>epoch=11, lrate=0.500, error=2.377
>epoch=12, lrate=0.500, error=2.153
>epoch=13, lrate=0.500, error=1.953
>epoch=14, lrate=0.500, error=1.774
>epoch=15, lrate=0.500, error=1.614
>epoch=16, lrate=0.500, error=1.472
>epoch=17, lrate=0.500, error=1.346
>epoch=18, lrate=0.500, error=1.233
>epoch=19, lrate=0.500, error=1.132
[{'weights': [-1.4688375095432327, 1.850887325439514, 1.0858178629550297], 'output': 0.029980305604426185, 'delta': 0.0059546604162323625}, {'weights': [0.37711098142462157, -0.0625909894552989, 0.2765123702642716], 'output': 0.9456229000211323, 'delta': -0.0026279652850863837}]
[{'weights': [2.515394649397849, -0.3391927502445985, -0.9671565426390275], 'output': 0.23648794202357587, 'delta': 0.04270059278364587}, {'weights': [-2.5584149848484263, 1.0036422106209202, 0.42383086467582715], 'output': 0.7790535202438367, 'delta': -0.03803132596437354}]

一旦网络训练完成,我们需要使用它来做出预测。

5. 预测

使用训练好的神经网络进行预测是足够简单的。

我们已经看到如何将输入模式前向传播以获得输出。这就是我们进行预测所需做的全部工作。我们可以直接将输出值用作模式属于每个输出类别的概率。

将这个输出重新转换为一个明确的类别预测可能更有用。我们可以通过选择具有较大概率的类别值来实现这一点。这也被称为arg max 函数。

下面是一个名为predict()的函数,该函数实现了这个过程。它返回网络输出中概率最大的索引。它假设类别值已经转换为从0开始的整数。

# Make a prediction with a network
def predict(network, row):outputs = forward_propagate(network, row)return outputs.index(max(outputs))

我们可以将上面的输入正向传播代码与我们的小型编造数据集结合起来,测试使用已训练的网络进行预测。该示例硬编码了上一步训练的网络。

完整的示例如下所示。

from math import exp# Calculate neuron activation for an input
def activate(weights, inputs):activation = weights[-1]for i in range(len(weights)-1):activation += weights[i] * inputs[i]return activation# Transfer neuron activation
def transfer(activation):return 1.0 / (1.0 + exp(-activation))# Forward propagate input to a network output
def forward_propagate(network, row):inputs = rowfor layer in network:new_inputs = []for neuron in layer:activation = activate(neuron['weights'], inputs)neuron['output'] = transfer(activation)new_inputs.append(neuron['output'])inputs = new_inputsreturn inputs# Make a prediction with a network
def predict(network, row):outputs = forward_propagate(network, row)return outputs.index(max(outputs))# Test making predictions with the network
dataset = [[2.7810836,2.550537003,0],[1.465489372,2.362125076,0],[3.396561688,4.400293529,0],[1.38807019,1.850220317,0],[3.06407232,3.005305973,0],[7.627531214,2.759262235,1],[5.332441248,2.088626775,1],[6.922596716,1.77106367,1],[8.675418651,-0.242068655,1],[7.673756466,3.508563011,1]]
network = [[{'weights': [-1.482313569067226, 1.8308790073202204, 1.078381922048799]}, {'weights': [0.23244990332399884, 0.3621998343835864, 0.40289821191094327]}],[{'weights': [2.5001872433501404, 0.7887233511355132, -1.1026649757805829]}, {'weights': [-2.429350576245497, 0.8357651039198697, 1.0699217181280656]}]]
for row in dataset:prediction = predict(network, row)print('Expected=%d, Got=%d' % (row[-1], prediction))

运行此示例将打印训练数据集中每个记录的预期输出,以及网络做出的 crisp预测。

这表明该网络在这个小数据集上实现了100%的准确率。

Expected=0, Got=0
Expected=0, Got=0
Expected=0, Got=0
Expected=0, Got=0
Expected=0, Got=0
Expected=1, Got=1
Expected=1, Got=1
Expected=1, Got=1
Expected=1, Got=1
Expected=1, Got=1

6. 小麦种子数据集

本节将反向传播算法应用于小麦种子数据集。

第一步是加载数据集,并将加载的数据转换为我们可以在神经网络中使用的数字。为此,我们将使用助手函数load_csv()来加载文件,str_column_to_float()将字符串数字转换为浮点数,str_column_to_int()将类别列转换为整数值。

输入值的尺度不同,需要归一化到0到1的范围内。通常将输入值归一化到所选传递函数的范围内是良好的实践,本例中为输出0到1之间值的Sigmoid函数。使用了dataset_minmax()normalize_dataset()辅助函数来归一化输入值。

我们将使用k折交叉验证评估算法,分为5折。这意味着每折有201/5=40.2或40个记录。我们将使用辅助函数evaluate_algorithm()来使用交叉验证评估算法,并使用accuracy_metric()来计算预测的准确性。

开发了一个名为back_propagation()的新函数,用于管理反向传播算法的应用,首先初始化一个网络,然后在训练数据集上训练它,最后使用训练好的网络对测试数据集进行预测。

完整的示例如下所示。

# Backprop on the Seeds Dataset
from random import seed
from random import randrange
from random import random
from csv import reader
from math import exp# Load a CSV file
def load_csv(filename):dataset = list()with open(filename, 'r') as file:csv_reader = reader(file)for row in csv_reader:if not row:continuedataset.append(row)return dataset# Convert string column to float
def str_column_to_float(dataset, column):for row in dataset:row[column] = float(row[column].strip())# Convert string column to integer
def str_column_to_int(dataset, column):class_values = [row[column] for row in dataset]unique = set(class_values)lookup = dict()for i, value in enumerate(unique):lookup[value] = ifor row in dataset:row[column] = lookup[row[column]]return lookup# Find the min and max values for each column
def dataset_minmax(dataset):minmax = list()stats = [[min(column), max(column)] for column in zip(*dataset)]return stats# Rescale dataset columns to the range 0-1
def normalize_dataset(dataset, minmax):for row in dataset:for i in range(len(row)-1):row[i] = (row[i] - minmax[i][0]) / (minmax[i][1] - minmax[i][0])# Split a dataset into k folds
def cross_validation_split(dataset, n_folds):dataset_split = list()dataset_copy = list(dataset)fold_size = int(len(dataset) / n_folds)for i in range(n_folds):fold = list()while len(fold) < fold_size:index = randrange(len(dataset_copy))fold.append(dataset_copy.pop(index))dataset_split.append(fold)return dataset_split# Calculate accuracy percentage
def accuracy_metric(actual, predicted):correct = 0for i in range(len(actual)):if actual[i] == predicted[i]:correct += 1return correct / float(len(actual)) * 100.0# Evaluate an algorithm using a cross validation split
def evaluate_algorithm(dataset, algorithm, n_folds, *args):folds = cross_validation_split(dataset, n_folds)scores = list()for fold in folds:train_set = list(folds)train_set.remove(fold)train_set = sum(train_set, [])test_set = list()for row in fold:row_copy = list(row)test_set.append(row_copy)row_copy[-1] = Nonepredicted = algorithm(train_set, test_set, *args)actual = [row[-1] for row in fold]accuracy = accuracy_metric(actual, predicted)scores.append(accuracy)return scores# Calculate neuron activation for an input
def activate(weights, inputs):activation = weights[-1]for i in range(len(weights)-1):activation += weights[i] * inputs[i]return activation# Transfer neuron activation
def transfer(activation):return 1.0 / (1.0 + exp(-activation))# Forward propagate input to a network output
def forward_propagate(network, row):inputs = rowfor layer in network:new_inputs = []for neuron in layer:activation = activate(neuron['weights'], inputs)neuron['output'] = transfer(activation)new_inputs.append(neuron['output'])inputs = new_inputsreturn inputs# Calculate the derivative of an neuron output
def transfer_derivative(output):return output * (1.0 - output)# Backpropagate error and store in neurons
def backward_propagate_error(network, expected):for i in reversed(range(len(network))):layer = network[i]errors = list()if i != len(network)-1:for j in range(len(layer)):error = 0.0for neuron in network[i + 1]:error += (neuron['weights'][j] * neuron['delta'])errors.append(error)else:for j in range(len(layer)):neuron = layer[j]errors.append(neuron['output'] - expected[j])for j in range(len(layer)):neuron = layer[j]neuron['delta'] = errors[j] * transfer_derivative(neuron['output'])# Update network weights with error
def update_weights(network, row, l_rate):for i in range(len(network)):inputs = row[:-1]if i != 0:inputs = [neuron['output'] for neuron in network[i - 1]]for neuron in network[i]:for j in range(len(inputs)):neuron['weights'][j] -= l_rate * neuron['delta'] * inputs[j]neuron['weights'][-1] -= l_rate * neuron['delta']# Train a network for a fixed number of epochs
def train_network(network, train, l_rate, n_epoch, n_outputs):for epoch in range(n_epoch):for row in train:outputs = forward_propagate(network, row)expected = [0 for i in range(n_outputs)]expected[row[-1]] = 1backward_propagate_error(network, expected)update_weights(network, row, l_rate)# Initialize a network
def initialize_network(n_inputs, n_hidden, n_outputs):network = list()hidden_layer = [{'weights':[random() for i in range(n_inputs + 1)]} for i in range(n_hidden)]network.append(hidden_layer)output_layer = [{'weights':[random() for i in range(n_hidden + 1)]} for i in range(n_outputs)]network.append(output_layer)return network# Make a prediction with a network
def predict(network, row):outputs = forward_propagate(network, row)return outputs.index(max(outputs))# Backpropagation Algorithm With Stochastic Gradient Descent
def back_propagation(train, test, l_rate, n_epoch, n_hidden):n_inputs = len(train[0]) - 1n_outputs = len(set([row[-1] for row in train]))network = initialize_network(n_inputs, n_hidden, n_outputs)train_network(network, train, l_rate, n_epoch, n_outputs)predictions = list()for row in test:prediction = predict(network, row)predictions.append(prediction)return(predictions)# Test Backprop on Seeds dataset
seed(1)
# load and prepare data
filename = 'seeds_dataset.csv'
dataset = load_csv(filename)
for i in range(len(dataset[0])-1):str_column_to_float(dataset, i)
# convert class column to integers
str_column_to_int(dataset, len(dataset[0])-1)
# normalize input variables
minmax = dataset_minmax(dataset)
normalize_dataset(dataset, minmax)
# evaluate algorithm
n_folds = 5
l_rate = 0.3
n_epoch = 500
n_hidden = 5
scores = evaluate_algorithm(dataset, back_propagation, n_folds, l_rate, n_epoch, n_hidden)
print('Scores: %s' % scores)
print('Mean Accuracy: %.3f%%' % (sum(scores)/float(len(scores))))

一个隐藏层有5个神经元,输出层有3个神经元的网络被构建。网络以0.3的学习率训练了500个时期。这些参数是通过一点试错找到的,但你可能可以做得更好。

运行示例将打印每个折叠的平均分类准确率以及所有折叠的平均性能。

你可以看到,反向传播和所选配置达到了约93%的平均分类准确率,这比仅略高于28%准确率的零规则算法好了很多。

Scores: [92.85714285714286, 92.85714285714286, 97.61904761904762, 92.85714285714286, 90.47619047619048]
Mean Accuracy: 93.333%

扩展

本节列出了一些您可以探索的教程扩展。

  • 调整算法参数。尝试训练更长时间或更短时间的较大或较小的网络。看看你是否能在seeds数据集上获得更好的性能。
  • 附加方法。尝试不同的权重初始化技术(例如小随机数)和不同的激活函数(例如 tanh)。
  • 更多层。增加对更多隐藏层的支持,这些隐藏层的训练方式与本教程中使用的单个隐藏层相同。
  • 回归。修改网络,使输出层只有一个神经元,并预测一个实值。选择一个回归数据集进行练习。输出层的神经元可以使用线性传输函数,或者将所选数据集的输出值缩放为0到1之间的值。
  • 批量梯度下降。将训练程序从在线更改为批量梯度下降,并且仅在每个时期的末尾更新权重。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/95553.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/95553.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/95553.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

算法148. 排序链表

题目&#xff1a;给你链表的头结点 head &#xff0c;请将其按 升序 排列并返回 排序后的链表 。示例 1&#xff1a;输入&#xff1a;head [4,2,1,3] 输出&#xff1a;[1,2,3,4] 示例 2&#xff1a;输入&#xff1a;head [-1,5,3,4,0] 输出&#xff1a;[-1,0,3,4,5] 示例 3&a…

在腾讯云CodeBuddy上实现一个AI聊天助手

在腾讯云CodeBuddy上实现一个AI聊天助手项目 在当今数字化时代&#xff0c;AI聊天助手已经成为一种非常流行的应用&#xff0c;广泛应用于客户服务、智能助手等领域。今天&#xff0c;我们将通过腾讯云CodeBuddy平台&#xff0c;实现一个基于Spring Boot和OpenAI API的AI聊天助…

JavaScript Array.prototype.flatMap ():数组 “扁平化 + 映射” 的高效组合拳

在 JavaScript 数组处理中&#xff0c;我们经常需要先对每个元素进行转换&#xff08;映射&#xff09;&#xff0c;再将结果 “铺平”&#xff08;扁平化&#xff09;。比如将数组中的每个字符串按空格拆分&#xff0c;然后合并成一个新数组。传统做法是先用map()转换&#xf…

区块链与元宇宙:数字资产的守护者

1 区块链支撑元宇宙数字资产的底层逻辑1.1 不可篡改性构建信任基石区块链的不可篡改性为元宇宙数字资产提供了坚实的信任基础。其核心在于分布式账本技术&#xff0c;当一笔数字资产交易发生时&#xff0c;会被打包成区块并广播至网络中的所有节点。每个节点都会对这笔交易进行…

Linux软件编程:进程和线程(进程)

进程一、基本概念进程&#xff1a;是程序动态执行过程&#xff0c;包括创建、调度、消亡程序&#xff1a;存放在外存的一段数据的集合二、进程创建&#xff08;一&#xff09;进程空间分布每个进程运行起来后&#xff0c;操作系统开辟0-4G的虚拟空间进程空间&#xff1a;用户空…

Mybatis学习笔记(五)

分页插件与性能优化 分页插件配置 简要描述&#xff1a;MybatisPlus分页插件是基于物理分页实现的高性能分页解决方案&#xff0c;支持多种数据库的分页语法&#xff0c;能够自动识别数据库类型并生成对应的分页SQL。 核心概念&#xff1a; 物理分页&#xff1a;直接在SQL层面进…

企业可商用的conda:「Miniforge」+「conda-forge」

文章目录一、彻底卸载现有 Anaconda/Miniconda二、安装 Miniforge&#xff08;推荐&#xff09;macOS/Linux检查Windows检查三、将通道固定为 conda-forge&#xff08;严格优先&#xff09;四、验证是否仍引用 Anaconda 源五、常见问题&#xff08;FAQ&#xff09;六、参考命令…

Flutter ExpansionPanel组件(可收缩的列表)

可以展开或者收缩的面板组件&#xff0c;收缩面板组件效果由ExpansionPanelList组件和ExpansionPanel组件共同完成。 ExpansionPanelList属性说明属性说明children子元素expansionCallback设置回调事件ExpansionPanel属性说明headerBuilder收缩的标题body内容isExpanded设置内容…

C/C++ 进阶:深入解析 GCC:从源码到可执行程序的魔法四步曲

引言距离上一篇博客更新已经过去了大概一两周的时间&#xff0c;而对于 Linux 系统的基本指令以及 Shell 编程的学习其实基本讲解完毕&#xff0c;Linux基础一块的知识就将告一段落了&#xff0c;如果有细节性的知识&#xff0c;我也会及时分享给各位&#xff0c;作为一名正在攀…

云服务器运行持续强化学习COOM框架的问题

1 环境要求 下载地址&#xff1a;https://github.com/TTomilin/COOM tensorflow 2.11以上 python 3.9以上 tensorflow2.12.0&#xff0c;需要安装tensorflow-probability0.19 2 修改代码 COOM/wrappers/reward.py 将 from gym import RewardWrapper修改为 from gymnasium impor…

MyBatis Interceptor 深度解析与应用实践

MyBatis Interceptor 深度解析与应用实践 一、MyBatis Interceptor概述 1.1 什么是MyBatis Interceptor MyBatis Interceptor&#xff0c;也称为MyBatis 插件&#xff0c;是 MyBatis 提供的一种扩展机制&#xff0c;用于在 MyBatis 执行 SQL 的过程中插入自定义逻辑。它类似…

【自动化测试】Web自动化测试 Selenium

&#x1f525;个人主页&#xff1a; 中草药 &#x1f525;专栏&#xff1a;【Java】登神长阶 史诗般的Java成神之路 测试分类 了解各种各样的测试方法分类&#xff0c;不是为了墨守成规按照既定方法区测试&#xff0c;而是已了解思维为核心&#xff0c;并了解一些专业名词 根…

2025 电赛 C 题完整通关攻略:从单目标定到 2 cm 测距精度的全流程实战

摘要 2025 年全国大学生电子设计竞赛 C 题要求“仅用一颗固定摄像头”在 5 s 内完成 100 cm~200 cm 距离、误差 ≤2 cm 的单目测距&#xff0c;并实时显示功耗。本文整合国一选手方案、CSDN 高分博文、B 站实测视频及官方说明&#xff0c;给出从硬件选型→离线标定→在线算法→…

Day 10: Mini-GPT完整手写实战 - 从组件组装到文本生成的端到端实现

Day 10-2: Mini-GPT完整手写实战 - 从组件组装到文本生成的端到端实现 📚 今日学习目标 掌握GPT架构组装:将Transformer组件组装成完整的生成模型 理解生成式预训练:掌握自回归语言建模的核心机制 端到端代码实现:从数据预处理到模型训练的完整流程 文本生成实战:训练Mi…

深入解析Prompt缓存机制:原理、优化与实践经验

深入解析Prompt缓存机制&#xff1a;原理、优化与实践经验 概述 在大型语言模型应用中&#xff0c;API请求的延迟和成本始终是开发者关注的核心问题。Prompt缓存&#xff08;Prompt Caching&#xff09;技术通过智能地复用重复内容&#xff0c;有效减少了API响应时间和运行成本…

CV 医学影像分类、分割、目标检测,之【3D肝脏分割】项目拆解

CV 医学影像分类、分割、目标检测&#xff0c;之【3D肝脏分割】项目拆解第1行&#xff1a;from posixpath import join第2行&#xff1a;from torch.utils.data import DataLoader第3行&#xff1a;import os第4行&#xff1a;import sys第5行&#xff1a;import random第6行&a…

Mybatis学习笔记(七)

Spring Boot集成 简要描述&#xff1a;MyBatis-Plus与Spring Boot的深度集成&#xff0c;提供了自动配置、启动器等特性&#xff0c;大大简化了配置和使用。 核心概念&#xff1a; 自动配置&#xff1a;基于条件的自动配置机制启动器&#xff1a;简化依赖管理的starter配置属性…

机器人伴侣的智能升级:Deepoc具身智能模型如何重塑成人伴侣体验

引言&#xff1a;机器人伴侣市场的技术变革需求随着人工智能技术的飞速发展和人们情感需求的多元化&#xff0c;机器人成人伴侣市场正在经历前所未有的增长。传统机器人伴侣已经能够满足基础的交互需求&#xff0c;但在智能化、情感化和个性化方面仍存在明显不足。这正是深算纪…

metabase基础使用技巧 (dashboard, filter)

这是metabase系列分享文章的第2部分。本文将介绍metabase的基础概念和使用介绍 question question是metabase中提供的通过UI化操作就能实现简单的 快捷 直接的BI查询。 点击右侧的New -> Question即可创建Question&#xff0c;可以理解为一个格式化的查询&#xff1a; 这里…

机器人成人伴侣的智能化升级:Deepoc具身模型赋能沉浸式体验

引言&#xff1a;成人机器人市场的技术革新需求随着人工智能和机器人技术的快速发展&#xff0c;成人陪伴机器人行业正经历从简单机械运动到智能化交互的转型。据市场研究数据显示&#xff0c;全球成人机器人市场规模预计将在2026年突破100亿美元&#xff0c;年复合增长率保持在…