Периодически я получаю запросы относительно исходных кодов нейронных сетей, использованных в моих работах по анализу тональности, генерации текстов, а также в статьях на Хабре. Поэтому я решил выложить всё-таки их в открытый доступ, вместе с библиотекой на которую они опираются, несмотря на достаточно сырой код. В этой статье я напишу где взять, что можно сделать и немного о том, как пользоваться. Написана библиотка на языке F#, но использовать ее можно из любого .NET языка.

Реализация
Библиотека для нейронных сетей написана мною на языке F#. Почему F#? Если коротко, то F# как язык имеет достоинства популярного сейчас Python (например такие как: простой синтаксис, возможность запускать скрипты в интерактивном режиме, кросс-платформенность) и очень хорошо подходит для быстрого прототипирования программ. Но существенным (для меня) отличием является статическая сильная типизация. F# имеет такую функцию как автоматический вывод типов, т.е. компилятор без явного объявления типов переменных может определить тип на этапе компиляции. Раньше я использовал Python и могу сказать, что лично мне переход на F# экономит много бессонных ночей отладки. У Python есть свои плюсы конечно, но в для меня они не перевесили минусов.

Одна беда, если для Python нейросетевых библиотек сегодня можно выбирать на любой вкус и цвет, то на F#, их не так много. А на момент начала разработки ситуация была еще хуже — было несколько библиотек для .NET Framework типа Encog, например, но новые архитектуры реализовать было на них нельзя без существенных изменений. Поэтому я написал свою с нуля. Это, в любом случае, весьма полезное упражнение для любого, кто хочет заниматься тематикой серьезно, т. к. позволяет глубже понять основные принципы.

Возможности
На сегодняшний день
  • Нейронные сети прямого распространения, произвольные комбинации слоев
  • Рекуррентные слои. Обучение методом обратного распространения ошибки через время
  • Двунаправленные рекуррентные сети
  • Разные функции активации, включая softmax и relU, возможность определять свои функции
  • Dropout, Noise layers, (можно сделать автокодировщик)
  • Проекционные слои (для векторов слов)
  • Одномерные сверточные слои для обработки текста, поддержка k-max pooling
  • Обучение: SGD, SGD+Momentum, RMSProp, RPROP
  • Полезные функции для работы с текстами, подсчета F1 и т. п.
  • Сохранение любой модели на диск и чтение с диска
  • Работа из любого .NET языка
  • Малый размер. Кроме .Net framework или mono не нужно дополнительно ставить ничего.
  • Есть пример кода, как сделать выделение терминов из текста
  • Разные экспериментальные дополнения

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


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

Для чего может быть полезно? Как простая библиотека нейронных сетей для интеграции функционала в .NET приложения (я недавно использовал чтобы экспортировать в .NET проект разветвленную модель из библиотеки keras). Для опытов. Для желающих использовать F#. Для учебных целей — весь код реализован с нуля на F#, ничего не спрятано в разные другие библиотеки. Всем, кто спрашивал у меня исходные коды.

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

Где взять?
https://github.com/Durham/NeuThink

Как начать использовать?
Вот пример реализации простой нейронной сети для функции XOR на F#:

open NeuThink.Neuron
open NeuThink.NeuronTraining

let() =
 let nn = new GeneralNetwork()
 nn.AddPerceptronLayer(4,2,[|1|],true,0)
 nn.AddPerceptronLayer(1,4,[|-1|],false,1)
 nn.FinalizeNet()

 let outputs = [|[|-1.0|];[|-1.0|];[|1.0|];[|1.0|]|]
 let inputs = [|[|1.0;1.0|];[|0.0;0.0|];[|1.0;0.0|];[|0.0;1.0|]|]

 MomentumSGD 100 nn (new NeuThink.DataSources.SimpleProvider(inputs)) (new NeuThink.DataSources.SimpleProvider(outputs)) 0.2  (Some([|0;0;0;0|])) None
 nn.SetInput([|1.0;1.0|])
 System.Console.WriteLine(nn.Compute().[0])


Здесь создана нейронная сеть с одним скрытым слоем из 4 нейронов и одним выходным слоем из 1 одного нейрона. Вообще-то для XOR достаточно 2 нейронов в скрытом слое, но минимальная сеть чаще сваливается в локальный минимум при обучении. Вместо 0 и 1 для выхода XOR использованы -1 и 1, потому что по умолчанию функция активации во всех слоях tanh, с выходным диапазоном от -1 до 1.

nn.AddPerceptronLayer(4,2,[|1|],true,0)

Означает добавление одного полностью соединенного слоя (их еще называют Dense или MLP слоями). 4 — число нейронов в слое, 2- число входов. [|1|] — это массив номеров слоев, к которым будет подсоединен данный слой (неудобно, да. зато работает и можно определить произвольный граф). Если данные никуда подавать не надо, нужно указать -1 (почему не пустой массив? так исторически получилось...). Следующий параметр означает, что входные данные будут поступать в этот слой, а 0 — указывает на порядок обработки слоя.

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

Входные данные организованы в виде массива массивов float, каждый внутренний массив соответствует одному обучающему примеру. Выходные данные организованы также. Для обучения сети из массива надо сделать Источник Данных для нейронной сети. Это должен быть класс, реализующий интерфейс IInputProvider (для выходных данных) или IOutputProvider. Так сложно сделано для того случая, когда данные генерируются динамически или читаются с диска, потому, что не влезают в память. В простом случае сложностей не нужно и мы используем встроенный класс SimpleProvider, инициализируя его данными из массива.

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

Стоит отметить, что у F# есть интерактивная консоль, можно работать из нее:



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

Комментарии (5)


  1. lostmsu
    28.01.2016 18:46

    Респект за проделанную работу! Чем не устраивал Accord.NET? Не хочет ли автор переключиться на этот проект и помогать его разработке? Я, например, некоторое время назад пытался к нему прикрутить OpenCL через FSCL (кажется). Пока, правда забросил из-за отсутствия документации на FSCL.


    1. Durham
      28.01.2016 19:33
      +1

      Спасибо за положительный отзыв. Я работаю с рекуррентными сетями, в Accord.NET судя по документации с этим, к сожалению, очень туго. Потом Accord.NET он реализуется исключительно на C#, который мне кажется для быстрого прототипирования разных архитектур нейронных сетей слишком громоздким. До начала работы (и после) я смотрел разные библиотеки, но не нашел ничего подходящего. Сейчас ситуация меняется, но медленно. FSCL когда-то я тоже пытался приделать к своей библиотеке (было бы очень красивое решение) но, на тот момент получил замедление работы в 5 раз, из-за накладных расходов. Вернутся к этому руки не доходят.


  1. build_your_web
    28.01.2016 19:41
    +1

    Такое ощущение, что про F# стали забывать. Спасибо автору, что не поленился написать статью в хабр.


  1. mephistopheies
    28.01.2016 21:18
    +2

    а разве строгая типизация (вы привели один аргумент в пользу фшарпа) перевешивает профит который дает нумпая? то что нумпай использует например linalg? это же реальное ускорение работы в десятки раз, а так же современные НС без ГПУ как бы годны только для игрушечных примеров, а дотнет не сильно дружит с кудой

    в питоне если даже не обращать внимание на либы, есть скажем cudamat которые реализует интерфейс нумпая, и достаточно заменить нумпай на него, что бы все вычисления перевести на ГПУ

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

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

    ну а если по хардкору, то вот Максим пишет на сях нейросети milakov.github.io/nnForge


    1. Durham
      28.01.2016 21:45
      +2

      нет никаго «профита» от numpy ибо никто не мешает вызвать нативные функции из .NET, тот же BLAS (я даже начал это постепенно внедрять). Никто не мешает вызвать cuBLAS если на то пошло, любой GPU код написанный на C. Поэтому это по крайней мере не хуже, чем ситуация с python. Есть даже компиляторы с ограниченного подмножества F# в CUDA C или в OpenCL.

      И в python не все так просто. Чтобы получить действительно эффективный код для сложных нейросетевых архитектур, недостаточно перенести только математические функции на GPU, нужно перенести целый блок вызовов этих функций вместе с логикой кода, которая будет на python. Это решается в Theano или TensorFlow тем, что создается средствами подмножества языка из некоторых предопределенных операций вычислительный граф, который компилируется и выполняется на GPU. То же самое можно сделать из F#, даже лучше. Так что принципиальных проблем нет, есть просто факт того, что мейнстрим работает с python'ом.

      Более того, я не соглашусь, что без GPU можно посчитать только игрушечные примеры. На распознавании изображений, возможно, но кроме распознавания изображений есть много чего. А еще многие сейчас от избытка ресурсов используют излишни неэффективно спроектированные нейросетевые модели. Например, вставляют везде LSTM ячейки, даже там где они не нужны вообще.

      Да, вот это было обучено на CPU, кстати. 160 миллиардов параметров. Совсем не слабо.