Class: Sbn::Variable

Inherits:
Object show all
Defined in:
lib/sbn/learning.rb,
lib/sbn/variable.rb

Direct Known Subclasses

NumericVariable, StringCovariable, StringVariable

Constant Summary collapse

NEGLIGIBLE_PROBABILITY =
0.0001

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(net, name = '', probabilities = [0.5, 0.5], states = [:true, :false]) ⇒ Variable

Returns a new instance of Variable.



5
6
7
8
9
10
11
12
13
14
15
16
17
18
# File 'lib/sbn/variable.rb', line 5

def initialize(net, name = '', probabilities = [0.5, 0.5], states = [:true, :false])
  @net = net
  @@variable_count ||= 0
  @@variable_count += 1
  name = "variable_#{@@variable_count}" if name.is_a? String and name.empty?
  @name = name.to_underscore_sym
  @children = []
  @parents = []
  @states = []
  @state_frequencies = {} # used for storing sample points
  set_states(states)
  set_probabilities(probabilities)
  net.add_variable(self)
end

Instance Attribute Details

#childrenObject (readonly)

Returns the value of attribute children.



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

def children
  @children
end

#nameObject (readonly)

Returns the value of attribute name.



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

def name
  @name
end

#parentsObject (readonly)

Returns the value of attribute parents.



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

def parents
  @parents
end

#probability_tableObject (readonly)

Returns the value of attribute probability_table.



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

def probability_table
  @probability_table
end

#statesObject (readonly)

Returns the value of attribute states.



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

def states
  @states
end

Instance Method Details

#==(obj) ⇒ Object



20
# File 'lib/sbn/variable.rb', line 20

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

#===(obj) ⇒ Object



22
# File 'lib/sbn/variable.rb', line 22

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

#add_child(variable) ⇒ Object



65
66
67
68
# File 'lib/sbn/variable.rb', line 65

def add_child(variable)
  add_child_no_recurse(variable)
  variable.add_parent_no_recurse(self)
end

#add_child_no_recurse(variable) ⇒ Object

:nodoc:



84
85
86
87
88
89
90
91
92
# File 'lib/sbn/variable.rb', line 84

def add_child_no_recurse(variable) # :nodoc:
  return if variable == self or @children.include?(variable)
  if variable.is_a?(StringVariable)
    @children.concat variable.covariables
  else
    @children << variable
  end
  variable.generate_probability_table
end

#add_parent(variable) ⇒ Object



70
71
72
73
# File 'lib/sbn/variable.rb', line 70

def add_parent(variable)
  add_parent_no_recurse(variable)
  variable.add_child_no_recurse(self)
end

#add_parent_no_recurse(variable) ⇒ Object

:nodoc:



94
95
96
97
98
99
100
101
102
# File 'lib/sbn/variable.rb', line 94

def add_parent_no_recurse(variable) # :nodoc:
  return if variable == self or @parents.include?(variable)
  if variable.is_a?(StringVariable)
    @parents.concat variable.covariables
  else
    @parents << variable
  end
  generate_probability_table
end

#add_sample_point(evidence) ⇒ Object



23
24
25
26
27
28
29
30
31
32
# File 'lib/sbn/learning.rb', line 23

def add_sample_point(evidence)
  # reject incomplete evidence sets
  raise "Incomplete sample points" unless is_complete_evidence?(evidence)
  
  # Because string variables add new variables to the net during learning,
  # the process of determining state frequencies has to be deferred until
  # the end.  For now, we'll just store the evidence and use it later.
  @sample_points ||= []
  @sample_points << evidence
end

#can_be_evaluated?(evidence) ⇒ Boolean

A variable can’t be evaluated unless its parents have been observed

Returns:

  • (Boolean)


114
115
116
117
118
# File 'lib/sbn/variable.rb', line 114

def can_be_evaluated?(evidence) # :nodoc:
  returnval = true
  parents.each {|p| returnval = false unless p.set_in_evidence?(evidence) }
  returnval
end

#eql?(obj) ⇒ Boolean

Returns:

  • (Boolean)


21
# File 'lib/sbn/variable.rb', line 21

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

#evaluate_marginal(state, event) ⇒ Object

:nodoc:



146
147
148
149
150
151
152
# File 'lib/sbn/variable.rb', line 146

def evaluate_marginal(state, event) # :nodoc:
  temp_probs = @probability_table.dup
  remove_irrelevant_states(temp_probs, state, event)
  sum = 0.0
  temp_probs.each {|e| sum += e[1] }
  sum
end

#evidence_nameObject

:nodoc:



80
81
82
# File 'lib/sbn/variable.rb', line 80

def evidence_name # :nodoc:
  @name
end

#generate_probability_tableObject

:nodoc:



137
138
139
140
141
142
143
144
# File 'lib/sbn/variable.rb', line 137

def generate_probability_table # :nodoc:
  @probab
  @probability_table = nil
  if @probabilities and @probabilities.size == state_combinations.size
    probs = @probabilities.dup
    @probability_table = state_combinations.collect {|e| [e, probs.shift] }
  end
end

#get_observed_state(evidence) ⇒ Object

:nodoc:



108
109
110
# File 'lib/sbn/variable.rb', line 108

def get_observed_state(evidence) # :nodoc:
  evidence[@name]
end

#get_random_state(event = {}) ⇒ Object

In order to draw uniformly from the probabilty space, we can’t just pick a random state. Instead we generate a random number between zero and one and iterate through the states until the cumulative sum of their probabilities exceeds our random number.



124
125
126
# File 'lib/sbn/variable.rb', line 124

def get_random_state(event = {}) # :nodoc:
  seek_state {|s| evaluate_marginal(s, event) }
end

#get_random_state_with_markov_blanket(event) ⇒ Object

similar to get_random_state() except it evaluates a variable’s markov blanket in addition to the variable itself.



130
131
132
133
134
135
# File 'lib/sbn/variable.rb', line 130

def get_random_state_with_markov_blanket(event) # :nodoc:
  evaluations = []
  @states.each {|s| evaluations << evaluate_markov_blanket(s, event) }
  evaluations.normalize!
  seek_state {|s| evaluations.shift }
end

#is_complete_evidence?(evidence) {|varnames| ... } ⇒ Boolean

:nodoc:

Yields:

  • (varnames)

Returns:

  • (Boolean)


5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# File 'lib/sbn/learning.rb', line 5

def is_complete_evidence?(evidence) # :nodoc:
  varnames = [evidence_name.to_s]
  @parents.each {|p| varnames << p.name.to_s }
  yield(varnames) if block_given?
  
  # ignore covariables when determining whether evidence is complete or not
  varnames.map! do |n|
    n = n.split('_').first if n =~ /covar/
    n
  end
  varnames.uniq!
  varnames.sort!
  
  keys = evidence.keys.map {|k| k.to_s }
  keys.sort!
  varnames & keys == varnames
end

#set_in_evidence?(evidence) ⇒ Boolean

:nodoc:

Returns:

  • (Boolean)


104
105
106
# File 'lib/sbn/variable.rb', line 104

def set_in_evidence?(evidence) # :nodoc:
  evidence.has_key?(evidence_name)
end

#set_probabilities(probs) ⇒ Object



75
76
77
78
# File 'lib/sbn/variable.rb', line 75

def set_probabilities(probs)
  @probabilities = probs
  generate_probability_table
end

#set_probabilities_from_sample_points!Object



34
35
36
37
38
39
40
41
42
43
44
45
46
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
# File 'lib/sbn/learning.rb', line 34

def set_probabilities_from_sample_points!
  return unless @sample_points
  accumulate_state_frequencies
  
  # find the sums for each parent combination so we
  # know how to normalize their associated states
  sums = {}
  state_combinations.each do |comb|
    parent_comb = comb.dup

    # remove state for this node so that all
    # that is left is the parent combination
    parent_comb.pop
    @state_frequencies[comb] ||= 0
    sums[parent_comb] ||= 0

    sums[parent_comb] += @state_frequencies[comb]
  end

  probabilities = []
  count_of_zero_prob_states = count_of_nonzero_prob_states = {}
  last_state = @states.first
  state_combinations.each do |comb|
    state = comb.last
    parent_comb = comb.dup
    parent_comb.pop
    prob = @state_frequencies[comb] / sums[parent_comb].to_f
    probabilities << (prob == 0.0 ? NEGLIGIBLE_PROBABILITY : prob)
    
    # Keep track of how many of this node's states were
    # empty for this particular parent combination, so that
    # we can pad them with tiny numbers later.  Otherwise,
    # some exact inference algorithms will fail.
    if prob == 0.0
      count_of_zero_prob_states[parent_comb] ||= 0
      count_of_zero_prob_states[parent_comb] += 1
    else
      count_of_nonzero_prob_states[parent_comb] ||= 0
      count_of_nonzero_prob_states[parent_comb] += 1
    end
  end
  
  # pad the zero probabilities
  count = 0
  state_combinations.each do |comb|
    state = comb.last
    parent_comb = comb.dup
    parent_comb.pop
    amount_to_subtract = count_of_zero_prob_states[parent_comb] *
                         NEGLIGIBLE_PROBABILITY /
                         count_of_nonzero_prob_states[parent_comb].to_f
    p = probabilities[count]
    p = (p > NEGLIGIBLE_PROBABILITY ? p - amount_to_subtract : p)
    probabilities[count] = p
    count += 1
  end
  
  # assign new probabilities
  set_probabilities(probabilities)
end

#set_probability(probability, event) ⇒ Object



52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/sbn/variable.rb', line 52

def set_probability(probability, event)
  event = @net.symbolize_evidence(event)
  unless can_be_evaluated?(event)
    raise "A valid state was not supplied for variable #{@name} and all its parents in call to set_probability()"
  end
  combination_for_this_event = []
  @parents.each {|p| combination_for_this_event << event[p.name] }
  combination_for_this_event << event[@name]
  index = state_combinations.index(combination_for_this_event)
  @probabilities[index] = probability
  generate_probability_table
end

#set_states(states) ⇒ Object



46
47
48
49
50
# File 'lib/sbn/variable.rb', line 46

def set_states(states)
  states.symbolize_values!
  @states = states
  generate_probability_table
end

#to_sObject



24
25
26
# File 'lib/sbn/variable.rb', line 24

def to_s
  @name.to_s
end

#to_xmlbif_definition(xml) ⇒ Object



37
38
39
40
41
42
43
44
# File 'lib/sbn/variable.rb', line 37

def to_xmlbif_definition(xml)
  xml.definition do
    xml.for(@name.to_s)
    @parents.each {|p| xml.given(p.name.to_s) }
    xml.table(@probability_table.transpose.last.join(' '))
    yield(xml) if block_given?
  end
end

#to_xmlbif_variable(xml) ⇒ Object



28
29
30
31
32
33
34
35
# File 'lib/sbn/variable.rb', line 28

def to_xmlbif_variable(xml)
  xml.variable(:type => "nature") do
    xml.name(@name.to_s)
    @states.each {|s| xml.outcome(s.to_s) }
    xml.property("SbnVariableType = #{self.class.to_s}")
    yield(xml) if block_given?
  end
end

#transform_evidence_value(val) ⇒ Object

:nodoc:



154
155
156
# File 'lib/sbn/variable.rb', line 154

def transform_evidence_value(val) # :nodoc:
  val.to_underscore_sym       
end