bayesnet 0.0.3 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +6 -29
- data/README.md +5 -2
- data/Rakefile +5 -2
- data/bayesnet.gemspec +0 -1
- data/lib/bayesnet/dsl.rb +4 -0
- data/lib/bayesnet/error.rb +2 -0
- data/lib/bayesnet/factor.rb +21 -13
- data/lib/bayesnet/graph.rb +15 -1
- data/lib/bayesnet/node.rb +13 -1
- data/lib/bayesnet/parsers/bif.rb +2484 -0
- data/lib/bayesnet/parsers/bif.treetop +250 -0
- data/lib/bayesnet/parsers/builder.rb +37 -0
- data/lib/bayesnet/version.rb +1 -1
- data/lib/bayesnet.rb +5 -0
- metadata +5 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9a746d994d25c279f3246613b9a918fb84720c7c9c78f85ce1ffdc5fbd6bcf9c
|
4
|
+
data.tar.gz: 3b8ee59eab90bf75172239601ddef479926f5a27db475688f1191c71298ca757
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 72985a24e9d529b04e8d275a46cc6eadbd55aa4380f2aed73a41d2e3c3c7e7528419aa99fa0004d4d3aab1692484021c3b577e1f9262ee9ef4d89664523d8335
|
7
|
+
data.tar.gz: 8be39618f74ccd85750569a74e18a0a384aa3a12c4c52a35315958d8d4ad4045abbe8bee83d510459e3fd6b29de68bab4120e3540cee0cadc9a024f1b2389ffb
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.5.0] - 2022-02-26
|
4
|
+
|
5
|
+
- Constructing networks out of the `.BIF` ([Interchange Format for Bayesian Networks](https://www.cs.washington.edu/dm/vfml/appendixes/bif.htm)) files.
|
6
|
+
- Fixing inference bug
|
7
|
+
- Network children nodes could be specified ***before** their parents
|
8
|
+
|
9
|
+
## [0.0.3] - 2021-12-29
|
10
|
+
|
11
|
+
- Fixing terminoloty used in Factor class
|
12
|
+
|
3
13
|
## [0.0.2] - 2021-12-28
|
4
14
|
|
5
15
|
- README, CI/CD for Ruby 2.6, 2.7, 3.1 added
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,12 +1,11 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
bayesnet (0.0
|
4
|
+
bayesnet (0.1.0)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
8
8
|
specs:
|
9
|
-
ast (2.4.2)
|
10
9
|
byebug (11.1.3)
|
11
10
|
coderay (1.1.3)
|
12
11
|
m (1.5.1)
|
@@ -14,38 +13,16 @@ GEM
|
|
14
13
|
rake (>= 0.9.2.2)
|
15
14
|
method_source (1.0.0)
|
16
15
|
minitest (5.15.0)
|
17
|
-
|
18
|
-
parser (3.0.3.2)
|
19
|
-
ast (~> 2.4.1)
|
16
|
+
polyglot (0.3.5)
|
20
17
|
pry (0.13.1)
|
21
18
|
coderay (~> 1.1)
|
22
19
|
method_source (~> 1.0)
|
23
20
|
pry-byebug (3.9.0)
|
24
21
|
byebug (~> 11.0)
|
25
22
|
pry (~> 0.13.0)
|
26
|
-
rainbow (3.0.0)
|
27
23
|
rake (13.0.6)
|
28
|
-
|
29
|
-
|
30
|
-
rubocop (1.23.0)
|
31
|
-
parallel (~> 1.10)
|
32
|
-
parser (>= 3.0.0.0)
|
33
|
-
rainbow (>= 2.2.2, < 4.0)
|
34
|
-
regexp_parser (>= 1.8, < 3.0)
|
35
|
-
rexml
|
36
|
-
rubocop-ast (>= 1.12.0, < 2.0)
|
37
|
-
ruby-progressbar (~> 1.7)
|
38
|
-
unicode-display_width (>= 1.4.0, < 3.0)
|
39
|
-
rubocop-ast (1.15.0)
|
40
|
-
parser (>= 3.0.1.1)
|
41
|
-
rubocop-performance (1.12.0)
|
42
|
-
rubocop (>= 1.7.0, < 2.0)
|
43
|
-
rubocop-ast (>= 0.4.0)
|
44
|
-
ruby-progressbar (1.11.0)
|
45
|
-
standard (1.5.0)
|
46
|
-
rubocop (= 1.23.0)
|
47
|
-
rubocop-performance (= 1.12.0)
|
48
|
-
unicode-display_width (2.1.0)
|
24
|
+
treetop (1.6.11)
|
25
|
+
polyglot (~> 0.3)
|
49
26
|
|
50
27
|
PLATFORMS
|
51
28
|
x86_64-darwin-19
|
@@ -57,7 +34,7 @@ DEPENDENCIES
|
|
57
34
|
minitest (~> 5.0)
|
58
35
|
pry-byebug (~> 3.9.0)
|
59
36
|
rake (~> 13.0)
|
60
|
-
|
37
|
+
treetop (~> 1.6)
|
61
38
|
|
62
39
|
BUNDLED WITH
|
63
|
-
2.
|
40
|
+
2.3.3
|
data/README.md
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# Bayesnet
|
2
2
|
|
3
|
-
This gem provides an
|
3
|
+
This gem provides an DSL for constructing Bayesian networks and let to execute basic inference queries. It is also capable of parsing .BIF format ([The Interchange Format for Bayesian Networks](https://www.cs.washington.edu/dm/vfml/appendixes/bif.htm)).
|
4
|
+
|
4
5
|
|
5
6
|
### Example:
|
6
7
|
|
@@ -66,12 +67,14 @@ The inference is based on summing over joint distribution, i.e. it is the simple
|
|
66
67
|
most expensive way to calculate it. No optimization is implemented in this version; the code
|
67
68
|
is more a proof of API.
|
68
69
|
|
70
|
+
### [Another example](https://afurmanov.com/reducing-anxiety-with-bayesian-network) of using this gem
|
71
|
+
|
69
72
|
## Installation
|
70
73
|
|
71
74
|
Add this line to your application's Gemfile:
|
72
75
|
|
73
76
|
```ruby
|
74
|
-
|
77
|
+
em 'bayesnet'
|
75
78
|
```
|
76
79
|
|
77
80
|
And then execute:
|
data/Rakefile
CHANGED
@@ -9,6 +9,9 @@ Rake::TestTask.new(:test) do |t|
|
|
9
9
|
t.test_files = FileList["test/**/*_test.rb"]
|
10
10
|
end
|
11
11
|
|
12
|
-
|
12
|
+
Rake::TestTask.new("regen-bif") do |t|
|
13
|
+
`rm ./lib/bayesnet/parsers/bif.rb`
|
14
|
+
`tt ./lib/bayesnet/parsers/bif.treetop`
|
15
|
+
end
|
13
16
|
|
14
|
-
task default: %i[test
|
17
|
+
task default: %i[test]
|
data/bayesnet.gemspec
CHANGED
@@ -34,7 +34,6 @@ Gem::Specification.new do |spec|
|
|
34
34
|
spec.add_development_dependency "m", "~> 1.5.0"
|
35
35
|
spec.add_development_dependency "minitest", "~> 5.0"
|
36
36
|
spec.add_development_dependency "pry-byebug", "~> 3.9.0"
|
37
|
-
spec.add_development_dependency "standard", "~> 1.3"
|
38
37
|
|
39
38
|
# For more information and examples about making a new gem, checkout our
|
40
39
|
# guide at: https://bundler.io/guides/creating_gem.html
|
data/lib/bayesnet/dsl.rb
CHANGED
data/lib/bayesnet/error.rb
CHANGED
data/lib/bayesnet/factor.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Bayesnet
|
2
4
|
# Factor if a function of sevaral variables (A, B, ...) each defined on values from finite set
|
3
5
|
class Factor
|
@@ -14,9 +16,7 @@ module Bayesnet
|
|
14
16
|
|
15
17
|
# Specifies value for a scope context. Value is the last element in `context_and_val`
|
16
18
|
def val(*context_and_val)
|
17
|
-
if context_and_val.size == 1 && context_and_val[0].is_a?(Array)
|
18
|
-
context_and_val = context_and_val[0]
|
19
|
-
end
|
19
|
+
context_and_val = context_and_val[0] if context_and_val.size == 1 && context_and_val[0].is_a?(Array)
|
20
20
|
@vals[context_and_val[0..-2]] = context_and_val[-1]
|
21
21
|
end
|
22
22
|
|
@@ -26,10 +26,10 @@ module Bayesnet
|
|
26
26
|
|
27
27
|
def [](*context)
|
28
28
|
key = if context.size == 1 && context[0].is_a?(Hash)
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
29
|
+
context[0].slice(*var_names).values
|
30
|
+
else
|
31
|
+
context
|
32
|
+
end
|
33
33
|
@vals[key]
|
34
34
|
end
|
35
35
|
|
@@ -39,6 +39,7 @@ module Bayesnet
|
|
39
39
|
|
40
40
|
def contextes(*var_names)
|
41
41
|
return [] if var_names.empty?
|
42
|
+
|
42
43
|
@scope[var_names[0]].product(*var_names[1..].map { |var_name| @scope[var_name] })
|
43
44
|
end
|
44
45
|
|
@@ -49,29 +50,35 @@ module Bayesnet
|
|
49
50
|
def normalize
|
50
51
|
vals = @vals.clone
|
51
52
|
norm_factor = vals.map(&:last).sum * 1.0
|
52
|
-
vals.each { |k,
|
53
|
+
vals.each { |k, _v| vals[k] /= norm_factor }
|
53
54
|
self.class.new(@scope.clone, vals)
|
54
55
|
end
|
55
56
|
|
56
57
|
def reduce_to(context)
|
57
|
-
#
|
58
|
+
# TODO: use Hash#except when Ruby 2.6 support no longer needed
|
58
59
|
context_keys_set = context.keys.to_set
|
59
60
|
scope = @scope.reject { |k, _| context_keys_set.include?(k) }
|
60
61
|
|
61
62
|
context_vals = context.values
|
62
63
|
indices = context.keys.map { |k| index_by_var_name[k] }
|
63
|
-
vals = @vals.select { |k,
|
64
|
-
vals.transform_keys! { |k| k
|
64
|
+
vals = @vals.select { |k, _v| indices.map { |i| k[i] } == context_vals }
|
65
|
+
vals.transform_keys! { |k| delete_by_indices(k, indices) }
|
65
66
|
|
66
67
|
self.class.new(scope, vals)
|
67
68
|
end
|
68
69
|
|
70
|
+
def delete_by_indices(array, indices)
|
71
|
+
result = array.dup
|
72
|
+
indices.map { |i| result[i] = nil }
|
73
|
+
result.compact
|
74
|
+
end
|
75
|
+
|
69
76
|
# groups by `var_names` having same context and sum out values.
|
70
77
|
def marginalize(var_names)
|
71
78
|
scope = @scope.slice(*var_names)
|
72
79
|
|
73
80
|
indices = scope.keys.map { |k| index_by_var_name[k] }
|
74
|
-
vals = @vals.group_by { |context,
|
81
|
+
vals = @vals.group_by { |context, _val| indices.map { |i| context[i] } }
|
75
82
|
vals.transform_values! { |v| v.map(&:last).sum }
|
76
83
|
|
77
84
|
self.class.new(scope, vals)
|
@@ -86,8 +93,9 @@ module Bayesnet
|
|
86
93
|
|
87
94
|
def index_by_var_name
|
88
95
|
return @index_by_var_name if @index_by_var_name
|
96
|
+
|
89
97
|
@index_by_var_name = {}
|
90
|
-
@scope.each_with_index { |(k,
|
98
|
+
@scope.each_with_index { |(k, _v), i| @index_by_var_name[k] = i }
|
91
99
|
@index_by_var_name
|
92
100
|
end
|
93
101
|
end
|
data/lib/bayesnet/graph.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "bayesnet/node"
|
2
4
|
|
3
5
|
module Bayesnet
|
6
|
+
# Acyclic graph
|
4
7
|
class Graph
|
5
8
|
attr_reader :nodes
|
6
9
|
|
@@ -14,11 +17,18 @@ module Bayesnet
|
|
14
17
|
|
15
18
|
def node(name, parents: [], &block)
|
16
19
|
raise Error, "DSL error, #node requires a &block" unless block
|
17
|
-
|
20
|
+
|
21
|
+
node = Node.new(name, parents)
|
18
22
|
node.instance_eval(&block)
|
19
23
|
@nodes[name] = node
|
20
24
|
end
|
21
25
|
|
26
|
+
def resolve_factors
|
27
|
+
@nodes.values.each do |node|
|
28
|
+
node.resolve_factor(@nodes.slice(*node.parent_nodes))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
22
32
|
def distribution(over: [], evidence: {})
|
23
33
|
joint_distribution
|
24
34
|
.reduce_to(evidence)
|
@@ -61,5 +71,9 @@ module Bayesnet
|
|
61
71
|
end
|
62
72
|
@joint_distribution = factor.normalize
|
63
73
|
end
|
74
|
+
|
75
|
+
def parameters
|
76
|
+
nodes.values.map(&:parameters).sum
|
77
|
+
end
|
64
78
|
end
|
65
79
|
end
|
data/lib/bayesnet/node.rb
CHANGED
@@ -24,6 +24,14 @@ module Bayesnet
|
|
24
24
|
when Array
|
25
25
|
raise Error, "DSL error, #values requires a &block when first argument is an Array" unless block
|
26
26
|
@values = hash_or_array
|
27
|
+
@factor = block
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def resolve_factor(parent_nodes)
|
32
|
+
@parent_nodes = parent_nodes
|
33
|
+
if @factor.is_a?(Proc)
|
34
|
+
proc = @factor
|
27
35
|
node = self
|
28
36
|
@factor = Factor.build do
|
29
37
|
scope node.name => node.values
|
@@ -31,7 +39,7 @@ module Bayesnet
|
|
31
39
|
scope parent_node_name => parent_node.values
|
32
40
|
end
|
33
41
|
end
|
34
|
-
instance_eval(&
|
42
|
+
instance_eval(&proc)
|
35
43
|
end
|
36
44
|
end
|
37
45
|
|
@@ -39,6 +47,10 @@ module Bayesnet
|
|
39
47
|
instance_eval(&block)
|
40
48
|
end
|
41
49
|
|
50
|
+
def parameters
|
51
|
+
(values.size - 1) * parent_nodes.values.reduce(1) { |mul, n| mul * n.values.size }
|
52
|
+
end
|
53
|
+
|
42
54
|
def as(distribution, given:)
|
43
55
|
@values.zip(distribution).each do |value, probability|
|
44
56
|
@factor.val [value] + given + [probability]
|