В общем, начитавшись Хайкина , у меня стали чесаться лапки поделать что-нить интересненькое с нейронными сетями. Писать, понятное дело, при этом я собирался на Haskell. Беглый поиск по Hackage выявил наличие множества библиотек для работы с нейронными сетями, из которых instinct и HaskellNN не только неплохо выглядели, но и устанавливались. Однако у этих библиотек есть большой недостаток (помимо фатального ), заключающийся в том, что они не способны использовать всю мощь современных многоядерных процессоров за счет параллелизма . Что было дальше, вы уже и сами поняли 🙂

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

Приведу, пожалуй, простенький пример:

import AI . NeuralNetworks . Simple
import Text . Printf
import System . Random
import Control . Monad

calcXor net x y =
let [ r ] = runNeuralNetwork net [ x , y ]
in r

mse net =
let square x = x * x
e1 = square $ calcXor net 0 0 0
e2 = square $ calcXor net 1 0 1
e3 = square $ calcXor net 0 1 1
e4 = square $ calcXor net 1 1 0
in 0.5 * ( e1 + e2 + e3 + e4 )

stopf best gnum = do
let e = mse best
when ( gnum ` rem ` 100 == 0 ) $
printf «Generation: %02d, MSE: %.4f n » gnum e
return $ e < 0.002 || gnum >= 10000

main = do
gen <- newStdGen
let af = Logistic
( rn , _ ) = randomNeuralNetwork gen [ 2 , 2 , 1 ] [ af , af ] 0.45
examples = [ ( [ 0 , 0 ] , [ 0 ] ) , ( [ 0 , 1 ] , [ 1 ] ) , ( [ 1 , 0 ] , [ 1 ] ) , ( [ 1 , 1 ] , [ 0 ] ) ]
net <- backpropagationBatchParallel rn examples 0.4 stopf
putStrLn «»
putStrLn $ «Result: » ++ show net
printf «0 xor 0 = %.4f n » ( calcXor net 0 0 )
printf «1 xor 0 = %.4f n » ( calcXor net 1 0 )
printf «0 xor 1 = %.4f n » ( calcXor net 0 1 )
printf «1 xor 1 = %.4f n » ( calcXor net 1 1 )

Здесь происходит следующее. Случайным образом генерируется нейронная сеть (randomNeuralNetwork), состоящая из одного входного, одного скрытого и одного выходного слоя. У входного и скрытого слоя по два нейрона, у выходного — один. В слоях используется логистическая функция активации. Весам нейронной сети присваиваются случайные числа от -0.45 до 0.45.

Затем на четырех примерах прогоняется алгоритм обратного распространения ошибки в пакетном режиме со скоростью обучения 0.4. В итоге получается нейронная сеть, вычисляющая функцию XOR.

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

Моя библиотечка «из коробки» предоставляет функции для обучения нейросети в стохастическом и пакетном режимах. За счет многоядерности в силу понятных причин выигрывает только последний. При решении более сложной задачи на четырехядерном процессоре Intel Core i7-3770 3.40GHz я видел ускорение алгоритма обучения в 3.2 раза. С помощью функций, экспортируемых пакетом, вы можете реализовать какие угодно дополнительные режимы обучения.

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

Экспериментируя с генетическими алгоритмами, я оптимизировал библиотеку в плане объема памяти , необходимого для хранения нейронной сети. Поэтому не удивляйтесь, когда найдете в коде всякие странные вещи с Word64 и битовыми операциями. В действительности, в следующих версиях библиотеки я полон решимости сделать еще один шаг в этом направлении и перейти на IntMap. Еще из планов на будущее — реализовать параллельное вычисление , а не только обучение. А также оптимизировать алгоритм обучения. Сейчас библиотечка при обратном проходе честно вычисляет производные от функций активации, в то время, как их можно вычислить на основе выходов нейронной сети, полученных при прямом проходе.

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

EnglishRussianUkrainian