Создавайте и обучайте нейронные сети с нуля

Нейронные сети
Создавайте и обучайте нейронные сети с нуля

Обзор


До этого было сказано много теоретических знаний, и теперь пришло время приступить к реальному бою. Давайте попробуем построить нейронную сеть с нуля и обучить ее, собрав все воедино.

Чтобы быть более интуитивным и простым для понимания, мы следуем следующим принципам:

  1. Не используйте сторонние библиотеки для упрощения логики;
  2. Отсутствие оптимизации производительности: избегайте введения дополнительных концепций и методов для увеличения сложности;

набор данных


Во-первых, нам нужен набор данных. Чтобы облегчить визуализацию, мы используем бинарную функцию в качестве целевой функции, а затем генерируем набор данных на основе его выборки.Примечание. В реальных инженерных проектах целевая функция неизвестна, но на ее основе может выполняться выборка.

фиктивная целевая функция

o(x,y)={1x2+y2<10разноеo(x, y) = \begin{case} 1 & x^2 + y^2

код показывает, как показано ниже:

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)

Выражение нейрона:sigmoid(wx+b)\text{sigmoid}( \mathbf {w} \mathbf x + b)

  • w\mathbf {w}: вектор, соответствующий массиву весов в коде
  • bb: соответствует смещению в коде

Примечание. Все параметры нейрона инициализируются случайным образом. Однако для обеспечения воспроизводимости экспериментов обычно фиксируется случайное начальное число (начальное число (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)

Примечание. Здесь используется относительно большая скорость обучения, что связано с ситуацией в проекте. Скорость обучения в реальных проектах обычно очень мала

训练后的神经网络函数图像

Суммировать

Фактические шаги следующие:

  1. Строится фиктивная целевая функция:o(x,y)o(x, y);
  2. на основеo(x,y)o(x, y)Выборка для получения набора данных, то есть функция набора данных:d(x,y)d(x, y)
  3. Строится полносвязная нейронная сеть с одним скрытым слоем, то есть функция нейронной сети:f(x,y)f(x, y)
  4. обучить нейронную сеть с помощью градиентного спуска, пустьf(x,y)f(x, y)приблизительныйd(x,y)d(x, y)

Самая сложная часть — найти градиент, в котором используется алгоритм обратного распространения ошибки. В реальных проектах использование основной среды глубокого обучения для разработки может сэкономить код для поиска градиентов, а пороговое значение ниже.

В эксперименте «3D-классификация» в лаборатории второй набор данных очень похож на этот реальный бой, и вы можете войти и управлять им.

Эталонное программное обеспечение

Для получения дополнительной информации и интерактивной версии, пожалуйста, обратитесь к приложению:

神经网络与深度学习Нейронные сети и глубокое обучение

Доступно для скачивания в App Store, Mac App Store, Google Play.