Обзор
До этого было сказано много теоретических знаний, и теперь пришло время приступить к реальному бою. Давайте попробуем построить нейронную сеть с нуля и обучить ее, собрав все воедино.
Чтобы быть более интуитивным и простым для понимания, мы следуем следующим принципам:
- Не используйте сторонние библиотеки для упрощения логики;
- Отсутствие оптимизации производительности: избегайте введения дополнительных концепций и методов для увеличения сложности;
набор данных
Во-первых, нам нужен набор данных. Чтобы облегчить визуализацию, мы используем бинарную функцию в качестве целевой функции, а затем генерируем набор данных на основе его выборки.Примечание. В реальных инженерных проектах целевая функция неизвестна, но на ее основе может выполняться выборка.
фиктивная целевая функция
код показывает, как показано ниже:
def o(x, y):
return 1.0 if x*x + y*y < 1 else 0.0
Создать набор данных
sample_density = 10
xs = [
[-2.0 + 4 * x/sample_density, -2.0 + 4 * y/sample_density]
for x in range(sample_density+1)
for y in range(sample_density+1)
]
dataset = [
(x, y, o(x, y))
for x, y in xs
]
Результирующие данные: [[-2.0, -2.0, 0.0], [-2.0, -1.6, 0.0], ...]
Изображение выглядит следующим образом:
Построить нейронную сеть
функция активации
import math
def sigmoid(x):
return 1 / (1 + math.exp(-x))
нейроны
from random import seed, random
seed(0)
class Neuron:
def __init__(self, num_inputs):
self.weights = [random()-0.5 for _ in range(num_inputs)]
self.bias = 0.0
def forward(self, inputs):
# z = wx + b
z = sum([
i * w
for i, w in zip(inputs, self.weights)
]) + self.bias
return sigmoid(z)
Выражение нейрона:
-
: вектор, соответствующий массиву весов в коде -
: соответствует смещению в коде
Примечание. Все параметры нейрона инициализируются случайным образом. Однако для обеспечения воспроизводимости экспериментов обычно фиксируется случайное начальное число (начальное число (0)).
Нейронные сети
class MyNet:
def __init__(self, num_inputs, hidden_shapes):
layer_shapes = hidden_shapes + [1]
input_shapes = [num_inputs] + hidden_shapes
self.layers = [
[
Neuron(pre_layer_size)
for _ in range(layer_size)
]
for layer_size, pre_layer_size in zip(layer_shapes, input_shapes)
]
def forward(self, inputs):
for layer in self.layers:
inputs = [
neuron.forward(inputs)
for neuron in layer
]
# return the output of the last neuron
return inputs[0]
Постройте нейронную сеть следующим образом:
net = MyNet(2, [4])
На данный момент мы получаем нейронную сеть (net), которая может вызывать функцию нейронной сети, которую она представляет:
print(net.forward([0, 0]))
Получите значение функции 0,55..., нейронная сеть в это время является необученной сетью.
обучать нейронную сеть
функция потерь
Сначала определите функцию потерь:
def square_loss(predict, target):
return (predict-target)**2
Рассчитать градиент
Вычисление градиентов более сложное, особенно для глубоких нейронных сетей.Алгоритм обратного распространенияЭто алгоритм, специально разработанный для расчета градиента нейронных сетей.
Из-за сложности он не будет здесь описываться, если вам интересно, вы можете обратиться к подробному коду ниже. А текущий фреймворк глубокого обучения имеет функцию автоматического поиска градиента.
Задайте производную функцию:
def sigmoid_derivative(x):
_output = sigmoid(x)
return _output * (1 - _output)
def square_loss_derivative(predict, target):
return 2 * (predict-target)
Найдите частную производную (часть данных кэшируется в прямой функции для облегчения вывода):
class Neuron:
...
def forward(self, inputs):
self.inputs_cache = inputs
# z = wx + b
self.z_cache = sum([
i * w
for i, w in zip(inputs, self.weights)
]) + self.bias
return sigmoid(self.z_cache)
def zero_grad(self):
self.d_weights = [0.0 for w in self.weights]
self.d_bias = 0.0
def backward(self, d_a):
d_loss_z = d_a * sigmoid_derivative(self.z_cache)
self.d_bias += d_loss_z
for i in range(len(self.inputs_cache)):
self.d_weights[i] += d_loss_z * self.inputs_cache[i]
return [d_loss_z * w for w in self.weights]
class MyNet:
...
def zero_grad(self):
for layer in self.layers:
for neuron in layer:
neuron.zero_grad()
def backward(self, d_loss):
d_as = [d_loss]
for layer in reversed(self.layers):
da_list = [
neuron.backward(d_a)
for neuron, d_a in zip(layer, d_as)
]
d_as = [sum(da) for da in zip(*da_list)]
- Частные производные хранятся в d_weights и d_bias соответственно.
- Функция zero_grad используется для очистки градиента, включая каждую частную производную.
- Обратная функция используется для вычисления частных производных и сохранения их значений в накоплении
обновить параметры
Обновить параметры с помощью градиентного спуска:
class Neuron:
...
def update_params(self, learning_rate):
self.bias -= learning_rate * self.d_bias
for i in range(len(self.weights)):
self.weights[i] -= learning_rate * self.d_weights[i]
class MyNet:
...
def update_params(self, learning_rate):
for layer in self.layers:
for neuron in layer:
neuron.update_params(learning_rate)
выполнять обучение
def one_step(learning_rate):
net.zero_grad()
loss = 0.0
num_samples = len(dataset)
for x, y, z in dataset:
predict = net.forward([x, y])
loss += square_loss(predict, z)
net.backward(square_loss_derivative(predict, z) / num_samples)
net.update_params(learning_rate)
return loss / num_samples
def train(epoch, learning_rate):
for i in range(epoch):
loss = one_step(learning_rate)
if i == 0 or (i+1) % 100 == 0:
print(f"{i+1} {loss:.4f}")
Поезд 2000 шагов:
train(2000, learning_rate=10)
Примечание. Здесь используется относительно большая скорость обучения, что связано с ситуацией в проекте. Скорость обучения в реальных проектах обычно очень мала
Суммировать
Фактические шаги следующие:
- Строится фиктивная целевая функция:
; - на основе
Выборка для получения набора данных, то есть функция набора данных: - Строится полносвязная нейронная сеть с одним скрытым слоем, то есть функция нейронной сети:
- обучить нейронную сеть с помощью градиентного спуска, пусть
приблизительный
Самая сложная часть — найти градиент, в котором используется алгоритм обратного распространения ошибки. В реальных проектах использование основной среды глубокого обучения для разработки может сэкономить код для поиска градиентов, а пороговое значение ниже.
В эксперименте «3D-классификация» в лаборатории второй набор данных очень похож на этот реальный бой, и вы можете войти и управлять им.
Эталонное программное обеспечение
Для получения дополнительной информации и интерактивной версии, пожалуйста, обратитесь к приложению:
Нейронные сети и глубокое обучение
Доступно для скачивания в App Store, Mac App Store, Google Play.