Как построить свою первую нейронную сеть, написав 9 строчек на Python

На моем пути к пониманию искусственного интеллекта, я поставил для себя задачу написать простую нейронную сеть на Python. Чтобы удостовериться, что я действительно разобрался в этой области, мне нужно было создать одну нейронную сеть с нуля, без использования готовых библиотек. И благодаря отличной статье Эндрю Траска я сумел это сделать. Вот моя нейронная сеть, которая уместилась в 9 строчек кода:

from numpy import exp, array, random, dot

training_set_inputs = array([[0, 0, 1], [1, 1, 1], [1, 0, 1], [0, 1, 1]])

training_set_outputs = array([[0, 1, 1, 0]]).T

random.seed(1)

synaptic_weights = 2 * random.random((3, 1)) - 1

for iteration in xrange(10000):

    output = 1 / (1 + exp(-(dot(training_set_inputs, synaptic_weights))))

    synaptic_weights += dot(training_set_inputs.T, (training_set_outputs - output) * output * (1 - output))

print 1 / (1 + exp(-(dot(array([1, 0, 0]), synaptic_weights))))

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

Курс по нейронным сетям
Идет набор в группу 4 200₽ в месяц

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

Рис. 1

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

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

Наша задача — научить нейронную сеть решать задачу, которая изображена на рисунке ниже. Первые четыре примера будут нашим тренировочным набором. Получилось ли у вас увидеть закономерность? Что должно быть на месте вопросительного знака — 0 или 1? 

Рис. 2

Вы могли заметить, что вывод всегда равен значению левого столбца. Так что ответом будет 1.

Курс по Machine Learning
Идет набор в группу 3 800₽ в месяц

Процесс тренировки

Но как научить наш нейрон правильно отвечать на заданный вопрос? Для этого мы зададим каждому входящему сигналу вес, который может быть положительным или отрицательным числом. Если на входе будет сигнал с большим положительным весом или отрицательным весом, то это сильно повлияет на решение нейрона, которое он подаст на выход. Прежде чем мы начнем обучение модели, зададим для каждого примера случайное число в качестве веса. После этого мы можем приняться за тренировочный процесс, который будет выглядеть следующим образом:

  1. В качестве входных данных мы возьмем примеры из тренировочного набора. Потом мы воспользуемся специальной формулой для расчета выхода нейрона, которая будет учитывать случайные веса, которые мы задали для каждого примера.
  2. Далее посчитаем размер ошибки, который вычисляется как разница между числом, которое нейрон подал на выход и желаемым числом из примера.
  3. В зависимости от того, в какую сторону нейрон ошибся, мы немного отрегулируем вес этого примера.
  4. Повторим этот процесс 10 000 раз.
Рис. 3

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

Формула для вычисления выхода нейронной сети

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

После этого мы нормализуем это выражение, чтобы результат был между 0 и 1. Для этого, в этом примере, я использую математическую функцию, которая называется сигмоидой:

Если мы нарисуем график этой функции, то он будет выглядеть как кривая в форме буквы S (рис. 4).

Рис. 4: Сигмоида

Подставив первое уравнения во второе, мы получим итоговую формулу выхода нейрона. 

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

Курс «Python для анализа данных»
Идет набор в группу 2 700₽ в месяц

Формула корректировки весов

Во время тренировочного цикла (он изображен на рисунке 3) мы постоянно корректируем веса. Но на сколько? Для того, чтобы вычислить это, мы воспользуемся следующей формулой:

Давайте поймем почему формула имеет такой вид. Сначала нам нужно учесть то, что мы хотим скорректировать вес пропорционально размеру ошибки. Далее ошибка умножается на значение, поданное на вход нейрона, что, в нашем случае, 0 или 1. Если на вход был подан 0, то вес не корректируется. И в конце выражение умножается на градиент сигмоиды. Разберемся в последнем шаге по порядку:

  1. Мы использовали сигмоиду для того, чтобы посчитать выход нейрона.
  2. Если на выходе мы получаем большое положительное или отрицательное число, то это значит, что нейрон был весьма уверен в том или ином решении.
  3. На рисунке 4 мы можем увидеть, что при больших значениях переменной градиент принимает маленькие значения.
  4. Если нейрон уверен в том, что заданный вес верен, то мы не хотим сильно корректировать его. Умножение на градиент сигмоиды позволяет добиться такого эффекта.

Градиент сигмоиды может быть найден по следующей формуле:

Таким образом, подставляя второе уравнение в первое, конечная формула для корректировки весов будет выглядеть следующим образом:

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

Как написать это на Python

Хотя мы не будем использовать специальные библиотеки для нейронных сетей, мы импортируем следующие 4 метода из математической библиотеки numpy:

  • exp — функция экспоненты
  • array — метод создания матриц
  • dot — метод перемножения матриц
  • random — метод, подающий на выход случайное число

Теперь мы можем, например, представить наш тренировочный набор с использованием array():

training_set_inputs = array([[0, 0, 1], [1, 1, 1], [1, 0, 1], [0, 1, 1]])=
training_set_outputs = array([[0, 1, 1, 0]]).T

Функция .T транспонирует матрицу из горизонтальной в вертикальную. В результате компьютер хранит эти числа таким образом:

Хорошо, теперь мы готовы к более изящной версии кода. После нее я добавлю несколько финальных замечаний.

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

Итак, вот полноценно работающий пример нейронной сети, написанный на Python:

from numpy import exp, array, random, dot
 
class NeuralNetwork():
    def __init__(self):
        # Задаем порождающий элемент для генератора случайных чисел, чтобы он генерировал одинаковые числа при каждом запуске программы
        random.seed(1)
 
        # Мы моделируем единственный нейрон с тремя входящими связями и одним выходом. Мы задаем случайные веса в матрице размера 3 x 1, где значения весов варьируются от -1 до 1, а среднее значение равно 0.
        self.synaptic_weights = 2 * random.random((3, 1)) - 1
 
    # Функция сигмоиды, график которой имеет форму буквы S.
    # Мы используем эту функцию, чтобы нормализовать взвешенную сумму входных сигналов.
    def __sigmoid(self, x):
        return 1 / (1 + exp(-x))
 
    # Производная от функции сигмоиды. Это градиент ее кривой. Его значение указывает насколько нейронная сеть уверена в правильности существующего веса.
    def __sigmoid_derivative(self, x):
        return x * (1 - x)
 
    # Мы тренируем нейронную сеть методом проб и ошибок, каждый раз корректируя вес синапсов.
    def train(self, training_set_inputs, training_set_outputs, number_of_training_iterations):
        for iteration in xrange(number_of_training_iterations):
            # Тренировочный набор передается нейронной сети (одному нейрону в нашем случае).
            output = self.think(training_set_inputs)
 
            # Вычисляем ошибку (разницу между желаемым выходом и выходом, предсказанным нейроном).
            error = training_set_outputs - output
 
            # Умножаем ошибку на входной сигнал и на градиент сигмоиды. В результате этого, те веса, в которых нейрон не уверен, будут откорректированы сильнее. Входные сигналы, которые равны нулю, не приводят к изменению веса. 
            adjustment = dot(training_set_inputs.T, error * self.__sigmoid_derivative(output))
 
            # Корректируем веса.
            self.synaptic_weights += adjustment
 
    # Заставляем наш нейрон подумать.
    def think(self, inputs):
        # Пропускаем входящие данные через нейрон. 
        return self.__sigmoid(dot(inputs, self.synaptic_weights))
 
 
if __name__ == "__main__":
 
    #Инициализируем нейронную сеть, состоящую из одного нейрона.
    neural_network = NeuralNetwork()
 
    print "Random starting synaptic weights: "
    print neural_network.synaptic_weights
 
    # Тренировочный набор для обучения. У нас это 4 примера, состоящих из 3 входящих значений и 1 выходящего значения. 
    training_set_inputs = array([[0, 0, 1], [1, 1, 1], [1, 0, 1], [0, 1, 1]])
    training_set_outputs = array([[0, 1, 1, 0]]).T
 
    # Обучаем нейронную сеть на тренировочном наборе, повторяя процесс 10000 раз, каждый раз корректируя веса.
    neural_network.train(training_set_inputs, training_set_outputs, 10000)
 
    print "New synaptic weights after training: "
    print neural_network.synaptic_weights
 
    # Тестируем нейрон на новом примере.
    print "Considering new situation [1, 0, 0] -> ?: "
    print neural_network.think(array([1, 0, 0]))

Этот код также можно найти на GitHub. Обратите внимание, что если вы используете Python 3, то вам будет нужно заменить команду  “xrange” на “range”.

Несколько финальных замечаний

Попробуйте теперь запустить нейронную сеть, используя в терминале эту команду:

python main.py

Результат должен быть таким:

Random starting synaptic weights:
[[-0.16595599]
[ 0.44064899]
[-0.99977125]]
 
New synaptic weights after training:
[[ 9.67299303]
[-0.2078435 ]
[-4.62963669]]
 
Considering new situation [1, 0, 0] -> ?:
[ 0.99993704]

Ура, мы построили простую нейронную сеть с помощью Python!

Сначала нейронная сеть задала себе случайные веса, затем обучилась на тренировочном наборе. После этого она предсказала в качестве ответа 0.99993704 для нового примера  [1, 0, 0]. Верный ответ был 1, так что это очень близко к правде!

Традиционные компьютерные программы обычно не способны обучаться. И это то, что делает нейронные сети таким поразительным инструментом: они способны учиться, адаптироваться и реагировать на новые обстоятельства. Точно так же, как и человеческий мозг.

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

Оригинал: How to build a simple neural network in 9 lines of Python code

Перевод: Ухарова Елена

Поделиться: