Один пример построения нейронной сети с нуля

Первоначальная цель этого поста заключалась в том, чтобы я просто коснулся математики в нейронных сетях, так как мне нравится хорошо разбираться во внутренней работе алгоритмов и разбираться в сути вещей. Затем я думаю, что с таким же успехом мог бы составить историю, а не просто пересматривать формулы в своем блокноте снова и снова. Хотя вы можете найти ряд руководств по созданию простой нейронной сети с нуля. У разных людей разные углы зрения, а также акцент в учебе. Другой способ мышления может в некотором смысле улучшить понимание. Итак, приступим.

Коротко о нейронной сети

Ядро нейронной сети - это большая функция, которая сопоставляет некоторые входные данные с желаемым целевым значением, на промежуточном этапе выполняет операцию по созданию сети, которая заключается в умножении весов и добавлении смещения в сценарии конвейера, который делает это снова и снова. . Процесс обучения нейронной сети заключается в определении набора параметров, которые минимизируют разницу между ожидаемым значением и результатом модели. Это выполняется с помощью градиентного спуска (также известного как обратное распространение), который по определению включает два шага: вычисление градиентов функции потерь / ошибок, затем обновление существующих параметров в ответ на градиенты, что и происходит при спуске. Этот цикл повторяется до достижения минимумов функции потерь. Этот процесс обучения можно описать простым уравнением: W (t + 1) = W (t) - dJ (W) / dW (t).

Математическая интуиция

Для моей практической цели мне нравится использовать небольшую сеть с одним скрытым слоем, как показано на диаграмме. В этом макете X представляет вход, индексы i, j, k обозначают количество единиц во входном, скрытом и выходном слоях соответственно; w_ij представляет собой веса, соединяющие входной слой со скрытым слоем, а w_jk - веса, соединяющие скрытый с выходным слоем.

Расчет выходных данных модели в этом случае будет следующим:

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

Сложность определения входных данных для скрытых весов заключается в том, что они косвенно влияют на ошибку выходных данных. Выход каждого скрытого модуля влияет на выход модели, таким образом, ввод для скрытых весов w_ij зависит от ошибок на всех модулях, к которым он подключен. Вывод начинается так же, просто чтобы расширить цепное правило в z_k до подфункции.

Еще мысли:

Обратите внимание, что градиенты двух весов имеют похожую форму. Ошибка передается обратно через производную функции активации, а затем взвешивается по входу (выходу активации) из предыдущего слоя. Во второй формуле ошибка обратного распространения из выходного слоя далее проецируется на w_jk, затем повторяется тот же способ обратного распространения и взвешивается входными данными. Этот процесс обратного распространения повторяется вплоть до самого первого уровня нейронной сети произвольного уровня. «Таким образом, градиенты по каждому параметру считаются вкладом параметра в ошибку и следует отрицать во время обучения ».

Помещение вышеуказанного процесса в код:

Ниже приведен полный пример:

import numpy as np
class NeuralNetwork:
    def __init__(self):
        np.random.seed(10) # for generating the same results
        self.wij   = np.random.rand(3,4) # input to hidden layer weights
        self.wjk   = np.random.rand(4,1) # hidden layer to output weights
        
    def sigmoid(self, x, w):
        z = np.dot(x, w)
        return 1/(1 + np.exp(-z))
    
    def sigmoid_derivative(self, x, w):
        return self.sigmoid(x, w) * (1 - self.sigmoid(x, w))
    
    def gradient_descent(self, x, y, iterations):
        for i in range(iterations):
            Xi = x
            Xj = self.sigmoid(Xi, self.wij)
            yhat = self.sigmoid(Xj, self.wjk)
            # gradients for hidden to output weights
            g_wjk = np.dot(Xj.T, (y - yhat) * self.sigmoid_derivative(Xj, self.wjk))
            # gradients for input to hidden weights
            g_wij = np.dot(Xi.T, np.dot((y - yhat) * self.sigmoid_derivative(Xj, self.wjk), self.wjk.T) * self.sigmoid_derivative(Xi, self.wij))
            # update weights
            self.wij += g_wij
            self.wjk += g_wjk
        print('The final prediction from neural network are: ')
        print(yhat)
if __name__ == '__main__':
    neural_network = NeuralNetwork()
    print('Random starting input to hidden weights: ')
    print(neural_network.wij)
    print('Random starting hidden to output weights: ')
    print(neural_network.wjk)
    X = np.array([[0, 0, 1], [1, 1, 1], [1, 0, 1], [0, 1, 1]])
    y = np.array([[0, 1, 1, 0]]).T
    neural_network.gradient_descent(X, y, 10000)

Использованная литература:

  1. Https://theclevermachine.wordpress.com/2014/09/06/derivation-error-backpropagation-gradient-descent-for-neural-networks/
  2. Https://towardsdatascience.com/how-to-build-your-own-neural-network-from-scratch-in-python-68998a08e4f6