pgm 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +4 -0
- data/README.md +15 -0
- data/contributors.txt +1 -0
- data/lib/pgm.rb +44 -0
- data/lib/pgm/bidirectional.rb +126 -0
- data/lib/pgm/model.rb +209 -0
- data/lib/pgm/variable.rb +180 -0
- data/pgm.gemspec +46 -0
- data/spec/model_spec.rb +150 -0
- data/spec/spec_helper.rb +8 -0
- metadata +105 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a8801783d85c563cfc9bdbf262d6ebf52db065a1
|
4
|
+
data.tar.gz: d60116e3e47e50f95fa90de363ff2aa36a58f60f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: df4d59efac1f7cc6912c4f2f5c163dfd6428e271b7991a8e46f2756fe3fcf057dfc872812620cc39138b6fb9bf55f03c923b68d3184b49db66c3deb54490e9a5
|
7
|
+
data.tar.gz: 294e6adb7dabe1aafc2726bbb92612b6666a7794b47792d563ea14a18a934bc96781a3013606c819655c68d689657f041f908739e3800a1dc5d2896ea6d3d866
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# PGM: Probabilistic Graphical Model Library
|
2
|
+
|
3
|
+
[![Bitbucket](https://img.shields.io/badge/bitbucket-natapol--pmg-blue.svg)](https://bitbucket.org/natapol/pgm)
|
4
|
+
[![Documentation](http://img.shields.io/badge/docs-rdoc.info-blue.svg)](http://rubydoc.org/gems/pgm/frames)
|
5
|
+
[![Gem Version](https://badge.fury.io/rb/pgm.svg)](https://bitbucket.org/natapol/pgm/releases)
|
6
|
+
[![License](http://img.shields.io/badge/license-GPL--3-yellowgreen.svg)](#license)
|
7
|
+
|
8
|
+
## Synopsis
|
9
|
+
This library is expected to provide basic functions for Probabilistic Graphical Model analysis. However, the library is still in development process. Only Bayesian network with discrete random variable module is available at this time.
|
10
|
+
|
11
|
+
## Feature List
|
12
|
+
**1. Bayesian network with discrete random variable**
|
13
|
+
|
14
|
+
## Design principles
|
15
|
+
The model library is derived from
|
data/contributors.txt
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Natapol Pornputtapong
|
data/lib/pgm.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# Probabilistic Graphical Model Library
|
2
|
+
#
|
3
|
+
# Copyright (C) 2016
|
4
|
+
#
|
5
|
+
# author: Natapol Pornputtapong <natapol.por@gmail.com>
|
6
|
+
#
|
7
|
+
# Documentation: Natapol Pornputtapong
|
8
|
+
#
|
9
|
+
|
10
|
+
raise "Please, use ruby 1.9.0 or later." if RUBY_VERSION < "1.9.0"
|
11
|
+
|
12
|
+
$: << File.join(File.expand_path(File.dirname(__FILE__)))
|
13
|
+
|
14
|
+
#Internal library
|
15
|
+
require 'i18n'
|
16
|
+
|
17
|
+
#External library
|
18
|
+
|
19
|
+
|
20
|
+
I18n.enforce_available_locales = false
|
21
|
+
|
22
|
+
|
23
|
+
|
24
|
+
#library
|
25
|
+
require 'pgm/model'
|
26
|
+
|
27
|
+
# A ruby library for probabilistic graphical model
|
28
|
+
module PGM
|
29
|
+
|
30
|
+
# version of the library
|
31
|
+
VERSION = "0.0.1"
|
32
|
+
|
33
|
+
# Exception class for NotDirectedError
|
34
|
+
class NotDirectedError < RuntimeError; end
|
35
|
+
|
36
|
+
# Exception class for NotUndirectedError
|
37
|
+
class NotUndirectedError < RuntimeError; end
|
38
|
+
|
39
|
+
# Exception class for NoVertexError
|
40
|
+
class NoVertexError < IndexError; end
|
41
|
+
|
42
|
+
# Exception class for NoEdgeError
|
43
|
+
class NoEdgeError < IndexError; end
|
44
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# Probabilistic Graphical Model Library
|
2
|
+
#
|
3
|
+
# Copyright (C) 2016
|
4
|
+
#
|
5
|
+
# author: Natapol Pornputtapong <natapol.por@gmail.com>
|
6
|
+
#
|
7
|
+
# Documentation: Natapol Pornputtapong
|
8
|
+
#
|
9
|
+
|
10
|
+
module PGM
|
11
|
+
|
12
|
+
# describe bidirectionally function to Graph
|
13
|
+
module Bidirectional
|
14
|
+
|
15
|
+
# Instantiate DiscreateVariable
|
16
|
+
#
|
17
|
+
# @param edgelist_class [Object] Object to store edge class
|
18
|
+
# @param other_graphs [PGM::BayesianNet] List of variable states
|
19
|
+
def initialize(edgelist_class = Set, *other_graphs)
|
20
|
+
@vertices_rev_dict = Hash.new
|
21
|
+
@variables = Hash.new
|
22
|
+
super
|
23
|
+
end
|
24
|
+
|
25
|
+
# Copy internal vertices_dict
|
26
|
+
#
|
27
|
+
# @param orig [PGM::BayesianNet] Copy from other graph
|
28
|
+
def initialize_copy(orig)
|
29
|
+
super
|
30
|
+
@vertices_rev_dict = orig.instance_eval { @vertices_rev_dict }.dup
|
31
|
+
@vertices_rev_dict.keys.each do |v|
|
32
|
+
@vertices_rev_dict[v] = @vertices_rev_dict[v].dup
|
33
|
+
end
|
34
|
+
@variables = orig.instance_eval { @variables }.dup
|
35
|
+
end
|
36
|
+
|
37
|
+
# Return variable with the name
|
38
|
+
#
|
39
|
+
# @param name [String, Symbol] The name of variable
|
40
|
+
# @return [PGM::Variable] Variable object
|
41
|
+
def var(name=nil)
|
42
|
+
if nil
|
43
|
+
return @variables
|
44
|
+
else
|
45
|
+
return @variables[name.to_sym]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Create variable
|
50
|
+
#
|
51
|
+
# @param name [String, Symbol] Name of variable
|
52
|
+
# @param states [Array<String, Symbol>] List of variable states
|
53
|
+
# @param parents [Array<PGM::Variable>] List of parent variable
|
54
|
+
# @param type [PGM::Variable] type of variable
|
55
|
+
def create_var(name, states, parents: [], type: PGM::DiscreateVariable)
|
56
|
+
add_vertex(name)
|
57
|
+
@variables[name.to_sym] = type.new(self, name, states, parents: parents)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Check if model has the variable named as input
|
61
|
+
#
|
62
|
+
# @param name [String, Symbol] Name of variable
|
63
|
+
# @return [Boolean] if model has the variable
|
64
|
+
def has_var?(name)
|
65
|
+
return @variables.has_key?(name.to_sym)
|
66
|
+
end
|
67
|
+
|
68
|
+
# assign link from u to v
|
69
|
+
#
|
70
|
+
# @param u [PGM::Variable] Variable to be linked
|
71
|
+
# @param v [PGM::Variable] Variable to be linked
|
72
|
+
def create_link(u, v)
|
73
|
+
if has_var?(u) && has_var?(v)
|
74
|
+
var(v).to(var(u))
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
protected
|
79
|
+
|
80
|
+
def basic_add_edge(u, v)
|
81
|
+
super
|
82
|
+
@vertices_rev_dict[v].add(u)
|
83
|
+
end
|
84
|
+
|
85
|
+
# See MutableGraph#add_vertex.
|
86
|
+
#
|
87
|
+
# If the vertex is already in the graph (using eql?), the method does
|
88
|
+
# nothing.
|
89
|
+
#
|
90
|
+
def add_vertex(v)
|
91
|
+
super
|
92
|
+
@vertices_rev_dict[v] ||= @edgelist_class.new
|
93
|
+
end
|
94
|
+
|
95
|
+
# See MutableGraph#remove_vertex.
|
96
|
+
#
|
97
|
+
def remove_vertex(v)
|
98
|
+
super
|
99
|
+
@vertices_rev_dict.delete(v)
|
100
|
+
# remove v from all adjacency lists
|
101
|
+
@vertices_rev_dict.each_value { |adjList| adjList.delete(v) }
|
102
|
+
end
|
103
|
+
|
104
|
+
# See MutableGraph::remove_edge.
|
105
|
+
#
|
106
|
+
# Also removes (v,u)
|
107
|
+
#
|
108
|
+
def remove_edge(u, v)
|
109
|
+
super
|
110
|
+
@vertices_rev_dict[v].delete(u) unless @vertices_dict[v].nil?
|
111
|
+
end
|
112
|
+
|
113
|
+
def each_parent(v, &b) # :nodoc:
|
114
|
+
parent_list = (@vertices_rev_dict[v] or raise NoVertexError, "No vertex #{v}.")
|
115
|
+
parent_list.each(&b)
|
116
|
+
end
|
117
|
+
|
118
|
+
def parent_vertices(v)
|
119
|
+
r = []
|
120
|
+
each_parent(v) { |u| r << u }
|
121
|
+
r
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
data/lib/pgm/model.rb
ADDED
@@ -0,0 +1,209 @@
|
|
1
|
+
# Probabilistic Graphical Model Library
|
2
|
+
#
|
3
|
+
# Copyright (C) 2016
|
4
|
+
#
|
5
|
+
# author: Natapol Pornputtapong <natapol.por@gmail.com>
|
6
|
+
#
|
7
|
+
# Documentation: Natapol Pornputtapong
|
8
|
+
#
|
9
|
+
|
10
|
+
require 'rgl/adjacency'
|
11
|
+
require 'rgl/topsort'
|
12
|
+
require 'rgl/dot'
|
13
|
+
|
14
|
+
require 'pgm/variable'
|
15
|
+
require 'pgm/bidirectional'
|
16
|
+
|
17
|
+
module PGM
|
18
|
+
# Bayesian Network Class
|
19
|
+
#
|
20
|
+
# Example:
|
21
|
+
#
|
22
|
+
# create model
|
23
|
+
# bayenet = PGM::BayesianNet.new()
|
24
|
+
# bayenet.create_var(:cloudy, [:t, :f])
|
25
|
+
# bayenet.create_var(:sprinkler, [:t, :f])
|
26
|
+
# bayenet.create_var(:rain, [:t, :f])
|
27
|
+
# bayenet.create_var(:grass_wet, [:t, :f])
|
28
|
+
# bayenet.create_link(:cloudy, :sprinkler)
|
29
|
+
# bayenet.create_link(:cloudy, :rain)
|
30
|
+
# bayenet.create_link(:sprinkler, :grass_wet)
|
31
|
+
# bayenet.create_link(:rain, :grass_wet)
|
32
|
+
#
|
33
|
+
# learn with data
|
34
|
+
# bayenet.learn([
|
35
|
+
# {:cloudy => :t, :sprinkler => :f, :rain => :t, :grass_wet => :t},
|
36
|
+
# {:cloudy => :t, :sprinkler => :t, :rain => :f, :grass_wet => :t},
|
37
|
+
# {:cloudy => :f, :sprinkler => :f, :rain => :t, :grass_wet => :t},
|
38
|
+
# {:cloudy => :t, :sprinkler => :f, :rain => :t, :grass_wet => :t},
|
39
|
+
# {:cloudy => :f, :sprinkler => :t, :rain => :f, :grass_wet => :f},
|
40
|
+
# {:cloudy => :f, :sprinkler => :f, :rain => :f, :grass_wet => :f},
|
41
|
+
# {:cloudy => :f, :sprinkler => :f, :rain => :f, :grass_wet => :f},
|
42
|
+
# {:cloudy => :t, :sprinkler => :f, :rain => :t, :grass_wet => :t},
|
43
|
+
# {:cloudy => :t, :sprinkler => :f, :rain => :f, :grass_wet => :f},
|
44
|
+
# {:cloudy => :f, :sprinkler => :f, :rain => :f, :grass_wet => :f},
|
45
|
+
# ])
|
46
|
+
#
|
47
|
+
# print out the conditional_probability_table
|
48
|
+
# bayenet.puts_conditional_probability_table
|
49
|
+
#
|
50
|
+
# create graph from model
|
51
|
+
# bayenet.write_to_graphic_file('jpg')
|
52
|
+
class BayesianNet < RGL::DirectedAdjacencyGraph
|
53
|
+
|
54
|
+
include PGM::Bidirectional
|
55
|
+
|
56
|
+
# Find common parents of input nodes
|
57
|
+
#
|
58
|
+
# @param vs [Array<PGM::Variable>] List of vertices
|
59
|
+
# @return [Array<PGM::Variable>] List parent vertices
|
60
|
+
def common_parents(*vs)
|
61
|
+
r = nil
|
62
|
+
vs.each do |v|
|
63
|
+
if r
|
64
|
+
r &= parent_vertices(v)
|
65
|
+
else
|
66
|
+
r = parent_vertices(v)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
r ||= []
|
70
|
+
end
|
71
|
+
|
72
|
+
# Check if there are common parents of input vertices
|
73
|
+
#
|
74
|
+
# @param vs [Array<PGM::Variable>] List of variables
|
75
|
+
# @return [Boolean] Is there common parents
|
76
|
+
def common_parents?(*vs)
|
77
|
+
return !common_parents(*vs).empty?
|
78
|
+
end
|
79
|
+
|
80
|
+
# Find common children of input vertices
|
81
|
+
#
|
82
|
+
# @param vs [Array<PGM::Variable>] List of vertices
|
83
|
+
# @return [Array<PGM::Variable>] List child vertices
|
84
|
+
def common_children(*vs)
|
85
|
+
r = nil
|
86
|
+
vs.each do |v|
|
87
|
+
if r
|
88
|
+
r &= adjacent_vertices(v)
|
89
|
+
else
|
90
|
+
r = adjacent_vertices(v)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
r ||= []
|
94
|
+
end
|
95
|
+
|
96
|
+
# Check if there are common children of input vertices
|
97
|
+
#
|
98
|
+
# @param vs [Array<PGM::Variable>] List of vertices
|
99
|
+
# @return [Boolean] Is there common children
|
100
|
+
def common_children?(*vs)
|
101
|
+
return !common_children(*vs).empty?
|
102
|
+
end
|
103
|
+
|
104
|
+
# Check if vertices are in cascading topology
|
105
|
+
#
|
106
|
+
# @param vs [Array<PGM::Variable>] List of vertices
|
107
|
+
# @return [Boolean] Are vertices in cascading topology
|
108
|
+
def cascade?(*vs)
|
109
|
+
prev_children = nil
|
110
|
+
r = true
|
111
|
+
if vs.length > 2
|
112
|
+
vs.each do |v|
|
113
|
+
prev_children ||= [v]
|
114
|
+
if !prev_children.include?(v)
|
115
|
+
r = false
|
116
|
+
break
|
117
|
+
else
|
118
|
+
prev_children = adjacent_vertices(v)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
else
|
122
|
+
r = false
|
123
|
+
end
|
124
|
+
return r
|
125
|
+
end
|
126
|
+
|
127
|
+
# Check if vertices are in V-Structure
|
128
|
+
#
|
129
|
+
# @param vs [Array<PGM::Variable>] List of vertices
|
130
|
+
# @return [Boolean] Are vertices in V-Structure
|
131
|
+
def v_tructure?(*vs)
|
132
|
+
if vs.length == 3
|
133
|
+
return parent_vertices(vs[0]).sort == vs[1..-1].sort
|
134
|
+
else
|
135
|
+
return false
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Generate string notation of Baysian Network
|
140
|
+
#
|
141
|
+
# @return [String] Model notation
|
142
|
+
def model_notation
|
143
|
+
line = []
|
144
|
+
each_vertex do |v|
|
145
|
+
parents = parent_vertices(v)
|
146
|
+
line.push("P(#{v}#{parents.empty? ? '' : "|#{parents.join(',')}"})")
|
147
|
+
end
|
148
|
+
return "P(#{vertices.sort.join(',')}) = #{line.sort.join('')}"
|
149
|
+
end
|
150
|
+
|
151
|
+
# learn model from array of condition and automatically calculate the
|
152
|
+
# conditional probability table. Condition is in a Hash format { var_name1: state_name1, var_name2: state_name2 }
|
153
|
+
#
|
154
|
+
# @param arr [Array<Hash{Symbol => Symbol,String}>] Array of data for learning in Hash objects
|
155
|
+
def learn(arr)
|
156
|
+
@variables.each_value do |var|
|
157
|
+
var.learn(arr)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# Learn the model with discrete data.
|
162
|
+
# Condition is in a Hash format { var_name1: state_name1, var_name2: state_name2 }
|
163
|
+
#
|
164
|
+
# @param condition [Hash{Symbol => Symbol,String}] Data for learning in a Hash objects
|
165
|
+
def add_data(condition)
|
166
|
+
@variables.each_value do |var|
|
167
|
+
var.add_data(condition)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
# Set conditional probability to a random variable
|
172
|
+
# Condition is in a Hash format { var_name1: state_name1, var_name2: state_name2 }
|
173
|
+
#
|
174
|
+
# @param varname [String, Symbol] Random variable name
|
175
|
+
# @param prob [Float] Probability
|
176
|
+
# @param condition [Hash{Symbol => Symbol,String}] Data for learning in Hash objects
|
177
|
+
def set_conditional_probability(varname, prob, condition)
|
178
|
+
var(varname.to_sym).set_conditional_probability(prob, condition)
|
179
|
+
end
|
180
|
+
|
181
|
+
# Calculate conditional probability table from learned data
|
182
|
+
#
|
183
|
+
def calculate_conditional_probability_table
|
184
|
+
@variables.each_value do |var|
|
185
|
+
var.calculate_conditional_probability_table
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
alias_method :cal_cpt, :calculate_conditional_probability_table
|
190
|
+
|
191
|
+
# puts conditional probability table on the screen
|
192
|
+
#
|
193
|
+
# @param cellsize [Integer] Size of each cell
|
194
|
+
def puts_conditional_probability_table(cellsize = 25)
|
195
|
+
@variables.each_value do |val|
|
196
|
+
val.puts_conditional_probability_table(cellsize)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
alias_method :puts_cpt, :puts_conditional_probability_table
|
201
|
+
|
202
|
+
# Order random variable by ancestral order
|
203
|
+
#
|
204
|
+
def ancestral_ordering
|
205
|
+
return self.topsort_iterator.to_a
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
end
|
data/lib/pgm/variable.rb
ADDED
@@ -0,0 +1,180 @@
|
|
1
|
+
# Probabilistic Graphical Model Library
|
2
|
+
#
|
3
|
+
# Copyright (C) 2016
|
4
|
+
#
|
5
|
+
# author: Natapol Pornputtapong <natapol.por@gmail.com>
|
6
|
+
#
|
7
|
+
# Documentation: Natapol Pornputtapong
|
8
|
+
#
|
9
|
+
|
10
|
+
require 'narray'
|
11
|
+
|
12
|
+
module PGM
|
13
|
+
|
14
|
+
# Most ancestral class for Random variable
|
15
|
+
class Variable
|
16
|
+
def initialize
|
17
|
+
raise NotImplementedError
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# A class for discrete random variable
|
22
|
+
class DiscreateVariable < Variable
|
23
|
+
|
24
|
+
attr_reader :name, :model, :states, :parents, :children
|
25
|
+
|
26
|
+
# Instantiate DiscreateVariable
|
27
|
+
#
|
28
|
+
# @param model [PGM::BayesianNet] Model for variable to be added
|
29
|
+
# @param name [String, Symbol] Name of variable
|
30
|
+
# @param states [Array<String, Symbol>] List of variable states
|
31
|
+
# @param parents [Array<PGM::Variable>] List of parent vertices
|
32
|
+
# @param children [Array<PGM::Variable>] List of child vertices
|
33
|
+
def initialize(model, name, states, parents: [], children: [])
|
34
|
+
@name = name.to_sym
|
35
|
+
@model = model
|
36
|
+
@states = states.map{|x| x.to_sym}.sort
|
37
|
+
@parents = parents
|
38
|
+
@children = children
|
39
|
+
self.create_conditional_probability_table
|
40
|
+
end
|
41
|
+
|
42
|
+
# compare variables
|
43
|
+
#
|
44
|
+
# @param other [PGM::Variable] Model for variable to be added
|
45
|
+
# @return [Integer] List of child vertices
|
46
|
+
def <=>(other)
|
47
|
+
if other.is_a?(PGM::Variable)
|
48
|
+
if @model == other.model
|
49
|
+
return @name <=> other.name
|
50
|
+
else
|
51
|
+
return @model <=> other.model
|
52
|
+
end
|
53
|
+
else
|
54
|
+
raise TypeError, "#{other} is not PGM::Variable"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# assign link from self to other
|
59
|
+
#
|
60
|
+
# @param other [PGM::Variable] Variable to be linked
|
61
|
+
def to(other)
|
62
|
+
other.add_parent(self)
|
63
|
+
self.add_child(other)
|
64
|
+
@model.add_edge(@name, other.name)
|
65
|
+
end
|
66
|
+
|
67
|
+
# learn model from array of condition and automatically calculate the
|
68
|
+
# conditional probability table
|
69
|
+
#
|
70
|
+
# @param arr [Array<Hash{Symbol => Symbol,String}>] Array of data for learning in Hash objects
|
71
|
+
def learn(arr)
|
72
|
+
arr.each do |item|
|
73
|
+
self.add_data(item)
|
74
|
+
end
|
75
|
+
self.calculate_conditional_probability_table
|
76
|
+
end
|
77
|
+
|
78
|
+
# learn model from data.
|
79
|
+
# Condition is in a Hash format { var_name1: state_name1, var_name2: state_name2 }
|
80
|
+
#
|
81
|
+
# @param condition [Hash{Symbol => Symbol,String}] Data for learning in Hash objects
|
82
|
+
def add_data(condition)
|
83
|
+
@count_table[*self.data_coor(condition)] += 1
|
84
|
+
end
|
85
|
+
|
86
|
+
# calculate conditional probability table
|
87
|
+
def calculate_conditional_probability_table
|
88
|
+
@probability_table = @count_table.floor / @count_table.sum(1).to_f
|
89
|
+
end
|
90
|
+
|
91
|
+
alias_method :cal_cpt, :calculate_conditional_probability_table
|
92
|
+
|
93
|
+
# puts conditional probability table on the screen
|
94
|
+
#
|
95
|
+
# @param cellsize [Integer] Size of each cell
|
96
|
+
def puts_conditional_probability_table(cellsize = 25)
|
97
|
+
puts "#{@name.to_s.center(cellsize)}|#{@adjoint_var_list.map{|x| "P(#{x})".center(cellsize)}.join('|')}"
|
98
|
+
0.upto(@states.length - 1).each do |ind|
|
99
|
+
puts "#{@states[ind].to_s.rjust(cellsize)}|#{@probability_table[true, ind].to_a.map{|x| x.round(5).to_s.rjust(cellsize)}.join('|')}"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
alias_method :puts_cpt, :puts_conditional_probability_table
|
104
|
+
|
105
|
+
# assign conditional probability for specified condition.
|
106
|
+
# Condition is in a Hash format { var_name1: state_name1, var_name2: state_name2 }
|
107
|
+
#
|
108
|
+
# @param prob [Float] probability
|
109
|
+
# @param condition [Hash{Symbol => Symbol,String}] condition in Hash objects
|
110
|
+
def set_conditional_probability(prob, condition)
|
111
|
+
@probability_table[*self.data_coor(condition)] = prob
|
112
|
+
end
|
113
|
+
|
114
|
+
alias_method :set_cp, :set_conditional_probability
|
115
|
+
|
116
|
+
# return conditional probability of the specified condition.
|
117
|
+
# Condition is in a Hash format { var_name1: state_name1, var_name2: state_name2 }
|
118
|
+
#
|
119
|
+
# @param condition [Hash{Symbol => Symbol,String}] condition in Hash objects
|
120
|
+
# @return [Float] probability
|
121
|
+
def conditional_probability(condition)
|
122
|
+
return @probability_table[*self.data_coor(condition)]
|
123
|
+
end
|
124
|
+
|
125
|
+
protected
|
126
|
+
|
127
|
+
def data_coor(condition)
|
128
|
+
#[column, row]
|
129
|
+
notation = []
|
130
|
+
@parents.map{|x| x.name.to_sym}.each do |name|
|
131
|
+
notation.push("#{name}=#{condition[name]}")
|
132
|
+
end
|
133
|
+
return [
|
134
|
+
@adjoint_var_list.include?(notation.join(',')) ? @adjoint_var_list.index(notation.join(',')) : true,
|
135
|
+
@states.include?(condition[@name]) ? @states.index(condition[@name]) : true
|
136
|
+
]
|
137
|
+
end
|
138
|
+
|
139
|
+
def state_notation
|
140
|
+
notations = []
|
141
|
+
self.states.each do |state|
|
142
|
+
notations.push("#{name}=#{state}")
|
143
|
+
end
|
144
|
+
return notations
|
145
|
+
end
|
146
|
+
|
147
|
+
def create_conditional_probability_table
|
148
|
+
list = []
|
149
|
+
@parents.each do |parent|
|
150
|
+
list.push(parent.state_notation)
|
151
|
+
end
|
152
|
+
if list.length == 0
|
153
|
+
@adjoint_var_list = [@name]
|
154
|
+
elsif list.length == 1
|
155
|
+
@adjoint_var_list = list[0]
|
156
|
+
else
|
157
|
+
@adjoint_var_list = list[0].product(*list[1..-1]).map{|x| x.join(',')}
|
158
|
+
end
|
159
|
+
@count_table = NArray.sfloat(@adjoint_var_list.length, @states.length).fill(0.0000001)
|
160
|
+
@probability_table = NArray.sfloat(@adjoint_var_list.length, @states.length)
|
161
|
+
end
|
162
|
+
|
163
|
+
def add_parent(var)
|
164
|
+
@parents.push(var)
|
165
|
+
@parents.sort!
|
166
|
+
self.create_conditional_probability_table
|
167
|
+
end
|
168
|
+
|
169
|
+
def add_child(var)
|
170
|
+
@children.push(var)
|
171
|
+
@children.sort!
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
175
|
+
|
176
|
+
# A class for concrete random variable
|
177
|
+
class ConcreteVariable < Variable
|
178
|
+
|
179
|
+
end
|
180
|
+
end
|
data/pgm.gemspec
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require 'pgm'
|
4
|
+
require 'date'
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = 'pgm'
|
8
|
+
s.version = PGM::VERSION
|
9
|
+
s.date = Date.today.strftime('%Y-%m-%d')
|
10
|
+
s.platform = Gem::Platform::RUBY
|
11
|
+
|
12
|
+
s.summary = "A ruby library for probabilistic graphical model"
|
13
|
+
s.description = "A ruby library for probabilistic graphical model"
|
14
|
+
s.authors = ["Natapol Pornputtapong"]
|
15
|
+
s.email = ['natapol.por@gmail.com']
|
16
|
+
|
17
|
+
s.homepage = 'http://rubygems.org/gems/pgm'
|
18
|
+
s.licenses = ['GPL-3.0']
|
19
|
+
|
20
|
+
# s.rubyforge_project = "neography"
|
21
|
+
|
22
|
+
s.files = `git ls-files`.split("\n")
|
23
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
24
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
25
|
+
s.require_paths = ['lib']
|
26
|
+
s.bindir = 'bin'
|
27
|
+
|
28
|
+
s.add_dependency "rgl", "~> 0.5"
|
29
|
+
s.add_dependency "narray", "~> 0.6"
|
30
|
+
# s.add_dependency "bson_ext", "~> 1.9"
|
31
|
+
# s.add_dependency "rdf", "~> 1.1"
|
32
|
+
|
33
|
+
|
34
|
+
s.add_development_dependency "rspec", ">= 3.4"
|
35
|
+
|
36
|
+
#### Documentation and testing.
|
37
|
+
|
38
|
+
s.has_rdoc = true
|
39
|
+
s.extra_rdoc_files = ['README.md']
|
40
|
+
s.rdoc_options += [
|
41
|
+
'--title', 'PGM - Probabilistic Graphical Model Library',
|
42
|
+
'--main', 'README.md',
|
43
|
+
'--line-numbers'
|
44
|
+
]
|
45
|
+
|
46
|
+
end
|
data/spec/model_spec.rb
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
# in spec/calculator_spec.rb
|
2
|
+
# - RSpec adds ./lib to the $LOAD_PATH
|
3
|
+
require "pgm/model"
|
4
|
+
|
5
|
+
RSpec.describe PGM::BayesianNet do
|
6
|
+
|
7
|
+
context "test graph module" do
|
8
|
+
def build_model
|
9
|
+
PGM::BayesianNet[*'ACCFBDBEDFEHFHFG'.chars]
|
10
|
+
end
|
11
|
+
|
12
|
+
before(:example) do
|
13
|
+
@model = build_model
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '#common_parents' do
|
17
|
+
it 'returns the blank array' do
|
18
|
+
expect(@model.common_parents).to eq([])
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'returns the common parent list' do
|
22
|
+
expect(@model.common_parents(*'GH'.chars)).to eq(['F'])
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#common_parents?' do
|
27
|
+
it 'returns false when blank input' do
|
28
|
+
expect(@model.common_parents?).to eq(false)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'returns false when no common parents' do
|
32
|
+
expect(@model.common_parents?(*'CD'.chars)).to eq(false)
|
33
|
+
expect(@model.common_parents?(*'CDE'.chars)).to eq(false)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'returns true when there are common parents' do
|
37
|
+
expect(@model.common_parents?(*'GH'.chars)).to eq(true)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '#common_children' do
|
42
|
+
it 'returns the blank array' do
|
43
|
+
expect(@model.common_children).to eq([])
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'returns the common child list' do
|
47
|
+
expect(@model.common_children(*'CD'.chars)).to eq(['F'])
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe '#common_children?' do
|
52
|
+
it 'returns false when blank input' do
|
53
|
+
expect(@model.common_children?).to eq(false)
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'returns false when no common children' do
|
57
|
+
expect(@model.common_children?(*'CE'.chars)).to eq(false)
|
58
|
+
expect(@model.common_children?(*'CDE'.chars)).to eq(false)
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'returns true when there are common children' do
|
62
|
+
expect(@model.common_children?(*'CD'.chars)).to eq(true)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe '#cascade?' do
|
67
|
+
it 'returns false when blank inputs' do
|
68
|
+
expect(@model.cascade?).to eq(false)
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'returns false when not cascading' do
|
72
|
+
expect(@model.cascade?(*'AC'.chars)).to eq(false)
|
73
|
+
expect(@model.cascade?(*'ACH'.chars)).to eq(false)
|
74
|
+
expect(@model.cascade?(*'ADE'.chars)).to eq(false)
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'returns true when cascading' do
|
78
|
+
expect(@model.cascade?(*'ACF'.chars)).to eq(true)
|
79
|
+
expect(@model.cascade?(*'ACFH'.chars)).to eq(true)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe '#v_tructure?' do
|
84
|
+
it 'returns false when wrong number of node' do
|
85
|
+
expect(@model.v_tructure?).to eq(false)
|
86
|
+
expect(@model.v_tructure?(*'AC'.chars)).to eq(false)
|
87
|
+
expect(@model.v_tructure?(*'FCDB'.chars)).to eq(false)
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'returns false when not in v-structure' do
|
91
|
+
expect(@model.v_tructure?(*'CFD'.chars)).to eq(false)
|
92
|
+
expect(@model.v_tructure?(*'BDE'.chars)).to eq(false)
|
93
|
+
expect(@model.v_tructure?(*'FAB'.chars)).to eq(false)
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'returns true when v-structure' do
|
97
|
+
expect(@model.v_tructure?(*'FCD'.chars)).to eq(true)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe '#model_notation' do
|
102
|
+
it 'returns a joint ditribution string' do
|
103
|
+
expect(@model.model_notation).to eq('P(A,B,C,D,E,F,G,H) = P(A)P(B)P(C|A)P(D|B)P(E|B)P(F|C,D)P(G|F)P(H|E,F)')
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
context "testing probability net" do
|
109
|
+
def build_model
|
110
|
+
a = PGM::BayesianNet.new()
|
111
|
+
a.create_var(:cloudy, [:t, :f])
|
112
|
+
a.create_var(:sprinkler, [:t, :f])
|
113
|
+
a.create_var(:rain, [:t, :f])
|
114
|
+
a.create_var(:grass_wet, [:t, :f])
|
115
|
+
a.create_link(:cloudy, :sprinkler)
|
116
|
+
a.create_link(:cloudy, :rain)
|
117
|
+
a.create_link(:sprinkler, :grass_wet)
|
118
|
+
a.create_link(:rain, :grass_wet)
|
119
|
+
return a
|
120
|
+
end
|
121
|
+
|
122
|
+
before(:example) do
|
123
|
+
@model = build_model
|
124
|
+
end
|
125
|
+
|
126
|
+
describe '#model_notation' do
|
127
|
+
it 'returns a model notation string' do
|
128
|
+
expect(@model.model_notation).to eq("P(cloudy,grass_wet,rain,sprinkler) = P(cloudy|sprinkler,rain)P(grass_wet)P(rain|grass_wet)P(sprinkler|grass_wet)")
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
describe '#learning' do
|
133
|
+
it 'returns a joint ditribution string' do
|
134
|
+
@model.learn([
|
135
|
+
{:cloudy => :t, :sprinkler => :f, :rain => :t, :grass_wet => :t},
|
136
|
+
{:cloudy => :t, :sprinkler => :t, :rain => :f, :grass_wet => :t},
|
137
|
+
{:cloudy => :f, :sprinkler => :f, :rain => :t, :grass_wet => :t},
|
138
|
+
{:cloudy => :t, :sprinkler => :f, :rain => :t, :grass_wet => :t},
|
139
|
+
{:cloudy => :f, :sprinkler => :t, :rain => :f, :grass_wet => :f},
|
140
|
+
{:cloudy => :f, :sprinkler => :f, :rain => :f, :grass_wet => :f},
|
141
|
+
{:cloudy => :f, :sprinkler => :f, :rain => :f, :grass_wet => :f},
|
142
|
+
{:cloudy => :t, :sprinkler => :f, :rain => :t, :grass_wet => :t},
|
143
|
+
{:cloudy => :t, :sprinkler => :f, :rain => :f, :grass_wet => :f},
|
144
|
+
{:cloudy => :f, :sprinkler => :f, :rain => :f, :grass_wet => :f},
|
145
|
+
])
|
146
|
+
expect(@model.var(:cloudy).conditional_probability({:cloudy => :f}).to_a).to eq([0.75, 0.4999999403953552, 0.25, 0.0])
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pgm
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Natapol Pornputtapong
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-04-21 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rgl
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.5'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: narray
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.6'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.6'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.4'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.4'
|
55
|
+
description: A ruby library for probabilistic graphical model
|
56
|
+
email:
|
57
|
+
- natapol.por@gmail.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files:
|
61
|
+
- README.md
|
62
|
+
files:
|
63
|
+
- Gemfile
|
64
|
+
- README.md
|
65
|
+
- contributors.txt
|
66
|
+
- lib/pgm.rb
|
67
|
+
- lib/pgm/bidirectional.rb
|
68
|
+
- lib/pgm/model.rb
|
69
|
+
- lib/pgm/variable.rb
|
70
|
+
- pgm.gemspec
|
71
|
+
- spec/model_spec.rb
|
72
|
+
- spec/spec_helper.rb
|
73
|
+
homepage: http://rubygems.org/gems/pgm
|
74
|
+
licenses:
|
75
|
+
- GPL-3.0
|
76
|
+
metadata: {}
|
77
|
+
post_install_message:
|
78
|
+
rdoc_options:
|
79
|
+
- "--title"
|
80
|
+
- PGM - Probabilistic Graphical Model Library
|
81
|
+
- "--main"
|
82
|
+
- README.md
|
83
|
+
- "--line-numbers"
|
84
|
+
require_paths:
|
85
|
+
- lib
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
requirements: []
|
97
|
+
rubyforge_project:
|
98
|
+
rubygems_version: 2.0.14.1
|
99
|
+
signing_key:
|
100
|
+
specification_version: 4
|
101
|
+
summary: A ruby library for probabilistic graphical model
|
102
|
+
test_files:
|
103
|
+
- spec/model_spec.rb
|
104
|
+
- spec/spec_helper.rb
|
105
|
+
has_rdoc: true
|