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).