Beispiel #1
0
    def test_factor_graph_agrees_with_two_team_explicit(self):
        # given
        p1 = Gaussian(mu=MU, sigma=SIGMA)
        p2 = Gaussian(mu=MU, sigma=SIGMA)
        p3 = Gaussian(mu=MU, sigma=SIGMA)
        losing_team = {"player3": p3, "player2": p2}
        winning_team = {"player1": p1}
        teams = [winning_team] + [losing_team]

        # when
        # we update skills with the factor graph
        ts = TrueSkillEnv(teams)
        new_ratings = ts.update_ratings()
        # and update skills with the analytic method
        update_ratings_in_team(winning_team, losing_team)

        # then
        self.assertAlmostEqual(new_ratings["player1"].mu,
                               winning_team["player1"].mu, 2)
        self.assertAlmostEqual(new_ratings["player2"].mu,
                               losing_team["player2"].mu, 2)
        self.assertAlmostEqual(new_ratings["player3"].mu,
                               losing_team["player3"].mu, 2)
        self.assertAlmostEqual(new_ratings["player1"].sigma,
                               winning_team["player1"].sigma, 2)
        self.assertAlmostEqual(new_ratings["player2"].sigma,
                               new_ratings["player2"].sigma, 2)
        self.assertAlmostEqual(new_ratings["player3"].sigma,
                               losing_team["player3"].sigma, 2)
Beispiel #2
0
    def test_three_team_converges(self):
        # given
        p1 = Gaussian(mu=MU, sigma=SIGMA)
        p2 = Gaussian(mu=MU, sigma=SIGMA)
        p3 = Gaussian(mu=MU, sigma=SIGMA)
        teams = [{"player1": p1}, {"player2": p2}, {"player3": p3}]

        # when
        ts = TrueSkillEnv(teams)
        ts.update_ratings()
Beispiel #3
0
    def test_gaussian_multiply(self):
        # given
        x = Gaussian(pi=3, tau=4)
        y = Gaussian(pi=5, tau=6)

        # when
        z = x * y

        # then
        self.assertEqual(z.tau, x.tau + y.tau)
        self.assertEqual(z.pi, x.pi + y.pi)
Beispiel #4
0
    def test_update_player_rating(self):
        # given
        player = 'foo'
        rating = Gaussian(mu=1, sigma=1)
        self.source.data[player] = Gaussian(mu=0, sigma=10)

        # when
        self.source.update_player_rating(player, rating)

        # then
        self.assertEqual(self.source.data[player], rating)
Beispiel #5
0
    def test_gaussian_divide(self):
        # given
        x = Gaussian(pi=4, tau=8)
        y = Gaussian(pi=2, tau=2)

        # when
        z = x / y

        # then
        self.assertEqual(z.tau, x.tau - y.tau)
        self.assertEqual(z.pi, x.pi - y.pi)
Beispiel #6
0
    def test_prior_factor_down(self):
        # given
        var = Variable("player1")
        dyn_fact = 0.5
        val = Gaussian(mu=MU, sigma=SIGMA)
        pf = PriorFactor(var, val, dyn_fact)

        # when
        pf.down()

        # then
        expected_pi = 1 / (SIGMA**2 + dyn_fact**2)
        expected_marginal = Gaussian(pi=expected_pi, tau=val.tau)
        self.assertEqual(expected_marginal, pf.var.marginal)
Beispiel #7
0
    def test_sum_factor_helper(self):
        # given
        var = Variable()
        var1, var2 = Variable(), Variable()
        var1.pi, var2.pi, var1.tau, var2.tau = [1] * 4
        perf_vars = [var1, var2]
        perf_messages = [Gaussian(), Gaussian()]
        coeffs = [1, 2]
        sf = SumFactor(var, perf_vars, coeffs)

        # when
        sf._update_helper(var, perf_vars, perf_messages, coeffs)

        # then
        expected_pi = 1 / (1 / 1 + 1 / 2)
        expected_tau = expected_pi * (1 + 2)
        var.messages[sf] = Gaussian(pi=expected_pi, tau=expected_tau)
def update_ratings_in_team(winning_team: Dict[str, Gaussian],
                           losing_team: Dict[str, Gaussian],
                           perf_noise_sigma=PERFORMANCE_NOISE,
                           dynamics_factor=DYNAMIC_FACTOR):
    """Does TrueSkill update for players in a two team game.

    Args:
        winning_team: A dictionary of the skills of each player
        losing_team: A dictionary of the skills of each player
        perf_noise_sigma: Standard deviation of the performance noise
        dynamics_factor: Additional factor which allows uncertainty in skill
        to vary over time
    """
    total_players = len(winning_team) + len(losing_team)

    winning_mu = sum(winning_team[p].mu for p in winning_team)
    losing_mu = sum(losing_team[p].mu for p in losing_team)
    delta_mu = winning_mu - losing_mu
    c = math.sqrt(
        sum(winning_team[p].sigma**2 for p in winning_team) +
        sum(losing_team[p].sigma**2
            for p in losing_team) + total_players * perf_noise_sigma**2)

    # compute the additive and multiplicative correction factors
    v_game = v_truncate(delta_mu / c)
    w_game = w_truncate(delta_mu / c)

    # update the winning teams skills in place
    for player in winning_team:
        skill = winning_team[player]
        adjusted_var = skill.sigma**2 + dynamics_factor**2
        mu_multiplier = adjusted_var / c
        sigma_multiplier = adjusted_var / c**2
        mu = skill.mu + mu_multiplier * v_game
        sigma = math.sqrt(adjusted_var * (1 - w_game * sigma_multiplier))
        winning_team[player] = Gaussian(mu=mu, sigma=sigma)

    # update the losing teams skills in place
    for player in losing_team:
        skill = losing_team[player]
        adjusted_var = skill.sigma**2 + dynamics_factor**2
        mu_multiplier = adjusted_var / c
        sigma_multiplier = adjusted_var / c**2
        mu = skill.mu - mu_multiplier * v_game
        sigma = math.sqrt(adjusted_var * (1 - w_game * sigma_multiplier))
        losing_team[player] = Gaussian(mu=mu, sigma=sigma)
Beispiel #9
0
    def test_gaussian_multiply_throws_type_error(self):
        # given
        x = Gaussian()
        y = 3

        # when, then
        with self.assertRaises(TypeError):
            x * y
Beispiel #10
0
    def test_sum_factor_down(self, mock_helper):
        # given
        var = Variable()
        var1, var2 = Variable(), Variable()
        var1.pi, var2.pi, var1.tau, var2.tau = [1] * 4
        perf_vars = [var1, var2]
        perf_messages = [Gaussian(), Gaussian()]
        coeffs = [1, 2]
        sf = SumFactor(var, perf_vars, coeffs)

        # when
        sf.down()

        # then
        expected_messages = [var1.messages[sf], var2.messages[sf]]
        mock_helper.assert_called_with(var, perf_vars, expected_messages,
                                       coeffs)
def update_rating(winner: Gaussian,
                  loser: Gaussian,
                  perf_noise_sigma=PERFORMANCE_NOISE,
                  dynamics_factor=DYNAMIC_FACTOR) -> Tuple[Gaussian, Gaussian]:
    """Updates the skills of two players in a 1vs1 match.

    Args:
        winner: Skill of the winner
        loser: Skill of the loser
        perf_noise_sigma: The standard devication of the performance noise.
        dynamics_factor: The standard deviation of the dynamics factor on the
        prior skill which allows uncertainty in skill to vary over time.

    Returns:
        Two new Gaussian objects containing the updated winner skill and
        updated loser skill respectively.
    """
    c = math.sqrt(winner.sigma**2 + loser.sigma**2 + 2 * perf_noise_sigma**2)
    winner_adjusted_var = winner.sigma**2 + dynamics_factor**2
    loser_adjusted_var = loser.sigma**2 + dynamics_factor**2

    winning_mu = winner.mu
    losing_mu = loser.mu
    delta_mu = winning_mu - losing_mu

    # calculate the additive and multiplicative correction factors
    v_game = v_truncate(delta_mu / c)
    w_game = w_truncate(delta_mu / c)

    # update the winner
    mu_multiplier = winner_adjusted_var / c
    sigma_multiplier = winner_adjusted_var / c**2
    new_mu = winning_mu + mu_multiplier * v_game
    new_sigma = math.sqrt(winner_adjusted_var *
                          (1 - w_game * sigma_multiplier))
    updated_winner = Gaussian(mu=new_mu, sigma=new_sigma)

    # update the loser
    mu_multiplier = loser_adjusted_var / c
    sigma_multiplier = loser_adjusted_var / c**2
    new_mu = losing_mu - mu_multiplier * v_game
    new_sigma = math.sqrt(loser_adjusted_var * (1 - w_game * sigma_multiplier))
    updated_loser = Gaussian(mu=new_mu, sigma=new_sigma)

    return updated_winner, updated_loser
Beispiel #12
0
 def up(self):
     c = self.var.pi - self.var.messages[self].pi
     d = self.var.tau - self.var.messages[self].tau
     pi = c / (1 - w_truncate(d / math.sqrt(c)))
     tau = ((d + math.sqrt(c) * v_truncate(d / math.sqrt(c))) /
            (1 - w_truncate(d / math.sqrt(c))))
     old_marginal = self.var.marginal
     self.var.update_marginal(self, Gaussian(pi=pi, tau=tau))
     return old_marginal.kl_divergence(self.var.marginal)
Beispiel #13
0
 def connect_to_source(self, data_dir='.'):
     if os.path.exists(os.path.join(data_dir, self.DATA_SOURCE)):
         with open(os.path.join(data_dir, self.DATA_SOURCE), 'r') as f:
             line = f.readline().strip('\n')
             while line:
                 name, mu, sigma = line.split(',')
                 mu, sigma = float(mu), float(sigma)
                 rating = Gaussian(mu=mu, sigma=sigma)
                 self.data[name] = rating
                 line = f.readline().strip('\n')
Beispiel #14
0
    def test_load_player_ratings_for_new_player(self):
        # given
        new_player = 'hello TrueSkill'

        # when
        self.source.load_player_ratings(new_player)

        # then
        expected_rating = Gaussian(mu=MU, sigma=SIGMA)
        self.assertEqual(self.source.data[new_player], expected_rating)
Beispiel #15
0
    def update_ratings(self):
        self._run()  # run the message passing algorithm
        new_ratings = {}
        for player in self.players:
            name = player.name
            pi = player.pi
            tau = player.tau
            new_ratings[name] = Gaussian(pi=pi, tau=tau)

        return new_ratings
Beispiel #16
0
    def test_sum_factor_up(self, mock_helper):
        # given
        var = Variable()
        var1, var2 = Variable(), Variable()
        var1.pi, var2.pi, var1.tau, var2.tau = [1] * 4
        perf_vars = [var1, var2]
        perf_messages = [Gaussian(), Gaussian()]
        coeffs = [1, 2]
        sf = SumFactor(var, perf_vars, coeffs)

        # when
        sf.up(0)

        # then
        # expect all variables except that at index 0
        expected_vars = [var2, var]
        expected_messages = [var2.messages[sf], var.messages[sf]]
        expected_coeffs = [-2, 1]  # coefficients of rearranged equation
        mock_helper.assert_called_with(var1, expected_vars, expected_messages,
                                       expected_coeffs)
Beispiel #17
0
    def test_load_player_ratings_for_existing_player(self):
        # given
        existing_player = 'A True Skill Veteran'
        existing_rating = Gaussian(mu=1, sigma=1)
        self.source.data[existing_player] = existing_rating

        # when
        self.source.load_player_ratings(existing_player)

        # then
        self.assertEqual(self.source.data[existing_player], existing_rating)
Beispiel #18
0
    def test_factor_graph_agrees_with_two_player_explicit(self):
        # given
        winner = Gaussian(mu=MU, sigma=SIGMA)
        loser = Gaussian(mu=MU, sigma=SIGMA)
        teams = [{"winner": winner}, {"loser": loser}]

        # when
        # we update the skills with the analytic method
        updated_winner, updated_loser = update_rating(winner, loser)
        # and we update the skills with the factor graph
        ts = TrueSkillEnv(teams)
        new_ratings = ts.update_ratings()

        # then
        # expect the results to be almost equal due to floating point errors
        # TODO check that this level of error is expected.
        self.assertAlmostEqual(new_ratings["winner"].mu, updated_winner.mu, 2)
        self.assertAlmostEqual(new_ratings["loser"].mu, updated_loser.mu, 2)
        self.assertAlmostEqual(new_ratings["winner"].sigma,
                               updated_loser.sigma, 2)
        self.assertAlmostEqual(new_ratings["loser"].sigma, updated_loser.sigma,
                               2)
Beispiel #19
0
 def _update_helper(self, var: Variable, perf_vars: List[Variable],
                    perf_messages: List[Gaussian], coeffs: List[float]):
     # TODO: Make variables, messages and coeffs a list of named tuples.
     assert len(perf_vars) == len(perf_messages) == len(coeffs)
     pi = 1.0 / sum([
         coeffs[i]**2 / (perf_vars[i].pi - perf_messages[i].pi)
         for i in range(len(coeffs))
     ])
     tau = pi * sum([
         coeffs[i] * (perf_vars[i].tau - perf_messages[i].tau) /
         (perf_vars[i].pi - perf_messages[i].pi) for i in range(len(coeffs))
     ])
     new_message = Gaussian(pi=pi, tau=tau)
     var.update_message(self, new_message)
Beispiel #20
0
    def test_truncate_factor_updates_marginal(self, mock_v, mock_w, mock_kl):
        # given
        var = Variable()
        var.pi, var.tau = [1] * 2
        tf = TruncateFactor(var)

        # when
        tf.up()

        # then
        expected_tau = 1
        expected_pi = 1
        expected_marginal = Gaussian(pi=expected_pi, tau=expected_tau)
        self.assertEqual(var.marginal, expected_marginal)
Beispiel #21
0
    def test_truncate_factor_calls_kl_divergence(self, mock_v, mock_w,
                                                 mock_kl):
        # given
        var = Variable()
        var.pi, var.tau = [1] * 2
        tf = TruncateFactor(var)

        # when
        tf.up()

        # then
        expected_tau = 1
        expected_pi = 1
        expected_marginal = Gaussian(pi=expected_pi, tau=expected_tau)
        mock_kl.assert_called_with(expected_marginal)
Beispiel #22
0
 def marginal(self):
     return Gaussian(pi=self.pi, tau=self.tau)
Beispiel #23
0
 def _update_helper(self, variable_one: Variable, variable_two: Variable):
     msg = variable_one / variable_one.messages[self]
     a = 1 / (1 + self.beta**2 * msg.pi)
     message = Gaussian(pi=a * msg.pi, tau=a * msg.tau)
     variable_two.update_message(self, message)
Beispiel #24
0
 def down(self):
     pi = 1 / (self.value.pi**-1 + self.dynamic**2)
     value = Gaussian(pi=pi, tau=self.value.tau)
     self.var.update_marginal(self, value)
Beispiel #25
0
 def __init__(self, variables):
     self.vars = variables
     for var in self.vars:
         var.messages[self] = Gaussian()
Beispiel #26
0
 def load_player_ratings(self, player_name):
     if player_name not in self.data:
         self.data[player_name] = Gaussian(mu=MU, sigma=SIGMA)
     return self.data[player_name]