Как написать свою нейросеть на python

Время на прочтение
8 мин

Количество просмотров 502K

О чём статья

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

Дайте код!

X = np.array([ [0,0,1],[0,1,1],[1,0,1],[1,1,1] ])
y = np.array([[0,1,1,0]]).T
syn0 = 2*np.random.random((3,4)) - 1
syn1 = 2*np.random.random((4,1)) - 1
for j in xrange(60000):
    l1 = 1/(1+np.exp(-(np.dot(X,syn0))))
    l2 = 1/(1+np.exp(-(np.dot(l1,syn1))))
    l2_delta = (y - l2)*(l2*(1-l2))
    l1_delta = l2_delta.dot(syn1.T) * (l1 * (1-l1))
    syn1 += l1.T.dot(l2_delta)
    syn0 += X.T.dot(l1_delta)

Слишком сжато? Давайте разобьём его на более простые части.

Часть 1: Небольшая игрушечная нейросеть

Нейросеть, тренируемая через обратное распространение (backpropagation), пытается использовать входные данные для предсказания выходных.

Вход  	 		Выход
0 	0 	1 	0
1 	1 	1 	1
1 	0 	1 	1
0 	1 	1 	0

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

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

Нейросеть в два слоя

import numpy as np

# Сигмоида 
def nonlin(x,deriv=False):
    if(deriv==True):
        return f(x)*(1-f(x))
    return 1/(1+np.exp(-x))
    
# набор входных данных
X = np.array([  [0,0,1],
                [0,1,1],
                [1,0,1],
                [1,1,1] ])
    
# выходные данные            
y = np.array([[0,0,1,1]]).T

# сделаем случайные числа более определёнными
np.random.seed(1)

# инициализируем веса случайным образом со средним 0
syn0 = 2*np.random.random((3,1)) - 1

for iter in xrange(10000):

    # прямое распространение
    l0 = X
    l1 = nonlin(np.dot(l0,syn0))

    # насколько мы ошиблись?
    l1_error = y - l1

    # перемножим это с наклоном сигмоиды 
    # на основе значений в l1
    l1_delta = l1_error * nonlin(l1,True) # !!!

    # обновим веса
    syn0 += np.dot(l0.T,l1_delta) # !!!

print "Выходные данные после тренировки:"
print l1
Выходные данные после тренировки:
[[ 0.00966449]
 [ 0.00786506]
 [ 0.99358898]
 [ 0.99211957]]

Переменные и их описания.

X — матрица входного набор данных; строки – тренировочные примеры
y – матрица выходного набора данных; строки – тренировочные примеры
l0 – первый слой сети, определённый входными данными
l1 – второй слой сети, или скрытый слой
syn0 – первый слой весов, Synapse 0, объединяет l0 с l1.
«*» — поэлементное умножение – два вектора одного размера умножают соответствующие значения, и на выходе получается вектор такого же размера
«-» – поэлементное вычитание векторов
x.dot(y) – если x и y – это вектора, то на выходе получится скалярное произведение. Если это матрицы, то получится перемножение матриц. Если матрица только одна из них – это перемножение вектора и матрицы.

И это работает! Рекомендую перед прочтением объяснения поиграться немного с кодом и понять, как он работает. Он должен запускаться прямо как есть, в ipython notebook. С чем можно повозиться в коде:

  • сравните l1 после первой итерации и после последней
  • посмотрите на функцию nonlin.
  • посмотрите, как меняется l1_error
  • разберите строку 36 – основные секретные ингредиенты собраны тут (отмечена !!!)
  • разберите строку 39 – вся сеть готовится именно к этой операции (отмечена !!!)

Разберём код по строчкам

import numpy as np

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

def nonlin(x,deriv=False):

Наша нелинейность. Конкретно эта функция создаёт «сигмоиду». Она ставит в соответствие любое число значению от 0 до 1 и преобразовывает числа в вероятности, а также имеет несколько других полезных для тренировки нейросетей свойств.

image

if(deriv==True):

Эта функция также умеет выдавать производную сигмоиды (deriv=True). Это одно из её полезных свойств. Если выход функции – это переменная out, тогда производная будет out * (1-out). Эффективно.

X = np.array([  [0,0,1], …

Инициализация массива входных данных в виде numpy-матрицы. Каждая строка – тренировочный пример. Столбцы – это входные узлы. У нас получается 3 входных узла в сети и 4 тренировочных примера.

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

Инициализирует выходные данные. «.T» – функция переноса. После переноса у матрицы y есть 4 строки с одним столбцом. Как и в случае входных данных, каждая строка – это тренировочный пример, и каждый столбец (в нашем случае один) – выходной узел. У сети, получается, 3 входа и 1 выход.

np.random.seed(1)

Благодаря этому случайное распределение будет каждый раз одним и тем же. Это позволит нам проще отслеживать работу сети после внесения изменений в код.

syn0 = 2*np.random.random((3,1)) – 1

Матрица весов сети. syn0 означает «synapse zero». Так как у нас всего два слоя, вход и выход, нам нужна одна матрица весов, которая их свяжет. Её размерность (3, 1), поскольку у нас есть 3 входа и 1 выход. Иными словами, l0 имеет размер 3, а l1 – 1. Поскольку мы связываем все узлы в l0 со всеми узлами l1, нам требуется матрица размерности (3, 1).

Заметьте, что она инициализируется случайным образом, и среднее значение равно нулю. За этим стоит достаточно сложная теория. Пока просто примем это как рекомендацию. Также заметим, что наша нейросеть – это и есть эта самая матрица. У нас есть «слои» l0 и l1, но они представляют собой временные значения, основанные на наборе данных. Мы их не храним. Всё обучение хранится в syn0.

for iter in xrange(10000):

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

l0 = X

Первый слой, l0, это просто данные. В X содержится 4 тренировочных примера. Мы обработаем их все и сразу – это называется групповой тренировкой [full batch]. Итого мы имеем 4 разных строки l0, но их можно представить себе как один тренировочный пример – на этом этапе это не имеет значения (можно было загрузить их 1000 или 10000 без всяких изменений в коде).

l1 = nonlin(np.dot(l0,syn0))

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

В строке содержится два шага. Первый делает матричное перемножение l0 и syn0. Второй передаёт вывод через сигмоиду. Размерности у них следующие:

(4 x 3) dot (3 x 1) = (4 x 1)

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

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

l1_error = y - l1

Поскольку в l1 содержатся догадки, мы можем сравнить их разницу с реальностью, вычитая её l1 из правильного ответа y. l1_error – вектор из положительных и отрицательных чисел, характеризующий «промах» сети.

 l1_delta = l1_error * nonlin(l1,True)

А вот и секретный ингредиент. Эту строку нужно разбирать по частям.

Первая часть: производная

nonlin(l1,True)

l1 представляет три этих точки, а код выдаёт наклон линий, показанных ниже. Заметьте, что при больших значениях вроде x=2.0 (зелёная точка) и очень малые, вроде x=-1.0 (фиолетовая) линии имеют небольшой уклон. Самый большой угол у точки х=0 (голубая). Это имеет большое значение. Также отметьте, что все производные лежат в пределах от 0 до 1.

image

Полное выражение: производная, взвешенная по ошибкам

l1_delta = l1_error * nonlin(l1,True)

Математически существуют более точные способы, но в нашем случае подходит и этот. l1_error – это матрица (4,1). nonlin(l1,True) возвращает матрицу (4,1). Здесь мы поэлементно их перемножаем, и на выходе тоже получаем матрицу (4,1), l1_delta.

Умножая производные на ошибки, мы уменьшаем ошибки предсказаний, сделанных с высокой уверенностью. Если наклон линии был небольшим, то в сети содержится либо очень большое, либо очень малое значение. Если догадка в сети близка к нулю (х=0, у=0,5), то она не особенно уверенная. Мы обновляем эти неуверенные предсказания и оставляем в покое предсказания с высокой уверенностью, умножая их на величины, близкие к нулю.

syn0 += np.dot(l0.T,l1_delta)

Мы готовы к обновлению сети. Рассмотрим один тренировочный пример. В нём мы будем обновлять веса. Обновим крайний левый вес (9.5)

image

weight_update = input_value * l1_delta

Для крайнего левого веса это будет 1.0 * l1_delta. Предположительно, это лишь незначительно увеличит 9.5. Почему? Поскольку предсказание было уже достаточно уверенным, и предсказания были практически правильными. Небольшая ошибка и небольшой наклон линии означает очень небольшое обновление.

image

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

Понаблюдав за обновлением сети, вернёмся к нашим тренировочным данным. Когда и вход, и выход равны 1, мы увеличиваем вес между ними. Когда вход 1, а выход – 0, мы уменьшаем вес.

Вход              	Выход
0 	0 	1 	0
1 	1 	1 	1
1 	0 	1 	1
0 	1 	1 	0

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

Часть 2: задачка посложнее

Вход 	        Выход
0 	0 	1 	0
0 	1 	1 	1
1 	0 	1 	1
1 	1 	1 	0

Попробуем предсказать выходные данные на основе трёх входных столбцов данных. Ни один из входных столбцов не коррелирует на 100% с выходным. Третий столбец вообще ни с чем не связан, поскольку в нём всю дорогу содержатся единицы. Однако и тут можно увидеть схему – если в одном из двух первых столбцов (но не в обоих сразу) содержится 1, то результат также будет равен 1.

Это нелинейная схема, поскольку прямого соответствия столбцов один к одному не существует. Соответствие строится на комбинации входных данных, столбцов 1 и 2.

Интересно, что распознавание образов является очень похожей задачей. Если у вас есть 100 картинок одинакового размера, на которых изображены велосипеды и курительные трубки, присутствие на них определённых пикселей в определённых местах не коррелирует напрямую с наличием на изображении велосипеда или трубки. Статистически их цвет может казаться случайным. Но некоторые комбинации пикселей не случайны – те, что формируют изображение велосипеда (или трубки).

image
image

Стратегия

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

Вход  (l0) 	Скрытые веса (l1)	Выход (l2)
0 	0 	1 	0.1 	0.2 	0.5 	0.2 	0
0 	1 	1 	0.2 	0.6 	0.7 	0.1 	1
1 	0 	1 	0.3 	0.2 	0.3 	0.9 	1
1 	1 	1 	0.2 	0.1 	0.3 	0.8 	0

Случайным образом назначив веса, мы получим скрытые значения для слоя №1. Интересно, что у второго столбца скрытых весов уже есть небольшая корреляция с выходом. Не идеальная, но есть. И это тоже является важной частью процесса тренировки сети. Тренировка будет только усиливать эту корреляцию. Она будет обновлять syn1, чтобы назначить её соответствие выходным данным, и syn0, чтобы лучше получать данные со входа.

Нейросеть в три слоя

import numpy as np

def nonlin(x,deriv=False):
	if(deriv==True):
           return f(x)*(1-f(x))

	return 1/(1+np.exp(-x))
    
X = np.array([[0,0,1],
            [0,1,1],
            [1,0,1],
            [1,1,1]])
                
y = np.array([[0],
			[1],
			[1],
			[0]])

np.random.seed(1)

# случайно инициализируем веса, в среднем - 0
syn0 = 2*np.random.random((3,4)) - 1
syn1 = 2*np.random.random((4,1)) - 1

for j in xrange(60000):

	# проходим вперёд по слоям 0, 1 и 2
    l0 = X
    l1 = nonlin(np.dot(l0,syn0))
    l2 = nonlin(np.dot(l1,syn1))

    # как сильно мы ошиблись относительно нужной величины?
    l2_error = y - l2
    
    if (j% 10000) == 0:
        print "Error:" + str(np.mean(np.abs(l2_error)))
        
    # в какую сторону нужно двигаться?
    # если мы были уверены в предсказании, то сильно менять его не надо
    l2_delta = l2_error*nonlin(l2,deriv=True)

    # как сильно значения l1 влияют на ошибки в l2?
    l1_error = l2_delta.dot(syn1.T)
    
    # в каком направлении нужно двигаться, чтобы прийти к l1?
    # если мы были уверены в предсказании, то сильно менять его не надо
    l1_delta = l1_error * nonlin(l1,deriv=True)

    syn1 += l1.T.dot(l2_delta)
    syn0 += l0.T.dot(l1_delta)
Error:0.496410031903
Error:0.00858452565325
Error:0.00578945986251
Error:0.00462917677677
Error:0.00395876528027
Error:0.00351012256786

Переменные и их описания

X — матрица входного набор данных; строки – тренировочные примеры
y – матрица выходного набора данных; строки – тренировочные примеры
l0 – первый слой сети, определённый входными данными
l1 – второй слой сети, или скрытый слой
l2 – финальный слой, это наша гипотеза. По мере тренировки должен приближаться к правильному ответу
syn0 – первый слой весов, Synapse 0, объединяет l0 с l1.
syn1 – второй слой весов, Synapse 1, объединяет l1 с l2.
l2_error – промах сети в количественном выражении
l2_delta – ошибка сети, в зависимости от уверенности предсказания. Почти совпадает с ошибкой, за исключением уверенных предсказаний
l1_error – взвешивая l2_delta весами из syn1, мы подсчитываем ошибку в среднем/скрытом слое
l1_delta – ошибки сети из l1, масштабируемые по увеернности предсказаний. Почти совпадает с l1_error, за исключением уверенных предсказаний

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

l1_error = l2_delta.dot(syn1.T)

Использует ошибки, взвешенные по уверенности предсказаний из l2, чтобы подсчитать ошибку для l1. Получаем, можно сказать, ошибку, взвешенную по вкладам – мы подсчитываем, какой вклад в ошибки в l2 вносят значения в узлах l1. Этот шаг и называется обратным распространением ошибок. Затем мы обновляем syn0, используя тот же алгоритм, что и в варианте с нейросетью из двух слоёв.

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

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

Содержание статьи

  • Создание нейронных блоков
  • Простой пример работы с нейронами в Python
  • Создание нейрона с нуля в Python
  • Пример сбор нейронов в нейросеть
  • Пример прямого распространения FeedForward
  • Создание нейронной сети прямое распространение FeedForward
  • Пример тренировки нейронной сети — минимизация потерь, Часть 1
  • Пример подсчета потерь в тренировки нейронной сети
  • Python код среднеквадратической ошибки (MSE)
  • Тренировка нейронной сети — многовариантные исчисления, Часть 2
  • Пример подсчета частных производных
  • Тренировка нейронной сети: Стохастический градиентный спуск
  • Создание нейронной сети с нуля на Python

Создание нейронных блоков

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

Создание нейронных блоков

Здесь происходят три вещи. Во-первых, каждый вход умножается на вес (на схеме обозначен красным):

Создание нейронных блоков

Затем все взвешенные входы складываются вместе со смещением b (на схеме обозначен зеленым):

neural-networks-3

Наконец, сумма передается через функцию активации (на схеме обозначена желтым):

Нейроны функция активации

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

Функция сигмоида

Функция сигмоида выводит только числа в диапазоне (0, 1). Вы можете воспринимать это как компрессию от (−∞, +∞) до (0, 1). Крупные отрицательные числа становятся ~0, а крупные положительные числа становятся ~1.

Предположим, у нас есть нейрон с двумя входами, который использует функцию активации сигмоида и имеет следующие параметры:

формулы функции сигмоида

w = [0,1] — это просто один из способов написания w1 = 0, w2 = 1 в векторной форме. Присвоим нейрону вход со значением x = [2, 3]. Для более компактного представления будет использовано скалярное произведение.

Формула скалярного произведения

С учетом, что вход был x = [2, 3], вывод будет равен 0.999. Вот и все. Такой процесс передачи входных данных для получения вывода называется прямым распространением, или feedforward.

Создание нейрона с нуля в Python

Есть вопросы по Python?

На нашем форуме вы можете задать любой вопрос и получить ответ от всего нашего сообщества!

Telegram Чат & Канал

Вступите в наш дружный чат по Python и начните общение с единомышленниками! Станьте частью большого сообщества!

Паблик VK

Одно из самых больших сообществ по Python в социальной сети ВК. Видео уроки и книги для вас!

Приступим к имплементации нейрона. Для этого потребуется использовать NumPy. Это мощная вычислительная библиотека Python, которая задействует математические операции:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

import numpy as np

def sigmoid(x):

    # Наша функция активации: f(x) = 1 / (1 + e^(-x))

    return 1 / (1 + np.exp(x))

class Neuron:

    def __init__(self, weights, bias):

        self.weights = weights

        self.bias = bias

    def feedforward(self, inputs):

        # Вводные данные о весе, добавление смещения

        # и последующее использование функции активации

        total = np.dot(self.weights, inputs) + self.bias

        return sigmoid(total)

weights = np.array([0, 1])  # w1 = 0, w2 = 1

bias = 4  # b = 4

n = Neuron(weights, bias)

x = np.array([2, 3])  # x1 = 2, x2 = 3

print(n.feedforward(x))  # 0.9990889488055994

Узнаете числа? Это тот же пример, который рассматривался ранее. Ответ полученный на этот раз также равен 0.999.

Пример сбор нейронов в нейросеть

Нейронная сеть по сути представляет собой группу связанных между собой нейронов. Простая нейронная сеть выглядит следующим образом:

Схема нейронной сети

На вводном слое сети два входа – x1 и x2. На скрытом слое два нейтрона — h1 и h2. На слое вывода находится один нейрон – о1. Обратите внимание на то, что входные данные для о1 являются результатами вывода h1 и h2. Таким образом и строится нейросеть.

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

Пример прямого распространения FeedForward

Давайте используем продемонстрированную выше сеть и представим, что все нейроны имеют одинаковый вес w = [0, 1], одинаковое смещение b = 0 и ту же самую функцию активации сигмоида. Пусть h1, h2 и o1 сами отметят результаты вывода представленных ими нейронов.

Что случится, если в качестве ввода будет использовано значение х = [2, 3]?

Пример прямого распространения feedforward

Результат вывода нейронной сети для входного значения х = [2, 3] составляет 0.7216. Все очень просто.

Нейронная сеть может иметь любое количество слоев с любым количеством нейронов в этих слоях.

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

Создание нейронной сети прямое распространение FeedForward

Далее будет показано, как реализовать прямое распространение feedforward в отношении нейронной сети. В качестве опорной точки будет использована следующая схема нейронной сети:

Схема нейронной сети

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

import numpy as np

# … Здесь код из предыдущего раздела

class OurNeuralNetwork:

    «»»

    Нейронная сеть, у которой:

        — 2 входа

        — 1 скрытый слой с двумя нейронами (h1, h2)

        — слой вывода с одним нейроном (o1)

    У каждого нейрона одинаковые вес и смещение:

        — w = [0, 1]

        — b = 0

    «»»

    def __init__(self):

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

        bias = 0

        # Класс Neuron из предыдущего раздела

        self.h1 = Neuron(weights, bias)

        self.h2 = Neuron(weights, bias)

        self.o1 = Neuron(weights, bias)

    def feedforward(self, x):

        out_h1 = self.h1.feedforward(x)

        out_h2 = self.h2.feedforward(x)

        # Вводы для о1 являются выводами h1 и h2

        out_o1 = self.o1.feedforward(np.array([out_h1, out_h2]))

        return out_o1

network = OurNeuralNetwork()

x = np.array([2, 3])

print(network.feedforward(x))  # 0.7216325609518421

Мы вновь получили 0.7216. Похоже, все работает.

Пример тренировки нейронной сети — минимизация потерь, Часть 1

Предположим, у нас есть следующие параметры:

Имя/Name Вес/Weight (фунты) Рост/Height (дюймы) Пол/Gender 
Alice 133 65 F
Bob 160 72 M
Charlie 152 70 M
Diana 120 60 F

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

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

Мужчины Male будут представлены как 0, а женщины Female как 1. Для простоты представления данные также будут несколько смещены.

Имя/Name Вес/Weight (минус 135) Рост/Height (минус 66) Пол/Gender 
Alice -2 -1 1
Bob 25 6 0
Charlie 17 4 0
Diana -15 -6 1

Для оптимизации здесь произведены произвольные смещения 135 и 66. Однако, обычно для смещения выбираются средние показатели.

Потери

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

В данном случае будет использоваться среднеквадратическая ошибка (MSE) потери:

Среднеквадратичная ошибка формула

Давайте разберемся:

  • n – число рассматриваемых объектов, которое в данном случае равно 4. Это Alice, Bob, Charlie и Diana;
  • y – переменные, которые будут предсказаны. В данном случае это пол человека;
  • ytrue – истинное значение переменной, то есть так называемый правильный ответ. Например, для Alice значение ytrue будет 1, то есть Female;
  • ypred – предполагаемое значение переменной. Это результат вывода сети.

(ytrue - ypred)2 называют квадратичной ошибкой (MSE). Здесь функция потери просто берет среднее значение по всем квадратичным ошибкам. Отсюда и название ошибки. Чем лучше предсказания, тем ниже потери.

Лучшие предсказания = Меньшие потери.

Тренировка нейронной сети = стремление к минимизации ее потерь.

Пример подсчета потерь в тренировки нейронной сети

Скажем, наша сеть всегда выдает 0. Другими словами, она уверена, что все люди — Мужчины. Какой будет потеря?

Имя/Name ytrue ypred (ytrue — ypred)2
Alice 1 0 1
Bob 0 0 0
Charlie 0 0 0
Diana 1 0 1

Нейронные сети подсчет потерь

Python код среднеквадратической ошибки (MSE)

Ниже представлен код для подсчета потерь:

import numpy as np

def mse_loss(y_true, y_pred):

    # y_true и y_pred являются массивами numpy с одинаковой длиной

    return ((y_true y_pred) ** 2).mean()

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

y_pred = np.array([0, 0, 0, 0])

print(mse_loss(y_true, y_pred))  # 0.5

При возникновении сложностей с пониманием работы кода стоит ознакомиться с quickstart в NumPy для операций с массивами.

Тренировка нейронной сети — многовариантные исчисления, Часть 2

Текущая цель понятна – это минимизация потерь нейронной сети. Теперь стало ясно, что повлиять на предсказания сети можно при помощи изменения ее веса и смещения. Однако, как минимизировать потери?

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

Для простоты давайте представим, что в наборе данных рассматривается только Alice:

Имя/Name Вес/Weight (минус 135) Рост/Height (минус 66) Пол/Gender
Alice -2 -1 1

Затем потеря среднеквадратической ошибки будет просто квадратической ошибкой для Alice:

Тренировка нейронной сети формулы

Еще один способ понимания потери – представление ее как функции веса и смещения. Давайте обозначим каждый вес и смещение в рассматриваемой сети:

Тренировка нейронной сети схема

Затем можно прописать потерю как многовариантную функцию:

Вариативная функция формула

Представим, что нам нужно немного отредактировать w1. В таком случае, как изменится потеря L после внесения поправок в w1?

На этот вопрос может ответить частная производная Частная производная. Как же ее вычислить?

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

Для начала, давайте перепишем частную производную в контексте Частная производная формулы:

Тренировка нейронной сетиДанные вычисления возможны благодаря дифференцированию сложной функции. 

Подсчитать Вычисления настройка нейронной сети можно благодаря вычисленной выше L = (1 - ypred)2:

Формулы нейронные сети

Теперь, давайте определим, что делать с Вычисления частной производной. Как и ранее, позволим h1, h2, o1 стать результатами вывода нейронов, которые они представляют. Дальнейшие вычисления:

Тренировка нейронной сети и функция сигмоидаКак было указано ранее, здесь f является функцией активации сигмоида.

Так как w1 влияет только на h1, а не на h2, можно записать:

Формулы нейронные сетиИспользование дифференцирования сложной функции.

Те же самые действия проводятся для Вычисления частной производной:

Вычисления частной производнойЕще одно использование дифференцирования сложной функции.

В данном случае х1 — вес, а х2 — рост. Здесь f′(x) как производная функции сигмоида встречается во второй раз. Попробуем вывести ее:

Производная функции сигмоида

Функция f'(x) в таком виде будет использована несколько позже.

Вот и все. Теперь Формулы нейронной сети разбита на несколько частей, которые будут оптимальны для подсчета:

Система подсчета частных производных

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

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

Пример подсчета частных производных

В данном примере также будет задействована только Alice:

Имя/Name Вес/Weight (минус 135) Рост/Height (минус 66) Пол/Gender
Alice -2 -1 1

Здесь вес будет представлен как 1, а смещение как 0. Если выполним прямое распространение (feedforward) через сеть, получим:

Пример подсчета частных производных

Выдачи нейронной сети ypred = 0.524. Это дает нам слабое представление о том, рассматривается мужчина Male (0), или женщина Female (1). Давайте подсчитаем Формулы нейронной сети:

Пример подсчета частных производных

Напоминание: мы вывели f '(x) = f (x) * (1 - f (x)) ранее для нашей функции активации сигмоида.

У нас получилось! Результат говорит о том, что если мы собираемся увеличить w1, L немного увеличивается в результате.

Тренировка нейронной сети: Стохастический градиентный спуск

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

Тренировка нейронов стохастический градиентный спуск

η является константой под названием оценка обучения, что контролирует скорость обучения. Все что мы делаем, так это вычитаем Формулы нейронной сети из w1:

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

Наш процесс тренировки будет выглядеть следующим образом:

  1. Выбираем один пункт из нашего набора данных. Это то, что делает его стохастическим градиентным спуском. Мы обрабатываем только один пункт за раз;
  2. Подсчитываем все частные производные потери по весу или смещению. Это может быть Формулы нейронной сети, Формулы нейронной сети и так далее;
  3. Используем уравнение обновления для обновления каждого веса и смещения;
  4. Возвращаемся к первому пункту.

Давайте посмотрим, как это работает на практике.

Создание нейронной сети с нуля на Python

Наконец, мы реализуем готовую нейронную сеть:

Имя/Name Вес/Weight (минус 135) Рост/Height (минус 66) Пол/Gender
Alice -2 -1 1
Bob 25 6 0
Charlie 17 4 0
Diana -15 -6 1

Тренировка нейронной сети схема

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

import numpy as np

def sigmoid(x):

    # Функция активации sigmoid:: f(x) = 1 / (1 + e^(-x))

    return 1 / (1 + np.exp(x))

def deriv_sigmoid(x):

    # Производная от sigmoid: f'(x) = f(x) * (1 — f(x))

    fx = sigmoid(x)

    return fx * (1 fx)

def mse_loss(y_true, y_pred):

    # y_true и y_pred являются массивами numpy с одинаковой длиной

    return ((y_true y_pred) ** 2).mean()

class OurNeuralNetwork:

    «»»

    Нейронная сеть, у которой:

        — 2 входа

        — скрытый слой с двумя нейронами (h1, h2)

        — слой вывода с одним нейроном (o1)

    *** ВАЖНО ***:

    Код ниже написан как простой, образовательный. НЕ оптимальный.

    Настоящий код нейронной сети выглядит не так. НЕ ИСПОЛЬЗУЙТЕ этот код.

    Вместо этого, прочитайте/запустите его, чтобы понять, как работает эта сеть.

    «»»

    def __init__(self):

        # Вес

        self.w1 = np.random.normal()

        self.w2 = np.random.normal()

        self.w3 = np.random.normal()

        self.w4 = np.random.normal()

        self.w5 = np.random.normal()

        self.w6 = np.random.normal()

        # Смещения

        self.b1 = np.random.normal()

        self.b2 = np.random.normal()

        self.b3 = np.random.normal()

    def feedforward(self, x):

        # x является массивом numpy с двумя элементами

        h1 = sigmoid(self.w1 * x[0] + self.w2 * x[1] + self.b1)

        h2 = sigmoid(self.w3 * x[0] + self.w4 * x[1] + self.b2)

        o1 = sigmoid(self.w5 * h1 + self.w6 * h2 + self.b3)

        return o1

    def train(self, data, all_y_trues):

        «»»

        — data is a (n x 2) numpy array, n = # of samples in the dataset.

        — all_y_trues is a numpy array with n elements.

            Elements in all_y_trues correspond to those in data.

        «»»

        learn_rate = 0.1

        epochs = 1000 # количество циклов во всём наборе данных

        for epoch in range(epochs):

            for x, y_true in zip(data, all_y_trues):

                # — Выполняем обратную связь (нам понадобятся эти значения в дальнейшем)

                sum_h1 = self.w1 * x[0] + self.w2 * x[1] + self.b1

                h1 = sigmoid(sum_h1)

                sum_h2 = self.w3 * x[0] + self.w4 * x[1] + self.b2

                h2 = sigmoid(sum_h2)

                sum_o1 = self.w5 * h1 + self.w6 * h2 + self.b3

                o1 = sigmoid(sum_o1)

                y_pred = o1

                # — Подсчет частных производных

                # — Наименование: d_L_d_w1 представляет «частично L / частично w1»

                d_L_d_ypred = 2 * (y_true y_pred)

                # Нейрон o1

                d_ypred_d_w5 = h1 * deriv_sigmoid(sum_o1)

                d_ypred_d_w6 = h2 * deriv_sigmoid(sum_o1)

                d_ypred_d_b3 = deriv_sigmoid(sum_o1)

                d_ypred_d_h1 = self.w5 * deriv_sigmoid(sum_o1)

                d_ypred_d_h2 = self.w6 * deriv_sigmoid(sum_o1)

                # Нейрон h1

                d_h1_d_w1 = x[0] * deriv_sigmoid(sum_h1)

                d_h1_d_w2 = x[1] * deriv_sigmoid(sum_h1)

                d_h1_d_b1 = deriv_sigmoid(sum_h1)

                # Нейрон h2

                d_h2_d_w3 = x[0] * deriv_sigmoid(sum_h2)

                d_h2_d_w4 = x[1] * deriv_sigmoid(sum_h2)

                d_h2_d_b2 = deriv_sigmoid(sum_h2)

                # — Обновляем вес и смещения

                # Нейрон h1

                self.w1 -= learn_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_w1

                self.w2 -= learn_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_w2

                self.b1 -= learn_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_b1

                # Нейрон h2

                self.w3 -= learn_rate * d_L_d_ypred * d_ypred_d_h2 * d_h2_d_w3

                self.w4 -= learn_rate * d_L_d_ypred * d_ypred_d_h2 * d_h2_d_w4

                self.b2 -= learn_rate * d_L_d_ypred * d_ypred_d_h2 * d_h2_d_b2

                # Нейрон o1

                self.w5 -= learn_rate * d_L_d_ypred * d_ypred_d_w5

                self.w6 -= learn_rate * d_L_d_ypred * d_ypred_d_w6

                self.b3 -= learn_rate * d_L_d_ypred * d_ypred_d_b3

            # — Подсчитываем общую потерю в конце каждой фазы

            if epoch % 10 == 0:

                y_preds = np.apply_along_axis(self.feedforward, 1, data)

                loss = mse_loss(all_y_trues, y_preds)

                print(«Epoch %d loss: %.3f» % (epoch, loss))

# Определение набора данных

data = np.array([

    [2, 1],    # Alice

    [25, 6],     # Bob

    [17, 4],     # Charlie

    [15, 6], # Diana

])

all_y_trues = np.array([

    1, # Alice

    0, # Bob

    0, # Charlie

    1, # Diana

])

# Тренируем нашу нейронную сеть!

network = OurNeuralNetwork()

network.train(data, all_y_trues)

Вы можете поэкспериментировать с этим кодом самостоятельно. Он также доступен на Github.

Наши потери постоянно уменьшаются по мере того, как учится нейронная сеть:

График потери нейронной сети к фазам

Теперь мы можем использовать нейронную сеть для предсказания полов:

# Делаем предсказания

emily = np.array([7, 3])  # 128 фунтов, 63 дюйма

frank = np.array([20, 2])  # 155 фунтов, 68 дюймов

print(«Emily: %.3f» % network.feedforward(emily))  # 0.951 — F

print(«Frank: %.3f» % network.feedforward(frank))  # 0.039 — M

Что теперь?

У вас все получилось. Вспомним, как мы это делали:

  • Узнали, что такое нейроны, как создать блоки нейронных сетей;
  • Использовали функцию активации сигмоида в отношении нейронов;
  • Увидели, что по сути нейронные сети — это просто набор нейронов, связанных между собой;
  • Создали набор данных с параметрами вес и рост в качестве входных данных (или функций), а также использовали пол в качестве вывода (или маркера);
  • Узнали о функциях потерь и среднеквадратичной ошибке (MSE);
  • Узнали, что тренировка нейронной сети — это минимизация ее потерь;
  • Использовали обратное распространение для вычисления частных производных;
  • Использовали стохастический градиентный спуск (SGD) для тренировки нейронной сети.

Подробнее о построении нейронной сети прямого распросранения Feedforward можно ознакомиться в одной из предыдущих публикаций.

Спасибо за внимание!

Являюсь администратором нескольких порталов по обучению языков программирования Python, Golang и Kotlin. В составе небольшой команды единомышленников, мы занимаемся популяризацией языков программирования на русскоязычную аудиторию. Большая часть статей была адаптирована нами на русский язык и распространяется бесплатно.

E-mail: vasile.buldumac@ati.utm.md

Образование
Universitatea Tehnică a Moldovei (utm.md)

  • 2014 — 2018 Технический Университет Молдовы, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
  • 2018 — 2020 Технический Университет Молдовы, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»

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

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))))

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

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

Рис. 1

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

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

Рис. 2

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

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

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

Рис. 3

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

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

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

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

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

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

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

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

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

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

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

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

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

Хотя мы не будем использовать специальные библиотеки для нейронных сетей, мы импортируем следующие 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, так что это очень близко к правде!

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

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

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

Фреймворк TensorFlow — это относительно простой инструмент, который позволяет быстро создавать нейросети любой сложности. Он очень дружелюбен для начинающих, потому что содержит много примеров и уже готовых моделей машинного обучения, которые можно встроить в любое приложение. А продвинутым разработчикам TensorFlow предоставляет тонкие настройки и API для ускоренного обучения.

TensorFlow поддерживает несколько языков программирования. Главный из них — это Python. Кроме того, есть отдельные пакеты для C/C++, Golang и Java. А ещё — форк TensorFlow.js для исполнения кода на стороне клиента, в браузере, на JavaScript.

Этим возможности фреймворка TensorFlow не ограничиваются. Библиотеку также можно использовать для обучения моделей на смартфонах и умных устройствах (TensorFlow Lite) и создания корпоративных нейросетей (TensorFlow Extended).

Чтобы создать простую нейросеть на TensorFlow, достаточно понимать несколько основных принципов:

  • что такое машинное обучение;
  • как обучаются нейросети и какие методы для этого используются;
  • как весь процесс обучения выглядит в TensorFlow.

О каждом из этих пунктов мы расскажем подробнее ниже.

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

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

Обычное программирование — это когда входные данные поступают в программу, а она выдаёт результат
Иллюстрация: Оля Ежак для Skillbox Media

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

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

В машинном обучении всё работает по-другому: мы отдаём нейросети результат, а она выдаёт нам алгоритм
Иллюстрация: Оля Ежак для Skillbox Media

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

Чтобы начать пользоваться фреймворком TensorFlow, можно выбрать один из вариантов:

  • установить его на компьютер;
  • воспользоваться облачным сервисом Google Colab.

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

Заходим на сайт Google Colab и создаём новый notebook:

Создаём новое пространство
Скриншот: Skillbox Media

У нас появится новое пространство, в котором мы и будем писать весь код. Сверху слева можно изменить название документа:

Мы создали новый документ. Его можно переименовать сверху слева, если нужно
Скриншот: Skillbox Media

Google Colab состоит из ячеек с кодом или текстом. Чтобы создать ячейку с кодом, нужно нажать на кнопку + Code. Ниже появится ячейка, где можно писать Python‑код:

Создаём ячейку с кодом. Рекомендуем запомнить горячие клавиши
Скриншот: Skillbox Media

Теперь нам нужно проверить, что всё работает. Для этого попробуем экспортировать библиотеку в Google Colab. Делается это через команду import tensorflow as tf:

Мы импортировали библиотеку TensorFlow. Зелёная галочка слева означает, что всё работает
Скриншот: Skillbox Media

Всё готово. Рассмотрим второй способ, как можно подключить TensorFlow прямо на компьютере.

Чтобы использовать библиотеку TensorFlow на компьютере, её нужно установить через пакетный менеджер PIP.

Открываем терминал и вводим следующую команду:

pip install --upgrade pip

Мы обновили PIP до последней версии. Теперь скачиваем сам TensorFlow:

pip install tensorflow

Если всё прошло успешно, теперь вы можете подключать TensorFlow в Python-коде у вас на компьютере с помощью команды:

import tensorflow as tf

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

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

Допустим, у нас есть два набора чисел X и Y:

X: -1 0 1 2 3 4
Y: -4 1 6 11 16 21

Мы видим, что их значения связаны по какому-то правилу. Это правило: Y = 5X + 1. Но чтобы компьютер это понял, ему нужно научиться сопоставлять входные данные — X — с результатом — Y. У него сначала могут получаться странные уравнения типа: 2X — 5, 8X + 1, 4X + 2, 5X — 1. Но, обучившись немного, он найдёт наиболее близкую к исходной формулу.

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

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

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

import tensorflow as tf
import numpy as np
from tensorflow import keras

Первая зависимость — это наша библиотека TensorFlow, название которой мы сокращаем до tf, чтобы было удобнее её вызывать в программе. NumPy — это библиотека для эффективной работы с массивами чисел. Можно было, конечно, использовать и обычные списки, но NumPy будет работать намного быстрее, поэтому мы берём его. И последнее — Keras, встроенная в Tensorflow библиотека, которая умеет обучать нейросети.

Теперь создадим самую простую модель:

model = tf.keras.Sequential([keras.layers.Dense(units=1, input_shape=[1])])

Разберём код подробнее. Sequential — это тип нейросети, означающий, что процесс обучения будет последовательным. Это стандартный процесс обучения для простых нейросетей: в нём она сначала делает предсказания, затем тестирует их и сравнивает с результатом, а в конце — корректирует ошибки.

keras.layers.Dense — указывает на то, что мы хотим создать слой в нашей модели. Слой — это место, куда мы будем складывать нейроны, которые запоминают информацию об ошибках и которые отвечают за «умственные способности» нейросети. Dense — это тип слоя, который использует специальные алгоритмы для обучения.

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

  • units=1 означает, что модель состоит из одного нейрона, который будет запоминать информацию о предыдущих предположениях;
  • input_shape=[1] говорит о том, что на вход будет подаваться одно число, по которому нейросеть будет строить зависимости двух рядов чисел: X и Y.

Модель мы создали, теперь давайте её скомпилируем:

model.compile(optimizer='sgd', loss='mean_squared_error')

Здесь появляются два важных для машинного обучения элемента: функция оптимизации и функция потерь. Обе они нужны, чтобы постепенно стремиться к более точным результатам.

Функция потерь анализирует, насколько правильно нейросеть дала предсказание. А функция оптимизации исправляет эти предсказания в сторону более корректных результатов.

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

Теперь давайте зададим наборы данных:

xs = np.array([-1.0, 0.0, 1.0, 2.0, 3.0, 4.0], dtype=float)
ys = np.array([-4.0, 1.0, 6.0, 11.0, 16.0, 21.0], dtype=float)

Как видно, это обычные массивы чисел, которые мы передадим модели на обучение:

model.fit(xs, ys, epochs=500)

Функция fit как раз занимается обучением. Она берёт набор входных данных — xs — и сопоставляет с набором правильных результатов — ys. И так нейросеть обучается в течение 500 итераций — epochs=500. Мы использовали 500 итераций, чтобы наверняка прийти к правильному результату. Суть простая: чем больше итераций обучения, тем точнее будут результаты (однако улучшение точности с каждым повтором будет всё меньше и меньше).

На каждой итерации модель проходит следующие шаги:

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

После того как вы запустите предыдущую строчку кода, модель начнёт обучение
Скриншот: Skillbox Media

Можно заметить, что на каждой итерации TensorFlow выводит, насколько нейросеть сильно ошиблась — loss. Если это число уменьшается, то есть стремится к нулю, значит, она действительно обучается и с каждым шагом улучшает свои прогнозы.

Теперь давайте что-нибудь предскажем и поймём, насколько точно наша нейросеть обучилась:

print(model.predict([10.0]))

Мы вызываем у модели метод predict, который получает на вход элемент для предсказания. Результат будет таким:

Модель сделала предсказание, но оно оказалось не совсем точным
Скриншот: Skillbox Media

Получилось странно — мы ожидали, что будет число 51 (потому что подставили 10 в выражение 5X + 1) — но на выходе нейросеть выдала число 50.98739. А всё потому, что модель нашла очень близкую, но не до конца точную формулу — например, 4.891X + 0.993. Это одна из особенностей машинного обучения.

А ещё многое зависит от выбранного метода оптимизации — то есть того, как нейросеть корректирует формулу, чтобы прийти к нужным результатам. В библиотеке TensorFlow можно найти разные способы оптимизации, и на выходе каждой из них результаты могут различаться. Однако эта тема выходит за рамки нашей статьи — здесь уже необходимо достаточно глубоко погружаться в процесс машинного обучения и разбираться, как именно устроена оптимизация.

Если вы вдруг подумали, что можно просто увеличить число итераций и точность станет выше, то это справедливо лишь отчасти. У каждого метода оптимизации есть своя точность, до которой нейросеть может дойти. Например, она может вычислять результат с точностью до 0.00000001, однако абсолютно верным и точным результат не будет никогда. А значит, и абсолютно точного значения формулы мы никогда не получим — просто из-за погрешности вычислений и особенности функционирования компьютеров. Но если условно установить число итераций в миллиард, можно получить примерно такую формулу:

4.9999999999997X + 0.9999999999991

Она очень близка к настоящей, хотя и не равна ей. Поэтому математики и специалисты по машинному обучению решили, что будут считать две формулы равными, если значения их вычислений меньше, чем заранее заданная величина погрешности — например, 0.0000001. И если мы подставим в формулу выше и в настоящую вместо X число 5, то получим следующее:

5 · 5 + 1 = 26

4.9999999997 · 5 + 0.9999999991 = 25.9999999976

Если мы из первого числа вычтем второе, то получим:

26 — 25.9999999976 = 0.0000000024

А так как изначально мы сказали, что два числа будут равны, если разница между ними меньше 0.0000001, то обе формулы могут считаться идентичными, потому что получившаяся у нас на практике погрешность 0.0000000024 меньше допустимого значения, о котором мы договорились, — то есть 0.0000001. Вот такая интересная математика.

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

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

И ещё: нейросети — это на самом деле скучно, медленно и не очень эффектно в настройке. Мы привыкли сразу видеть классный и красивый результат, а то, что было до этого, нам обычно не показывают. Эта статья работает наоборот: долго показывает весь процесс, а финальный результат получается за пару секунд.

В этом суть нейросетей: долгая и кропотливая работа ради эффектной концовки.

Что сделаем

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

Как собрать и настроить такой датасет — расскажем в другой раз.

Что понадобится

Python версии 3.8 и выше, обязательно под архитектуру x64. Если взять 32-разрядную версию, то нужная в проекте библиотека tensorflow работать не будет. Мы использовали версию 3.9.7.

Остальное установим в процессе. Главное — рабочий Python (по ссылке — как его установить). 

👉 Все команды, которые есть в проекте, мы будем запускать в командной строке. Чтобы не было ошибок и затыков, лучше всего запустить её от имени администратора (в Windows) или с правами суперпользователя root (в Mac OS и Linux).

Создаём виртуальное окружение

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

Чтобы подключить себе виртуальное окружение, запускаем команду:

pip install --upgrade virtualenv

Запускаем нейросеть на домашнем компьютере

Теперь можно устанавливать окружение. Для этого придумаем ему название — мы выбрали tell-me, но вы можете выбрать любое другое:

virtualenv --system-site-packages tell-me

Запускаем нейросеть на домашнем компьютере

Запускаем окружение:

source tell-me/bin/activate (если у вас мак или линукс)
tell-mescriptsactivate (если у вас виндоус)

Эта команда создаст папку на компьютере (путь к ней можно посмотреть на предыдущем скриншоте на третьей строке, параметр «dest») и запустит в ней виртуальное окружение:

Запускаем нейросеть на домашнем компьютере

Устанавливаем tensorflow

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

Для установки пишем команду:

pip install tensorflow

Запускаем нейросеть на домашнем компьютере

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

Чтобы убедиться, что библиотека установилась правильно и работает штатно, проверим её простым тестом.

1. Пишем команду:

python

2. Начало командной строки поменялось на >>> — это значит, питон готов к приёму своих команд. Пишем по очереди такое:

hello = tf.constant('Hello, TensorFlow')
sess = tf.compat.v1.Session()
print(sess.run(hello))

Если в ответ питон нам выдал что-то вроде ‘Hello, TensorFlow’, это значит, что мы всё сделали правильно.

Запускаем нейросеть на домашнем компьютере

Устанавливаем классификатор

Классификатор в нейросетях — это алгоритм, который смотрит на объекты и пытается понять, к какой категории их отнести. То есть классифицировать.

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

  1. Качаем архив с классификатором.
  2. Распаковываем архив.

Запускаем нейросеть на домашнем компьютере

3.Копируем содержимое архива в папку tell-me. Если вы выбрали другое название для проекта, замените tell-me на своё название.

Добавляем фото для обучения

Скачиваем уже собранный датасет с цветами, распаковываем его и копируем в папку tell-me → tf_files.

Запускаем нейросеть на домашнем компьютере

Адаптируем скрипты под актуальную версию tensorflow

👉 На момент написания статьи актуальная версия tensorflow — 2.0. Но скрипты и алгоритмы, которые мы используем, заточены под старую версию, поэтому нужно применить немного магии автозамены:

  1. Переходим в каталог tell-me/scripts и находим файл retrain.py.
  2. Открываем его в любом редакторе кода, например Sublime Text 3.
  3. Нажимаем Ctrl + H или Command + H — включится режим поиска и автозамены текста.
  4. Первая строка (что заменить) → пишем tf. (с точкой).
  5. Вторая строка (на что заменить) → пишем tf.compat.v1. (тоже с точкой в конце).
  6. Нажимаем Replace All (Заменить всё).
  7. То же самое делаем в файле label-image.py.
  8. В том же файле label-image.py добавляем после строки 25 «import tensorflow as tf» такую строку:

tf.compat.v1.disable_eager_execution()

Благодаря этому колдунству мы заставим старый скрипт работать с новой библиотекой.

Обучаем нейросеть

  1. В командной строке командой cd переходим в папку tell-me (или в другую, если у вас проект называется по-другому).
  2. Запускаем команду:

python scripts/retrain.py
--output_graph=tf_files/retrained_graph.pb
--output_labels=tf_files/retrained_labels.txt
--image_dir=tf_files/flower_photos

Пошёл процесс обучения. В нём 4000 этапов, по времени занимает примерно 20 минут. За это время нейросеть обработает около 250 фото (это очень мало для нейросети) и научится отличать розу от ландышей:

Запускаем нейросеть на домашнем компьютере

Запускаем нейросеть

Чтобы проверить работу нашей нейросети, скачиваем любой файл с розой из интернета, кладём его в папку tell-me (или как у вас она называется) и пишем такую команду:

python scripts/label_image.py --image image.jpg

Нейросеть думает, а потом выдаёт ответ в виде процентов. В нашем случае она на 98% уверена, что это роза:

Запускаем нейросеть на домашнем компьютере

А вот как нейросеть реагирует на фото Цукерберга:

Запускаем нейросеть на домашнем компьютере

50% — что на фото тюльпан, и на 18% — что это одуванчик. А всё потому, что она умеет различать только 5 видов цветов, а не всяких там цукербергов.

Вёрстка:

Кирилл Климентьев

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

Шаг 0. Разбираемся, как устроены нейронные сети

Проще всего разобраться с принципами работы нейронных сетей можно на примере Teachable Machine — образовательного проекта Google.

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

Например, можно научить Teachable Machine при поднятой вверх ладони говорить «Hi». При поднятом вверх большом пальце — «Cool», а при удивленном лице с открытым ртом — «Wow».

Для начала нужно обучить нейросеть. Для этого поднимаем ладонь и нажимаем на кнопку «Train Green» — сервис делает несколько десятков снимков, чтобы найти на изображениях закономерность. Набор таких снимков принято называть «датасетом».

Теперь остается выбрать действие, которое нужно вызывать при распознании образа — произнести фразу, показать GIF или проиграть звук. Аналогично обучаем нейронную сеть распознавать удивленное лицо и большой палец.

Как только нейросеть обучена, её можно использовать. Teachable Machine показывает коэффициент «уверенности» — насколько система «уверена», что ей показывают один из навыков.

Кроткое видео о работе Teachable Machine

Шаг 1. Готовим компьютер к работе с нейронной сетью

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

Для создания собственной нейронной сети понадобится Python — один из наиболее минималистичных и распространенных языков программирования, и TensorFlow — открытая библиотека Google для создания и тренировки нейронных сетей.

Устанавливаем Python

Если у вас Windows: скачиваем установщик с официального сайта Python и запускаем его. При установке нужно поставить галочку «Add Python to PATH».

На macOS Python можно установить сразу через Terminal:

brew install python

Для работы с нейронной сетью подойдет Python 2.7 или более старшая версия.

Устанавливаем виртуальное окружение

Открываем командную строку на Windows или Terminal на macOS и последовательно вводим несколько команд:

pip install —upgrade virtualenv
virtualenv —system-site-packages Название
source Название/bin/activate

На компьютер будет установлен инструмент для запуска программ в виртуальном окружении. Он позволит устанавливать и запускать все библиотеки и приложения внутри одной папки — в команде она обозначена как «Название».

Устанавливаем TensorFlow

Вводим команду:

pip install tensorflow

Всё, библиотека TensorFlow установлена в выбранную папку. На macOS она находится по адресу Macintosh HD/Users/Имя_пользователя/, на Windows — в корне C://.

Можно проверить работоспособность библиотеки последовательно вводя команды:

python
import tensorflow as tf
hello = tf.constant(‘Hello, TensorFlow’)
sess = tf.Session()
print(sess.run(hello))

Если установка прошла успешно, то на экране появится фраза «Hello, Tensorflow».

Шаг 2. Добавляем классификатор

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

Открываем страницу «Tensorflow for poets» на Github, нажимаем на кнопку «Clone or download» и скачиваем классификатор в формате ZIP-файла.

Затем распаковываем архив в созданную на втором шаге папку.

Шаг 3. Добавляем набор данных

Набор данных нужен для обучения нейронной сети. Это входные данные, на основе которых нейронная сеть научится понимать, какой цветок расположен на картинке.

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

Содержимое архива нужно распаковать в папку /tf_files классификатора.

Шаг 4. Переобучаем модель

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

Переходим в папку с классификатором

Открываем командную строку и вводим команду, чтобы перейти в папку с классификатором.

Windows:

cd C://Название/

macOS:

cd Название

Запускаем процесс обучения

python scripts/retrain.py —output_graph=tf_files/retrained_graph.pb —output_labels=tf_files/retrained_labels.txt —image_dir=tf_files/flower_photos

Что указано в команде:

  • retrain.py — название Python-скрипта, который отвечает за запуск процесса обучения нейронной сети.
  • output_graph — создаёт новый файл с графом данных. Он и будет использоваться для определения того, что находится на картинке.
  • output_labels — создание нового файла с метками. В нашем примере это ромашки, подсолнухи, одуванчики, тюльпаны или розы.
  • image_dir — путь к папке, в которой находятся изображения с цветами.

Программа начнет создавать текстовые файлы bottleneck — это специальные текстовые файлы с компактной информацией об изображении. Они помогают классификатору быстрее определять подходящую картинку.

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

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

Перед тестированием нейросети нужно открыть файл label_image.py, находящийся в папке scripts в любом текстовом редакторе и заменить значения в строках:

input_height = 299
input_width = 299
input_mean = 0
input_std = 255
input_layer = «Mul»

Шаг 5. Тестирование

Выберите любое изображение цветка, которое нужно проанализировать, и поместите его в папку с нейронной сетью. Назовите файл image.jpg.

Для запуска анализа нужно ввести команду:

python scripts/label_image.py —image image.jpg

Нейросеть проверит картинку на соответствие одному из лейблов и выдаст результат.

Например:

Это значит, что с вероятностью 72% на картинке изображена роза.

Шаг 6. Учим нейронную сеть распознавать предпринимателей

Теперь можно расширить возможности нейронной сети — научить её распознавать на картинке не только цветы, но и известных предпринимателей. Например, Элона Маска и Марка Цукерберга.

Для этого нужно добавить новые изображения в датасет и переобучить нейросеть.

Собираем собственный датасет

Для создания датасета с фотографиями предпринимателей можно воспользоваться поиском по картинкам Google и расширением для Chrome, которое сохраняет все картинки на странице.

Папку с изображениями Элона Маска нужно поместить в tf_filesflower_photosmusk. Аналогично все изображения с основателем Facebook — в папку tf_filesflower_photoszuckerberg.

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

Переобучаем и проверяем

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

python scripts/retrain.py —output_graph=tf_files/retrained_graph.pb —output_labels=tf_files/retrained_labels.txt —image_dir=tf_files/flower_photos

python scripts/label_image.py —image image.jpg

Шаг 7. «Разгоняем» нейронную сеть

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

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

Создание сервера с Ubuntu

Нам понадобится сервер с операционной системой Ubuntu. Её можно установить самостоятельно, либо — если арендован сервер Selectel — через техподдержку компании.

Установка Python

sudo apt-get install python3-pip python3-dev

Установка TensorFlow

pip3 install tensorflow-gpu

Скачиваем классификатор и набор данных

Аналогично шагам 2 и 3 на компьютере, только архивы необходимо загрузить сразу на сервер.

Переобучаем модель

python3 scripts/retrain.py —output_graph=tf_files/retrained_graph.pb —output_labels=tf_files/retrained_labels.txt —image_dir=tf_files/flower_photos

Тестируем нейросеть

python scripts/label_image.py —image image.jpg

В этой статье мы рассмотрим, как создать собственную простейшую нейронную сеть с помощью языка программирования «Питон». Мы не только создадим нейронную сеть с нуля, но и не будем использовать никаких библиотек. И займёт это всё не более девяти строчек кода на «Питоне».

Вот как выглядит код простейшей нейронной сети на «Питоне»:

4-20219-2bc477.png

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

Что такое нейронная сеть (Neural Network)?

Прежде чем продолжить, вспомним, что из себя представляет нейронная сеть. Мы знаем, что мозг человека состоит из 100 млрд. клеток, которые мы называем нейроны. Они соединены синапсами, а когда нужное количество синаптических входов возбуждено, нейрон тоже возбуждается. Этот процесс учёные называют «мышлением».

1_HDWhvFz5t0KAjIAIzjKR1w_2-20219-7c47a9.png

Процесс можно смоделировать, если создать нейронную сеть на ПК. Причём нет необходимости моделировать сложнейшую модель мозга человека полностью, хватит лишь нескольких основных правил. Чтобы упростить реализацию, будем использовать классические матрицы и создадим модель из 3-х входных и одного выходного сигналов. И попробуем выполнить тренировку нейрона.

Первые 4 примера — это тренировочная выборка.

1_nEooKljI8XbKQh4cFbZu1Q_2-20219-2683ab.png

Обратите внимание, что значение столбца Output всегда равно значению самой левой колонки из столбца Input. Это значит, что правильный ответ в нашем случае будет равен 1.

Обучаем нейронную сеть

Теперь давайте добавим каждому входу вес (положительное или отрицательное число). Вход с большим отрицательным либо большим положительным весом существенно повлияет на выход нейрона. Но до начала обучения надо установить каждый вес случайной величиной. После этого можно приступать:
1. Возьмём входные данные из примера, скорректируем значения по весам и передадим их по формуле расчёта выхода нейрона.
2. Вычислим ошибочное значение (это, по сути, разница между выходом нейрона и желаемым нами выходом в примере используемого нами тренировочного набора).
3. Немного отрегулируем вес с учётом направления ошибки.
4. Повторим данный процесс десять тысяч раз.

1__1trgA6DUEaafJZv3k0mGw_1-20219-3abd97.jpeg

По итогу вес нейрона достигает оптимального значения для нашего обучающего набора. Теперь, если позволить нейрону «подумать», он сделает хороший прогноз.

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

Теперь посмотрим на формулу расчёта выхода нейрона. Поначалу возьмём взвешенную сумму входов:

1_RV7_CFkmmByfcXKkPcbAYQ_1-20219-f77039.png

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

1_5il5GLo0gamypklQQ_z0AA_1-20219-7b1689.png

Функция Sigmoid нарисует S-образную кривую:

1_sK6hjHszCwTE8GqtKNe1Yg_1-20219-8046bd.png

Подставим 1-е уравнение во 2-е и получим интересующую нас формулу выхода:

1_7YdyG6fc6f6zMmx3l0ZGsQ_1-20219-f6eb78.png

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

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

В процессе тренировочного цикла мы выполняем корректировку весов. Но насколько происходит корректировка? Тут подойдёт формула взвешенной ошибки:

1_SQBjpbBcCT3lTQlPEdr1eg_1-20219-3cabb6.png

Формула позволяет выполнять корректировку пропорционально величине ошибки. Также умножение происходит на входное значение, равное 0 либо 1. Когда входное значение будет равно 0, вес корректироваться не будет. Дополнительно мы выполняем умножение на градиент сигмовидной кривой. Что тут нужно учесть:
1. Сигмовидная кривая использовалась для расчёта выхода нейрона.
2. При больших числах кривая имеет небольшой градиент.

Если нейрон уверен в правильности существующего веса, он не хочет корректировать его слишком сильно. Умножение на градиент кривой именно это и делает.

Градиент Сигмоды образуется, если будем выполнять расчёты взятием производной:

1_HdHm9u3_wjwBPmwuLg3D3g_1-20219-c6d4b7.png

Если мы вычтем 2-е уравнение из 1-го, то получим необходимую итоговую формулу:

1_Jow4WVWNOp6rtiJ7vNQ0gQ_1-20219-ed752d.png

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

Пример нейронной сети на Python

Было заявлено, что библиотеки задействоваться не будут. Так-то оно так, но четыре метода из numpy импортировать придётся:
— exp — для экспоненцирования;
— dot — для перемножения матриц;
— array — для создания матрицы;
— random — для генерации случайных чисел.

Тот же array() можно применять для представления обучающего множества.

3-20219-aff6e0.png

В нашем случае “.T” является функцией транспонирования матриц.

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

Этот код вы найдёте и по ссылке на GitHub. Если будете работать с Python 3, замените лишь xrange на range.

1-20219-c92f17.png

Итог

Давайте запустим нейронную сеть через терминал:


Результат должен быть приблизительно следующим:

2-20219-f4a656.png

Если у вас всё получилось, поздравляем — вы только что написали простейшую нейросеть!

Смотрите, изначально нейросеть присваивала себе случайные значения весов, потом она обучалась с помощью тренировочного набора. Далее она рассмотрела новую ситуацию [1, 0, 0], предсказав 0.99993704. Так как правильный ответ равен единице, можно сказать, что предсказание получилось довольно точным.

Источник — How to build a simple neural network in 9 lines of Python code.

Поначалу может показаться, что всё просто. Но если вы хотите освоить работу нейронных сетей на практике и всерьёз, стоит подумать о специализированных курсах. На них вы узнаете про нейронные сети гораздо больше, ведь простого чтения статей из интернета явно недостаточно. Вы разберёте нейросети прямого распространения, обратного распространения, скрытого слоя и т. д. и т. п., узнаете про функции потерь, функцию активации, обучение с подкреплением, ознакомитесь с другой важной информацией. Записаться на курс по нейронным сетям в качестве студента можно по ссылке ниже:
https://otus.ru/lessons/deep-learning-engineer/.

Понравилась статья? Поделить с друзьями:
  • Как написать свою музыку на компьютере
  • Как написать свою музыку на гитаре
  • Как написать свою методику
  • Как написать свою мелодию на гитаре
  • Как написать свою мелодию для песни