love5an: (Default)
[personal profile] love5an
Вчера думал над тем, что бы написать о cells. Так и не придумал пока, но зато полез читать про разные dataflow-фреймворки, и решил накатать на лиспе какой-нибудь свой.

Вот что вышло: neural-flow
От cells отличается как минимум тем, что интерфейс невероятно простой, совершенно без макросов, используется метаобъектный протокол, а документации еще меньше.
Работает на SBCL, Clozure CL и clisp, как минимум.

Подробнее:

Почему `neural'?

Ну, я подумал, что потоки данных в контексте dataflow парадигмы очень похожи на импульсы в нейронах.

Вот, нашел такую картинку в гугле, и подрисовал кое-чего в пейнте:

Нейрон

Дендриты это такие штуковины, по которым нейрон из синапсов, от других нейронов, принимает какие-либо сигналы.
После того, как он их обрабатывает, через аксон он передает новые сигналы, другим нейронам и другим клеткам.

При чем здесь dataflow, опять же? При том, что обработчики данных связаны в сеть, каждый из них получает данные от других обработчиков, что-то с ними делает, и пересылает дальше, всем обработчикам, с которыми связан.

Вообще говоря, я даже не стал особо утруждать себя выбором названия для класса обработчиков.
(defclass neuron (closer-mop:funcallable-standard-object)
  ((name :initarg :name :type symbol)
   (owner :initarg :owner)
   (receivers :initarg :receivers :type list)
   (value :initarg :value)
   (%function :initarg :function :type function)
   (handler :initarg :handler :type function)
   (handler-filter :initarg :handler-filter))
  (:default-initargs
      :name nil
      :owner nil
      :function (lambda (value &optional sender)
                  (declare (ignore sender))
                  value)
      :receivers '()
      :handler (lambda (neuron condition)
                 (declare (ignore neuron condition))
                 (values))
      :handler-filter 'error)
  (:metaclass closer-mop:funcallable-standard-class))
Слот "receivers" это такой аксон - коллекция других нейронов или просто функций, в которые нейрон передает обработанные данные. Последние обработанные данные хранятся в слоте "value". Слот "%function" - функция, ответственная за процессинг данных. Слоты "handler" и "handler-filter" - для обработки ошибок -  первый из них хранит функцию-обработчик, второй - список классов и объектов ошибок, к которым обработчик применяется.

А где же дендриты?
В качестве них выступаем мы сами. Ну, или другие нейроны:
(defgeneric update-neuron (neuron)
  (:method ((neuron neuron))
    (loop :with neuron-value = (neuron-value neuron)
      :for receiver :in (slot-value neuron 'receivers)
      :do (funcall receiver neuron-value neuron))))

(defun (setf neuron-value) (new-value neuron &optional sender)
  (block function
    (let ((new-value (handler-bind
                       ((condition (lambda (c) (%neuron-handler neuron c))))
                       (prog ()
                           start
                           (restart-case
                             (return (funcall (neuron-function neuron) new-value sender))
                             (retry-processing (&optional (value new-value))
                                 (setf new-value value)
                                 (go start))
                             (abort-processing (&optional return-value)
                                 (return-from function return-value)))))))
      (prog1 (setf (slot-value neuron 'value) new-value)
       (update-neuron neuron)))))
В случае ошибки, или другого сигнала, управление передается функции-обработчику-сигнала(тот самый "handler"), которая может как минимум два действия совершить - прервать конкретную ветвь потока данных(перезапуск "abort-processing"), или же попробовать вызвать функцию, обрабатывающую данные, заново(возможно, с новым значением)(перезапуск "retry-processing").

Как видно, нейрон сделан funcallable-standard-object'ом не зря - с таким подходом мы можем выдавать обработанные данные не только другим нейронам, но и любым функциям, не утруждая себя typecase'ами и прочим.

Кстати, сама функция funcallable-объекта представляет собой по сути тот же самый (setf neuron-value):
(closer-mop:set-funcallable-instance-function
  object (lambda (value &optional sender)
           (setf (neuron-value object sender) value)))


Казалось бы - ну, обычный паттерн observer, что удивительного?
На самом деле, не все так просто.

Основная фича библиотеки - метакласс dataflow-class:
(defclass dataflow-class (closer-mop:standard-class)
  ()
  (:metaclass closer-mop:standard-class))
Конечно, он не просто так вот определяется. Там, в библиотеке, много магии из метаобъектного протокола - желающие могут посмотреть.

Вобщем, суть в том, что все слоты классов, которые являются экземплярами метакласса dataflow-class - представляют собой нейроны, описанные выше.

При этом, мы можем продолжать с ними работать как с обычными слотами обычных классов - через функцию slot-value и :reader/:writer/:accessor методы. В случае изменения значения слота (т.е. через (setf slot-value) или :writer-метод) нижележащий нейрон автоматически распространит поток данных по всем слотам других(и даже этого же самого) объектов dataflow-классов, по всем нейронам, и по всем функциям, с которыми связан.

Кстати, собственно связывание нейронов/функций делается с помощью функции add-connection:
(defun add-connection (source destination &key source-name destination-name (test #'eq))
  (declare (type (or neuron dataflow-object) source)
           (type (or neuron function symbol cons dataflow-object) destination)
           (type symbol source-name destination-name)
           (type (or symbol function) test))
  (let ((source (if (typep source 'neuron)
                  source
                  (slot-neuron source source-name)))
        (destination (typecase destination
                       ((or neuron function) destination)
                       ((or symbol cons) (fdefinition destination))
                       (T (slot-neuron destination destination-name)))))
    (pushnew destination (slot-value source 'receivers) :test test)
    (values source destination)))
А разрываются связи с помощью remove-connection.

Ну, теперь пара примеров.

Первый демонстрирует то, что event-driven парадигма является частным случаем подхода, реализованного в библиотеке:
(defclass my-object (dataflow-object)
  ;; :neuron-name позволяет задавать нейрону имя, отличное от имени слота
  ((%event :initform nil :neuron-name event))
  (:metaclass dataflow-class))

(defun fire-event (instance)
  ;;update-slot просто вызывает update-neuron,
  ;; не перевычисляя значение слота
  (update-slot instance 'event))

(defparameter *my-object* (make-instance 'my-object))

(add-connection *my-object* (lambda (data &optional sender)
                              (declare (ignore data sender))
                              (write-line "An event was fired"))
                :source-name 'event)

(defun event-example ()
  (fire-event *my-object*))

;; (event-example)
;; ==> `An event was fired' на *standard-output*

Второй пример посложнее, и показывает, как приятно было бы на основе моей библиотеки реализовать gui-фреймворк:
(defclass textbox (dataflow-object)
  ((text :initform "Hello, world!" :accessor textbox-text))
  (:metaclass dataflow-class))

(defparameter *textbox* (make-instance 'textbox))

;;устанавливаем слоту `text' функцию-обработчик-данных
(setf (slot-function *textbox* 'text)
      (lambda (data &optional sender)
        (declare (ignore sender))
        ;;если пользователь, или кто еще, вдруг решил положить
        ;;в слот что-то отличное от строки, сигнализируем ошибку
        (unless (typep data 'string)
          (error "Textbox `text' slot only accepts strings"))
        data))

;;функция-обработчик-ошибок. Просто пишет на *error-output* сведения об ошибке
;; и останавливает поток данных(не тот, который thread, и даже не тот, который
;; stream, а именно тот, который `flow of data')
;;Почему-то подумалось про монаду Maybe.
(defun abort-flow-handler (neuron cond)
  (declare (ignore neuron))
  (format *error-output* "~&Something gone wrong: ~a~%" cond)
  (invoke-restart 'abort-processing))

;; устанавливаем нейрону слота обработчик ошибок
(setf (slot-handler *textbox* 'text) #'abort-flow-handler)

;; Следующий объект в цепочке у нас просто нейрон, но точно так же можно
;; было бы сделать его и слотом объекта какого-либо класса, понятное дело.
(defparameter *text-processor*
    (make-instance 'neuron
      ;; функция, обрабатывающая данные
      :function (lambda (text &optional sender)
                  (declare (ignore sender))
                  ;;считываем какой-нибудь объект
                  ;; из строки с помощью лиспового reader'а
                  (let* ((*read-eval* nil)
                         (data (read-from-string text)))
                    ;; и если он вдруг не является числом,
                    ;; кидаем исключение
                    (unless (numberp data)
                      (error "Invalid input: ~s" data))
                    ;; обработанные данные - cons-ячейка, в которой
                    ;; car - считанное число, а cdr - оно же, помноженное на 2
                    (cons data (* data 2))))
      ;; обработчик ошибок у нейрона тот же, что и у слота объекта *textbox*
      ;; кстати, handler-filter по умолчанию - error, т.е. отлавливает
      ;; только сигналы оного класса
      :handler #'abort-flow-handler))

;; связываем слот *textbox* и нейрон *text-processor*
(add-connection *textbox* *text-processor* :source-name 'text)

;; Данные, полученные от *text-processor*, у нас поставляются
;; просто-напросто в функцию, которая печатает их на *standard-output*
(add-connection *text-processor* (lambda (data &optional sender)
                                   (declare (ignore sender))
                                   (format t "~&~a + ~:*~a = ~a~%"
                                           (car data) (cdr data))))

(defun textbox-example (&optional (text "123"))
  (setf (textbox-text *textbox*) text)
  (setf (textbox-text *textbox*) 123)
  (setf (textbox-text *textbox*) "abc"))

;; (textbox-example)
;; ==> 123 + 123 = 246
;;     Something gone wrong: Textbox `text' slot only accepts strings
;;     Something gone wrong: Invalid input: ABC

Вобщем, библиотека под MIT-лицензией, так что можно, ничего не боясь, тырить оттуда код в коммерческие closed-source проекты - главное не забывать говорить мне спасибо.

Date: 2011-01-26 10:29 pm (UTC)
From: [identity profile] auben.livejournal.com
А мне иногда хочется, как в старые времена, поговорить о наркотиках, особенно в такие моменты.

Date: 2011-01-27 05:52 am (UTC)
From: [identity profile] love5an.livejournal.com
да, раньше было весело

Date: 2011-01-27 11:46 am (UTC)
From: (Anonymous)
А ты уже не употребляешь?

Date: 2011-01-27 12:04 pm (UTC)
From: (Anonymous)
Мило, Лавсанчик "изобрел" сигналы со слотами. Еще лет десять и "изобретет" реактивное программирование.

Date: 2011-01-27 12:06 pm (UTC)
From: [identity profile] love5an.livejournal.com
Иди-ка нахуй, это как раз обобщение сигналов со слотами.
Типа Rx.

Date: 2011-01-27 12:09 pm (UTC)
From: [identity profile] love5an.livejournal.com
Причем, более того, оно позволяет рулить способом рассылки сообщений через методы update-neuron.

Date: 2011-01-27 03:22 pm (UTC)
From: (Anonymous)
Кэп замечает, что это совершенно неэффективно в случае твоего примера с нейросетями.

Date: 2011-01-28 04:08 am (UTC)
From: [identity profile] love5an.livejournal.com
да, у меня есть уже пара идей для улучшения

Date: 2011-01-28 07:18 am (UTC)
From: (Anonymous)
надеюсь, первая - это отказаться от data flow, вторая - от переизбытка clos :)

Date: 2011-01-27 12:38 pm (UTC)
From: (Anonymous)
У Rx есть killer-фича в виде LINQ to Events, а без нее - это банальный слот-сигнал с небольшими модификациями. Да, обобщение, но какой-то принципиальной разницы (типа работать с потоком событий как с произвольной последовательностью, например со списком) нет.

Вот сделай, чтобы можно было писать хотя бы как-то так:
(map (lambda (event) (when (left-button-clickp event) (do-any))) events)

Date: 2011-01-27 01:02 pm (UTC)
From: [identity profile] love5an.livejournal.com
Надо понимать, что LINQ это просто сахар.

В моем случае достаточно в функции нейрона или в методе update-neuron выдавать коллекцию, чтобы с ней получатель мог как с коллекцией работать.

Date: 2011-01-27 01:12 pm (UTC)
From: [identity profile] love5an.livejournal.com
поток, а не коллекцию, строго говоря

Date: 2011-01-27 01:18 pm (UTC)
From: [identity profile] love5an.livejournal.com
т.о. достаточно просто отнаследоваться от neuron и добавить новый слот
и, возможно, переопределить на него метод update-neuron

Date: 2011-01-28 04:27 am (UTC)
From: (Anonymous)
> Надо понимать, что LINQ это просто сахар.

Конечно, нет.

> В моем случае достаточно

Конечно, недостаточно. Ты сперва попробуй, а потом говори.

Date: 2011-01-28 04:42 am (UTC)
From: [identity profile] love5an.livejournal.com
>Конечно, нет.

А что это? Магия? Пиздец.

>Конечно, недостаточно. Ты сперва попробуй, а потом говори.

Достаточно. А вот как в Rx припилить мою модель обработки исключений? Или как сделать чтобы обычные проперти работали как ISubject? Никак.

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

Date: 2011-01-28 05:00 am (UTC)
From: (Anonymous)
> А что это? Магия? Пиздец.

По-твоему что-то может быть либо магией либо синтаксическим сахаром? Других вариантов нет? Пиздец.

> Достаточно.

Недостаточно. Повторяю - попробуй, потом пизди.

> которая в CL пишется на коленке за два вечера и куда органичнее вписывается в язык

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

Date: 2011-01-28 05:22 am (UTC)
From: [identity profile] love5an.livejournal.com
Что непонятного? Делается слот, в котором хранится очередь. Делается метод pull, который берет евент сверху очереди, в upadate-neuron всем обсерверам передаем эту самую очередь.

Ни LINQ, ни Rx уникальными, органично вписывающимися в язык технологиями не являются. А являются прилепленной сбоку на соплях херней.

Я сегодня допилю библиотеку до модели много -> много, как у настоящих нейронов, с динамически изменяемыми связями. Тогда посмотрим, чья там технология уникальная и органично вписывающаяся в язык

Date: 2011-01-28 06:36 am (UTC)
From: (Anonymous)
> Что непонятного? Делается слот, в котором хранится очередь. Делается метод pull, который берет евент сверху очереди, в upadate-neuron всем обсерверам передаем эту самую очередь.

Я тебя не спрашиваю, как сделать эту очередь, это задача тривиальная и обсуждения не заслуживает. Вопрос в том, что дальше? Как сделать так, чтобы эта очередь могла быть обработана стандартными языковыми средствами? Чтобы я мог ее filter/map, например? Или засунуть в loop/iterate?

> Я сегодня допилю библиотеку до модели много -> много, как у настоящих нейронов, с динамически изменяемыми связями.

Ох, вау, теперь в твоем холодильнике можно будет еще и одежду стирать?

Date: 2011-01-29 12:59 pm (UTC)
From: (Anonymous)
Чо, ласвесанчек, саснул?

Date: 2011-01-28 05:03 am (UTC)
From: (Anonymous)
> Достаточно. А вот как в Rx припилить мою модель обработки исключений? Или как сделать чтобы обычные проперти работали как ISubject? Никак.

"В нашем уникальном холодильнике нету морозилки, зато вместо нее мы встроили микроволновую печь и радио".

Date: 2011-01-28 07:29 pm (UTC)
From: [identity profile] love5an.livejournal.com
это настолько крутое устройство, что совершенно похуй на то, что оно уже не холодильник

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

Date: 2011-01-29 04:38 am (UTC)
From: (Anonymous)
Только на практике нужен как раз холодильник, а не "крутое устройство", которое может все, что угодно, кроме того, что нужно (охлаждать продукты).

Date: 2011-01-29 10:09 am (UTC)
From: [identity profile] love5an.livejournal.com
На практике дохуя чего нужно.
Но если у нас есть криокамера, например, холодильник нам нахуй не нужен.

Date: 2011-01-29 10:52 am (UTC)
From: (Anonymous)
В том-то и дело, что у тебя нихуя не криокамера, а микроволновая печь. Там, где требуется холодильник.

Date: 2011-01-29 10:54 am (UTC)
From: [identity profile] love5an.livejournal.com
http://juick.com/lovesan/1187435

Date: 2011-01-29 11:49 am (UTC)
From: (Anonymous)
А в чем проблема сделать хвостовую рекурсию? И никакого стековерфлоу.

Date: 2011-01-29 12:10 pm (UTC)
From: [identity profile] love5an.livejournal.com
Хвостовую рекурсию в алгоритме сортировки? Какая разница, цикл или хвостовая рекурсия.
Но проблема не в этом, а в том, что надо сортировать только подграф.

Если не про сортировку, а про хвостовую рекурсию вместо сортировки - для propagation она не подходит, проход по графу рекурсивный по своей сути, так как граф может быть и не деревом.
http://en.wikipedia.org/wiki/Reactive_programming#Evaluation_models_of_Reactive_Programming

Date: 2011-01-29 12:26 pm (UTC)
From: (Anonymous)
> Если не про сортировку, а про хвостовую рекурсию вместо сортировки

Именно так. Любая рекурсия приводится к хвосторекурсивному виду, иначе CPS-преобразования бы не существовало.

Date: 2011-01-29 12:46 pm (UTC)
From: [identity profile] love5an.livejournal.com
Мне делать больше нехуй, да, кроме как массивами замыканий направо и налево раскидываться.

Топологическая сортировка еще и убирает лишние вычисления.

Date: 2011-01-29 02:10 pm (UTC)
From: (Anonymous)
Блять, в твоем случае сам cps нахуй не нужен, просто передаешь рекурсивно список нейронов следующего уровня и все. Грубо говоря тот же стек, только руками.

> Топологическая сортировка еще и убирает лишние вычисления.

Если ты про те вычисления, о который в педивикии (про ромб), то тут фейл, тебе так убирать нельзя. У тебя каждый нейрон должен обязательно среагировать на все импульсы, а не только на один. Подобную оптимизацию можно провести только в чистом data-flow (когда в модели нету событий).

Date: 2011-01-29 02:15 pm (UTC)
From: [identity profile] love5an.livejournal.com
>Подобную оптимизацию можно провести только в чистом data-flow (когда в модели нету событий).

Тогда сейчас и так все работает.

Date: 2011-01-29 02:18 pm (UTC)
From: (Anonymous)
С рекурсией будет O(m) (m - количество путей в подграфе), причем большая часть уйдет на расчет нового значения в нейроне (остальное там 1 cons/car/cdr, пара вызовов) и это неулучшаемая оценка, по памяти тоже O(m) но это вырожденный случай (когда глубину графа = 1), реально что-то вроде O(m/n), где n - глубина графа.

Date: 2011-01-29 02:25 pm (UTC)
From: [identity profile] love5an.livejournal.com
Это то же самое, что
  (labels ((topological-sort ()
             (let ((unsorted (cons (list neuron) nil)))
               (setf (cdr unsorted) (car unsorted))
               (loop :until (null (car unsorted)) :collect
                 (let ((next (pop (car unsorted))))
                   (loop :for node :in (compute-synapse next (slot-value next '%dependents))
                     :for cell = (cons node nil)
                     :do (setf (cddr unsorted) cell
                               (cdr unsorted) cell
                               (car unsorted) (or (car unsorted) cell)))
                   (setf (slot-value next '%dependents) '())
                   next)))))

Date: 2011-01-29 02:31 pm (UTC)
From: (Anonymous)
Ну да, как-то так. А по-другому не сделать, т.к. у тебя не совсем стандартный data-flow, потому что нейроны могут при обновлении учитывать свое предыдущее состояние, а не только сигналы от других нейронов (я правильно понял смысл old-value?), так что можно сделать, например, нейрон-сумматор (аналог fold), и он по определению должен среагировать на _все_ сигналы, а не только на один из них.

Date: 2011-01-29 12:01 pm (UTC)
From: (Anonymous)
И сделай ты какую-нибудь макрообертку для объявления и соединения нейронов с некоторым предопределенным функционалом. Делов-то на пять минут, зато будет выглядеть все на порядок пиздаче, чем пидорашение кучи (make-instance 'neuron).

Date: 2011-01-29 12:11 pm (UTC)
From: [identity profile] love5an.livejournal.com
основная фича будет не отдельные нейроны, а слоты-нейроны в объектах CLOS, выглядящие как обычные слоты.

Date: 2011-01-29 12:19 pm (UTC)
From: (Anonymous)
На мой взгляд гораздо лучше было бы _отдельно_ в виде дсл описывать всю сеть нейронов, чтобы было просто, ясно и наглядно, а потом просто напихать эти нейроны в слоты. Профиты очевидны - дополнительный уровень абстракции, значительная часть логики отделена, удобно представлена и ее легко изменить, не затрагивая все остальное.

Date: 2011-01-27 12:40 pm (UTC)
From: (Anonymous)
Кстати, почему ты суешь везде CLOS? Почему нельзя реализовать это дело отдельно, чтобы потом можно было использовать и без CLOS?

Date: 2011-01-27 01:03 pm (UTC)
From: [identity profile] love5an.livejournal.com
Потому что CLOS клёвая. И позволяет все реализовать с минимальными усилиями. Зачем без нее, когда можно и с ней?

Date: 2011-01-27 12:45 pm (UTC)
From: (Anonymous)
Господи, убей уже всех этих фриков, заполонивших мой лисп. Они - рак, который убивает его и отпугивает нормальных людей. Сделай так, пожалуйста, чтобы и золотце, и андрюша, и лавсанчик горели огнём. Спасибо.

Date: 2011-01-27 01:00 pm (UTC)
From: [identity profile] love5an.livejournal.com
Кто такие золотце и андрюша? Почему лисп твой? Почему фрики? Ты кто?

Date: 2011-01-28 07:20 pm (UTC)
From: (Anonymous)
Золотце, это один хуй с нулича/c/, херачит какой-то неведомый язык поверх CL.

Date: 2011-02-04 01:21 pm (UTC)
From: (Anonymous)
Аноним представтесь, почитали бы ваш блог)

Date: 2011-02-04 09:45 pm (UTC)
From: [identity profile] basp.livejournal.com
По-моему, терминология из нейробиологии тут не к месту. Если бы назвали обычно "слоты", "сигналы" и т.д. - было бы лучше.

Date: 2011-02-04 11:35 pm (UTC)
From: [identity profile] love5an.livejournal.com
я обновил бибилотеку
сейчас там не просто слоты и сигналы

Profile

love5an: (Default)
Dmitry Ignatiev

June 2020

S M T W T F S
 123456
78910 111213
14151617181920
21222324252627
282930    

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Jul. 22nd, 2025 12:23 pm
Powered by Dreamwidth Studios