Понимание математики!
Нейронные сети — это мощные модели машинного обучения, которые произвели революцию в области искусственного интеллекта. Нейронную сеть можно рассматривать как функциональную единицу глубокого обучения, которая имитирует поведение человеческого мозга при решении сложных задач, связанных с данными. Их можно использовать для решения широкого круга задач, включая распознавание изображений и речи, обработку естественного языка и прогнозное моделирование. Но как на самом деле работают нейронные сети? Действительно ли они имитируют нейроны нашего мозга?
В этой статье мы поговорим об основах построения нейронных сетей и узнаем, как математика помогает этому чуду на самом деле стать реальностью.
Интуиция
Допустим, вы изучаете новый вид спорта. Во-первых, вы пройдете тренировку для новичков и ознакомитесь с основными правилами этого вида спорта. Точно так же нейронная сеть также обучается на основе предоставленных ей основных параметров и набора данных. Это называется «Пересылка вперед».
Теперь, когда ваша тренировка закончена, ваш тренер заставляет вас сыграть в игру. Во время первой игры вы действительно понимаете, как следует играть, наблюдая за другими игроками и за собой. Вы оцениваете игровой процесс, замечая различия между своими действиями и действиями других игроков, которые на самом деле опытны в этом виде спорта. Точно так же наша нейронная сеть также вычисляет, насколько далеко наш прогноз от фактического результата, и пытается исправить его, настраивая параметры. Это называется «обратным распространением», когда выполняется оценка производительности и исправление ошибок.
Наконец, вы станете профессиональным игроком, только потренировавшись в игре. Наша нейронная сеть также повторяет этот итеративный процесс прямого и обратного распространения до тех пор, пока не будет правильно предсказывать результат. Таким образом, конечная цель состоит в том, чтобы свести к минимуму ошибку и повысить точность путем непрерывного обучения.
Точно так же, как начинающий спортсмен, который тренируется и повторяет свои движения, пока они не станут автоматическими, нейронная сеть должна многократно обучаться на большом наборе данных, чтобы изучать закономерности и взаимосвязи между входными и выходными данными.
Сеть
Нейронная сеть состоит из нескольких слоев взаимосвязанных нейронов. Соединяя вместе несколько слоев нейронов, нейронная сеть может научиться моделировать сложные функции, которые отображают функции входных данных в выходные прогнозы. Давайте попробуем предсказать вывод вентиля XOR.
Архитектура
Рассмотрим приведенную ниже нейронную сеть. Он содержит входной слой, 1 скрытый слой и выходной слой. Входной слой содержит 3 нейрона, скрытый слой содержит 4, а конечный выходной слой содержит один нейрон.
1. Нейроны
Каждый нейрон представляет собой математическую функцию, которая принимает входные данные и производит выходные данные, которые передаются другим нейронам следующего слоя. Результат создается на основе определенных параметров, называемых весами и смещениями. Входные данные для каждого нейрона представляют собой взвешенную сумму точек данных и смещения 𝑊𝑥+𝑏. Давайте определим класс с этими основными компонентами.
2. Веса и смещения
Веса определяют важность или вклад этой конкретной функции в результат. Их можно рассматривать как параметры нашей модели данных. У нас есть 2 набора весов, один между входным слоем и скрытым слоем, а другой между скрытым слоем и выходным слоем.
class NeuralNetwork: def __init__(self, x, y): self.input = x self.weights1 = np.random.rand(self.input.shape[1],4) self.weights2 = np.random.rand(4,1) self.y = y self.output = np.zeros(y.shape)
3. Функция активации
Функция активации — это математическая функция, которая применяется к выходу нейрона в нейронной сети. Существует несколько функций активации, таких как сигмоид, гиперболический тангенс и ReLu, и выбор зависит от характера проблемы. В нашем примере мы будем использовать функцию sigmoid. Оказывается, это хорошее приближение к реальной функции активации нейронов человека.
def sigmoid(x): return 1.0/(1+ np.exp(-x))
4. Упреждающая связь
Если на входе 𝑥, то на выходе ŷ простой двухслойной нейронной сети, подобной приведенной выше, для каждого из наших двух слоев:
Здесь 𝑦1 — это выходные данные первого уровня, которые передаются в качестве входных данных второму слою. W1 и W2 — веса, а b1 и b2 — байзы. Объединение двух приведенных выше уравнений:
Но эти переменные — не просто числа. Здесь мы имеем дело с векторами и матрицами. Чтобы закодировать эти уравнения, давайте разберемся с размерами.
- Входной слой: (3,1) — XOR входы
- Веса 1: (3,4) — лежат между входным слоем с 3 нейронами и скрытым слоем с 4 нейронами
- Веса 2: (4,1) — соединяют из скрытого слоя с 4 нейронами в выходной слой с одним нейроном
- Bais 1: (4,1) — это смещение применяется к скрытому слою с 4 нейронами.
- Смещение 2: (1,1) — применяется к выходному слою с одним нейроном
def feedforward(self): self.layer1 = sigmoid(np.dot(self.input, self.weights1)) self.output = sigmoid(np.dot(self.layer1, self.weights2))
Хороший! Мы построили базовую обучающую сеть. Отличная работа! Теперь пришло время узнать и исправить неправильные выводы, исправление ошибок.
5. Функция потерь
Функция потерь, также называемая функцией стоимости, представляет собой математическую функцию, которая измеряет разницу между прогнозируемым выходом нейронной сети и фактическим выходом, также известным как основная правда. Он оценивает производительность модели и указывает корректировки, которые необходимо внести в веса и смещения для повышения точности. Цель состоит в том, чтобы найти оптимальный набор гирь и байзов, чтобы минимизировать функцию потерь. Выбор функции потерь зависит от категории задачи. Здесь мы будем использовать простую ошибку суммы квадратов в качестве нашей функции потерь: ошибка суммы квадратов — это просто сумма разницы между каждым предсказанным наблюдением 𝑦̂ и фактическим наблюдением 𝑦 . Разница возводится в квадрат, поэтому мы измеряем абсолютное значение разницы:
6. Обратное распространение
Вот и наступил важный шаг! Теперь, когда у нас есть мера качества прогнозов, которая является функцией потерь, мы обновим веса и смещения с целью минимизировать ошибку. Для этого возьмем производную (градиентный спуск) функции потерь по весам и байзам. Градиентный спуск дает наклон функции и говорит нам, насколько мы далеки от минимумов. Поэтому мы будем обновлять наши веса (и смещения) следующим образом:
где ∂ — частная производная и L = Loss (𝑦,𝑦̂)
7. Математика!
Обновление весов.Поскольку у нас есть два набора весов, нам нужны два вывода Loss(𝑦,𝑦̂):
Функция потерь зависит от y и 𝑦̂, а не от весов. Но y и 𝑦̂ сами по себе являются функциями весов. Таким образом, мы можем переписать уравнения как:
Давайте сначала решим для общего термина,
Подставляя это и уравнения для 𝑧 и 𝑦̂, мы получаем:
Чтобы взять производные, мы применим цепное правило. Цепное правило формулируется следующим образом:
Итак, с заменой переменной 𝑊2→𝑞:
Таким образом, первое уравнение становится:
Аналогично, изменив переменную 𝑊1→𝑞:
И второе уравнение:
Обновление смещений.Аналогичным образом у нас есть два смещения, которые нужно скорректировать.
Мы можем переписать уравнения как:
Решение для отдельных условий:
Как рассчитали ранее, имеем:
Таким образом, первое уравнение становится:
И второе уравнение становится:
Ух! Это была сложная математика! Поздравляю! Мы закончили с трудной частью. Теперь давайте добавим функцию обратного распространения в наш код Python.
#function to calculate the deivative of sigmoid activation function def sigmoid_derivative(x): return sigmoid(x) * (1.0 - sigmoid(x)) def backpropagation(self): # application of the chain rule to find derivative of the loss function with respect to weights2 and weights1 sigmoid_derivative_1 = self.sigmoid_derivative(self.z1) #sigma'(W1 x + b1) sigmoid_derivative_2 = self.sigmoid_derivative(self.z2) #sigma'(W2 z + b2) d_weights2 = np.dot(self.layer1.T, (2*(self.y - self.output) * sigmoid_derivative_2)) d_weights1 = np.dot(self.input.T, np.dot(2*(self.y - self.output) * sigmoid_derivative_2, self.weights2.T) * sigmoid_derivative_1) # update the weights self.weights1 += d_weights1 self.weights2 += d_weights2 d_bias2 = np.sum((2*(self.y - self.output) * sigmoid_derivative_2), axis=0) d_bias1 = np.sum((np.dot(2*(self.y - self.output) * sigmoid_derivative_2, self.weights2.T)), axis=0) # update the biases self.bias1 += d_bias1 self.bias2 += d_bias2
8. Обучение
Соберем все вместе в класс и обучим нашу нейросеть
import numpy as np import matplotlib.pyplot as plt class NeuralNetwork: def __init__(self, x, y): self.input = x self.weights1 = np.random.rand(self.input.shape[1],4) self.bias1 = np.random.rand(4) self.weights2 = np.random.rand(4,1) self.bias2 = np.random.rand(1) self.y = y self.output = np.zeros(self.y.shape) def feedforward(self): self.z1 = np.dot(self.input, self.weights1) + self.bias1 self.layer1 = self.sigmoid(self.z1) self.z2 = np.dot(self.layer1, self.weights2) + self.bias2 self.output = self.sigmoid(self.z2) return self.calculate_loss() def reload(self, x): self.input = x def predict(self): return self.output def calculate_loss(self): return (self.y - self.output) ** 2 def sigmoid(self,x): return 1.0/(1+ np.exp(-x)) #function to calculate the deivative of sigmoid activation function def sigmoid_derivative(self,x): return self.sigmoid(x) * (1.0 - self.sigmoid(x)) def backprop(self): # application of the chain rule to find derivative of the loss function with respect to weights2 and weights1 sigmoid_derivative_1 = self.sigmoid_derivative(self.z1) #sigma'(W1 x + b1) sigmoid_derivative_2 = self.sigmoid_derivative(self.z2) #sigma'(W2 z + b2) d_weights2 = np.dot(self.layer1.T, (2*(self.y - self.output) * sigmoid_derivative_2)) d_weights1 = np.dot(self.input.T, np.dot(2*(self.y - self.output) * sigmoid_derivative_2, self.weights2.T) * sigmoid_derivative_1) # update the weights with the derivative (slope) of the loss function self.weights1 += d_weights1 self.weights2 += d_weights2 d_bias2 = np.sum((2*(self.y - self.output) * sigmoid_derivative_2), axis=0) d_bias1 = np.sum((np.dot(2*(self.y - self.output) * sigmoid_derivative_2, self.weights2.T) * sigmoid_derivative_1), axis=0) # update the weights with the derivative (slope) of the loss function self.bias1 += d_bias1 self.bias2 += d_bias2
Мы тренируемся на наборе данных XOR. Таким образом, наши X и y будут такими, как показано ниже:
X = np.array([[0,0,1], [0,1,1], [1,0,1], [1,1,1]]) y = np.array([[0],[1],[1],[0]])
Давайте тренироваться !!!
nn = NeuralNetwork(X,y) for i in range(1500): nn.feedforward() nn.backprop() print(nn.output)
Полученные результаты
Давайте посмотрим на окончательный прогноз (выход) нейронной сети после 1500 итераций. Прогнозы после 1500 итераций обучения:
Поздравляем, теперь у вас есть полнофункциональная двухслойная нейронная сеть для задачи бинарной классификации.
Полный рабочий код вы можете найти здесь.
Спасибо профессору Дино за понимание и поддержку!