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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ca0c7b763a97d4516fb893a7c79716bd4f7279be3fba1b0a9d55f43279aa95cf
4
- data.tar.gz: 55da869a7d0436bd7eb08a8787bc5d19d56e71e90ac283f4568931c391d3f407
3
+ metadata.gz: 9a746d994d25c279f3246613b9a918fb84720c7c9c78f85ce1ffdc5fbd6bcf9c
4
+ data.tar.gz: 3b8ee59eab90bf75172239601ddef479926f5a27db475688f1191c71298ca757
5
5
  SHA512:
6
- metadata.gz: 203475b19fc5bfa4151b4586243ebedce938fcf6a5c5ed3d561f7553ea011e3c8cc797d45360be790f691cecf7462623a0e179160d770454a5bc5cd8ed1a77a2
7
- data.tar.gz: fd014b35e9f98908694b5a25655f279b8885f01e59e30edf17c34a433758dca7d5c271b6318b5d67df921ce39a63c0e643fb84de241064d8bf8ec95e9a39b94d
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
@@ -6,10 +6,10 @@ source "https://rubygems.org"
6
6
  gemspec
7
7
 
8
8
  gem "rake", "~> 13.0"
9
+ gem "treetop", "~> 1.6"
9
10
 
10
11
  group :development, :test do
11
12
  gem "m", "~> 1.5.0"
12
13
  gem "minitest", "~> 5.0"
13
14
  gem "pry-byebug", "~> 3.9.0"
14
- gem "standard", "~> 1.3"
15
15
  end
data/Gemfile.lock CHANGED
@@ -1,12 +1,11 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- bayesnet (0.0.3)
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
- parallel (1.21.0)
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
- regexp_parser (2.2.0)
29
- rexml (3.2.5)
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
- standard (~> 1.3)
37
+ treetop (~> 1.6)
61
38
 
62
39
  BUNDLED WITH
63
- 2.2.32
40
+ 2.3.3
data/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # Bayesnet
2
2
 
3
- This gem provides an API for building a Bayesian network and executing some queries against it.
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
- gem 'bayesnet'
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
- require "standard/rake"
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 standard]
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
@@ -1,10 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "bayesnet/graph"
2
4
 
3
5
  module Bayesnet
6
+ # Bayesnet::DSL.define ...
4
7
  module DSL
5
8
  def define(&block)
6
9
  graph = Graph.new
7
10
  graph.instance_eval(&block) if block
11
+ graph.resolve_factors
8
12
  graph
9
13
  end
10
14
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bayesnet
2
4
  class Error < StandardError
3
5
  end
@@ -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
- context[0].slice(*var_names).values
30
- else
31
- context
32
- end
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, v| vals[k] /= norm_factor }
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
- # todo: use Hash#except when Ruby 2.6 support no longer needed
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, v| indices.map { |i| k[i] } == context_vals }
64
- vals.transform_keys! { |k| k - context_vals }
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, val| indices.map { |i| context[i] } }
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, v), i| @index_by_var_name[k] = i }
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
@@ -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
- node = Node.new(name, @nodes.slice(*parents))
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(&block)
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]