bn4r 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG ADDED
@@ -0,0 +1,3 @@
1
+ *0.1*
2
+
3
+ First release
data/README ADDED
@@ -0,0 +1,62 @@
1
+
2
+ = Bayesian Networks for Ruby ( bn4r )
3
+
4
+ bn4r is a bayesian networks library on ruby that provides
5
+ the user with classes for create bayesian networks and
6
+ diverse algorithms for solve them.
7
+
8
+ Its algorithms implementation are based on: S.Russell, P.Norving, "Artificial
9
+ Intelligence, A Modern Approach", 2nd Edition.
10
+
11
+ Website:
12
+ http://bn4r.rubyforge.org
13
+
14
+ Rubyforge Project:
15
+ http://rubyforge.org/projects/bn4r
16
+
17
+ = Dependencies
18
+
19
+ * rgl-0.2.3 ( Ruby Graph Library ), http://rgl.rubyforge.org
20
+
21
+ = Design principles
22
+
23
+ The library consists on the object BayesNet thinked to be filled with
24
+ BayesNetNode, these objects are defined in bn.rb. BayesNet object is a
25
+ especialization of RGL::DirectedAdjacencyGraph ( http://rgl.rubyforge.org ).
26
+
27
+
28
+ The file bn_algorithms.rb has the implementation of the inference algorithms
29
+ that can be used to solve BayesNet structures.
30
+
31
+ Finally, a set of objects and methods are given to automaticly fill BayesNetNode
32
+ probabilities tables.
33
+
34
+
35
+ = Documentation
36
+
37
+ Documentation can be found at
38
+ http://bn4r.rubyforge.org/rdoc
39
+ or can be generated using rdoc tool under the source code with:
40
+ rdoc README lib
41
+
42
+ = Credits
43
+
44
+ Thanks to N�ria Bel ( http://www.upf.edu/pdi/iula/nuria.bel ) for her work in this project
45
+ without her it cannot be done.
46
+
47
+ Thanks to Ryan Dahl for his work in http://www.math.rochester.edu/people/grads/rld/bayesnets
48
+ that was the inspiration of the project.
49
+
50
+ Also thanks to all the ruby community.
51
+
52
+ == Copying
53
+
54
+ This work is developed by Sergio Espeja ( http://www.upf.edu/pdi/iula/sergio.espeja, sergio.espeja at gmail.com )
55
+ mainly in Institut Universitari de Ling�istica Aplicada of Universitat Pompeu Fabra ( http://www.iula.upf.es ),
56
+ and also in bee.com.es ( http://bee.com.es ).
57
+
58
+ It is free software, and may be redistributed under GPL license.
59
+
60
+ == Support
61
+
62
+ Please contact me in http://rubyforge.org/projects/bn4r.
data/Rakefile ADDED
@@ -0,0 +1,85 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/clean'
4
+ require 'rake/testtask'
5
+ require 'rake/packagetask'
6
+ require 'rake/gempackagetask'
7
+ require 'rake/rdoctask'
8
+ require 'rake/contrib/rubyforgepublisher'
9
+ require 'fileutils'
10
+ include FileUtils
11
+ require File.join(File.dirname(__FILE__), 'lib', 'bn4r', 'version')
12
+
13
+ AUTHOR = "Sergio Espeja"
14
+ EMAIL = "sergio.espeja@gmail.com"
15
+ DESCRIPTION = "bn4r is a bayesian networks library on ruby that provides the user with classes for create bayesian networks and diverse algorithms for solve them."
16
+ RUBYFORGE_PROJECT = "bn4r"
17
+ HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
18
+ BIN_FILES = %w( )
19
+
20
+
21
+ NAME = "bn4r"
22
+ REV = File.read(".svn/entries")[/committed-rev="(d+)"/, 1] rescue nil
23
+ VERS = ENV['VERSION'] || (Bn4r::VERSION::STRING + (REV ? ".#{REV}" : ""))
24
+ CLEAN.include ['**/.*.sw?', '*.gem', '.config']
25
+ RDOC_OPTS = ['--quiet', '--title', "bn4r documentation",
26
+ "--opname", "index.html",
27
+ "--line-numbers",
28
+ "--main", "README",
29
+ "--inline-source"]
30
+
31
+ desc "Packages up bn4r gem."
32
+ task :default => [:test]
33
+ task :package => [:clean]
34
+
35
+ Rake::TestTask.new("test") { |t|
36
+ t.libs << "test"
37
+ t.pattern = "test/**/*_test.rb"
38
+ t.verbose = true
39
+ }
40
+
41
+ spec =
42
+ Gem::Specification.new do |s|
43
+ s.name = NAME
44
+ s.version = VERS
45
+ s.platform = Gem::Platform::RUBY
46
+ s.has_rdoc = true
47
+ s.extra_rdoc_files = ["README", "CHANGELOG"]
48
+ s.rdoc_options += RDOC_OPTS + ['--exclude', '^(examples|extras)/']
49
+ s.summary = DESCRIPTION
50
+ s.description = DESCRIPTION
51
+ s.author = AUTHOR
52
+ s.email = EMAIL
53
+ s.homepage = HOMEPATH
54
+ s.executables = BIN_FILES
55
+ s.rubyforge_project = RUBYFORGE_PROJECT
56
+ s.bindir = "bin"
57
+ s.require_path = "lib"
58
+ s.autorequire = "bn4r"
59
+
60
+ s.add_dependency('rgl', '>=0.2.3')
61
+ #s.required_ruby_version = '>= 1.8.2'
62
+
63
+ s.files = %w(README CHANGELOG Rakefile) +
64
+ Dir.glob("{bin,doc,test,lib,templates,generator,extras,website,script}/**/*") +
65
+ Dir.glob("ext/**/*.{h,c,rb}") +
66
+ Dir.glob("examples/**/*.rb") +
67
+ Dir.glob("tools/*.rb")
68
+
69
+ # s.extensions = FileList["ext/**/extconf.rb"].to_a
70
+ end
71
+
72
+ Rake::GemPackageTask.new(spec) do |p|
73
+ p.need_tar = true
74
+ p.gem_spec = spec
75
+ end
76
+
77
+ task :install do
78
+ name = "#{NAME}-#{VERS}.gem"
79
+ sh %{rake package}
80
+ sh %{sudo gem install pkg/#{name}}
81
+ end
82
+
83
+ task :uninstall => [:clean] do
84
+ sh %{sudo gem uninstall #{NAME}}
85
+ end
data/lib/bn4r/bn.rb ADDED
@@ -0,0 +1,249 @@
1
+ ##############################################################
2
+ #
3
+ # Bayesian Network Library for Ruby
4
+ #
5
+ # Author: Sergio Espeja ( http://www.upf.edu/pdi/iula/sergio.espeja, sergio.espeja at gmail.com )
6
+ #
7
+ # Developed in: IULA ( http://www.iula.upf.es ) and
8
+ # in bee.com.es ( http://bee.com.es )
9
+ #
10
+ # Based on work by Ryan Dahl in
11
+ # http://www.math.rochester.edu/people/grads/rld/bayesnets
12
+ #
13
+ ##############################################################
14
+ require 'rgl/adjacency'
15
+ require 'rgl/dot'
16
+
17
+ include RGL
18
+
19
+ class BayesNet < DirectedAdjacencyGraph
20
+
21
+ # DirectedAdjacencyGraph redefined methods
22
+ # ----------------------------------------
23
+
24
+ # Adds a directed edge between parent and child BayesNetNodes labeled
25
+ # with tag ( if tag is included, othewise the label is nil ).
26
+ def add_edge(parent, child, tag=nil)
27
+ raise "Nodes must be of the class BayesNetNodes" if parent.class != BayesNetNode or child.class != BayesNetNode
28
+ edge = super(parent, child)
29
+ child.parents << parent
30
+ child.relations << tag if !tag.nil?
31
+ edge
32
+ end
33
+
34
+ # BN Methods
35
+ # ----------
36
+
37
+ # Clears the value of all BayesNetNodes in Bayes Net.
38
+ def clear_values!
39
+ vertices.each { |v| v.clear_value }
40
+ end
41
+
42
+ # Gets the variable with given name.
43
+ def get_variable( text )
44
+ vertices.each { |v| return v if v.name == text }
45
+ end
46
+
47
+ # Returns the root nodes of the Bayes Net.
48
+ def roots
49
+ vertices.select { |v| root?(v) }
50
+ end
51
+
52
+ # Returns true/false if given Node is root.
53
+ def root?(v)
54
+ return true if num_parents(v) == 0
55
+ false
56
+ end
57
+
58
+ # Returns the number of parents of a node.
59
+ def num_parents(v)
60
+ return v.parents.size
61
+ end
62
+
63
+ # Return the probability of a distribution in the bayes net
64
+ # all nodes in the Bayes Net must have a value, otherwise
65
+ # will raise a exception
66
+ def inference_by_enumeration
67
+ prob = 1.0;
68
+ vertices.each {|v| prob = prob * p_v_cond_parents(v)}
69
+ prob
70
+ end
71
+
72
+ # Returns the probability of a node conditioned to his parents:
73
+ # P(v|parents(v))
74
+ def p_v_cond_parents(v)
75
+ givens_assignments = v.parents.collect {|parents| parents.value}
76
+ v.get_probability(v.value, givens_assignments).to_f
77
+ end
78
+
79
+ # Returns true if all nodes have values.
80
+ def all_nodes_with_values?
81
+ vertices.select {|v| !v.value.nil? }.size == vertices.size
82
+ end
83
+
84
+ # Returns nodes ordered by dependencies ( from those who haven't ( roots )
85
+ # to leaves ).
86
+ def nodes_ordered_by_dependencies(nodes = vertices, bn_ordered = Array.new)
87
+ nodes.each { |v|
88
+ next if bn_ordered.include?(v)
89
+ nodes_ordered_by_dependencies(v.parents, bn_ordered) if !v.root?
90
+ bn_ordered << v
91
+ }
92
+ return bn_ordered.flatten
93
+ end
94
+
95
+
96
+ end
97
+
98
+ class BayesNetNode
99
+ attr_reader :name, :outcomes, :extra_info, :value, :relations
100
+
101
+ # create de BayesNetNode
102
+ # name is the identifier of the node in the Bayesian Network
103
+ # outcomes is an array with the posible values that this node
104
+ # can have, by default [true, false].
105
+ # [optional] extra_info extra information that can be putted in the node
106
+ def initialize ( name , outcomes = [true, false], extra_info = nil)
107
+ @name = name
108
+ @outcomes = outcomes
109
+ @extra_info = extra_info
110
+ @givens = []
111
+ @relations = []
112
+ end
113
+
114
+ # Returns a copy of the node itself
115
+ def copy
116
+ tmp = BayesNetNode.new(@name, @outcomes, @extra_info)
117
+ tmp.set_value(@value)
118
+ tmp.set_probability_table(@givens, @table)
119
+ tmp
120
+ end
121
+
122
+ def set_value(value)
123
+ @value = value
124
+ end
125
+
126
+ def clear_value
127
+ @value = nil
128
+ end
129
+
130
+ # Return node parents
131
+ def parents
132
+ return @givens
133
+ end
134
+
135
+ # Return node relations
136
+ def relations
137
+ return @relations
138
+ end
139
+
140
+ # Return the number of parents
141
+ def num_parents
142
+ parents.size
143
+ end
144
+
145
+ # Returns true if the node is a root node ( doesn't have parents ).
146
+ def root?
147
+ return true if num_parents == 0
148
+ false
149
+ end
150
+
151
+ # Returns true if all parents of the node in the bn have values
152
+ def all_parents_with_values?
153
+ parents.select {|v| !v.value.nil? }.size == parents.size
154
+ end
155
+
156
+ def to_s
157
+ return name + (value.nil? ? "" : (" = " + value.to_s))
158
+ end
159
+
160
+ # if givens is nil, then internal givens is assumed
161
+ # table must have probability values in order like BAD!!!
162
+ # [g0=pos0 & g1=pos0 & ... & node_value=pos0, ..., g0=pos0 & g1=pos0 & ... & node_value=posN,
163
+ #
164
+ def set_probability_table (givens, table)
165
+ # perhaps we should do some error checking on the table entries here?
166
+ @table_is_a_proc = (table.class != Array)
167
+ @givens = givens if !givens.nil?
168
+
169
+ raise "Error table incorrect number of positions (" \
170
+ + table.size.to_s + " of " + self.get_table_size.to_s \
171
+ + ")" if table.size != self.get_table_size
172
+
173
+ @table = table
174
+ end
175
+
176
+ def parents_assignments
177
+ parents.collect { |p| p.value }
178
+ end
179
+
180
+ # returns the number of cells that conditional probability table ( CPT )
181
+ # haves.
182
+ def get_table_size
183
+ num = @outcomes.size
184
+ @givens.each { |given| num = num * given.outcomes.size }
185
+ return num
186
+ end
187
+
188
+ # Sets a probability to the node with a node_assignment conditioned to given_assignments
189
+ # P (node = node_assignment | givens = givens_assignments) = number
190
+ def set_probability(number, node_assignment, givens_assignments)
191
+ @table[get_table_index(node_assignment, givens_assignments)] = number
192
+ end
193
+
194
+ # Returns the probability of an assignment to a node conditioned to given_assignments
195
+ # P(node = node_assignment | givens = givens_assignments)
196
+ #
197
+ # All givens_assigments must have value.
198
+ def get_probability(node_assignment = value, givens_assignments = parents_assignments)
199
+ # raise "Node must have a value and a givens_assignments, otherwise put" \
200
+ # + "them in function call" if node_assignment.nil? or givens_assignments.nil?
201
+ # if there's a cached table take the index
202
+ return @table[get_table_index(node_assignment, givens_assignments)] if @table_is_a_proc.nil? or !@table_is_a_proc
203
+ # if the value is calculated on the fly using a function instead of
204
+ # a table
205
+ return @table[node_assignment, givens_assignments]
206
+ end
207
+
208
+ # Returns the corresponding index for the probability table given
209
+ # <i>node_assignment</i> and <i>givens_assignments</i>
210
+ def get_table_index(node_assignment, givens_assignments)
211
+ x = []
212
+ indices = []
213
+ index = 0
214
+
215
+ if givens_assignments.length != @givens.length
216
+ raise "Error. Number of assignments does not match node."
217
+ end
218
+
219
+ if @givens.length > 0
220
+ # create a indices array with the position of each value of
221
+ # given assignments.
222
+ givens_assignments.length.times { |i|
223
+ assignment = givens_assignments[i]
224
+ indices[i] = @givens[i].outcomes.index(assignment)
225
+ }
226
+
227
+ # create a array with the number of possible values each
228
+ # given node and this node itself can have ( node.outcomes.length )
229
+ # plus all next nodes.
230
+ x[givens_assignments.length-1] = @outcomes.length
231
+ (givens_assignments.length-2).downto(0) { |j|
232
+ x[j] = x[j+1] * @givens[j+1].outcomes.length
233
+ }
234
+
235
+ # to get the index, sum for each assignment the
236
+ # product of each given assignment outcomes size
237
+ # by its value index.
238
+ givens_assignments.length.times { |i|
239
+ index += x[i] * indices[i]
240
+ }
241
+ end
242
+
243
+ index += @outcomes.index(node_assignment)
244
+
245
+ return index
246
+ end
247
+
248
+
249
+ end
@@ -0,0 +1,215 @@
1
+ ##############################################################
2
+ #
3
+ # Inference Algorithms for Bayesian Network Library for Ruby
4
+ #
5
+ # Author: Sergio Espeja ( http://www.upf.edu/pdi/iula/sergio.espeja, sergio.espeja at gmail.com )
6
+ #
7
+ # Developed in: IULA ( http://www.iula.upf.es ) and
8
+ # in bee.com.es ( http://bee.com.es )
9
+ #
10
+ # == Current implemented algorithms
11
+ # * enumeration_ask
12
+ # * prior_sample
13
+ # * rejection_sampling
14
+ # * likelihood_weighting
15
+ #
16
+ ##############################################################
17
+ class BayesNet < DirectedAdjacencyGraph
18
+
19
+ # Inference Algorithms
20
+
21
+ # ENUMERATION ASK algorithm
22
+ #
23
+ # Implementation based on: S.Russell, P.Norving, "Artificial
24
+ # Intelligence, A Modern Approach", 2nd Edition. pp 506
25
+ #
26
+ # <b>x</b> --> query variable
27
+ #
28
+ # <b>e</b> --> variables with observed values
29
+ def enumeration_ask(x,e, bn_vertices = vertices)
30
+ e << x
31
+ q = []
32
+ #p bn_vertices.collect { |v| v.name }
33
+ x.outcomes.each {|outcome|
34
+ x.set_value(outcome)
35
+ q << enumerate_all(bn_vertices, e)
36
+ }
37
+ q
38
+ end
39
+
40
+ # Returns a sample from prior joint distribution specified by the network.
41
+ #
42
+ # Implementation based on: S.Russell, P.Norving, "Artificial
43
+ # Intelligence, A Modern Approach", 2nd Edition. pp 511-512
44
+ #
45
+ # The input are the nodes of the bn ordered by dependencies see nodes_ordered_by_dependencies
46
+ def prior_sample(nodes_ordered = nodes_ordered_by_dependencies)
47
+ sample = Array.new
48
+ nodes_ordered.each { |v|
49
+ value = rand < v.get_probability(true)
50
+ v.set_value(value)
51
+ sample << v.copy
52
+ }
53
+ # leave the bn clear of values.
54
+ nodes_ordered.each { |v| v.clear_value }
55
+
56
+ return sample
57
+ end
58
+
59
+ # Returns an estimation of P(X=x|e) = <P(X=x|e), 1 - P(X=x|e)> obtained. Generates samples from prior joint
60
+ # distribution specified by the network, rejects all those that do not match the evidence,
61
+ # and finally counts hoy often X = x occurs in remaining samples.
62
+ #
63
+ # Caution, this algorthm is unusable for complex problems because rejects many samples!
64
+ #
65
+ # Implementation based on: S.Russell, P.Norving, "Artificial
66
+ # Intelligence, A Modern Approach", 2nd Edition. pp 513
67
+ #
68
+ # <b>x</b> --> query variable
69
+ #
70
+ # <b>e</b> --> variables with observed values
71
+ #
72
+ # <b>n</b> --> Number of samples generated
73
+ #
74
+ def rejection_sampling( x, e, n, bn = self )
75
+
76
+ evidece_list = [e] if e.class != Array
77
+ x_list = [x] if x.class != Array
78
+
79
+ nodes_ordered = bn.nodes_ordered_by_dependencies
80
+ evidence_vector = get_vector_value(evidece_list, nodes_ordered)
81
+ x_vector = get_vector_value(x_list, nodes_ordered)
82
+
83
+ total_valid = 0; total_correct = 0
84
+ n.times do
85
+ sample_vector = bn.prior_sample(nodes_ordered).collect {|v| v.value}
86
+
87
+ valid = true; correct = true
88
+ for i in 0..(sample_vector.size-1) do
89
+ correct = false if !x_vector[i].nil? and sample_vector[i] != x_vector[i]
90
+ valid = false and break if !evidence_vector[i].nil? and sample_vector[i] != evidence_vector[i]
91
+ end
92
+
93
+ next if !valid
94
+ total_valid += 1
95
+ total_correct += 1 if correct
96
+ end
97
+
98
+ p_true = total_correct.to_f/total_valid.to_f
99
+ return [p_true, 1-p_true]
100
+ #return [total_correct.to_f, total_valid.to_f]
101
+ end
102
+
103
+ # Returns an estimation of P(X=x|e) = <P(X=x|e), 1 - P(X=x|e)> obtained.
104
+ #
105
+ # Implementation based on: S.Russell, P.Norving, "Artificial
106
+ # Intelligence, A Modern Approach", 2nd Edition. pp 515
107
+ #
108
+ # <b>x</b> --> query variable
109
+ #
110
+ # <b>e</b> --> variables with observed values
111
+ #
112
+ # <b>n</b> --> Number of samples generated
113
+ #
114
+ def likelihood_weighting( x, e, n, bn = self )
115
+
116
+ retval = [0.0, 0.0]
117
+ n.times {
118
+ w_sample, w = weighted_sample(e)
119
+ value = w_sample.select { |v| v.name == x.name }[0].value
120
+ #p value
121
+ if value == x.value
122
+ retval[1] += w
123
+ else
124
+ retval[0] += w
125
+ end
126
+ }
127
+
128
+ norm = retval[1].to_f / (retval[0]+retval[1]).to_f
129
+
130
+ return [norm, 1-norm]
131
+ end
132
+
133
+ protected
134
+ # Auxiliar function to compute Enumeration Ask Algorithm
135
+ def enumerate_all(vars, e)
136
+
137
+ return 1.0 if vars.empty?
138
+
139
+ y = vars.first; i = 1
140
+ while !y.all_parents_with_values? and i < vars.size
141
+ y = vars[i]
142
+ i = i + 1
143
+ end
144
+ raise "Error bayes net not computable with enumeration-ask " + \
145
+ "algorithm" if i == vars.size and !y.all_parents_with_values?
146
+
147
+ if e.include?(y)
148
+ return p_v_cond_parents(y) * enumerate_all(vars-[y], e)
149
+ else
150
+ prob = 0.0
151
+ y.outcomes.each { |outcome|
152
+ y.set_value(outcome)
153
+ prob = prob + p_v_cond_parents(y) * enumerate_all(vars-[y], e+[y])
154
+ y.clear_value
155
+ }
156
+ return prob
157
+ end
158
+ end
159
+
160
+ # Returns an event and a weight.
161
+ #
162
+ # Implementation based on: S.Russell, P.Norving, "Artificial
163
+ # Intelligence, A Modern Approach", 2nd Edition. pp 515
164
+ #
165
+ # <b>e</b> --> variables with observed values
166
+ #
167
+ def weighted_sample(e, bn = self)
168
+
169
+ nodes_ordered = bn.nodes_ordered_by_dependencies
170
+
171
+ sample = Array.new
172
+ w = 1.0
173
+ nodes_ordered.each { |v|
174
+ node_actual = e.select { |node| node.name == v.name } if e.class == Array
175
+ node_actual = [e] if e.class == BayesNetNode and e.name == v.name
176
+ if !node_actual.nil? and node_actual.size == 1
177
+ value = node_actual[0].value
178
+ w = w * v.get_probability(value)
179
+ else
180
+ value = rand < v.get_probability(true)
181
+ end
182
+ v.set_value(value)
183
+ sample << v.copy
184
+ }
185
+
186
+ # leave the bn clear of values.
187
+ bn.clear_values!
188
+ return sample, w
189
+ end
190
+
191
+ # Axiliar function that returns an array of bn_vertices_ordered.size positions
192
+ # with nil if position aren't in vertices_vector, and value if there's a match
193
+ # in vertices_vector.
194
+ def get_vector_value(vertices_vector, bn_vertices_ordered)
195
+ bn_vertices_ordered.collect { |v|
196
+ if !vertices_vector.nil?
197
+ node_actual = vertices_vector.select { |node| node.name == v.name }
198
+
199
+ case node_actual.size
200
+ when 0
201
+ nil
202
+ when 1
203
+ node_actual[0].value
204
+ else
205
+ raise "Error in get_vector_value"
206
+ end
207
+ else
208
+ nil
209
+ end
210
+ }
211
+ end
212
+
213
+
214
+
215
+ end
@@ -0,0 +1,65 @@
1
+ ##############################################################
2
+ #
3
+ # Methods for poulating Bayes Net Probabilities tables.
4
+ #
5
+ # Author: Sergio Espeja ( http://www.upf.edu/pdi/iula/sergio.espeja, sergio.espeja at gmail.com )
6
+ #
7
+ # Developed in: IULA ( http://www.iula.upf.es )
8
+ #
9
+ ##############################################################
10
+
11
+
12
+ # Bayes Net Table Probabilities Generator
13
+ class BnTableProbabilitiesGenerator
14
+ def get_node_probability_from_boolean_combination(boolean_combination)
15
+ 0.0
16
+ end
17
+ end
18
+
19
+ # Bayes Net Table Probabilities Generator from Positive Negative Relations
20
+ class BNTPGFromPositiveNegativeRelations < BnTableProbabilitiesGenerator
21
+
22
+ public
23
+ # type_of_position_impact is a array of boolean values showing the
24
+ # relation ( positive | negative ) beetwen the node and its parents.
25
+ def table_probabilities_for_node(node, type_of_position_impact)
26
+ raise "Node parents and type_of_position_impact with different size" if node.parents.size != type_of_position_impact.size
27
+ boolean_combinations = []
28
+ (2**node.parents.size).times { |i|
29
+ boolean_combination = Array.new(node.parents.size, false)
30
+ actual_value = i
31
+ (node.parents.size).times { |j|
32
+ boolean_combination[j] = !(actual_value%2 == 0)
33
+ actual_value = actual_value / 2
34
+ }
35
+ boolean_combinations << boolean_combination
36
+ }
37
+ #p boolean_combinations
38
+ table_probabilities = [] # Array.new(2**(node.parents.size+1))
39
+ boolean_combinations.each { |boolean_combination|
40
+ [true,false].each { |node_value|
41
+ prob = BNTPGFromPositiveNegativeRelations.get_node_probability_from_boolean_combination(boolean_combination, type_of_position_impact)
42
+ prob = 1 - prob if node_value == false
43
+ table_probabilities[node.get_table_index(node_value, boolean_combination)] = prob
44
+ #p "pos :" + node.get_table_index(node_value, boolean_combination).to_s
45
+ #p "Ok :" + node.get_table_index(node_value, boolean_combination).to_s if node.get_table_index(node_value, boolean_combination) > 2**node.parents.size
46
+ }
47
+ }
48
+ #p table_probabilities
49
+ table_probabilities
50
+ end
51
+
52
+ # returns P(node=yes| parents={boolean_combination}) where parents have
53
+ # relations with the node showed in type_of_position_impact
54
+ # type_of_position_impact is a array of boolean values showing the
55
+ # relation ( positive | negative ) beetwen the node and its parents.
56
+ def self.get_node_probability_from_boolean_combination(boolean_combination, type_of_position_impact)
57
+ num_eq = 0.0
58
+ boolean_combination.size.times { |i|
59
+ num_eq = num_eq + 1.0 if type_of_position_impact[i] && boolean_combination[i]
60
+ num_eq = num_eq + 1.0 if !type_of_position_impact[i] && !boolean_combination[i]
61
+ }
62
+ num_eq / boolean_combination.size.to_f
63
+ end
64
+ end
65
+
@@ -0,0 +1,9 @@
1
+ module Bn4r #:nodoc:
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 1
5
+ TINY = 0
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
data/lib/bn4r.rb ADDED
@@ -0,0 +1,11 @@
1
+ ##############################################################
2
+ #
3
+ # Bayesian Network Library for Ruby
4
+ #
5
+ # Author: Sergio Espeja ( sergio.espeja@gmail.com )
6
+ #
7
+ # Developed in: IULA ( http://www.iula.upf.es ) and
8
+ # in bee.com.es ( http://bee.com.es )
9
+ #
10
+ ##############################################################
11
+ Dir[File.join(File.dirname(__FILE__), 'bn4r/**/*.rb')].sort.each { |lib| require lib }
data/test/bn4r_test.rb ADDED
@@ -0,0 +1,290 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ class Bn4rTest < Test::Unit::TestCase
4
+
5
+ # Returns the BayesNet used as example in "Artificial Intelligence A
6
+ # Modern Approach, Rusell & Norvig, 2nd Ed." pp.494
7
+ def self.bayes_net_aima
8
+ bn_aima = BayesNet.new
9
+
10
+ b = BayesNetNode.new("Burglary")
11
+ e = BayesNetNode.new("Earthquake")
12
+ a = BayesNetNode.new("Alarm")
13
+ j = BayesNetNode.new("JohnCalls")
14
+ m = BayesNetNode.new("MaryCalls")
15
+
16
+ bn_aima.add_vertex(b)
17
+ bn_aima.add_vertex(e)
18
+ bn_aima.add_vertex(a)
19
+ bn_aima.add_vertex(j)
20
+ bn_aima.add_vertex(m)
21
+
22
+ bn_aima.add_edge(b,a)
23
+ bn_aima.add_edge(e,a)
24
+ bn_aima.add_edge(a,j)
25
+ bn_aima.add_edge(a,m)
26
+
27
+ b.set_probability_table([], [0.001, 0.999] )
28
+ e.set_probability_table([], [0.002, 0.998] )
29
+
30
+ a.set_probability_table([b,e], [0.95, 0.05, 0.94, 0.06, 0.29, 0.61, 0.001,0.999] )
31
+
32
+ j.set_probability_table([a], [0.90,0.10,0.05,0.95])
33
+ m.set_probability_table([a], [0.70,0.30,0.01,0.99])
34
+
35
+ bn_aima
36
+ end
37
+
38
+ # Returns the BayesNet used as example in "Artificial Intelligence A
39
+ # Modern Approach, Rusell & Norvig, 2nd Ed." pp.510
40
+ def self.bayes_net_aima2
41
+ bn_aima = BayesNet.new
42
+
43
+ cloudy = BayesNetNode.new("Cloudy")
44
+ sprinkler = BayesNetNode.new("Sprinkler")
45
+ rain = BayesNetNode.new("Rain")
46
+ wetgrass = BayesNetNode.new("WetGrass")
47
+
48
+ bn_aima.add_vertex(cloudy)
49
+ bn_aima.add_vertex(sprinkler)
50
+ bn_aima.add_vertex(rain)
51
+ bn_aima.add_vertex(wetgrass)
52
+
53
+ bn_aima.add_edge(cloudy,sprinkler)
54
+ bn_aima.add_edge(cloudy,rain)
55
+ bn_aima.add_edge(sprinkler,wetgrass)
56
+ bn_aima.add_edge(rain,wetgrass)
57
+
58
+ cloudy.set_probability_table([], [0.5, 0.5] )
59
+
60
+ sprinkler.set_probability_table([cloudy], [0.1, 0.9, 0.5, 0.5] )
61
+ rain.set_probability_table([cloudy], [0.8, 0.2, 0.2, 0.8] )
62
+
63
+ wetgrass.set_probability_table([sprinkler, rain], [0.99, 0.01, 0.9, 0.1, 0.9, 0.1, 0.0, 1.0] )
64
+ bn_aima
65
+ end
66
+
67
+ def bayes_net_aaile
68
+ bn = BayesNet.new
69
+ rel = BayesNetNode.new("relational")
70
+ q = BayesNetNode.new("qualificative")
71
+ a = BayesNetNode.new("Adverbial")
72
+ bn.add_vertex(rel)
73
+ bn.add_vertex(q)
74
+ bn.add_vertex(a)
75
+
76
+ # ["preN", ...]
77
+ preN = BayesNetNode.new("preN")
78
+ bn.add_vertex(preN)
79
+
80
+ postN = BayesNetNode.new("postN")
81
+ bn.add_vertex(postN)
82
+
83
+ # bn.add_edge("ser")
84
+ # bn.add_edge("G")
85
+ # bn.add_edge("prep")
86
+
87
+ bn.add_edge(rel, preN)
88
+ bn.add_edge(q, preN)
89
+ bn.add_edge(a, preN)
90
+ bn.add_edge(q, postN)
91
+
92
+ bn
93
+ end
94
+
95
+ def setup
96
+ end
97
+
98
+ # TESTS
99
+
100
+ def test_create_sample_bn
101
+ bn = bayes_net_aaile
102
+
103
+ assert_equal bn.vertices.size, 5
104
+ assert_equal bn.edges.size, 4
105
+ end
106
+
107
+
108
+ def test_graph_viz
109
+ bn = bayes_net_aaile
110
+ # print "\n\n" + bn.to_dot_graph.to_s
111
+
112
+ assert bn.to_dot_graph.to_s.size > 0, "bn.to_dot_graph gives no output."
113
+ end
114
+
115
+ def test_probability_assingment
116
+ bn_aima = Bn4rTest.bayes_net_aima
117
+ b = bn_aima.get_variable("Burglary")
118
+ a = bn_aima.get_variable("Alarm")
119
+ j = bn_aima.get_variable("JohnCalls")
120
+ m = bn_aima.get_variable("MaryCalls")
121
+
122
+ assert_equal b.get_probability(true, []), 0.001
123
+ assert_equal b.get_probability(false, []), 0.999
124
+ assert_equal b.get_probability(false, []), 0.999
125
+
126
+ assert_equal a.get_probability(true, [false,false]), 0.001
127
+ assert_equal a.get_probability(true, [true,false]), 0.94
128
+ assert_equal a.get_probability(false, [true,true]), 0.05
129
+
130
+ assert_equal j.get_probability(true, [true]), 0.90
131
+ assert_equal m.get_probability(true, [false]), 0.01
132
+
133
+ end
134
+
135
+ def test_CPT_size
136
+ bn_aima = Bn4rTest.bayes_net_aima
137
+ b = bn_aima.get_variable("Burglary")
138
+ a = bn_aima.get_variable("Alarm")
139
+ j = bn_aima.get_variable("JohnCalls")
140
+ m = bn_aima.get_variable("MaryCalls")
141
+
142
+ assert_equal b.get_table_size, 2
143
+ assert_equal a.get_table_size, 8
144
+ assert_equal j.get_table_size, 4
145
+ assert_equal m.get_table_size, 4
146
+ end
147
+
148
+ def test_base_methods
149
+ bn_aima = Bn4rTest.bayes_net_aima
150
+ b = bn_aima.get_variable("Burglary")
151
+ a = bn_aima.get_variable("Alarm")
152
+ assert bn_aima.root?(b)
153
+ assert !bn_aima.root?(a)
154
+
155
+ end
156
+
157
+ def test_inference_by_enumeration
158
+ bn_aima = Bn4rTest.bayes_net_aima
159
+ b = bn_aima.get_variable("Burglary")
160
+ e = bn_aima.get_variable("Earthquake")
161
+ a = bn_aima.get_variable("Alarm")
162
+ j = bn_aima.get_variable("JohnCalls")
163
+ m = bn_aima.get_variable("MaryCalls")
164
+
165
+ assert_equal bn_aima.vertices.size, 5
166
+ assert_equal bn_aima.edges.size, 4
167
+
168
+ assert !bn_aima.all_nodes_with_values?
169
+ b.set_value(false); assert !bn_aima.all_nodes_with_values?
170
+ e.set_value(false); assert !bn_aima.all_nodes_with_values?
171
+ a.set_value(true); assert !bn_aima.all_nodes_with_values?
172
+ j.set_value(true); assert !bn_aima.all_nodes_with_values?
173
+ m.set_value(true)
174
+
175
+ assert bn_aima.all_nodes_with_values?
176
+
177
+ value = bn_aima.inference_by_enumeration
178
+ assert_in_delta 0.0006281112, value, 10**(-10)
179
+
180
+ bn_aima.clear_values!
181
+ assert !bn_aima.all_nodes_with_values?
182
+
183
+ end
184
+
185
+ def test_inference_by_enumeration_ask
186
+ bn_aima = Bn4rTest.bayes_net_aima
187
+ b = bn_aima.get_variable("Burglary")
188
+ e = bn_aima.get_variable("Earthquake")
189
+ a = bn_aima.get_variable("Alarm")
190
+ j = bn_aima.get_variable("JohnCalls")
191
+ m = bn_aima.get_variable("MaryCalls")
192
+ j.set_value(true)
193
+ m.set_value(true)
194
+
195
+ assert_equal bn_aima.vertices.size, 5
196
+ assert_equal bn_aima.edges.size, 4
197
+ value1 = bn_aima.enumeration_ask(b,[j,m])
198
+ value2 = bn_aima.enumeration_ask(b,[j,m], [j,m,a,b,e])
199
+ value3 = bn_aima.enumeration_ask(b,[j,m], [b,e,a,j,m])
200
+ assert_equal value1[0], value2[0]
201
+ assert_equal value1[0].to_f, value3[0].to_f
202
+ assert_in_delta value1[1].to_f, value2[1].to_f, 10**(-10)
203
+ assert_equal value1[1], value3[1]
204
+ assert_in_delta 0.000592242, bn_aima.enumeration_ask(b,[j,m])[0], 10**(-9)
205
+ bn_aima.clear_values!
206
+ assert !bn_aima.all_nodes_with_values?
207
+
208
+ end
209
+
210
+
211
+ def test_table_probabilities_for_node
212
+ bn_aima = Bn4rTest.bayes_net_aima
213
+ b = bn_aima.get_variable("Burglary")
214
+ e = bn_aima.get_variable("Earthquake")
215
+ a = bn_aima.get_variable("Alarm")
216
+ BNTPGFromPositiveNegativeRelations.new.table_probabilities_for_node(a, [true,true])
217
+
218
+ # BNTPGFromPositiveNegativeRelations.new.populate_bn_with_tags(bn_aima)
219
+ assert true
220
+ end
221
+
222
+ def test_prior_sampling
223
+
224
+ bn = Bn4rTest.bayes_net_aima2
225
+
226
+ hash = Hash.new(0)
227
+ nodes_ordered = bn.nodes_ordered_by_dependencies
228
+ 10000.times { hash[ bn.prior_sample(nodes_ordered).collect {|v| v.value} ] += 1 }
229
+
230
+ combination_of_prob = nodes_ordered.collect { |v|
231
+ case v.name
232
+ when "Cloudy"
233
+ true
234
+ when "Sprinkler"
235
+ false
236
+ when "Rain"
237
+ true
238
+ when "WetGrass"
239
+ true
240
+ else
241
+ raise "Incorrect BayesNet created at Bn4rTest.bayes_net_aima2"
242
+ end
243
+ }
244
+
245
+ prob = hash[combination_of_prob].to_f/10000.0
246
+
247
+ assert_in_delta 0.3, prob, 0.1, "Its inprobable but possible that this error occurs, \
248
+ because we are working with statistics and random data, \
249
+ try again and if still occurs take care of it."
250
+
251
+ end
252
+
253
+ def test_rejection_sampling
254
+ bn = Bn4rTest.bayes_net_aima2
255
+
256
+ rain = bn.get_variable("Rain").copy
257
+ sprinkler = bn.get_variable("Sprinkler").copy
258
+
259
+ rain.set_value(true)
260
+ sprinkler.set_value(true)
261
+
262
+ results = bn.rejection_sampling(rain, sprinkler, 1000)
263
+
264
+ str_error = " Its improbable but possible that this error occurs, \
265
+ because we are working with statistics and random data, \
266
+ try again and if still occurs take care of it."
267
+ assert ((results[0] > 0.2) and (results[0] < 0.4)), "Results aren't in interval [0.2,0.4] --> " + results[0].to_s + str_error
268
+ assert ((results[1] > 0.6) and (results[1] < 0.8)), "Results aren't in interval [0.6,0.8] --> " + results[1].to_s + str_error
269
+ end
270
+
271
+ def test_likelihood_weighting
272
+ bn = Bn4rTest.bayes_net_aima2
273
+
274
+ rain = bn.get_variable("Rain").copy
275
+ sprinkler = bn.get_variable("Sprinkler").copy
276
+
277
+ rain.set_value(true)
278
+ sprinkler.set_value(true)
279
+
280
+ results = bn.likelihood_weighting(rain, sprinkler, 100)
281
+
282
+ str_error = " Its improbable but possible that this error occurs, \
283
+ because we are working with statistics and random data, \
284
+ try again and if still occurs take care of it."
285
+ assert ((results[0] > 0.2) and (results[0] < 0.4)), "Results aren't in interval [0.2,0.4] --> " + results[0].to_s + str_error
286
+ assert ((results[1] > 0.6) and (results[1] < 0.8)), "Results aren't in interval [0.6,0.8] --> " + results[1].to_s + str_error
287
+ end
288
+
289
+
290
+ end
@@ -0,0 +1,2 @@
1
+ require 'test/unit'
2
+ require File.dirname(__FILE__) + '/../lib/bn4r'
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.11
3
+ specification_version: 1
4
+ name: bn4r
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.1.0
7
+ date: 2006-10-31 00:00:00 +01:00
8
+ summary: bn4r is a bayesian networks library on ruby that provides the user with classes for create bayesian networks and diverse algorithms for solve them.
9
+ require_paths:
10
+ - lib
11
+ email: sergio.espeja@gmail.com
12
+ homepage: http://bn4r.rubyforge.org
13
+ rubyforge_project: bn4r
14
+ description: bn4r is a bayesian networks library on ruby that provides the user with classes for create bayesian networks and diverse algorithms for solve them.
15
+ autorequire: bn4r
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ authors:
29
+ - Sergio Espeja
30
+ files:
31
+ - README
32
+ - CHANGELOG
33
+ - Rakefile
34
+ - test/test_helper.rb
35
+ - test/bn4r_test.rb
36
+ - lib/bn4r
37
+ - lib/bn4r.rb
38
+ - lib/bn4r/version.rb
39
+ - lib/bn4r/bn.rb
40
+ - lib/bn4r/bn_table_probabilities.rb
41
+ - lib/bn4r/bn_algorithms.rb
42
+ test_files: []
43
+
44
+ rdoc_options:
45
+ - --quiet
46
+ - --title
47
+ - bn4r documentation
48
+ - --opname
49
+ - index.html
50
+ - --line-numbers
51
+ - --main
52
+ - README
53
+ - --inline-source
54
+ - --exclude
55
+ - ^(examples|extras)/
56
+ extra_rdoc_files:
57
+ - README
58
+ - CHANGELOG
59
+ executables: []
60
+
61
+ extensions: []
62
+
63
+ requirements: []
64
+
65
+ dependencies:
66
+ - !ruby/object:Gem::Dependency
67
+ name: rgl
68
+ version_requirement:
69
+ version_requirements: !ruby/object:Gem::Version::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: 0.2.3
74
+ version: