Class: Bayesnet::Factor
- Inherits:
-
Object
- Object
- Bayesnet::Factor
- Defined in:
- lib/bayesnet/factor.rb
Overview
Factor if a function of several variables (A, B, …), where every variable cold take values from some finite set
Class Method Summary collapse
-
.build(&block) ⇒ Object
+++ Factor DSL +++.
Instance Method Summary collapse
- #*(other) ⇒ Object
-
#[](*context) ⇒ Object
accessor factor value, i.e “‘ factor = Bayesnet::Factor.build do scope weather: %i[sunny cloudy] scope mood: %i[bad good] val :sunny, :bad, 0.1 …
-
#contextes(*var_names) ⇒ Object
returns all combinations of values of ‘var_names`.
- #eliminate(var_name) ⇒ Object
- #group_by_scope_values(scope_keys) ⇒ Object
-
#marginalize(var_names) ⇒ Object
Returns new context defined over ‘var_names`, all other variables get eliminated.
-
#normalize ⇒ Object
returns new normalized factor, i.e.
-
#reduce_to(context) ⇒ Object
Returns factor built as follows: 1.
-
#scope(var_name_to_values = nil) ⇒ Object
Factor DSL Defining variable with list of its possible values looks like: “‘ Bayesnet::Factor.build do scope weather: %i[sunny cloudy] scope mood: %i[bad good] …
- #select(subcontext) ⇒ Object
-
#val(*context_and_val) ⇒ Object
Factor DSL Specifies factor value for some set of variable values, i.e.
-
#values ⇒ Object
returns all possible values.
-
#var_names ⇒ Object
List of variable names.
Class Method Details
.build(&block) ⇒ Object
+++ Factor DSL +++
Factor DSL entry point:
10 11 12 13 14 |
# File 'lib/bayesnet/factor.rb', line 10 def self.build(&block) factor = new factor.instance_eval(&block) factor end |
Instance Method Details
#*(other) ⇒ Object
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
# File 'lib/bayesnet/factor.rb', line 140 def *(other) common_scope = @scope.keys & other.scope.keys new_scope = scope.merge(other.scope) new_vals = {} group1 = group_by_scope_values(common_scope) group2 = other.group_by_scope_values(common_scope) group1.each do |scope, vals1| combo = vals1.product(group2[scope]) combo.each do |(val1, val2)| # values in scope must match variables order in new_scope, i.e. # they must match `new_scope.var_names` # The code bellow ensures it by merging two hashes in the same # wasy as `new_scope`` is constructed above val_by_name1 = var_names.zip(val1.first).to_h val_by_name2 = other.var_names.zip(val2.first).to_h new_vals[val_by_name1.merge(val_by_name2).values] = val1.last*val2.last end end Factor.new(new_scope, new_vals) end |
#[](*context) ⇒ Object
accessor factor value, i.e “‘ factor = Bayesnet::Factor.build do
scope weather: %i[sunny cloudy]
scope mood: %i[bad good]
val :sunny, :bad, 0.1
...
end factor[:sunny, :bad] # 0.1 “‘
66 67 68 69 70 71 72 73 |
# File 'lib/bayesnet/factor.rb', line 66 def [](*context) key = if context.size == 1 && context[0].is_a?(Hash) context[0].slice(*var_names).values else context end @vals[key] end |
#contextes(*var_names) ⇒ Object
returns all combinations of values of ‘var_names`
76 77 78 79 80 |
# File 'lib/bayesnet/factor.rb', line 76 def contextes(*var_names) return [] if var_names.empty? @scope[var_names[0]].product(*var_names[1..].map { |var_name| @scope[var_name] }) end |
#eliminate(var_name) ⇒ Object
128 129 130 131 132 |
# File 'lib/bayesnet/factor.rb', line 128 def eliminate(var_name) keep_var_names = var_names keep_var_names.delete(var_name) marginalize(keep_var_names) end |
#group_by_scope_values(scope_keys) ⇒ Object
161 162 163 164 |
# File 'lib/bayesnet/factor.rb', line 161 def group_by_scope_values(scope_keys) indices = scope_keys.map { |k| index_by_var_name[k] } @vals.group_by { |context, _val| indices.map { |i| context[i] } } end |
#marginalize(var_names) ⇒ Object
Returns new context defined over ‘var_names`, all other variables get eliminated. For every combination of `var_names`’s values the value of new factor is defined by summing up values in original factor having compatible value
118 119 120 121 122 123 124 125 126 |
# File 'lib/bayesnet/factor.rb', line 118 def marginalize(var_names) scope = @scope.slice(*var_names) indices = scope.keys.map { |k| index_by_var_name[k] } vals = @vals.group_by { |context, _val| indices.map { |i| context[i] } } vals.transform_values! { |v| v.map(&:last).sum } self.class.new(scope, vals) end |
#normalize ⇒ Object
returns new normalized factor, i.e. where sum of all values is 1.0
88 89 90 91 92 93 |
# File 'lib/bayesnet/factor.rb', line 88 def normalize vals = @vals.clone norm_factor = vals.map(&:last).sum * 1.0 vals.each { |k, _v| vals[k] /= norm_factor } self.class.new(@scope.clone, vals) end |
#reduce_to(context) ⇒ Object
Returns factor built as follows:
-
Original factor gets filtered out by variables having values compatible with ‘context`
-
Returned factor does not have any variables from ‘context` (because they have
same values, after step 1) The ‘context` argument supposed to be an evidence, somewhat like `:sunny`
101 102 103 104 105 106 107 108 109 110 111 112 |
# File 'lib/bayesnet/factor.rb', line 101 def reduce_to(context) limited_context = context.slice(*scope.keys) return self.class.new(@scope, @vals) if limited_context.empty? limited_scope = @scope.slice(*(@scope.keys - limited_context.keys)) context_vals = limited_context.values indices = limited_context.keys.map { |k| index_by_var_name[k] } vals = @vals.select { |k, _v| indices.map { |i| k[i] } == context_vals } vals.transform_keys! { |k| delete_by_indices(k, indices) } self.class.new(limited_scope, vals) end |
#scope(var_name_to_values = nil) ⇒ Object
Factor DSL Defining variable with list of its possible values looks like: “‘ Bayesnet::Factor.build do
scope weather: %i[sunny cloudy]
scope mood: %i[bad good]
...
“‘ ^ this code defines to variables `weather` and `mood`, where `weather` could be :sunny or :cloudy, and `mood` could be :bad or :good
27 28 29 30 31 32 33 |
# File 'lib/bayesnet/factor.rb', line 27 def scope(var_name_to_values = nil) if var_name_to_values @scope.merge!(var_name_to_values) else @scope end end |
#select(subcontext) ⇒ Object
134 135 136 137 138 |
# File 'lib/bayesnet/factor.rb', line 134 def select(subcontext) @vals.select do |context, _| var_names.zip(context).slice(subcontext.keys) == subcontext end end |
#val(*context_and_val) ⇒ Object
Factor DSL Specifies factor value for some set of variable values, i.e. “‘ Bayesnet::Factor.build do
scope weather: %i[sunny cloudy]
scope mood: %i[bad good]
val :sunny, :bad, 0.1
...
“‘ ^ this code says the value of factor for [weather == :sunny, mood == :bad] is 0.1
45 46 47 48 |
# File 'lib/bayesnet/factor.rb', line 45 def val(*context_and_val) context_and_val = context_and_val[0] if context_and_val.size == 1 && context_and_val[0].is_a?(Array) @vals[context_and_val[0..-2]] = context_and_val[-1] end |
#values ⇒ Object
returns all possible values
83 84 85 |
# File 'lib/bayesnet/factor.rb', line 83 def values @vals.values end |
#var_names ⇒ Object
List of variable names
52 53 54 |
# File 'lib/bayesnet/factor.rb', line 52 def var_names @scope.keys end |