当前位置: 首页 > news >正文

用Python从零开始实现神经网络

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

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

在这个教程中,你将学习如何用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.lryc.cn/news/617788.html

相关文章:

  • 【08-神经网络介绍】
  • STM32 HAL库 HAL_TIM_OC_Start函数解读
  • maven项目打包成sdk后在别的项目使用
  • 深度解析三大HTTP客户端(Fetch API、Axios 和 Alova)——优劣与选择策略
  • 【03】厦门立林科技——立林科技 嵌入式 校招笔试,题目记录及解析
  • REDIS 各种数据结构有什么作用?都能干什么?
  • 写一篇Ping32和IP-Guard的对比,重点突出Ping32
  • 使用行为树控制机器人(一) —— 节点
  • 芯片学习 8 :IP集成、cluster、lint
  • 大语言模型(LLM)核心概念与应用技术全解析:从Prompt设计到向量检索
  • AI入门学习--如何写好prompt?
  • MySQL 数据操作全流程:创建、读取、更新与删除实战
  • 高精度蓝牙定位:技术、应用与未来发展
  • 【Docker实战进阶】Docker 实战命令大全
  • 从零构建企业级K8S:高可用集群部署指南
  • LeetCode算法日记 - Day 8: 串联所有单词的子串、最小覆盖子串
  • kubeadm搭建生产环境的双master节点k8s高可用集群
  • Android视频编辑方案测评:轻量化剪辑工具的性能表现
  • LAZADA跨境电商自养号测评环境搭建:安全与合规的底层逻辑解析
  • Centos8系统在安装Git包时,报错:“没有任何匹配: git”
  • 【图像处理基石】UE输出渲染视频,有哪些画质相关的维度和标准可以参考?
  • LVPECL、LVDS、LVTTL、LVCMOS四种逻辑电平标准的全面对比
  • redis(1)-基本概念
  • ESP32 输入密码后执行程序
  • 【bug】diff-gaussian-rasterization Windows下编译 bug 解决
  • 苹果个人开发者如何实现应用下载安装
  • 【Unity】打包学习笔记
  • IEEE754 double 类型步长规律,从1.0的二进制表示、紧挨着1.0略大和略小的数开始归纳
  • perl notes【1】
  • 【linux】企业级WEB应用服务器tomcat