swizard (swizard) wrote,
swizard
swizard

Аннонс: cl-bpnet

Оформил "на вынос" свой очередной проект: cl-bpnet.

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

  • Разумеется, мощный и удобный 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))))))

И проверяем:


Как видно из рисунка, практически один в один, только на краях децл врет :)

Что ж, по-идее, для пользователя должно быть удобно, красиво и гигиенично. Теперь, что касается собственно исходного кода библиотеки.

Как бы так сказать: я не особо рекомендую, туда заглядывать =) Основные причины такой каши там две:

Ну как то так, пожалуй. Соответственно, не стесняйтесь писать комментарии, я их очень люблю =)
Tags: announce, cl-bpnet, code, common lisp, library, lisp, release
Subscribe
  • Post a new comment

    Error

    default userpic

    Your IP address will be recorded 

    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.
  • 56 comments