Class: Sbn::Net

Inherits:
Object show all
Defined in:
lib/sbn/net.rb,
lib/sbn/formats.rb,
lib/sbn/learning.rb,
lib/sbn/inference.rb

Constant Summary collapse

MCMC_DEFAULT_SAMPLE_COUNT =
2000

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name = '') ⇒ Net

Returns a new instance of Net.



5
6
7
8
9
10
11
# File 'lib/sbn/net.rb', line 5

def initialize(name = '')
  @@net_count ||= 0
  @@net_count += 1
  @name = (name.empty? ? "net_#{@@net_count}" : name.to_underscore_sym)
  @variables = {}
  @evidence = {}
end

Instance Attribute Details

#nameObject (readonly)

Returns the value of attribute name.



3
4
5
# File 'lib/sbn/net.rb', line 3

def name
  @name
end

#variablesObject (readonly)

Returns the value of attribute variables.



3
4
5
# File 'lib/sbn/net.rb', line 3

def variables
  @variables
end

Class Method Details

.from_xmlbif(source) ⇒ Object

Reconstitute a saved network.



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/sbn/formats.rb', line 47

def self.from_xmlbif(source)
  # convert tags to lower case
  source.gsub!(/(<.*?>)/, '\\1'.downcase)
  
  doc = XmlSimple.xml_in(source)
  netname = doc['network'].first['name'].first
        
  # find net name
  returnval = Net.new(netname)
  
  # find variables
  count = 0
  variables = {}
  variable_elements = doc['network'].first['variable'].each do |var|
    varname = var['name'].first.to_sym
    properties = var['property']
    vartype = nil
    manager_name = nil
    text_to_match = ""
    options = {}
    thresholds = []
    properties.each do |prop|
      key, val = prop.split('=').map {|e| e.strip }
      vartype = val if key == 'SbnVariableType'
      manager_name = val if key == 'ManagerVariableName'
      text_to_match = eval(val) if key == 'TextToMatch'
      options[key.to_sym] = val.to_i if key =~ /stdev_state_count/
      thresholds = val.map {|e| e.to_f } if key == 'StateThresholds'
    end
    states = var['outcome']
    table = []
    doc['network'].first['definition'].each do |defn|
      if defn['for'].first.to_sym == varname
        table = defn['table'].first.split.map {|prob| prob.to_f }
      end
    end
    count += 1
    variables[varname] = case vartype
      when "Sbn::StringVariable" then StringVariable.new(returnval, varname)
      when "Sbn::NumericVariable" then NumericVariable.new(returnval, varname, table, thresholds, options)
      when "Sbn::Variable" then Variable.new(returnval, varname, table, states)
      when "Sbn::StringCovariable" then StringCovariable.new(returnval, manager_name, text_to_match, table)
    end
  end

  # find relationships between variables

  # connect covariables to their managers
  variable_elements = doc['network'].first['variable'].each do |var|
    varname = var['name'].first.to_sym
    properties = var['property']
    vartype = nil
    covars = nil
    parents = nil
    properties.each do |prop|
      key, val = prop.split('=').map {|e| e.strip }
      covars = val.split(',').map {|e| e.strip.to_sym } if key == 'Covariables'
      parents = val.split(',').map {|e| e.strip.to_sym } if key == 'Parents'
      vartype = val if key == 'SbnVariableType'
    end
    if vartype == "Sbn::StringVariable"
      parents.each {|p| variables[varname].add_parent(variables[p]) } if parents
      covars.each {|covar| variables[varname].add_covariable(variables[covar]) } if covars
    end
  end

  # connect all other variables to their parents
  doc['network'].first['definition'].each do |defn|
    varname = defn['for'].first.to_sym
    parents = defn['given']
    parents.each {|p| variables[varname].add_parent(variables[p.to_sym]) } if parents
  end
  returnval
end

Instance Method Details

#==(obj) ⇒ Object



13
# File 'lib/sbn/net.rb', line 13

def ==(obj); test_equal(obj); end

#===(obj) ⇒ Object



15
# File 'lib/sbn/net.rb', line 15

def ===(obj); test_equal(obj); end

#add_sample_point(evidence) ⇒ Object



116
117
118
119
# File 'lib/sbn/learning.rb', line 116

def add_sample_point(evidence)
  evidence = symbolize_evidence(evidence)
  @variables.keys.each {|key| @variables[key].add_sample_point(evidence) }
end

#add_variable(variable) ⇒ Object



17
18
19
20
21
22
23
# File 'lib/sbn/net.rb', line 17

def add_variable(variable)
  name = variable.name
  if @variables.has_key? name
    raise "Variable of name #{name} has already been added to this net"
  end
  @variables[name] = variable
end

#eql?(obj) ⇒ Boolean

Returns:

  • (Boolean)


14
# File 'lib/sbn/net.rb', line 14

def eql?(obj); test_equal(obj); end

#learn(data) ⇒ Object

Expects data to be an array of hashes containing complete sets of evidence for all variables in the network. Constructs probability tables for each variable based on the data.



111
112
113
114
# File 'lib/sbn/learning.rb', line 111

def learn(data)
  data.each {|evidence| add_sample_point(evidence) }
  set_probabilities_from_sample_points!
end

#query_variable(varname, callback = nil) ⇒ Object

possible state for the specified variable, based on previously-supplied evidence, using the Markov Chain Monte Carlo algorithm. The MCMC algorithm generates each event by making a random change to the preceding event. The next state is generated by randomly sampling a value for one of the nonevidence variables Xi, conditioned on the current values of the variables in the Markov blanket of Xi. MCMC basically wanders randomly around the state space–the space of possible complete assignments–flipping one variable at a time, but keeping the evidence variables fixed. The sampling process works because it settles into a “dynamic equilibrium” in which the long-run fraction of time spent in each state is proportional to its posterior probability.

Optionally accepts a block that receives a number between 0 and 1 indicating the percentage of completion.



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/sbn/inference.rb', line 20

def query_variable(varname, callback = nil)
  # keep track of number of times a state has been observed
  state_frequencies = {}
  varname = varname.to_underscore_sym
  states = @variables[varname].states
  states.each {|s| state_frequencies[s] ||= 0 }

  e = generate_random_event
  relevant_evidence = e.reject {|key, val| @variables[key].set_in_evidence?(@evidence) }

  MCMC_DEFAULT_SAMPLE_COUNT.times do |n|
    state = e[varname]
    state_frequencies[state] += 1

    relevant_evidence.each do |vname, vstate|
      e[vname] = @variables[vname].get_random_state_with_markov_blanket(e)
    end
    yield(n / MCMC_DEFAULT_SAMPLE_COUNT.to_f) if block_given?
  end

  # normalize results
  magnitude = 0
  returnval = {}
  state_frequencies.each_value {|count| magnitude += count }
  state_frequencies.each {|state, count| returnval[state] = count / magnitude.to_f }
  returnval
end

#set_evidence(event) ⇒ Object



35
36
37
# File 'lib/sbn/net.rb', line 35

def set_evidence(event)
  @evidence = symbolize_evidence(event)
end

#set_probabilities_from_sample_points!Object



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/sbn/learning.rb', line 121

def set_probabilities_from_sample_points!
  # we must first conduct learning on parents then their children
  unlearned_variables = @variables.keys
  
  count = 0
  size = @variables.size.to_f
  until unlearned_variables.empty?
    learnable_variables = @variables.reject do |name, var|
      reject = false
      var.parents.each {|p| reject = true if unlearned_variables.include?(p.name) }
      reject
    end
    learnable_variables.keys.each do |key|
      @variables[key].set_probabilities_from_sample_points!
      count += 1
      unlearned_variables.delete(key)
    end
  end
end

#symbolize_evidence(evidence) ⇒ Object

:nodoc:



25
26
27
28
29
30
31
32
33
# File 'lib/sbn/net.rb', line 25

def symbolize_evidence(evidence) # :nodoc:
  newevidence = {}
  evidence.each do |key, val|
    key = key.to_underscore_sym
    raise "Invalid variable name #{key}." unless @variables.has_key?(key)
    newevidence[key] = @variables[key].transform_evidence_value(val)
  end
  newevidence
end

#to_xmlbifObject

Returns a string containing a representation of the network in XMLBIF format. www.cs.cmu.edu/afs/cs/user/fgcozman/www/Research/InterchangeFormat



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/sbn/formats.rb', line 8

def to_xmlbif
  xml = Builder::XmlMarkup.new(:indent => 2)
  xml.instruct!
  xml.comment! <<-EOS
  
    Bayesian network in XMLBIF v0.3 (BayesNet Interchange Format)
    Produced by SBN (Simple Bayesian Network library)
    Output created #{Time.now}
  EOS
  xml.text! "\n"
  xml.comment! "DTD for the XMLBIF 0.3 format"
  xml.declare! :DOCTYPE, :bif do
    xml.declare! :ELEMENT, :bif, :"(network)*"
    xml.declare! :ATTLIST, :bif, :version, :CDATA, :"#REQUIRED"
    xml.declare! :ELEMENT, :"network (name, (property | variable | definition)*)"
    xml.declare! :ELEMENT, :name, :"(#PCDATA)"
    xml.declare! :ELEMENT, :"variable (name, (outcome | property)*)"
    xml.declare! :ATTLIST, :"variable type (nature | decision | utility) \"nature\""
    xml.declare! :ELEMENT, :outcome, :"(#PCDATA)"
    xml.declare! :ELEMENT, :definition, :"(for | given | table | property)*"
    xml.declare! :ELEMENT, :for, :"(#PCDATA)"
    xml.declare! :ELEMENT, :given, :"(#PCDATA)"
    xml.declare! :ELEMENT, :table, :"(#PCDATA)"
    xml.declare! :ELEMENT, :property, :"(#PCDATA)"
  end
  xml.bif :version => 0.3 do
    xml.network do
      xml.name(@name.to_s)
      xml.text! "\n"
      xml.comment! "Variables"
      @variables.each {|name, variable| variable.to_xmlbif_variable(xml) }
      xml.text! "\n"
      xml.comment! "Probability distributions"
      @variables.each {|name, variable| variable.to_xmlbif_definition(xml) }
    end
  end
end