当前位置:嗨网首页>书籍在线阅读

14-神经网络的实现

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

7.4.3 神经网络的实现

神经网络对象本身只包含一种状态数据,即神经网络管理的层对象。 Network 类负责初始化其构成层。

__init__() 方法的参数为描述网络结构的 int 列表。例如,列表 [2, 4, 3] 描述的网络为:输入层有2个神经元,隐藏层有4个神经元,输出层有3个神经元。在这个简单的网络中,假设网络中的所有层都将采用相同的神经元激活函数和学习率。具体代码如代码清单7-7所示。

代码清单7-7 network.py

from __future__ import annotations
from typing import List, Callable, TypeVar, Tuple
from functools import reduce
from layer import Layer
from util import sigmoid, derivative_sigmoid
T = TypeVar('T') # output type of interpretation of neural network
class Network:
    def __init__(self, layer_structure: List[int], learning_rate: float,
     activation_function: Callable[[float], float] = sigmoid, 
     derivative_activation_function:
     Callable[[float], float] = derivative_sigmoid) -> None:
      if len(layer_structure) < 3:
          raise ValueError("Error: Should be at least 3 layers (1 input, 1 hidden,
 1 output)")
      self.layers: List[Layer] = []
      # input layer
      input_layer: Layer = Layer(None, layer_structure[0], learning_rate, 
       activation_function, derivative_activation_function)
      self.layers.append(input_layer)
      # hidden layers and output layer
      for previous, num_neurons in enumerate(layer_structure[1::]):
          next_layer = Layer(self.layers[previous], num_neurons, learning_rate, 
           active ation_function, derivative_activation_function)
          self.layers.append(next_layer)

神经网络的输出是信号经由其所有层传递之后的结果。注意简洁的 reduce() 函数是如何用在 outputs() 中,将信号反复从一层传递到下一层,进而传遍整个网络的。具体代码如代码清单7-8所示。

代码清单7-8 network.py(续)

# Pushes input data to the first layer, then output from the first
# as input to the second, second to the third, etc.
def outputs(self, input: List[float]) -> List[float]:
    return reduce((lambda inputs, layer: layer.outputs(inputs)), self.layers, input)

backpropagate() 方法负责计算网络中每个神经元的delta。它会依次调用 Layer 类的 calculate_deltas_for_output_layer() 方法和 calculate_deltas_for_hidden_layer() 方法。还记得吧,在反向传播时要反向计算delta。它会把给定输入集的预期输出值传递给 calculate_ deltas_for_output_layer() 。该方法将用预期输出值求出误差,以供计算delta时使用。具体代码如代码清单7-9所示。

代码清单7-9 network.py(续)

# Figure out each neuron's changes based on the errors of the output
# versus the expected outcome
def backpropagate(self, expected: List[float]) -> None:
    # calculate delta for output layer neurons
    last_layer: int = len(self.layers) - 1
    self.layers[last_layer].calculate_deltas_for_output_layer(expected)
    # calculate delta for hidden layers in reverse order
    for l in range(last_layer - 1, 0, -1):
        self.layers[l].calculate_deltas_for_hidden_layer(self.layers[l + 1])

backpropagate() 的确负责计算所有的delta,但它不会真的去修改网络中的权重。 update_weights() 必须在 backpropagate() 之后才能被调用,因为权重的修改依赖delta。 update_weights() 方法直接来自图7-6中的公式。具体代码如代码清单7-10所示。

代码清单7-10 network.py(续)

# backpropagate() doesn't actually change any weights
# this function uses the deltas calculated in backpropagate() to
# actually make changes to the weights
def update_weights(self) -> None:
    for layer in self.layers[1:]: # skip input layer
        for neuron in layer.neurons:
            for w in range(len(neuron.weights)):
                neuron.weights[w] = neuron.weights[w] + (neuron.learning_rate *   
                  (layer.previous_layer.output_cache[w]) * neuron.delta)

在每轮训练结束时,会对神经元的权重进行修改。必须向神经网络提供训练数据集(同时给出输入与预期的输出)。 train() 方法的参数即为输入列表的列表和预期输出列表的列表。 train() 方法在神经网络上运行每一组输入,然后以预期输出为参数调用 backpropagate() ,然后再调用 update_weights() ,以更新网络的权重。不妨试着在 train() 方法中添加代码,使得在神经网络中传递训练数据集时能把误差率打印出来,以便于查看梯度下降过程中误差率是如何逐渐降低的。具体代码如代码清单7-11所示。

代码清单7-11 network.py(续)

# train() uses the results of outputs() run over many inputs and compared
# against expecteds to feed backpropagate() and update_weights()
def train(self, inputs: List[List[float]], expecteds: List[List[float]]) -> None:
    for location, xs in enumerate(inputs):
        ys: List[float] = expecteds[location]
        outs: List[float] = self.outputs(xs)
        self.backpropagate(ys)
        self.update_weights()

在神经网络经过训练后,我们需要对其进行测试。 validate() 的参数为输入和预期输出(与 train() 的参数没什么区别),但它们不会用于训练,而会用来计算准确度的百分比。这里假定网络已经过训练。 validate() 还有一个参数是函数 interpret_output() ,该函数用于解释神经网络的输出,以便将其与预期输出进行比较。或许预期输出不是一组浮点数,而是像 "Amphibian" 这样的字符串。 interpret_output() 必须读取作为网络输出的浮点数,并将其转换为可以与预期输出相比较的数据。 interpret_output() 是特定于某数据集的自定义函数。 validate() 将返回分类成功的类别数量、通过测试的样本总数和成功分类的百分比。具体代码如代码清单7-12所示。

代码清单7-12 network.py(续)

# for generalized results that require classification 
# this function will return the correct number of trials 
# and the percentage correct out of the total
def validate(self, inputs: List[List[float]], expecteds: List[T], interpret_output: 
     Callable[[List[float]], T]) -> Tuple[int, int, float]:
    correct: int = 0
    for input, expected in zip(inputs, expecteds):
        result: T = interpret_output(self.outputs(input))
        if result == expected:
            correct += 1
    percentage: float = correct / len(inputs)
    return correct, len(inputs), percentage

至此神经网络就完工了!已经可以用来进行一些实际问题的测试了。虽然此处构建的架构是通用的,足以应对各种不同的问题,但这里将重点解决一种流行的问题,即分类问题。