Clojure DAG (сеть Байесов)

Я хотел бы построить байесовскую сеть в clojure, так как я не нашел никакого подобного проекта.

Я изучил много теории BN, но все же я не вижу, как реализуется сеть (я не то, что люди называют "гуру" для чего угодно, но особенно не для функционального программирования).

Я знаю, что BN - это не что иное, как DAG и таблица с большими вероятностями (по одному для каждого node), но теперь у меня нет клей, как реализовать DAG.

Моя первая идея была огромным набором (DAG) с некоторыми небольшими картами (node DAG), каждая карта должна иметь имя (вероятно, a: key) таблицу вероятностей (другая карта?). Вектор родителей и, наконец, вектор не -оценки.

Теперь я не знаю, как реализовать ссылку родителей и не-потомков (что я должен положить в два вектора). Я предполагаю, что указатель должен быть совершенным, но clojure недостаток его; Я мог бы вставить вектор: имя другого node, но он будет медленным, не так ли?

Я думал, что вместо вектора я мог бы использовать больше наборов, таким образом быстрее найти потомков node.

Аналогичная проблема для таблицы вероятности, где мне еще нужна ссылка на других узлах.

Наконец, я также хотел бы изучить BN (построить сеть, начиная с данных), это означает, что я буду менять много как таблицы вероятностей, края, так и узлы.

Должен ли я использовать изменяемые типы или они только увеличивали бы сложность?

Ответы

Ответ 1

Это не полный ответ, но вот возможная кодировка для примера сети из статьи wikipedia. Каждый node имеет имя, список преемников (детей) и таблицу вероятностей:

(defn node [name children fn]
  {:name name :children children :table fn})

Кроме того, здесь есть небольшие вспомогательные функции для построения истинных/ложных вероятностей:

;; builds a true/false probability map
(defn tf [true-prob] #(if % true-prob (- 1.0 true-prob)))

Вышеприведенная функция возвращает замыкание, которое при задании значения true (соответственно false) возвращает вероятность события X=true (для переменной X, которую мы кодируем).

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

(let [gw (node "grass wet" [] (fn [& {:keys [sprinkler rain]}]
                            (tf (cond (and sprinkler rain) 0.99
                                      sprinkler 0.9
                                      rain 0.8
                                      :else 0.0))))

  sk (node "sprinkler" [gw]
           (fn [& {:keys [rain]}] (tf (if rain 0.01 0.4))))

  rn (node "rain" [sk gw]
           (constantly (tf 0.2)))]

  (def dag {:nodes {:grass-wet gw :sprinkler sk :rain rn}
        :joint (fn [g s r]
                 (*
                  (((:table gw) :sprinkler s :rain r) g)
                  (((:table sk) :rain r) s)
                  (((:table rn)) r)))}))

Таблица вероятностей каждого node задается как функция состояний родительских узлов и возвращает вероятность для значений true и false. Например,

((:table (:grass-wet dag)) :sprinkler true :rain false)

... возвращает {:true 0.9, :false 0.09999999999999998}.

Полученная совместная функция объединяет вероятности в соответствии с этой формулой:

P(G,S,R) = P(G|S,R).P(S|R).P(R)

И ((:joint dag) true true true) возвращает 0.0019800000000000004. Действительно, каждое значение, возвращаемое ((:table <x>) <args>), является замыканием вокруг if, которое возвращает вероятность, зная состояние переменной вероятности. Мы вызываем каждое замыкание с соответствующим значением true/false, чтобы извлечь соответствующую вероятность и умножить их.

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

Ответ 2

В общем, способ вычисления совместного распределения BN равен

prod( P(node | parents of node) ) 

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

  • node name
  • список родителей
  • таблица вероятностей
  • список детей

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

Узлы без родителей имеют только одну строку.

Каждая строка должна быть нормализована, после чего P (node | parents) = table [row, col]

Вам действительно не нужен список детей, но он может облегчить топологическую сортировку. DAG должна быть способна топологически сортироваться.

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

Запрос DAG - это другое дело, и лучший способ для этого зависит от размера и достаточного приблизительного ответа. Здесь не хватает места, чтобы покрыть их. Поиск Мерфи и Bayes Net Toolbox может быть полезным

Я понимаю, что вы специально ищете реализацию, но, с небольшой работой, вы можете сворачивать самостоятельно.

Ответ 3

Вы можете попытаться сделать еще более плоский и иметь несколько карт, индексированных с помощью node ids: одна карта для таблиц вероятностей, одна для родителей и одна для не-потомков (я не эксперт BN: что это, как это сделать используется и т.д. Это похоже на то, что можно было бы пересчитать из таблицы родителей ^ W отношение ^ W map).