Программирование нейронной сети python команды. Самая простая нейронная сеть на Python. Что такое нейронная сеть

Некоторые из вас наверняка недавно проходили Stanford"ские курсы, в частности ai-class и ml-class . Однако, одно дело просмотреть несколько видео-лекций, поотвечать на вопросики quiz"ов и написать десяток программ в Matlab / Octave , другое дело начать применять полученные знания на практике. Дабы знания полученые от Andrew Ng не угодили в тот же тёмный угол моего мозга, где заблудились dft , Специальная теория относительности и Уравнение Эйлера Лагранжа , я решил не повторять институтских ошибок и, пока знания ещё свежи в памяти, практиковаться как можно больше.

И тут как раз на наш сайтик приехал DDoS. Отбиваться от которого можно было админско-программерскими (grep / awk / etc) способами или же прибегнуть к использованию технологий машинного обучения.

Пример построения словаря и feature-vector"а

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

Запись из "плохого" лога:
0.0.0.0 - - "POST /forum/index.php HTTP/1.1" 503 107 "http://www.mozilla-europe.org/" "-"

Запись из "хорошего" лога:
0.0.0.0 - - "GET /forum/rss.php?topic=347425 HTTP/1.0" 200 1685 "-" "Mozilla/5.0 (Windows; U; Windows NT 5.1; pl; rv:1.9) Gecko/2008052906 Firefox/3.0"

Получившийся словарь:
["__UA___OS_U", "__UA_EMPTY", "__REQ___METHOD_POST", "__REQ___HTTP_VER_HTTP/1.0", "__REQ___URL___NETLOC_", "__REQ___URL___PATH_/forum/rss.php", "__REQ___URL___PATH_/forum/index.php", "__REQ___URL___SCHEME_", "__REQ___HTTP_VER_HTTP/1.1", "__UA___VER_Firefox/3.0", "__REFER___NETLOC_www.mozilla-europe.org", "__UA___OS_Windows", "__UA___BASE_Mozilla/5.0", "__CODE_503", "__UA___OS_pl", "__REFER___PATH_/", "__REFER___SCHEME_http", "__NO_REFER__", "__REQ___METHOD_GET", "__UA___OS_Windows NT 5.1", "__UA___OS_rv:1.9", "__REQ___URL___QS_topic", "__UA___VER_Gecko/2008052906"]

Тестовая запись:
0.0.0.0 - - "GET /forum/viewtopic.php?t=425550 HTTP/1.1" 502 107 "-" "BTWebClient/3000(25824)"

Её feature-vector:

Заметьте, насколько "разрежен" (sparse) feature-vector - такое поведение будет наблюдаться для всех запросов.

Разделение Dataset"а

Хорошей практикой является разделение dataset "а на несколько частей. Я бил на две части в пропорции 70/30:
  • Training set . На нём мы обучаем нашу нейронную сеть.
  • Test set . Им мы проверяем, насколько хорошо обучена наша нейронная сеть.
Такое разбиение обусловлено тем фактом, что нейронная сеть с наименьшим training error (ошибкой на training set) будет выдавать бо льшую ошибку на новых данных, ибо мы «переобучили» сеть, заточив её под training set.
В дальнейшем, если придётся озадачиться выбором оптимальных констант, dataset надо будет разбить на 3 части в соотношении 60/20/20: Training set , Test set и Cross validation . Последний как раз и будет служить для выбора оптимальных параметров нейронной сети (например weightdecay).

Нейронная сеть в частности

Теперь, когда у нас на руках больше нет никаких текстовых логов, а есть только матрицы из feature-vector "ов, можно приступать к построению самой нейронной сети.

Начнём с выбора структуры. Я выбрал сеть из одного скрытого слоя размером с удвоенный входной слой. Почему? Всё просто: так завещал Andrew Ng в случае, если не знаете с чего начать. Думаю, в дальнейшем с этим можно поиграться, порисовав графики обучения.
Функцией активации для скрытого слоя выбрана многострадальная сигмойда, а для выходного слоя - Softmax . Последний выбран на случай, если придётся делать
многоклассовую класиффикацию c mutually exclusive классами. Например, "хорошие" запросы отправлять на бэкенд, "плохие" - в бан на фаерволе, а "серые" - разгадывать капчу.

Нейронная сеть склонна к уходу в локальный минимум, поэтому у себя в коде я строю несколько сетей и выбираю ту, у которой наименьший Test error (Заметьте, именно ошибка на test set , а не trainig set).

Disclaimer

Я не настоящий сварщик. О Machine Learning я знаю только то, что подчерпнул из ml-class и ai-class. На питоне программировать начал относительно недавно, а код ниже был написан минут за 30 (время, как вы понимаете, поджимало) и в дальнейшем был лишь слегка подпилен напильником.

Также этот код не самодостаточен. Ему всё равно нужна скриптовая обвязка. Например, если IP сделал N плохих запросов в течение X минут, то банить его на firewall"е.

Производительность

  • lfu_cache. Портировал с ActiveState, дабы сильно ускорить обработку запросов-"высокочастотников". Down-side - повышенное потребление памяти.
  • PyBrain, внезапно, написан на python и поэтому не очень быстр, однако, он может использовать ATLAS-based -модуль arac , если при создании сети указать Fast=True . Подробнее про это можно почитать в документации к PyBrain .
  • Распараллеливание. Свою нейронную сеть я обучил на довольно "толстом" серверном Nehalem"е, однако, даже там чувствовалась ущербность однопоточного обучения. Можно поразмыслить на тему распараллеливания обучения нейронной сети. Простое решение - тренировать сразу несколько нейронных сетей параллельно и из них выбирать лучшую, но это создаст дополнительную нагрузку на память, что тоже не очень хорошо. Хотелось бы более универсальное решение. Возможно имеет смысл просто переписать всё на C, благо вся теоретическая база в ml-class"е была расжевана.
  • Потребление памяти и кол-во features. Хорошей оптимизацией по памяти являлся переход со стндартных питоновских массивов на numpy"ные. Так же уменьшение размера dictionary и/или использование PCA может очень хорошо помочь, об этом чуть ниже.

На будущее

  • Дополнительные поля в лог. В combined лог можно добавить ещё много всего, стоит подумать на тему, какие поля помогут в идентификации ботов. Возможно, имеет смысл учитывать первый октет IP адреса, ибо в не интернациональном web-проекте китайские пользователи вероятнее всего боты.

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

Из статьи вы узнаете, как с помощью Keras создать нейронную сеть, предсказывающую оценку продукта пользователями по их отзывам, классифицируя ее по двум категориям: положительная или отрицательная. Эта задача называется анализом настроений (сентимент-анализ) , и мы решим ее с помощью сайта с кинорецензиями IMDb. Модель, которую мы построим, также может быть применена для решения других задач после незначительной модификации.

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

  • Что такое Keras?
  • Что такое анализ настроений?
  • Датасет IMDB
  • Изучение данных
  • Подготовка данных
  • Создание и обучение модели

Что такое Keras?

Keras — это библиотека для Python с открытым исходным кодом, которая позволяет легко создавать нейронные сети. Библиотека совместима с , Microsoft Cognitive Toolkit, Theano и MXNet. Tensorflow и Theano являются наиболее часто используемыми численными платформами на Python для разработки алгоритмов глубокого обучения, но они довольно сложны в использовании.


Оценка популярности фреймворков машинного обучения по 7 категориям

Keras, наоборот, предоставляет простой и удобный способ создания моделей глубокого обучения. Ее создатель, François Chollet, разработал ее для того, чтобы максимально ускорить и упростить процесс создания нейронных сетей. Он сосредоточил свое внимание на расширяемости, модульности, минимализме и поддержке Python. Keras можно использовать с GPU и CPU; она поддерживает как Python 2, так и Python 3. Keras компании Google внесла большой вклад в коммерциализацию глубокого обучения и , поскольку она содержит cовременные алгоритмы глубокого обучения, которые ранее были не только недоступными, но и непригодными для использования.

Что такое анализ настроений (сентимент-анализ)?

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


Пример шкалы анализа настроений

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

Датасет IMDb


Рецензии на сайте IMDb

Датасет IMDb состоит из 50 000 обзоров фильмов от пользователей, помеченных как положительные (1) и отрицательные (0).

  • Рецензии предварительно обрабатываются, и каждая из них кодируется последовательностью индексов слов в виде целых чисел.
  • Слова в обзорах индексируются по их общей частоте появления в датасете. Например, целое число «2» кодирует второе наиболее частое используемое слово.
  • 50 000 обзоров разделены на два набора: 25 000 для обучения и 25 000 для тестирования.

Датасет был создан исследователями Стэнфордского университета и представлен в статье 2011 года, в котором достигнутая точность предсказаний была равна 88,89%. Датасет также использовался в рамках конкурса сообщества Keggle «Bag of Words Meets Bags of Popcorn» в 2011 году.

Импорт зависимостей и получение данных

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

%matplotlib inline import matplotlib import matplotlib.pyplot as plt import numpy as np from keras.utils import to_categorical from keras import models from keras import layers

Загрузим датесет IMDb, который уже встроен в Keras. Поскольку мы не хотим иметь данные обучения и тестирования в пропорции 50/50, мы сразу же объединим эти данные после загрузки для последующего разделения в пропорции 80/20 :

From keras.datasets import imdb (training_data, training_targets), (testing_data, testing_targets) = imdb.load_data(num_words=10000) data = np.concatenate((training_data, testing_data), axis=0) targets = np.concatenate((training_targets, testing_targets), axis=0)

Изучение данных

Изучим наш датасет:

Print("Categories:", np.unique(targets)) print("Number of unique words:", len(np.unique(np.hstack(data)))) Categories: Number of unique words: 9998 length = print("Average Review length:", np.mean(length)) print("Standard Deviation:", round(np.std(length))) Average Review length: 234.75892 Standard Deviation: 173.0

Можно видеть, что все данные относятся к двум категориям: 0 или 1, что представляет собой настроение обзора. Весь датасет содержит 9998 уникальных слов, средний размер обзора составляет 234 слова со стандартным отклонением 173.

Рассмотрим простой способ обучения:

Print("Label:", targets) Label: 1 print(data)

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

Index = imdb.get_word_index() reverse_index = dict([(value, key) for (key, value) in index.items()]) decoded = " ".join() print(decoded) # this film was just brilliant casting location scenery story direction everyone"s really suited the part they played and you could just imagine being there robert # is an amazing actor and now the same being director # father came from the same scottish island as myself so i loved the fact there was a real connection with this film the witty remarks throughout the film were great it was just brilliant so much that i bought the film as soon as it was released for # and would recommend it to everyone to watch and the fly fishing was amazing really cried at the end it was so sad and you know what they say if you cry at a film it must have been good and this definitely was also # to the two little boy"s that played the # of norman and paul they were just brilliant children are often left out of the # list i think because the stars that play them all grown up are such a big profile for the whole film but these children are amazing and should be praised for what they have done don"t you think the whole story was so lovely because it was true and was someone"s life after all that was shared with us all

Подготовка данных

Пришло время подготовить данные. Нужно векторизовать каждый обзор и заполнить его нулями, чтобы вектор содержал ровно 10 000 чисел. Это означает, что каждый обзор, который короче 10 000 слов, мы заполняем нулями. Это делается потому, что самый большой обзор имеет почти такой же размер, а каждый элемент входных данных нашей нейронной сети должен иметь одинаковый размер. Также нужно выполнить преобразование переменных в тип float .

Def vectorize(sequences, dimension = 10000): results = np.zeros((len(sequences), dimension)) for i, sequence in enumerate(sequences): results = 1 return results data = vectorize(data) targets = np.array(targets).astype("float32")

Разделим датасет на обучающий и тестировочный наборы. Обучающий набор будет состоять из 40 000 обзоров, тестировочный — из 10 000.

Test_x = data[:10000] test_y = targets[:10000] train_x = data train_y = targets

Создание и обучение модели

Теперь можно создать простую нейронную сеть. Начнем с определения типа модели, которую мы хотим создать. В Keras доступны два типа моделей: последовательные и с функциональным API .

Затем нужно добавить входные, скрытые и выходные слои. Для предотвращения переобучения будем использовать между ними исключение («dropout» ). Обратите внимание, что вы всегда должны использовать коэффициент исключения в диапазоне от 20% до 50%. На каждом слое используется функция «dense» для полного соединения слоев друг с другом. В скрытых слоях будем используем «relu» , потому это практически всегда приводит к удовлетворительным результатам. Не бойтесь экспериментировать с другими функциями активации. На выходном слое используем сигмоидную функцию, которая выполняет перенормировку значений в диапазоне от 0 до 1. Обратите внимание, что мы устанавливаем размер входных элементов датасета равным 10 000, потому что наши обзоры имеют размер до 10 000 целых чисел. Входной слой принимает элементы с размером 10 000, а выдает — с размером 50.

Наконец, пусть Keras выведет краткое описание модели, которую мы только что создали.

# Input - Layer model.add(layers.Dense(50, activation = "relu", input_shape=(10000,))) # Hidden - Layers model.add(layers.Dropout(0.3, noise_shape=None, seed=None)) model.add(layers.Dense(50, activation = "relu") model.add(layers.Dropout(0.2, noise_shape=None, seed=None)) model.add(layers.Dense(50, activation = "relu")) # Output- Layer model.add(layers.Dense(1, activation = "sigmoid"))model.summary() model.summary() _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= dense_1 (Dense) (None, 50) 500050 _________________________________________________________________ dropout_1 (Dropout) (None, 50) 0 _________________________________________________________________ dense_2 (Dense) (None, 50) 2550 _________________________________________________________________ dropout_2 (Dropout) (None, 50) 0 _________________________________________________________________ dense_3 (Dense) (None, 50) 2550 _________________________________________________________________ dense_4 (Dense) (None, 1) 51 ================================================================= Total params: 505,201 Trainable params: 505,201 Non-trainable params: 0 _________________________________________________________________

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

Model.compile(optimizer = "adam", loss = "binary_crossentropy", metrics = ["accuracy"])

Теперь можно обучить нашу модель. Мы будем делать это с размером партии 500 и только двумя эпохами, поскольку я выяснил, что модель начинает переобучаться , если тренировать ее дольше. Размер партии определяет количество элементов, которые будут распространяться по сети, а эпоха — это один проход всех элементов датасета. Обычно больший размер партии приводит к более быстрому обучению, но не всегда — к быстрой сходимости. Меньший размер партии обучает медленнее, но может быстрее сходиться. Выбор того или иного варианта определенно зависит от типа решаемой задачи, и лучше попробовать каждый из них. Если вы новичок в этом вопросе, я бы посоветовал вам сначала использовать размер партии 32 , что является своего рода стандартом.

Results = model.fit(train_x, train_y, epochs= 2, batch_size = 500, validation_data = (test_x, test_y)) Train on 40000 samples, validate on 10000 samples Epoch 1/2 40000/40000 [==============================] - 5s 129us/step - loss: 0.4051 - acc: 0.8212 - val_loss: 0.2635 - val_acc: 0.8945 Epoch 2/2 40000/40000 [==============================] - 4s 90us/step - loss: 0.2122 - acc: 0.9190 - val_loss: 0.2598 - val_acc: 0.8950

Проведем оценку работы модели:

Print(np.mean(results.history["val_acc"])) 0.894750000536

Отлично! Наша простая модель уже побила рекорд точности в статье 2011 года , упомянутой в начале поста. Смело экспериментируйте с параметрами сети и количеством слоев.

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

Import numpy as np from keras.utils import to_categorical from keras import models from keras import layers from keras.datasets import imdb (training_data, training_targets), (testing_data, testing_targets) = imdb.load_data(num_words=10000) data = np.concatenate((training_data, testing_data), axis=0) targets = np.concatenate((training_targets, testing_targets), axis=0) def vectorize(sequences, dimension = 10000): results = np.zeros((len(sequences), dimension)) for i, sequence in enumerate(sequences): results = 1 return results data = vectorize(data) targets = np.array(targets).astype("float32") test_x = data[:10000] test_y = targets[:10000] train_x = data train_y = targets model = models.Sequential() # Input - Layer model.add(layers.Dense(50, activation = "relu", input_shape=(10000,))) # Hidden - Layers model.add(layers.Dropout(0.3, noise_shape=None, seed=None)) model.add(layers.Dense(50, activation = "relu")) model.add(layers.Dropout(0.2, noise_shape=None, seed=None)) model.add(layers.Dense(50, activation = "relu")) # Output- Layer model.add(layers.Dense(1, activation = "sigmoid")) model.summary() # compiling the model model.compile(optimizer = "adam", loss = "binary_crossentropy", metrics = ["accuracy"]) results = model.fit(train_x, train_y, epochs= 2, batch_size = 500, validation_data = (test_x, test_y)) print("Test-Accuracy:", np.mean(results.history["val_acc"]))

Итоги

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

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

Эту модель (с небольшими изменениями) можно применить и для решения других задач машинного обучения.

  • Программирование
    • Перевод

    О чём статья

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

    Дайте код!

    X = np.array([ ,,, ]) y = np.array([]).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), пытается использовать входные данные для предсказания выходных.

    Предположим, нам нужно предсказать, как будет выглядеть колонка «выход» на основе входных данных. Эту задачу можно было бы решить, подсчитав статистическое соответствие между ними. И мы бы увидели, что с выходными данными на 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([ , , , ]) # выходные данные y = np.array([]).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.dot(y) – если x и y – это вектора, то на выходе получится скалярное произведение. Если это матрицы, то получится перемножение матриц. Если матрица только одна из них – это перемножение вектора и матрицы.

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

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

    import numpy as np

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

    Def nonlin(x,deriv=False):

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

    If(deriv==True):

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

    X = np.array([ , …

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

    Y = np.array([]).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 содержится 4 тренировочных примера. Мы обработаем их все и сразу – это называется групповой тренировкой . Итого мы имеем 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 – вектор из положительных и отрицательных чисел, характеризующий «промах» сети.

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

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

    Nonlin(l1,True)

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

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

    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)

    Weight_update = input_value * l1_delta

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

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

    Понаблюдав за обновлением сети, вернёмся к нашим тренировочным данным. Когда и вход, и выход равны 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 картинок одинакового размера, на которых изображены велосипеды и курительные трубки, присутствие на них определённых пикселей в определённых местах не коррелирует напрямую с наличием на изображении велосипеда или трубки. Статистически их цвет может казаться случайным. Но некоторые комбинации пикселей не случайны – те, что формируют изображение велосипеда (или трубки).


    Стратегия

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

    Вход (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([, , , ]) y = np.array([, , , ]) 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. Этим двум шагам посвящена на портале.

    Если все установлено и настроено, можно начинать.

    Переменные

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

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

    Слева мы объявляем переменную с именем x . Это равносильно тому, что мы приклеили на коробку именной ярлык. Далее идет знак равенства и число 10. Знак равенства здесь играет необычную роль. Он не означает, что «x равно 10». Равенство в данном случае кладет число 10 в коробку. Если говорить более корректно, то мы присваиваем переменной x число 10.

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

    Можно просто вывести значение этой переменной на экран:

    X=10 print(x)

    Надпись print(x) представляет собой вызов функции. Их мы будем рассматривать далее. Сейчас важно то, что эта функция выводит в консоль то, что расположено между скобками. Между скобками у нас стоит x . Ранее мы присвоили x значение 10. Именно 10 и выводится в консоли, если вы выполните программу выше.

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

    X = 2 y = 3 # Сложение z = x + y print(z) # 5 # Разность z = x - y print(z) # -1 # Произведение z = x * y print(z) # 6 # Деление z = x / y print(z) # 0.66666... # Возведение в степень z = x ** y print(z) # 8

    В коде выше мы вначале создаем две переменные, содержащие 2 и 3. Затем создаем переменную z , которая хранит результат операции с x и y и выводит результаты в консоль. На этом примере хорошо видно, что переменная может менять свое значение в ходе выполнения программы. Так, наша переменная z меняет свое значение аж 5 раз.

    Функции

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

    «Это очень важный текст
    «Этот текст нильзя ни прочитать»
    «Ошибка в верхней строчке допущена специально»
    «Привет и пока»
    «Конец»

    Наш код будет выглядеть так:

    X = 10 y = x + 8 - 2 print("Это очень важный текст!") print("Этот текст нильзя не прочитать") print("Ошибка в верхней строчке допущена специально") print("Привет и пока") print("Конец") z = x + y print("Это очень важный текст!") print("Этот текст нильзя не прочитать") print("Ошибка в верхней строчке допущена специально") print("Привет и пока") print("Конец") test = z print("Это очень важный текст!") print("Этот текст нильзя не прочитать") print("Ошибка в верхней строчке допущена специально") print("Привет и пока") print("Конец")

    Выглядит все это очень избыточно и неудобно. Кроме того, во второй строчке допущена ошибка. Ее можно исправить, но исправлять придется сразу в трех местах. А если у нас в проекте эти пять строчек вызываются 1000 раз? И все в разных местах и файлах?

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

    Функция - отдельный блок кода, который можно вызывать по имени.

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

    Def print_5_lines(): print("Это очень важный текст!") print("Этот текст нильзя не прочитать") print("Ошибка в верхней строчке допущена специально") print("Привет и пока") print("Конец")

    Теперь мы определили функцию print_5_lines() . Теперь, если в нашем проекте нам в очередной раз надо вставить пять строчек, то мы просто вызываем нашу функцию. Она автоматически выполнит все действия.

    # Определяем функцию def print_5_lines(): print("Это очень важный текст!") print("Этот текст нильзя не прочитать") print("Ошибка в верхней строчке допущена специально") print("Привет и пока") print("Конец") # Код нашего проекта x = 10 y = x + 8 - 2 print_5_lines() z = x + y print_5_lines() test = z print_5_lines()

    Удобно, не правда ли? Мы серьезно повысили читаемость кода. Кроме того, функции хороши еще и тем, что если вы хотите изменить какое-то из действий, то достаточно подправить саму функцию. Это изменение будет работать во всех местах, где вызывается ваша функция. То есть мы можем исправить ошибку во второй строчке выводимого текста («нильзя» > «нельзя») в теле функции. Правильный вариант автоматически будет вызываться во всех местах нашего проекта.

    Функции с параметрами

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

    Переменные, которые мы передаем в функцию, называются аргументами .

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

    Def sum(a, b): result = a + b return result

    Первая строчка выглядит почти так же, как и обычные функции. Но между скобок теперь находятся две переменные. Это параметры функции. Наша функция имеет два параметра (то есть принимает две переменные).

    Параметры можно использовать внутри функции как обычные переменные. На второй строчке мы создаем переменную result , которая равна сумме параметров a и b . На третьей строчке мы возвращаем значение переменной result .

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

    New = sum(2, 3) print(new)

    Мы вызываем функцию sum и по очереди передаем ей два аргумента: 2 и 3. 2 становится значением переменной a , а 3 становится значением переменной b . Наша функция возвращает значение (сумму 2 и 3), и мы используем его для создания новой переменной new .

    Запомните. В коде выше числа 2 и 3 - аргументы функции sum . А в самой функции sum переменные a и b - параметры. Другими словами, переменные, которые мы передаем функции при ее вызове называются аргументами. А вот внутри функции эти переданные переменные называются параметрами. По сути, это два названия одного и того же, но путать их не стоит.

    Рассмотрим еще один пример. Создадим функцию square(a) , которая принимает какое-то одно число и возводит его в квадрат:

    Def square(a): return a * a

    Наша функция состоит всего из одной строчки. Она сразу возвращает результат умножения параметра a на a .

    Я думаю вы уже догадались, что вывод данных в консоль мы тоже производим с помощью функции. Эта функция называется print() и она выводит в консоль переданный ей аргумент: число, строку, переменную.

    Массивы

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

    Array =

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

    Array = print(array)

    В консоли вы увидите число 89. Но почему 89, а не 1? Все дело в том, что в Python, как и во многих других языках программирования, нумерация массивов начинается с 0. Поэтому array дает нам второй элемент массива, а не первый. Для вызова первого надо было написать array .

    Размер массива

    Иногда бывает очень полезно получить количество элементов в массиве. Для этого можно использовать функцию len() . Она сама подсчитает количество элементов и вернет их число.

    Array = print(len(array))

    В консоли выведется число 4.

    Условия и циклы

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

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

    В первой ситуации помогают условия, а во второй - циклы.

    Условия

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

    В Python условия можно записывать с помощью конструкции if: ... else: ... . Пусть у нас есть некоторая переменная x = 10 . Если x меньше 10, то мы хотим разделить x на 2. Если же x больше или равно 10, то мы хотим создать другую переменную new , которая равна сумме x и числа 100. Вот так будет выглядеть код:

    X = 10 if(x < 10): x = x / 2 print(x) else: new = x + 100 print(new)

    После создания переменной x мы начинаем записывать наше условие.

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

    Затем идет ключевое слово else , после которого начинается блок действий, которые будут выполнены, если выражение в скобках после if ложное.

    Если она больше или равна 10, то мы создаем новую переменную new , которая равна x + 100 и тоже выводим ее в консоль.

    Циклы

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

    Print("Квадрат 1 равен " + str(1**2)) print("Квадрат 2 равен " + str(2**2)) print("Квадрат 3 равен " + str(3**2)) print("Квадрат 4 равен " + str(4**2)) print("Квадрат 5 равен " + str(5**2)) print("Квадрат 6 равен " + str(6**2)) print("Квадрат 7 равен " + str(7**2)) print("Квадрат 8 равен " + str(8**2)) print("Квадрат 9 равен " + str(9**2)) print("Квадрат 10 равен " + str(10**2))

    Пусть вас не удивляет тот факт, что мы складываем строки. «начало строки» + «конец» в Python означает просто соединение строк: «начало строкиконец». Так же и выше мы складываем строку «Квадрат x равен » и преобразованный с помощью функции str(x**2) результат возведения числа во 2 степень.

    Выглядит код выше очень избыточно. А что, если нам надо вывести квадраты первых 100 чисел? Замучаемся выводить…

    Именно для таких случаев и существуют циклы. Всего в Python 2 вида циклов: while и for . Разберемся с ними по очереди.

    Цикл while повторяет необходимые команды до тех пор, пока остается истинным условие.

    X = 1 while x <= 100: print("Квадрат числа " + str(x) + " равен " + str(x**2)) x = x + 1

    Сначала мы создаем переменную и присваиваем ей число 1. Затем создаем цикл while и проверяем, меньше (или равен) ли 100 наш x . Если меньше (или равен) то мы выполняем два действия:

    1. Выводим квадрат x
    2. Увеличиваем x на 1

    После второй команды программа возвращается к условию. Если условие снова истинно, то мы снова выполняем эти два действия. И так до тех пор, пока x не станет равным 101. Тогда условие вернет ложь и цикл больше не будет выполняться.

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

    For x in range(1,101): print("Квадрат числа " + str(x) + " равен " + str(x**2))

    Разберем первую строку. Мы используем ключевое слово for для создания цикла. Далее мы указываем, что хотим повторить определенные действия для всех x в диапазоне от 1 до 100. Функция range(1,101) создает массив из 100 чисел, начиная с 1 и заканчивая 100.

    Вот еще пример перебора массива с помощью цикла for:

    For i in : print(i * 2)

    Код выше выводит 4 цифры: 2, 20, 200 и 2000. Тут наглядно видно, как берет каждый элемент массива и выполняет набор действий. Затем берет следующий элемент и повторяет тот же набор действий. И так пока элементы в массиве не кончатся.

    Классы и объекты

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

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

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

    Важно понимать разницу между классом и объектом. Класс - схема , которая описывает объект. Объект - ее материальное воплощение . Класс кошки - описание ее свойств и действий. Объект кошки и есть сама реальная кошка. Может быть много разных реальных кошек - много объектов-кошек. Но класс кошки только один. Хорошей демонстрацией служит картинка ниже:

    Классы

    Для создания класса (схемы нашей кошки) надо написать ключевое слово class и затем указать имя этого класса:

    Class Cat:

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

    Метод - функция, определенная внутри класса.

    Словесно мы уже описали методы кошки выше: мурлыкать, шипеть, царапаться. Теперь сделаем это на языке Python.

    # Класс кошки class Cat: # Мурлыкать def purr(self): print("Муррр!") # Шипеть def hiss(self): print("Кшшш!") # Царапаться def scrabble(self): print("Царап-царап!")

    Вот так все просто! Мы взяли и определили три обычные функции, но только внутри класса.

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

    # Класс кошки class Cat: # Мурлыкать def purr(self): print("Муррр!") # Шипеть def hiss(self): print("Кшшш!") # Царапаться def scrabble(self): print("Царап-царап!") # Все вместе def all_in_one(self): self.purr() self.hiss() self.scrabble()

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

    Давайте теперь зададим свойства нашей кошки (цвет шерсти, цвет глаз, кличка). Как это сделать? В абсолютно любом классе можно определить функцию __init__() . Эта функция всегда вызывается, когда мы создаем реальный объект нашего класса.

    В выделенном выше методе __init__() мы задаем переменные нашей кошки. Как мы это делаем? Сначала мы передаем в этот метод 3 аргумента, отвечающие за цвет шерсти, цвет глаз и кличку. Затем, мы используем параметр self для того, чтобы при создании объекта сразу задать нашей кошки 3 описанных выше атрибута.

    Что означает эта строчка?

    Self.wool_color = wool_color

    В левой части мы создаем атрибут для нашей кошки с именем wool_color , а дальше мы присваиваем этому атрибуту значение, которое содержится в параметре wool_color , который мы передали в функцию __init__() . Как видите, строчка выше не отличается от обычного создания переменной. Только приставка self указывает на то, что эта переменная относится к классу Cat .

    Атрибут - переменная, которая относится к какому-то классу.

    Итак, мы создали готовый класс кошки. Вот его код:

    # Класс кошки class Cat: # Действия, которые надо выполнять при создании объекта "Кошка" def __init__(self, wool_color, eyes_color, name): self.wool_color = wool_color self.eyes_color = eyes_color self.name = name # Мурлыкать def purr(self): print("Муррр!") # Шипеть def hiss(self): print("Кшшш!") # Царапаться def scrabble(self): print("Царап-царап!") # Все вместе def all_in_one(self): self.purr() self.hiss() self.scrabble()

    Объекты

    Мы создали схему кошки. Теперь давайте создадим по этой схеме реальный объект кошки:

    My_cat = Cat("черный", "зеленые", "Зося")

    В строке выше мы создаем переменную my_cat , а затем присваиваем ей объект класса Cat . Выглядит все этот как вызов некоторой функции Cat(...) . На самом деле так и есть. Этой записью мы вызываем метод __init__() класса Cat . Функция __init__() в нашем классе принимает 4 аргумента: сам объект класса self , который указывать не надо, а также еще 3 разных аргумента, которые затем становятся атрибутами нашей кошки.

    Итак, с помощью строчки выше мы создали реальный объект кошки. Наша кошка имеет следующие атрибуты: черную шерсть, зеленые глаза и кличку Зося. Давайте выведем эти атрибуты в консоль:

    Print(my_cat.wool_color) print(my_cat.eyes_color) print(my_cat.name)

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

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

    My_cat.name = "Нюша"

    Теперь, если вы вновь выведете в консоль имя кошки, то вместо Зоси увидите Нюшу.

    Напомню, что класс нашей кошки позволяет ей выполнять некоторые действия. Если мы погладим нашу Зосю/Нюшу, то она начнет мурлыкать:

    My_cat.purr()

    Выполнение данной команды выведет в консоль текст «Муррр!». Как видите, обращаться к методам объекта так же просто, как и обращаться к его атрибутам.

    Модули

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

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

    На данный момент другие Python программисты написали уже свыше 110 000 разнообразных модулей. Упоминавшийся выше модуль numpy позволяет быстро и удобно работать с матрицами и многомерными массивами. Модуль math предоставляет множество методов для работы с числами: синусы, косинусы, переводы градусов в радианы и прочее и прочее…

    Установка модуля

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

    Если вы хотите использовать модуль, который не входит в стандартный набор, то вам потребуется установить его. Для установки модуля откройте командую строку (Win + R, затем введите в появившемся поле «cmd») и введите в нее команду:

    Pip install [название_модуля]

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

    Подключение и использование модуля

    Сторонний модуль подключается очень просто. Надо написать всего одну короткую строку кода:

    Import [название_модуля]

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

    Import math

    Как обратиться к функции модуля? Надо написать название модуля, затем поставить точку и написать название функции/класса. Например, факториал 10 находится так:

    Math.factorial(10)

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

    • Перевод

    О чём статья

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

    Дайте код!

    X = np.array([ ,,, ]) y = np.array([]).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), пытается использовать входные данные для предсказания выходных.

    Предположим, нам нужно предсказать, как будет выглядеть колонка «выход» на основе входных данных. Эту задачу можно было бы решить, подсчитав статистическое соответствие между ними. И мы бы увидели, что с выходными данными на 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([ , , , ]) # выходные данные y = np.array([]).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.dot(y) – если x и y – это вектора, то на выходе получится скалярное произведение. Если это матрицы, то получится перемножение матриц. Если матрица только одна из них – это перемножение вектора и матрицы.

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

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

    import numpy as np

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

    Def nonlin(x,deriv=False):

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

    If(deriv==True):

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

    X = np.array([ , …

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

    Y = np.array([]).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 содержится 4 тренировочных примера. Мы обработаем их все и сразу – это называется групповой тренировкой . Итого мы имеем 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 – вектор из положительных и отрицательных чисел, характеризующий «промах» сети.

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

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

    Nonlin(l1,True)

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

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

    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)

    Weight_update = input_value * l1_delta

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

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

    Понаблюдав за обновлением сети, вернёмся к нашим тренировочным данным. Когда и вход, и выход равны 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 картинок одинакового размера, на которых изображены велосипеды и курительные трубки, присутствие на них определённых пикселей в определённых местах не коррелирует напрямую с наличием на изображении велосипеда или трубки. Статистически их цвет может казаться случайным. Но некоторые комбинации пикселей не случайны – те, что формируют изображение велосипеда (или трубки).


    Стратегия

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

    Вход (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([, , , ]) y = np.array([, , , ]) 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, используя тот же алгоритм, что и в варианте с нейросетью из двух слоёв.



    
    Top