# tianyicui/trueskill forked from saulabs/trueskill

added first working version of the factor graph, but some layers are …

`…missing`
1 parent 6000b67 commit 5d689c52464e62e772ed6935e27fcd9e56a0e54b larskuhnt committed Apr 14, 2010
 @@ -24,6 +24,10 @@ def with_deviation(mean, deviation) dist.precision_mean = dist.precision * mean return dist end + + def with_variance(mean, variance) + Distribution.with_deviation(mean, Math.sqrt(variance)) + end def with_precision(mean, precision) Distribution.with_deviation(mean / precision, Math.sqrt(1 / precision)) @@ -49,13 +53,22 @@ def log_ratio_normalisation(x, y) end end + + # copy values from other distribution + def absorb!(other) + @precision = other.precision + @precision_mean = other.precision_mean + @mean = other.mean + @deviation = other.deviation + @variance = other.variance + end def *(other) - Rating.with_precision(self.precision_mean + other.precision_mean, self.precision + other.precision) + Distribution.with_precision(self.precision_mean + other.precision_mean, self.precision + other.precision) end def /(other) - Rating.with_precision(self.precision_mean - other.precision_mean, self.precision - other.precision) + Distribution.with_precision(self.precision_mean - other.precision_mean, self.precision - other.precision) end # absolute difference
 @@ -1,21 +1,11 @@ module Saulabs module TrueSkill - class Calculation - - @@tau = 1 - @@beta = 1 - @@draw_probability = 0.1 - - @@tau_squared = @@tau**2 - @@beta_squared = @@beta**2 - - def self.update_skills(game) - graph = Saulabs::Gauss::FactorGraphs::FactorGraph.new - end - - def self.match_quality(game) + class Calculator + def self.update_skills(teams) + graph = FactorGraph.new(teams, :tau => 0.1, :beta => 20, :draw_probability => 0.0) + return graph.evaluate end end
 @@ -0,0 +1,71 @@ +module Saulabs + module TrueSkill + + class FactorGraph + + attr_reader :beta, :beta_squared, :tau, :tau_squared, :draw_probability, :epsilon + + def initialize(teams, options = {}) + @tau = options[:tau] || 0.1 + @beta = options[:beta] || 20 + @draw_probability = options[:draw_probability] || 0.1 + @tau_squared = tau**2 + @beta_squared = beta**2 + @epsilon = -Math.sqrt(2.0 * @beta_squared) * Gauss::Functions.inv_cdf((1.0 - @draw_probability) / 2.0) + + @prior_layer = Layers::PriorToSkills.new(self, teams) + @layers = [ + @prior_layer + ] + end + + def evaluate + build_layers + run_schedule + [ranking_probability, updated_skills] + end + + private + + def ranking_probability + factor_list = [] + sum_log_z = 0.0 + sum_log_s = 0.0 + @layers.each do |layer| + layer.factors.each do |factor| + factor.reset_marginals + factor.messages.each_index { |i| sum_log_z += factor.send_message_at(i) } + sum_log_s += factor.log_normalisation + end + end + Math.exp(sum_log_z + sum_log_s) + end + + def updated_skills + @prior_layer.output + end + + def build_layers + output = nil + @layers.each do |layer| + layer.input = output if output + layer.build + output = layer.output + end + end + + def run_schedule + schedules = [] + @layers.each do |layer| + schedules << layer.create_prior_schedule + end + @layers.reverse.each do |layer| + schedules << layer.create_posterior_schedule + end + Schedules::Sequence.new(schedules.compact).visit + end + + end + + end +end
 @@ -1,37 +0,0 @@ -module Saulabs - module TrueSkill - module FactorGraphs - - class FactorGraph - - # layers: - def initialize(teams) - @prior_layer = Saulabs::TrueSkill::Layers::PriorToSkills.new(teams) - @layers = [ - @prior_layer, - Saulabs::TrueSkill::Layers::SkillsToPerformances.new(self), - Saulabs::TrueSkill::Layers::TeamDifferences.new(self) - ] - build - end - - def run_schedule - - end - - private - - def build - output = nil - @layers.each do |layer| - layer.input = output if output - layer.build - output = layer.output - end - end - - end - - end - end -end
 @@ -1,18 +0,0 @@ -module Saulabs - module TrueSkill - module FactorGraphs - - class Variable - - attr_accessor :value, :parent_index - - def initialize(value, parent_index) - @value = value - @parent_index = parent_index - end - - end - - end - end -end
 @@ -4,42 +4,49 @@ module Factors class Base - attr_accessor :messages, :binding, :variables + attr_accessor :messages, :bindings def initialize @messages = [] - @binding = {} - @variables = [] + @bindings = {} end def update_message_at(idx) - update_message(@messages[idx], @binding[@messages[idx]]) + raise "illegal message index: #{idx}" if idx < 0 || idx > message_count-1 + update_message(@messages[idx], @bindings[@messages[idx]]) end def update_message(message, variable) - raise "Abstract method Gauss::Factors::Base#update_message(message, variable) called" + raise "Abstract method Factors::Base#update_message(message, variable) called" + end + + def message_count + 0 + end + + def log_normalisation + 0.0 end def reset_marginals - @binding.values.each { |var| var.reset_to_prior } + @bindings.values.each { |var| var.absorb!(Gauss::Distribution.with_deviation(0.0, 0.0)) } end def send_message_at(idx) - self.send_message(@messages[idx], @binding[@messages[idx]]) + self.send_message(@messages[idx], @bindings[@messages[idx]]) end # message: normal distribution def send_message(message, variable) - log_z = Saulabs::Gauss::Distribution.log_product_normalisation(message, variable.value) - variable.value = message.value * variable.value + log_z = Saulabs::Gauss::Distribution.log_product_normalisation(message, variable) + variable.absorb!(message * variable) return log_z end def bind(variable) message = Saulabs::Gauss::Distribution.new @messages << message - @binding[message] = variable - @variables << variable + @bindings[message] = variable return message end
 @@ -2,32 +2,28 @@ module Saulabs module TrueSkill module Factors - class Prior < Saulabs::TrueSkill::Factors::Base - - attr_accessor :message + class Prior < Base + # def initialize(mean, variance, variable) - super - @message = Saulabs::Gauss::Distribution.with_deviation(mean, Math.sqrt(variance)) + super() + @message = Gauss::Distribution.with_variance(mean, variance) bind(variable) end def update_message(message, variable) - old_marginal = variable.value.clone - old_message = message.clone - new_marginal = generate_new_marginal(old_marginal, old_message) - variable.value = new_marginal - message = @message - return old_marginal - new_marginal + new_marginal = Gauss::Distribution.with_precision( + variable.precision_mean + @message.precision_mean - message.precision_mean, + variable.precision + @message.precision - message.precision + ) + diff = variable - message + variable.absorb!(new_marginal) + message.absorb!(@message) + return diff end - private - - def generate_new_marginal(old_marginal, old_message) - Saulabs::Gauss::Distribution.with_precision( - old_marginal.precision_mean + @message.precision_mean - old_message.precision_mean, - old_marginal.precision + @message.precision - old_message.precision - ) + def message_count + 1 end end
 @@ -4,13 +4,26 @@ module Layers class Base - attr_accessor :factors, :output, :input + attr_accessor :graph, :factors, :output, :input - def initialize + def initialize(graph) + @graph = graph @factors = [] @output = [] @input = [] end + + def build + raise "Abstract method Layers::Base#build called" + end + + def create_prior_schedule + nil + end + + def create_posterior_schedule + nil + end end
 @@ -2,21 +2,28 @@ module Saulabs module TrueSkill module Layers - class PriorToSkills + class PriorToSkills < Base - def initialize(teams) - super - teams.each do |team| + def initialize(graph, teams) + super(graph) + @teams = teams + end + + def build + @teams.each do |team| team_skills = [] team.each do |skill| - variable = Saulabs::TrueSkill::FactorGraphs::Variable.new() - @layers << Saulabs::TrueSkill::Factors::Prior.new(skill.mean, skill.deviation**2 + Saulabs::TrueSkill.tau, variable) + @factors << Factors::Prior.new(skill.mean, skill.variance + @graph.tau_squared, Gauss::Distribution.new) team_skills << skill end @output << team_skills end end + def create_prior_schedule + Schedules::Sequence.new(@factors.map { |f| Schedules::Step.new(f, 0) }) + end + end end
 @@ -0,0 +1,15 @@ +module Saulabs + module TrueSkill + module Schedules + + class Base + + def visit(depth = -1, max_depth = 0) + raise "Abstract method Schedules::Base#visit(depth, max_depth) called" + end + + end + + end + end +end