Permalink
Browse files

added performances to team performances layer

  • Loading branch information...
1 parent 67feff3 commit 7c297e87f3aa50f8e1c878b57ab9980fe4325c66 @larskuhnt larskuhnt committed Apr 15, 2010
Showing with 741 additions and 185 deletions.
  1. +47 −21 lib/saulabs/gauss/distribution.rb
  2. +0 −33 lib/saulabs/gauss/functions.rb
  3. +62 −0 lib/saulabs/gauss/truncated_correction.rb
  4. +2 −0 lib/saulabs/trueskill.rb
  5. +0 −14 lib/saulabs/trueskill/calculation.rb
  6. +20 −11 lib/saulabs/trueskill/factor_graph.rb
  7. +8 −6 lib/saulabs/trueskill/factors/base.rb
  8. +27 −5 lib/saulabs/trueskill/factors/greater_than.rb
  9. +0 −1 lib/saulabs/trueskill/factors/likelihood.rb
  10. +2 −2 lib/saulabs/trueskill/factors/prior.rb
  11. +80 −0 lib/saulabs/trueskill/factors/weighted_sum.rb
  12. +45 −0 lib/saulabs/trueskill/factors/within.rb
  13. +72 −0 lib/saulabs/trueskill/layers/iterated_team_performances.rb
  14. +31 −0 lib/saulabs/trueskill/layers/performances_to_team_performances.rb
  15. +3 −3 lib/saulabs/trueskill/layers/prior_to_skills.rb
  16. +3 −3 lib/saulabs/trueskill/layers/skills_to_performances.rb
  17. +27 −0 lib/saulabs/trueskill/layers/team_difference_comparision.rb
  18. +22 −0 lib/saulabs/trueskill/layers/team_performance_differences.rb
  19. +5 −5 lib/saulabs/trueskill/rating.rb
  20. +2 −0 lib/saulabs/trueskill/schedules/loop.rb
  21. +2 −1 lib/saulabs/trueskill/schedules/step.rb
  22. +73 −0 spec/saulabs/gauss/distribution_spec.rb
  23. +0 −21 spec/saulabs/gauss/functions_spec.rb
  24. +41 −0 spec/saulabs/gauss/truncated_correction_spec.rb
  25. +4 −6 spec/saulabs/trueskill/factor_graph_spec.rb
  26. +26 −0 spec/saulabs/trueskill/factors/greater_than_spec.rb
  27. +32 −0 spec/saulabs/trueskill/factors/likelihood_spec.rb
  28. +8 −48 spec/saulabs/trueskill/factors/prior_spec.rb
  29. +62 −0 spec/saulabs/trueskill/factors/weighted_sum_spec.rb
  30. +26 −0 spec/saulabs/trueskill/factors/within_spec.rb
  31. +1 −1 spec/saulabs/trueskill/layers/prior_to_skills_spec.rb
  32. +8 −4 spec/spec_helper.rb
@@ -1,38 +1,41 @@
module Saulabs
module Gauss
class Distribution
+
+ SQRT2 = Math.sqrt(2).freeze
+ INV_SQRT_2PI = (1 / Math.sqrt(2 * Math::PI)).freeze
+ LOG_SQRT_2PI = Math.log(Math.sqrt(2 * Math::PI)).freeze
# gaussian normal distribution values
attr_accessor :mean, :deviation, :variance, :precision, :precision_mean
-
- def initialize
- @mean = 0.0
- @deviation = 0.0
- @variance = 0.0
- @precision = 0.0
- @precision_mean = 0.0
+
+ def initialize(mean = 0.0, deviation = 0.0)
+ mean = 0.0 unless mean.to_f.finite?
+ deviation = 0.0 unless deviation.to_f.finite?
+ @mean = mean
+ @deviation = deviation
+ @variance = deviation * deviation
+ @precision = deviation == 0.0 ? 0.0 : 1 / @variance.to_f
+ @precision_mean = @precision * mean
end
class << self
+
+ def standard
+ @@standard ||= Distribution.new(0.0, 1.0)
+ @@standard
+ end
def with_deviation(mean, deviation)
- dist = Distribution.new
- if !mean.to_f.nan? and !deviation.to_f.nan? and deviation != 0.0
- dist.mean = mean
- dist.deviation = deviation
- dist.variance = deviation * deviation
- dist.precision = 1 / dist.variance.to_f
- dist.precision_mean = dist.precision * mean
- end
- return dist
+ Distribution.new(mean, deviation)
end
def with_variance(mean, variance)
- Distribution.with_deviation(mean, Math.sqrt(variance))
+ Distribution.new(mean, Math.sqrt(variance))
end
def with_precision(mean, precision)
- Distribution.with_deviation(mean / precision, Math.sqrt(1 / precision))
+ Distribution.new(mean / precision, Math.sqrt(1 / precision))
end
def absolute_difference(x, y)
@@ -43,17 +46,40 @@ def log_product_normalization(x, y)
return 0.0 if x.precision == 0.0 || y.precision == 0.0
variance_sum = x.variance + y.variance
mean_diff = x.mean - y.mean
- -Functions::LOG_SQRT_2PI - (Math.log(variance_sum) / 2.0) - (mean_diff**2 / 2.0 * variance_sum)
+ -LOG_SQRT_2PI - (Math.log(variance_sum) / 2.0) - (mean_diff**2 / (2.0 * variance_sum))
end
def log_ratio_normalization(x, y)
return 0.0 if x.precision == 0.0 || y.precision == 0.0
variance_diff = y.variance - x.variance
return 0.0 if variance_diff == 0.0
mean_diff = x.mean - y.mean
- Math.log(y.variance) + Functions::LOG_SQRT_2PI - (Math.log(variance_diff) / 2.0) + (mean_diff**2 / 2.0 * variance_diff)
+ Math.log(y.variance) + LOG_SQRT_2PI - (Math.log(variance_diff) / 2.0) + (mean_diff**2 / (2.0 * variance_diff))
+ end
+
+ # Computes the cummulative Gaussian distribution at a specified point of interest
+ def cumulative_distribution_function(x)
+ 0.5 * (1 + Math.erf(x / SQRT2))
end
+ alias_method :cdf, :cumulative_distribution_function
+
+ # Computes the Gaussian density at a specified point of interest
+ def probability_density_function(x)
+ INV_SQRT_2PI * Math.exp(-0.5 * (x**2))
+ end
+ alias_method :pdf, :probability_density_function
+
+ # The inverse of the cummulative Gaussian distribution function
+ def quantile_function(x)
+ -SQRT2 * Math.erfc(2.0 * x)
+ end
+ alias_method :inv_cdf, :quantile_function
+
+ end
+ def value_at(x)
+ exp = -(x - @mean)**2.0 / (2.0 * @variance)
+ (1.0/@deviation) * INV_SQRT_2PI * Math.exp(exp)
end
# copy values from other distribution
@@ -96,4 +122,4 @@ def to_s
end
end
-end
+end
@@ -1,33 +0,0 @@
-module Saulabs
- module Gauss
- class Functions
-
- SQRT2 = Math.sqrt(2).freeze
- INV_SQRT_2PI = (1 / Math.sqrt(2 * Math::PI)).freeze
- LOG_SQRT_2PI = Math.log(Math.sqrt(2 * Math::PI)).freeze
-
- class << self
-
- # Computes the cummulative Gaussian distribution at a specified point of interest
- def cumulative_distribution_function(x)
- 0.5 * (1 + Math.erf(x / SQRT2))
- end
- alias_method :cdf, :cumulative_distribution_function
-
- # Computes the Gaussian density at a specified point of interest
- def probability_density_function(x)
- INV_SQRT_2PI * Math.exp(-0.5 * (x**2))
- end
- alias_method :pdf, :probability_density_function
-
- # The inverse of the cummulative Gaussian distribution function
- def quantile_function(x)
- -SQRT2 * Math.erfc(2.0 * x)
- end
- alias_method :inv_cdf, :quantile_function
-
- end
-
- end
- end
-end
@@ -0,0 +1,62 @@
+module Saulabs
+ module Gauss
+ class TruncatedCorrection
+
+ class << self
+
+ def w_within_margin(perf_diff, draw_margin)
+ abs_diff = perf_diff.abs
+ denom = Distribution.cdf(draw_margin - abs_diff) - Distribution.cdf(-draw_margin - abs_diff)
+ return 1.0 if denom < 2.2e-162
+ vt = v_within_margin(abs_diff, draw_margin)
+ return vt**2 + (
+ (draw_margin - abs_diff) *
+ Gauss::Distribution.standard.value_at(draw_margin - abs_diff) -
+ (-draw_margin - abs_diff) *
+ Gauss::Distribution.standard.value_at(-draw_margin - abs_diff)
+ ) / denom
+ end
+
+ def v_within_margin(perf_diff, draw_margin)
+ abs_diff = perf_diff.abs
+ denom = Distribution.cdf(draw_margin - abs_diff) - Distribution.cdf(-draw_margin - abs_diff)
+ if denom < 2.2e-162
+ return perf_diff < 0 ? -perf_diff - draw_margin : -perf_diff + draw_margin
+ end
+ num = Gauss::Distribution.standard.value_at(-draw_margin - abs_diff) -
+ Gauss::Distribution.standard.value_at(draw_margin - abs_diff)
+ perf_diff < 0 ? -num/denom : num/denom
+ end
+
+ def w_exceeds_margin(perf_diff, draw_margin)
+ denom = Distribution.cdf(perf_diff - draw_margin)
+ if denom < 2.2e-162
+ return perf_diff < 0.0 ? 1.0 : 0.0
+ else
+ v = v_exceeds_margin(perf_diff, draw_margin)
+ return v * (v + perf_diff - draw_margin)
+ end
+ end
+
+ def v_exceeds_margin(perf_diff, draw_margin)
+ denom = Distribution.cdf(perf_diff - draw_margin)
+ denom < 2.2e-162 ? -perf_diff + draw_margin : Gauss::Distribution.standard.value_at(perf_diff - draw_margin)/denom
+ end
+
+ def exceeds_margin(perf_diff, draw_margin)
+ abs_diff = perf_diff.abs
+ denom = Distribution.cdf(draw_margin - abs_diff) - Distribution.cdf(-draw_margin - abs_diff)
+ if denom < 2.2e-162
+ return 1.0
+ else
+ v = v_exceeds_margin(abs_diff, draw_margin)
+ return v**2 +
+ ((draw_margin - abs_diff) * Gauss::Distribution.standard.value_at(draw_margin - abs_diff) -
+ (-draw_margin - abs_diff) * Gauss::Distribution.standard.value_at(-draw_margin - abs_diff)) / denom
+ end
+ end
+
+ end
+ end
+ end
+end
@@ -1,3 +1,5 @@
+require 'pp'
+
require "#{File.dirname(__FILE__)}/gauss.rb"
Dir.glob("#{File.dirname(__FILE__)}/trueskill/**/*.rb").each do |src|
@@ -1,14 +0,0 @@
-module Saulabs
- module TrueSkill
-
- 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
-
- end
-end
@@ -3,27 +3,36 @@ module TrueSkill
class FactorGraph
- attr_reader :beta, :beta_squared, :tau, :tau_squared, :draw_probability, :epsilon
+ attr_reader :teams, :beta, :beta_squared, :draw_probability, :epsilon, :layers
- def initialize(teams, options = {})
- @tau = options[:tau] || 0.1
- @beta = options[:beta] || 20
+ # teams: 2 dimensional array of ratings
+ def initialize(teams, ranks, options = {})
+ @teams = teams
+ @ranks = ranks
+ @beta = options[:beta] || 25/6.0
@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)
+ @beta_squared = @beta**2
+ @epsilon = -Math.sqrt(2.0 * @beta_squared) * Gauss::Distribution.inv_cdf((1.0 - @draw_probability) / 2.0)
- @prior_layer = Layers::PriorToSkills.new(self, teams)
+ @prior_layer = Layers::PriorToSkills.new(self, @teams)
@layers = [
@prior_layer,
- Layers::SkillsToPerformances.new(self)
+ Layers::SkillsToPerformances.new(self),
+ Layers::PerformancesToTeamPerformances.new(self),
+ Layers::IteratedTeamPerformances.new(self,
+ Layers::TeamPerformanceDifferences.new(self),
+ Layers::TeamDifferenceComparision.new(self, ranks)
+ )
]
end
+ def draw_margin
+ Gauss::Distribution.inv_cdf((@draw_probability + 1) / 2.0) * Math.sqrt(1 + 1) * @beta
+ end
+
def evaluate
build_layers
run_schedule
- puts "#{@layers.last.output.flatten.map(&:to_s).join(", ")}<br>"
[ranking_probability, updated_skills]
end
@@ -49,7 +58,7 @@ def updated_skills
def build_layers
output = nil
@layers.each do |layer|
- layer.input = output if output
+ layer.input = output
layer.build
output = layer.output
end
@@ -4,18 +4,21 @@ module Factors
class Base
- attr_accessor :messages, :bindings
-
def initialize
@messages = []
@bindings = {}
+ @variables = []
@priors = []
end
def update_message_at(index)
raise "Abstract method Factors::Base#update_message_at(index) called"
end
+ def message_count
+ @messages.size
+ end
+
def log_normalization
raise "Abstract method Factors::Base#log_normalization called"
end
@@ -25,10 +28,8 @@ def reset_marginals
end
def send_message_at(idx)
- self.send_message(@messages[idx], @bindings[@messages[idx]])
- end
-
- def send_message(message, variable)
+ message = @messages[idx]
+ variable = @variables[idx]
log_z = Gauss::Distribution.log_product_normalization(message, variable)
variable.replace(message * variable)
return log_z
@@ -38,6 +39,7 @@ def bind(variable)
message = Gauss::Distribution.new
@messages << message
@bindings[message] = variable
+ @variables << variable
return message
end
@@ -4,15 +4,37 @@ module Factors
class GreaterThan < Base
- attr_accessor :epsilon, :vaiable
-
def initialize(epsilon, variable)
+ super()
@epsilon = epsilon
- @variable = variable
+ bind(variable)
+ end
+
+ def log_normalization
+ msg = @variables[0] / @messages[0]
+ -Gauss::Distribution.log_product_normalization(msg, @messages[0]) +
+ Math.log(Gauss::Distribution.cdf((msg.mean - @epsilon) / msg.deviation))
end
- def update_message(message, variable)
-
+ def update_message_at(index)
+ raise "illegal message index: #{index}" if index < 0 || index > 0
+ message = @messages[index]
+ variable = @variables[index]
+ msg = variable / message
+ c = msg.precision
+ d = msg.precision_mean
+ sqrt_c = Math.sqrt(c)
+ d_sqrt_c = sqrt_c == 0 ? 0.0 : d / sqrt_c
+ e_sqrt_c = @epsilon * sqrt_c
+ denom = 1.0 - Gauss::TruncatedCorrection.w_exceeds_margin(d_sqrt_c, e_sqrt_c)
+ new_precision = c / denom
+ new_precision_mean = (d + sqrt_c * Gauss::TruncatedCorrection.v_exceeds_margin(d_sqrt_c, e_sqrt_c)) / denom
+ new_marginal = Gauss::Distribution.with_precision(new_precision_mean, new_precision)
+ new_message = message * new_marginal / variable
+ diff = new_marginal - variable
+ message.replace(new_message)
+ variable.replace(new_marginal)
+ return diff
end
end
@@ -27,7 +27,6 @@ def log_normalization
def update_helper(message1, message2, variable1, variable2)
a = @precision / (@precision + variable2.precision - message2.precision)
- # puts "#{a}<br>"
new_message = Gauss::Distribution.with_precision(
a * (variable2.precision_mean - message2.precision_mean),
a * (variable2.precision - message2.precision)
@@ -13,12 +13,12 @@ def initialize(mean, variance, variable)
def update_message_at(index)
raise "illegal message index: #{index}" if index < 0 || index > 0
message = @messages[index]
- variable = @bindings[@messages[index]]
+ variable = @variables[index]
new_marginal = Gauss::Distribution.with_precision(
variable.precision_mean + @message.precision_mean - message.precision_mean,
variable.precision + @message.precision - message.precision
)
- diff = variable - message
+ diff = variable - new_marginal
variable.replace(new_marginal)
message.replace(@message)
return diff
Oops, something went wrong.

0 comments on commit 7c297e8

Please sign in to comment.