Найти тему
Алексей Семенов

Как создать нейронную сеть на Python (+ пример)

Оглавление

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

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

Вы можете посмотреть на нее как на систему синапсированных нейронов, которые пересылают импульсы (данные) между собой.

Нейронная сеть состоит из трех слоев: входной слой, скрытый слой, и выходной слой, как показано на схеме 1.

Диаграмма 1. Слои нейронной сети.
Диаграмма 1. Слои нейронной сети.

Входной слой принимает входные данные для расчетов, в скрытом слое происходят все вычисления.

Результат этих вычислений отправляется на выходной слой.

На диаграмме выше кружки представляют нейроны, а стрелки - синапсы.

Каждому синапсу присваивается определенный вес, то есть число, которое, простым языком, определяет, насколько сильно переданное значение влияет на окончательный результат расчета.

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

Затем выходной нейрон выполняет вычисления со значениями, предоставленными синапсами, и отправляет результат в синапс, выходящий из него.

Обучение нейронной сети - это процесс, целью которого является выбор подходящих весов для синапсов.

Мы предполагаем, что способ вычисления внутри каждого нейрона не изменился. Обучение - это итеративный процесс.

Одна итерация состоит из двух шагов (в указанном порядке): распространение и обратное распространение .

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

Обратное распространение измеряет ошибку результата распространения (сравнивая его с ожидаемыми результатами вычислений, то есть данными обучения).

В зависимости от измеренной ошибки изменяются синаптические веса (можно сказать, что, регулируя веса, сеть «учится на своих ошибках»).

Пример

В этом разделе мы представим, как построить простую нейронную сеть, реализующую концепции, описанные выше.

Рассмотрим следующую проблему.

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

Ниже приведены некоторые примеры входных данных с ожидаемыми выходными значениями.

-3

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

Структура нейронной сети

На диаграмме 2 показана структура нашей нейронной сети.

Входной слой состоит из трех нейронов (каждому нейрону соответствует значение одного бита из двоичной записи числа из входа).

Скрытый слой состоит из двух нейронов: U1 и U2.

Нейрон U1 суммирует все числа, которые были отправлены ему (с соответствующими весами) входящими в него синапсами, а затем эта сумма передается нейрону U2.

Вес синапса от нейрона U1 к нейрону U2 равен 1 (если вес не определен, значение по умолчанию равно 1).

Далее нейрон U2 накладывает функцию активации (которая будет подробно описана ниже) на результат вычислений, произведенных в нейроне U1, и передает результат (снова с весом 1) на выходной слой.

Диаграмма 2. Модель расчетов.
Диаграмма 2. Модель расчетов.

Распространение

Из того, что было сказано выше, ясно, что веса должны иметь заранее определенное значение, когда распространение выполняется впервые.

Каждый из них будет инициализирован случайным образом выбранным числом из диапазона (-1, 1), только с одним ограничением.

Ожидаемое значение весов (по определенным теоретическим причинам, которые мы здесь пока не рассматриваем) должно быть 0.

Размножение производится следующим образом.

Сначала нейроны входного слоя инициализируются битами входного числа.

Затем значение каждого нейрона из входного слоя умножается на соответствующий вес и отправляется в нейрон U1.

Нейрон U1 суммирует все три значения.

Результат, вычисленный в нейроне U1, еще нужно «интерпретировать». Это значение можно рассматривать как меру разброса.

Например, если мы получаем число 332 482 в нейроне U1, то наша нейронная сеть утверждает, что с высокой вероятностью правильный результат для данных трех битов равен 1.

Если нейрон U1 вычислил число -54387, наша сеть предсказывает, что правильный результат равен 0.

Если, однако, нейрон U1 вычислил значение 0, наша нейронная сеть не знает, каков правильный результат.

Эта интерпретация происходит в нейроне U2 за счет использования соответствующей функции активации.

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

-5

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

Чем больше аргумент, тем ближе результат к единице, чем меньше аргумент, тем ближе число к нулю.

Обратите внимание, что для аргумента 0 функция принимает значение 0,5.

Обратное распространение

Предположим, мы запускаем одну итерацию процесса обучения для пары (110, 1) - первая координата в кортеже - это входные данные, вторая - ожидаемый результат.

Пусть R обозначает значение, вычисленное в нейроне U2 для входных данных, описанных выше, и для весов, присвоенных во время распространения синапсам, исходящим от нейронов из входного слоя.

Мы начнем обратное распространение, посчитав, насколько значение, вычисленное во время распространения, отличается от ожидаемого результата.

Затем - в зависимости от полученной ошибки - следует скорректировать синаптические веса.

В методе, используемом в этом примере, оптимизация одного веса выполняется следующим образом.

error = R - expected_result
weight = weight + expected_result * error * d_sigmoid(R)

где d_sigmoid (R) - производная сигмовидной функции при x = R.

Если вас интересует происхождение приведенной выше формулы, посмотрите описание логической регресси.

Примечательно, что функция измерения ошибки является выпуклой функцией, поэтому ее можно минимизировать, двигаясь «по ее градиенту», то есть к ее глобальному минимуму.

Код

import numpy as np

class SimpleNeuralNetwork:
"""
Простая нейронная сеть, которая проверяет, является ли данное
двоичное представление положительного числа четным
"""

def __init__(self):
np.random.seed(1)
self.weights = 2 * np.random.random((3, 1)) - 1

def sigmoid(self, x):
"""
Сигмовидная функция - функция, которая сопоставляет любое число с числом от 0 до 1
"""
return 1 / (1 + np.exp(-x))

def d_sigmoid(self, x):
"""
Производная сигмовидной функции
"""
return x * (1 - x)

def train(self, train_input, train_output, train_iters):
for _ in range(train_iters):
propagation_result = self.propagation(train_input)
self.backward_propagation(
propagation_result, train_input, train_output)

def propagation(self, inputs):
"""
Распространения
"""
return self.sigmoid(np.dot(inputs.astype(float), self.weights))

def backward_propagation(self, propagation_result, train_input, train_output):
"""
Обратное распространение
"""
error = train_output - propagation_result
self.weights += np.dot(
train_input.T, error * self.d_sigmoid(propagation_result)
)

Объяснение

Объясним, как представленный выше класс Python реализует эту концепцию (мы предполагаем, что читатель знает основы Python).

В конструкторе класса мы SimpleNeuralNetwork сначала инициализируем генератор случайных чисел:

np.random.seed(1))

Затем указываем начальные значения веса случайными числами:

self.weights = 2 * np.random.random((3, 1)) - 1

Начальные веса записываются как координаты вектора-столбца. Подробнее смотрите в документации пакета numpy .

Функция proparation отвечает за выполнение процесса распространения.

Входные данные (три бита двоичного числа) передаются в эту функцию как вектор с тремя координатами.

Строка

np.dot(inputs.astype(float), self.weights)

вычисляет скалярное произведение вектора весов и вектора входных данных, так что это значение нейрона U1.

Передав это значение в сигмоидальную функцию, мы получим результат вычислений от нейрона U2.

Функция backward_propagation реализует процесс обратного распространения.

Сначала ошибка вычисляется относительно ожидаемого результата:

error = train_output - propagation_result

Затем изменяются синаптические веса.

Код:

self.weights += np.dot(
train_input.T, error * self.d_sigmoid(propagation_result)
)

выполняет описанные ранее вычисления для каждой координаты вектора w.

Функция np.dot отвечает за умножение матриц.

Выражение train_input.T- это просто операция транспонирования вектора (матрицы) train_input.

Тестирование

Ниже представлена ​​короткая тестовая программа, демонстрирующая возможности класса SimpleNeuralNetwork в действии.

network = SimpleNeuralNetwork()

print(network.weights)

train_inputs = np.array(
[[1, 1, 0], [1, 1, 1], [1, 1, 0], [1, 0, 0], [0, 1, 1], [0, 1, 0], ]
)

train_outputs = np.array([[0, 1, 0, 0, 1, 0]]).T

train_iterations = 50000

network.train(train_inputs, train_outputs, train_iterations)

print(network.weights)

print("Testing the data")
test_data = np.array([[1, 1, 1], [1, 0, 0], [0, 1, 1], [0, 1, 0], ])

for data in test_data:
print(f"Result for {data} is:")
print(network.propagation(data))

Попробуйте самостоятельно пройтись по статье и попробовать сделать то, что в ней описано.

Резюме

Нейронные сети - это то, что сейчас набирает обороты. И это уже не будущее, они уже среди нас. Поэтому, изучайте их, вникайте, пробуйте что-то делать.

А я желаю вам удачи!

Если статья оказалась вам полезной - ставьте лайк :-)

Подписывайтесь на статьи https://zen.yandex.ru/id/5f56875a664c2c1f0b8dc68a

Наука
7 млн интересуются