Skip to content
Browse files

ScoreBasedBayesianRating: add partial update functionality to score b…

…ased rating
  • Loading branch information...
1 parent 522b201 commit dc10265a30f8b64647f356fdf07d89a018374024 Markus Düttmann committed
View
79 lib/saulabs/trueskill/score_based_bayesian_rating.rb
@@ -1,65 +1,68 @@
# -*- encoding : utf-8 -*-
module Saulabs
module TrueSkill
-
+
class ScoreBasedBayesianRating
-
+
# @return [Array<Array<TrueSkill::Rating>>]
attr_reader :teams
-
+
# @return [Float]
attr_reader :beta
-
+
# @return [Float]
attr_reader :beta_squared
-
+
# @return [Float]
attr_reader :gamma
-
+
# @return [Float]
attr_reader :gamma_squared
-
+
# @return [Boolean]
attr_reader :skills_additive
-
+
+
+
+
# Creates a new skill estimate for given scores and team configuration based on the given game parameters
# Works for the special case: two teams
#
- # @param [{ Array<TrueSkill::Rating>, Array<TrueSkill::Rating> }] teams
+ # @param [{ Array<TrueSkill::Rating>, Array<TrueSkill::Rating> }] teams
# player-ratings grouped in Arrays by teams
#
# @option options [Float] :beta (4.166667)
# the length of the skill-chain. Use a low value for games with a small amount of chance (Go, Chess, etc.) and
# a high value for games with a high amount of chance (Uno, Bridge, etc.)
- # @option options [Float] :gamma
+ # @option options [Float] :gamma
# variance of score distribution. should be extracted from user data. For games with few scoring events (Soccer, etc)
# gamma is small, for games with many scoring events (Shooter, etc), gamma is large
- # @param [Float] score
+ # @param [Float] score
# scores obtained by the respective teams s = s1 - s2 (can be negative)
# @option options [Boolean] :skills_additive (true)
# true is valid for games like Halo etc, where skill is additive (2 players are better than 1),
# false for card games like Skat, Doppelkopf, Bridge where skills are not additive,
# two players dont make the team stronger, skills averaged)
- #
+ #
# @example Calculating new skills of a two team game, where one team has one player and the other two
#
# require 'rubygems'
# require 'saulabs/trueskill'
- #
+ #
# include Saulabs::TrueSkill
- #
+ #
# # team 1 has just one player with a mean skill of 27.1, a skill-deviation of 2.13
# # and a play activity of 100 %
# team1 = [Rating.new(27.1, 2.13, 1.0)]
- #
+ #
# # team 2 has two players
# team2 = [Rating.new(22.0, 0.98, 0.8), Rating.new(31.1, 5.33, 0.9)]
- #
+ #
# # team 1 got 10.0 points and team 2 3.0
# graph = FactorGraph.new( team1 => 10.0, team2 => 3.0 )
- #
+ #
# # update the Ratings
# graph.update_skills
@@ -81,43 +84,47 @@ def initialize(score_teams_hash, options = {})
@gamma_squared = @gamma * @gamma
@teams = teams
-
+
end
def update_skills
#game can be 1vs1, 1vs2, 1vs3 or 2vs2
#
-
+
#team1 vs team2
# if @skills_additive = true: no averaging of skills and variance
- # otherwise: mean and skill_deviation averaged over team sizes
- n_team_1 = @skills_additive ? 1 : @teams[0].size.to_f
+ # otherwise: mean and skill_deviation averaged over team sizes
+ n_team_1 = @skills_additive ? 1 : @teams[0].size.to_f
n_team_2 = @skills_additive ? 1 : @teams[1].size.to_f
-
+
n_all = @teams[0].size.to_f + @teams[1].size.to_f
- var_team_1 = @teams[0].inject(0){|sum,item| sum + item.variance}
- var_team_2 = @teams[1].inject(0){|sum,item| sum + item.variance}
- mean_team_1 = @teams[0].inject(0){|sum,item| sum + item.mean}
- mean_team_2 = @teams[1].inject(0){|sum,item| sum + item.mean}
+ var_team_1 = @teams[0].inject(0){|sum,item| sum + item.variance}
+ var_team_2 = @teams[1].inject(0){|sum,item| sum + item.variance}
+ mean_team_1 = @teams[0].inject(0){|sum,item| sum + item.mean}
+ mean_team_2 = @teams[1].inject(0){|sum,item| sum + item.mean}
- @teams[0].map!{|rating|
- precision = 1.0/rating.variance + 1.0/( (n_all)*@beta_squared + 2.0*@gamma_squared + var_team_2/n_team_2 + var_team_1/n_team_1 -rating.variance/n_team_1)
- precision_mean = rating.mean/rating.variance + (@scores[0] - @scores[1] + n_team_1*(mean_team_2/n_team_2 -mean_team_1/n_team_1 + rating.mean/n_team_1))/( (n_all)*@beta_squared + 2.0*@gamma_squared + var_team_2/n_team_2 + var_team_1/n_team_1 -rating.variance/n_team_1)
- Gauss::Distribution.with_precision(precision_mean,precision )
+ @teams[0].map!{|rating|
+ precision = 1.0 / rating.variance + 1.0/ ( n_all * @beta_squared + 2.0 * @gamma_squared + var_team_2 / n_team_2 + var_team_1 / n_team_1 - rating.variance / n_team_1)
+ precision_mean = rating.mean / rating.variance + (@scores[0] - @scores[1] + n_team_1 * (mean_team_2 / n_team_2 - mean_team_1 / n_team_1 + rating.mean / n_team_1)) / ( n_all * @beta_squared + 2.0 * @gamma_squared + var_team_2 / n_team_2 + var_team_1 / n_team_1 - rating.variance / n_team_1)
+ partial_updated_precision = rating.precision + rating.activity*( precision - rating.precision)
+ partial_updated_precision_mean = rating.precision_mean + rating.activity * (precision_mean - rating.precision_mean)
+ Rating.new(partial_updated_precision_mean / partial_updated_precision, ( 1.0 / partial_updated_precision + rating.tau_squared)**0.5, rating.activity, rating.tau)
}
- @teams[1].map!{|rating|
- precision = 1.0/rating.variance + 1.0/( (n_all)*@beta_squared + 2.0*@gamma_squared + var_team_1/n_team_1 + var_team_2/n_team_2 -rating.variance/n_team_2)
- precision_mean = rating.mean/rating.variance + (@scores[1] - @scores[0] + n_team_2*(mean_team_1/n_team_1 -mean_team_2/n_team_2 + rating.mean/n_team_2))/( (n_all)*@beta_squared + 2.0*@gamma_squared + var_team_1/n_team_1 + var_team_2/n_team_2 -rating.variance/n_team_2)
- Gauss::Distribution.with_precision(precision_mean,precision )
+ @teams[1].map!{|rating|
+ precision = 1.0 / rating.variance + 1.0 / (n_all*@beta_squared + 2.0 * @gamma_squared + var_team_1 / n_team_1 + var_team_2 / n_team_2 - rating.variance / n_team_2)
+ precision_mean = rating.mean / rating.variance + (@scores[1] - @scores[0] + n_team_2 * (mean_team_1 / n_team_1 - mean_team_2 / n_team_2 + rating.mean / n_team_2)) / ( n_all * @beta_squared + 2.0 * @gamma_squared + var_team_1 / n_team_1 + var_team_2/n_team_2 - rating.variance / n_team_2)
+ partial_updated_precision = rating.precision + rating.activity*( precision - rating.precision)
+ partial_updated_precision_mean = rating.precision_mean + rating.activity * (precision_mean - rating.precision_mean)
+ Rating.new(partial_updated_precision_mean / partial_updated_precision, (1.0 / partial_updated_precision + rating.tau_squared)**0.5, rating.activity, rating.tau)
}
-
+
end
- end
+ end
end
end
View
108 spec/saulabs/trueskill/score_based_bayesian_rating_spec.rb
@@ -6,7 +6,7 @@
describe "Saulabs::TrueSkill::ScoreBasedBayesianRating: 2 vs 2: team 1=> 1.0, team2 => -1.0" do
-
+
before :each do
@team1 = [TrueSkill::Rating.new(25.0, 25.0/3.0, 1.0, 25.0/300.0),TrueSkill::Rating.new(25.0, 25.0/3.0, 1.0, 25.0/300.0)]
@@ -16,27 +16,27 @@
@results = { @team1 => 1.0, @team2 => -1.0 }
@graph = TrueSkill::FactorGraph.new(@results)
end
-
+
describe "#update_skills " do
-
+
it "should update the mean of the first player in team1: " do
test = TrueSkill::ScoreBasedBayesianRating.new(@results, {:gamma => 1, :beta => 10})
test.update_skills
- @teams[0][0].mean.should_not be_within(tolerance).of(25.0)
+ @teams[0][0].mean.should_not be_within(tolerance).of(25.0)
end
end
describe "#update_skills " do
-
+
it "should update the mean of the first player in team1: skills additive => false" do
test = TrueSkill::ScoreBasedBayesianRating.new(@results, {:gamma => 1, :beta => 10, :skills_additive => false})
test.update_skills
- @teams[0][0].mean.should_not be_within(tolerance).of(25.0)
+ @teams[0][0].mean.should_not be_within(tolerance).of(25.0)
end
end
end
describe "Saulabs::TrueSkill::ScoreBasedBayesianRating: 2 vs 2: team 1=> -1.0, team2 => 1.0" do
-
+
before :each do
@team1 = [TrueSkill::Rating.new(25.0, 25.0/3.0, 1.0, 25.0/300.0),TrueSkill::Rating.new(25.0, 25.0/3.0, 1.0, 25.0/300.0)]
@@ -46,28 +46,28 @@
@results = { @team1 => -1.0, @team2 => 1.0 }
@graph = TrueSkill::FactorGraph.new(@results)
end
-
+
describe "#update_skills " do
-
+
it "should update the mean of the first player in team1: " do
test = TrueSkill::ScoreBasedBayesianRating.new(@results, {:gamma => 1, :beta => 10})
test.update_skills
- @teams[0][0].mean.should_not be_within(tolerance).of(25.0)
+ @teams[0][0].mean.should_not be_within(tolerance).of(25.0)
end
end
describe "#update_skills " do
-
+
it "should update the mean of the first player in team1: skills additive => false" do
test = TrueSkill::ScoreBasedBayesianRating.new(@results, {:gamma => 1, :beta => 10, :skills_additive => false})
test.update_skills
- @teams[0][0].mean.should_not be_within(tolerance).of(25.0)
+ @teams[0][0].mean.should_not be_within(tolerance).of(25.0)
end
end
end
describe "Saulabs::TrueSkill::ScoreBasedBayesianRating: 1 vs 3: team1 => -1.0, team2 => 1.0" do
-
+
before :each do
@team1 = [TrueSkill::Rating.new(25.0, 25.0/3.0, 1.0, 25.0/300.0)]
@@ -77,27 +77,27 @@
@results = { @team1 => -1.0, @team2 => 1.0 }
@graph = TrueSkill::FactorGraph.new(@results)
end
-
+
describe "#update_skills " do
-
+
it "should update the mean of the first player in team1: " do
test = TrueSkill::ScoreBasedBayesianRating.new(@results, {:gamma => 1, :beta => 10})
test.update_skills
- @teams[0][0].mean.should_not be_within(tolerance).of(25.0)
+ @teams[0][0].mean.should_not be_within(tolerance).of(25.0)
end
end
describe "#update_skills " do
-
+
it "should update the mean of the first player in team1: skills additive => false" do
test = TrueSkill::ScoreBasedBayesianRating.new(@results, {:gamma => 1, :beta => 10, :skills_additive => false})
test.update_skills
- @teams[0][0].mean.should_not be_within(tolerance).of(25.0)
+ @teams[0][0].mean.should_not be_within(tolerance).of(25.0)
end
end
end
describe "Saulabs::TrueSkill::ScoreBasedBayesianRating: 1 vs 3: team1 => 1.0, team2 => -1.0" do
-
+
before :each do
@team1 = [TrueSkill::Rating.new(25.0, 25.0/3.0, 1.0, 25.0/300.0)]
@@ -107,21 +107,21 @@
@results = { @team1 => 1.0, @team2 => -1.0 }
@graph = TrueSkill::FactorGraph.new(@results)
end
-
+
describe "#update_skills " do
-
+
it "should update the mean of the first player in team1: " do
test = TrueSkill::ScoreBasedBayesianRating.new(@results, {:gamma => 1, :beta => 10})
test.update_skills
- @teams[0][0].mean.should_not be_within(tolerance).of(25.0)
+ @teams[0][0].mean.should_not be_within(tolerance).of(25.0)
end
end
describe "#update_skills " do
-
+
it "should update the mean of the first player in team1: skills additive => false" do
test = TrueSkill::ScoreBasedBayesianRating.new(@results, {:gamma => 1, :beta => 10, :skills_additive => false})
test.update_skills
- @teams[0][0].mean.should_not be_within(tolerance).of(25.0)
+ @teams[0][0].mean.should_not be_within(tolerance).of(25.0)
end
end
end
@@ -129,7 +129,7 @@
describe "Saulabs::TrueSkill::ScoreBasedBayesianRating: 1 vs 3: team1 => 100.0, team2 => -100.0" do
-
+
before :each do
@team1 = [TrueSkill::Rating.new(25.0, 25.0/3.0, 1.0, 25.0/300.0)]
@@ -139,27 +139,27 @@
@results = { @team1 => 100.0, @team2 => -100.0 }
@graph = TrueSkill::FactorGraph.new(@results)
end
-
+
describe "#update_skills " do
-
+
it "should update the mean of the first player in team1: " do
test = TrueSkill::ScoreBasedBayesianRating.new(@results, {:gamma => 1, :beta => 10})
test.update_skills
- @teams[0][0].mean.should_not be_within(tolerance).of(25.0)
+ @teams[0][0].mean.should_not be_within(tolerance).of(25.0)
end
end
describe "#update_skills " do
-
+
it "should update the mean of the first player in team1: skills additive => false" do
test = TrueSkill::ScoreBasedBayesianRating.new(@results, {:gamma => 1, :beta => 10, :skills_additive => false})
test.update_skills
- @teams[0][0].mean.should_not be_within(tolerance).of(25.0)
+ @teams[0][0].mean.should_not be_within(tolerance).of(25.0)
end
end
end
describe "Saulabs::TrueSkill::ScoreBasedBayesianRating: 1 vs 3: team1 => -100.0, team2 => 100.0" do
-
+
before :each do
@team1 = [TrueSkill::Rating.new(25.0, 25.0/3.0, 1.0, 25.0/300.0)]
@@ -169,27 +169,27 @@
@results = { @team1 => -100.0, @team2 => 100.0 }
@graph = TrueSkill::FactorGraph.new(@results)
end
-
+
describe "#update_skills " do
-
+
it "should update the mean of the first player in team1: " do
test = TrueSkill::ScoreBasedBayesianRating.new(@results, {:gamma => 1, :beta => 10})
test.update_skills
- @teams[0][0].mean.should_not be_within(tolerance).of(25.0)
+ @teams[0][0].mean.should_not be_within(tolerance).of(25.0)
end
end
describe "#update_skills " do
-
+
it "should update the mean of the first player in team1: skills additive => false" do
test = TrueSkill::ScoreBasedBayesianRating.new(@results, {:gamma => 1, :beta => 10, :skills_additive => false})
test.update_skills
- @teams[0][0].mean.should_not be_within(tolerance).of(25.0)
+ @teams[0][0].mean.should_not be_within(tolerance).of(25.0)
end
end
end
describe "Saulabs::TrueSkill::ScoreBasedBayesianRating: 1 vs 3: Draw: team1 => 100.0, team2 => 100.0" do
-
+
before :each do
@team1 = [TrueSkill::Rating.new(25.0, 25.0/3.0, 1.0, 25.0/300.0)]
@@ -199,23 +199,47 @@
@results = { @team1 => 100.0, @team2 => 100.0 }
@graph = TrueSkill::FactorGraph.new(@results)
end
-
+
describe "#update_skills " do
-
+
it "should update the mean of the first player in team1: " do
test = TrueSkill::ScoreBasedBayesianRating.new(@results, {:gamma => 1, :beta => 10})
test.update_skills
- @teams[0][0].mean.should_not be_within(tolerance).of(25.0)
+ @teams[0][0].mean.should_not be_within(tolerance).of(25.0)
end
end
describe "#update_skills " do
-
+
it "should update the mean of the first player in team1: skills additive => false" do
test = TrueSkill::ScoreBasedBayesianRating.new(@results, {:gamma => 1, :beta => 10, :skills_additive => false})
test.update_skills
- @teams[0][0].mean.should be_within(tolerance).of(25.0)
+ @teams[0][0].mean.should be_within(tolerance).of(25.0)
+ end
+ end
+end
+
+
+describe "Saulabs::TrueSkill::ScoreBasedBayesianRating: 1 vs 3: Draw: team1 => 100.0, team2 => 100.0, Test partial update" do
+
+
+ before :each do
+ @team1 = [TrueSkill::Rating.new(25.0, 25.0/3.0, 0.0, 25.0/300.0)]
+ @team2 = [TrueSkill::Rating.new(25.0, 25.0/3.0, 0.0, 25.0/300.0),TrueSkill::Rating.new(25.0, 25.0/3.0, 0.0, 25.0/300.0),TrueSkill::Rating.new(25.0, 25.0/3.0, 0.0, 25.0/300.0)]
+ @teams = [@team1,@team2]
+ @skill = @teams[0][0]
+ @results = { @team1 => 100.0, @team2 => 100.0 }
+ @graph = TrueSkill::FactorGraph.new(@results)
+ end
+
+ describe "#update_skills " do
+
+ it "should update the mean of the first player in team1: " do
+ test = TrueSkill::ScoreBasedBayesianRating.new(@results, {:gamma => 1, :beta => 10})
+ test.update_skills
+ @teams[0][0].mean.should be_within(tolerance).of(25.0)
end
end
+
end

0 comments on commit dc10265

Please sign in to comment.
Something went wrong with that request. Please try again.