Пример простой нейросети, как результат разобраться что к чему

в 11:08, , рубрики: c++, foxnn, open source, python, искусственный интеллект, машинное обучение, нейронные сети

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

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

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

В чём смысл делать свой проект.
Плюсы:

  1. Лучше понимаешь, как устроены нейронки
  2. Лучше понимаешь, как работать с уже с существующими библиотеками
  3. Параллельно изучаешь что-то новое
  4. Щекочешь своё Эго, создавая что-то своё

Минусы:

  1. Создаёшь велосипед, притом скорее всего хуже существующих
  2. Всем плевать на твой проект

Выбор языка.

На момент выбора языка я более-менее знал С++, и был знаком с основами Python. Работать с нейронками проще на Python, но С++ знал лучше и нет проще распараллеливания вычислений, чем OpenMP. Поэтому я выбрал С++, а API под Python, чтобы не заморачиваться, будет создавать swig, который работает на Windows и Linux. (Пример, как сделать из кода С++ библиотеку для Python)

OpenMP и GPU ускорение.
На данный момент в Visual Studio установлена OpenMP версии 2.0., в которой есть только CPU ускорение. Однако начиная с версии 3.0 OpenMP поддерживает и GPU ускорение, при этом синтаксис директив не усложнился. Осталось лишь дождаться, когда OpenMP 3.0 будет поддерживаться всеми компиляторами. А пока, для простоты, только CPU.

Мои первые грабли.

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

Выкладывание проекта на GitHub.

Я не первый, кто выкладывает своё творение на GitHub. Но в большинстве случаев, перейдя по ссылке, видишь лишь кучу кода с надписью в README.md «Это моя нейросеть, смотрите и изучайте». Чтобы быть лучше других хотя бы в этом, более-менее описал README.md и заполнил Wiki. Посыл же простой — заполняйте Wiki. Интересное наблюдение: если заголовок в Wiki на GitHub написан на русском языке, то якорь на этот заголовок не работает.

Лицензия.
Когда создаёшь свой маленький проект, лицензия — это опять же способ пощекотать своё Эго. Вот интересная статья на тему, для чего нужна лицензия. Я же остановил свой выбор на APACHE 2.0.

Описание сети.

Характеристики:

Название FoxNN (Fox-Neural-Network)
Операционная система Windows, Linux
Языки C++, Python
Ускорение CPU (GPU в планах)
Внешние зависимости Нет (чистый С++, STL, OpenMP)
Флаги компиляции -std=c++14 -fopenmp
Слои линейные (сверточные в планах)
Оптимизации Адам, Нестеров
Случайное изменение весов Есть
Википедия (инструкция) Есть

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

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

Давайте создадим сеть, получающую на вход три параметра, имеющую три слоя с 5-ью, 4-мя и 2-мя нейронами.

import foxnn
nn = foxnn.neural_network([3, 5, 4, 2]) 

Если взглянуть на рисунок, то можно как раз увидеть: сначала 3 входных параметра, затем слой с 5-ью нейронами, затем слой с 4-мя нейронами и, наконец, последний слой с 2-мя нейронами.

Пример простой нейросети, как результат разобраться что к чему - 1

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

В наличии самые популярные функции активации.

nn.get_layer(0).set_activation_function("gaussian")

Легко создать обучающую выборку. Первый вектор — входные данные, второй вектор — целевые данные.

data = foxnn.train_data()
data.add_data([1, 2, 3], [1, 0]) #на вход три параметра, на выход два параметра

Обучение сети:

nn.train(data_for_train=data, speed=0.01, max_iteration=100, size_train_batch=98)

Включение оптимизации:

nn.settings.set_mode("Adam")

И метод, чтобы просто получить значение сети:

nn.get_out([0, 1, 0.1])

Немного о названии метода.

Отдельно get переводится как получить, а outвыход. Хотел получить название "дай выходное значение", и получил это. Лишь позже заметил, что получилось выметайся. Но так забавнее, и решил оставить.

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

Уже вошло в негласную традицию тестировать любую сеть на базе MNIST. И я не стал исключением. Весь код с комментариями можно посмотреть тут.

Создаёт тренировочную выборку:

from mnist import MNIST
import foxnn

mndata = MNIST('C:download/')
mndata.gz = True
imagesTrain, labelsTrain = mndata.load_training()

def get_data(images, labels):
    train_data = foxnn.train_data()
    for im, lb in zip(images, labels):
        data_y = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] # len(data_y) == 10
        data_y[lb] = 1 
        data_x = im
        for j in range(len(data_x)):
            # приводим пиксель в диапазон (-1, 1)
            data_x[j] = ((float(data_x[j]) / 255.0) - 0.5) * 2.0  
        train_data.add_data(data_x, data_y) # добавляем в обучающую выборку
    return train_data

train_data = get_data(imagesTrain, labelsTrain)

Создаём сеть: три слоя, на вход 784 параметра, и 10 на выход:

nn = foxnn.neural_network([784, 512, 512, 10])
nn.settings.n_threads = 7 # распараллеливаем процесс обучения на 7 процессов
nn.settings.set_mode("Adam") # используем оптимизацию Адама

Обучаем:

nn.train(data_for_train=train_data, speed=0.001, max_iteration=10000, size_train_batch=98)

Что получилось:

Примерно за 10 минут (только CPU ускорение), можно получить точность 75%. С оптимизацией Адама за 5 минут можно получить точность 88% процентов. В конечном итоге мне удалось достичь точности в 97%.

Основные недостатки (уже есть в планах на доработку):

  1. В Python ещё не протянуты ошибки, т.е. в python ошибка не будет перехвачена и программа просто завершится с ошибкой.
  2. Пока обучение указывается в итерациях, а не в эпохах, как это принято в других сетях.
  3. Нет GPU ускорения
  4. Пока нет других видов слоёв.
  5. Надо залить проект на PyPi.

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

P.S.: Если вам для того, чтобы разобраться, нужно создать что-то своё, не бойтесь и творите.

Автор: RadioRedFox

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js