На этом шаге мы рассмотрим, как реализован нейрон.
Начнем с нейронов. Каждый нейрон должен хранить много элементов состояния, включая вес, дельту, скорость обучения, кэш последних выходных данных, функцию активации, а также производную от нее. Некоторые из этих элементов эффективнее было бы хранить в слое (в будущем классе Layer), но они включены в представленный далее класс Neuron из соображений наглядности (файл neuron.ру).
from typing import List, Callable from util import dot_product class Neuron: def __init__(self, weights: List[float], learning_rate: float, activation_function: Callable[[float], float], derivative_activation_function: Callable[[float], float]) -> None: self.weights: List[float] = weights self.activation_function: Callable[[float], float] = activation_function self.derivative_activation_function: Callable[[float], float] = \ derivative_activation_function self.learning_rate: float = learning_rate self.output_cache: float = 0.0 self.delta: float = 0.0 def output(self, inputs: List[float]) -> float: self.output_cache = dot_product(inputs, self.weights) return self.activation_function(self.output_cache)
Большинство этих параметров инициализируется в методе __init__(). Поскольку delta и output_cache неизвестны при первом создании Neuron, они просто инициализируются нулем. Все переменные нейрона изменяемые. На протяжении его жизни (по мере того как мы будем его применять) значения этих переменных могут никогда не измениться, но все же есть причина сделать их изменяемыми: гибкость. Если класс Neuron будет использоваться для других типов нейронных сетей, то, возможно, некоторые из этих значений изменятся в процессе выполнения программы. Существуют нейронные сети, которые изменяют скорость обучения по мере приближения к решению и автоматически пробуют разные функции активации. Поэтому мы стремимся сделать класс Neuron максимально гибким, чтобы он был пригоден для других приложений нейронных сетей.
Кроме __init__(), у класса есть только один метод - output(). Этот метод принимает входные сигналы (входные данные), поступающие в нейрон, и применяет к ним формулу, рассмотренную ранее на 82 шаге (рисунок 1). Входные сигналы объединяются с весами посредством скалярного произведения, и результат кэшируется в output_cache. Напомним, что это значение, полученное до того, как была задействована функция активации, используется для вычисления дельты (см. 84 шаг об обратном распространении). Наконец, прежде чем сигнал будет отправлен на следующий слой (будучи возвращенным из output()), к нему применяется функция активации.
Вот и все! Отдельный нейрон в этой сети довольно прост. Он не может сделать ничего другого, кроме как принять входной сигнал, преобразовать его и передать для дальнейшей обработки. Нейрон поддерживает несколько элементов состояния, которые используются другими классами.
На следующем шаге мы рассмотрим реализацию слоев.