Это нейросеть (многослойный перцептрон) с обучением через метод обратного распространения ошибки. По возможностям сети там ничего сверхординарного нет, основные ключевые фишки проекта следующие:
- Разумеется, мощный и удобный DSL для описания сети и алгоритма ее обучения.
- Основной упор на быстродействие результирующего кода (оптимизируем все, что только можно).
- Полезная (!) многопоточность (а не то бессмысленное говно на мьютексах, что обычно встречается в библиотеках).
- Сериализация/десериализация сетей. Ну это понятно.
На официальной страничке я там пытаюсь вести документацию в ихнем вики, основное там вроде описано, остальное потихоньку доделаем.
В директории с примерами есть стандартная XOR-сеть, аппроксиматор функции и стенд для сравнения производительности в однопоточном и многопоточном режимах.
Давайте для затравки попробую продемонстрировать все добро на задаче аппроксимации функции синуса (исходник полностью).
(defnet sin (layout (inputs 1) (outputs 1) (layers 5) (learning-rate 0.1) (momentum 0.4) (options gen-run-proc))) |
Дословно, здесь определяется многослойный перцептрон с одним входом, одним выходом и одним скрытым слоем из пяти элементов; задаются коэффициенты и форсируется генерация упрощенной функции для запуска сети (прямого прохода). Подробней про defnet DSL можно почитать в доке. В результате компиляции у нас в зубах должен оказаться класс net-sin и сгенерироваться специализация методов для него.
Теперь нужно задать алгоритм обучения. С источником примеров проблем нет (у нас же есть оригинальная функция sin), и обучить сеть достаточно на отрезке [0, pi/2] (за счет цикличности синуса).
(defnet-sin-train-method () (train/loop (train/before-propagate (let* ((x (random (/ pi 2))) (fx (sin x))) (declare (type double-float x) (type double-float fx)) (setf (train/input 0) (coerce x 'single-float)) (setf (train/expect 0) (coerce fx 'single-float)))) (train/before-backpropagate (summing train/avg-sq-error into (the single-float epoch-sum-error)) (when (zerop (mod (1+ train/count) 10000)) (when (or (< epoch-sum-error 1.0) (>= train/count 10000000)) (format t "Cycle: ~a, current sum error for 10000 cycles: ~a~%" train/count epoch-sum-error) (leave)) (setf epoch-sum-error 0.0))))) |
В DSL defnet-sin-train-method (заметим, что этот макрос был сгенерирован после описания defnet) собственно цикл обучения начинается c сегмента (train/loop &rest clauses). Здесь следует уточнить, что train/loop DSL определен поверх iterate, поэтому весь его синтаксис из train/loop свободно доступен. В нашем примере я воспользовался синтаксисом (summing ...) и оператором (leave).
Специально здесь описывать синтаксис языка train/loop не буду (но это можно посмотреть опять же в доке), я надеюсь, что должно получиться и так более или менее интуитивно понятно :) В этом же и должен быть смысл DSL-я.
На этом собственно все по функциональности аппроксиматора. Теперь можно экспериментировать:
NET-SIN> (defvar *my-net* (make-instance 'net-sin)) *MY-NET* NET-SIN> (reset *my-net*) NIL NET-SIN> (train *my-net*) Cycle: 1109999, current sum error for 10000 cycles: 0.98946446 NIL NET-SIN> (funcall (get-run-proc *my-net*) (coerce (/ pi 6) 'single-float)) 0.4991352 NET-SIN> (sin (/ pi 6)) 0.49999999999999994d0 |
Круто, оно работает! Давайте глянем, насколько точно получилось аппроксимировать. Для этого нарисуем рядом графики обычного синуса и нашего аппроксиматора (полный код net-sin-visualize.lisp):
(defun visualize (&key (start (* pi -2)) (end (* pi 2))) (let ((net (make-instance 'net-sin))) ;; train the network (reset net) (train net) ;; visualize the difference (call-with-cairo-context (let ((sin-fun (make-sin-drawing-proc start end :rgb '(0.3 0 0) :line-width 1)) (sin/approx-fun (make-sin/approx-drawing-proc net start end :rgb '(0 0.3 0) :line-width 2))) (lambda (w h) (funcall sin-fun w h) (funcall sin/approx-fun w h)))))) |
И проверяем:
Как видно из рисунка, практически один в один, только на краях децл врет :)
Что ж, по-идее, для пользователя должно быть удобно, красиво и гигиенично. Теперь, что касается собственно исходного кода библиотеки.
Как бы так сказать: я не особо рекомендую, туда заглядывать =) Основные причины такой каши там две:
- Я, возможно, немного перестарался с оптимизацией генерируемого кода.
- У меня там макросы, генерирующие макросы которые генерируют макросы... Признаюсь честно: я тупо не понимаю, как работает nested backquote :) Что, тем не менее, не мешает его мне использовать. Чисто методом тыка :)
Ну как то так, пожалуй. Соответственно, не стесняйтесь писать комментарии, я их очень люблю =)
← Ctrl ← Alt
Ctrl → Alt →
← Ctrl ← Alt
Ctrl → Alt →