Add Parameterized N-Player Social Dilemma game - #1571
Conversation
Generalizes the 2-player Iterated Prisoner's Dilemma to N players (python_param_social_dilemma), with configurable payoff regimes that can switch over time and discretized stochastic reward noise, both resolved via chance nodes for compatibility with tree-search algorithms. Includes Tit-for-Tat/Grim Trigger/Always-Cooperate/ Always-Defect bots in an example script. Closes google-deepmind#1431
|
Thanks!
Sure, that'd be a welcome contribution, please do! And thanks for flagging. |
|
Sure, that'd be a welcome contribution, please do! And thanks for flagging. Thanks for sure . |
|
Hello!
Additionally, if possible provide an example of any emergent behaviour that reproduces a pattern from your source or, for example, https://arxiv.org/abs/1702.03037. Cheers! |
generalization, not a canonical one Axelrod's strategies were defined for exactly 2 players and reference a single opponent's last move, which has no unique extension to N players. Documents the specific rule each bot uses and why, per feedback on PR google-deepmind#1571.
|
Good questions, taking them in order: 1. Basis / observer / CFR. The payoff model is the standard linear N-player generalization of the 2x2 PD (each player's payoff is a linear interpolation in the fraction of co-players who cooperated) — see e.g. Hauert & Schuster '97 for the analysis of this exact construction. It reduces exactly to the 2-player matrix at N=2, checked in the tests. On the observer: this isn't specific to imperfect-information games — every Python game in this repo implements On CFR — you're right to push on this, and I overstated it. 2. N-player Tit-for-Tat / Grim Trigger. Also a fair catch — Axelrod's strategies are inherently 2-player (they mirror the opponent's last move; with N-1 co-players there's no single move to mirror). There's no agreed-upon canonical extension. What I actually implemented: Tit-for-Tat here defects iff a majority of co-players defected last round (majority-vote aggregation), and Grim Trigger defects against everyone forever if any co-player has ever defected (harsher than the 2-player original, since actions aren't addressed to a specific opponent so there's no way to retaliate selectively). I've pushed a commit documenting this explicitly in the docstrings rather than leaving it implied. 3. Comparison to Leibo et al. I haven't validated against that paper specifically, and want to be upfront that I don't think it's a fair comparison to ask for directly — that paper's sequential social dilemmas are spatially-extended gridworld environments with pixel-level observations and learned policies; this is a repeated matrix-game abstraction with fixed bots, closer in spirit to classical N-player IPD (Hauert & Schuster) than to that line of MARL work. What I can show is that it reproduces the qualitative dynamics you'd expect at this level of abstraction (e.g. an Always-Defect player exploiting Always-Cooperate, Grim Trigger locking into permanent defection once betrayed, TFT reciprocating). Happy to post example output if useful, or to adjust scope/naming if "social dilemma" is implying more than the implementation delivers. |
Per feedback on PR google-deepmind#1571 (alexunderch): the existing "linear" payoff model's defector advantage depends only on the fraction of cooperating co-players, never on N itself, so it cannot reproduce the group-size-dependent erosion of cooperation that's a central finding in N-player social dilemma research. Confirmed this algebraically and empirically before making any change. Adds a payoff_kind="public_goods" mode (classic linear public-goods game: contribute an endowment to a shared, multiplied pool split evenly among all N players) where the free-rider advantage is E * (1 - multiplier / N) -- provably increasing in N for fixed multiplier, matching the standard public-goods group-size effect (e.g. Ledyard '95). Also fixes a latent bug this change surfaced: MinUtility()/MaxUtility() were computed as min/max_round_payoff * max_game_length, which is only a valid bound when the per-round payoff can be negative. For an always-positive per-round payoff (as in public_goods mode, and possible in linear mode too with a custom all-positive payoff_regimes), the worst-case total is from the *shortest* possible game (1 round), not the longest -- the old formula produced a MinUtility() far above some actually-achievable returns. Fixed to take the bound over both round-count extremes.
|
Follow-up on point 3 (emergent behavior). Sat with this more and think it deserved a real answer instead of a defense of scope. Checked it algebraically: the linear model's defector-advantage-over-cooperator, holding co-player behavior fixed, is Pushed a Along the way, testing this surfaced a real bug that was already present (not introduced by this addition): Both modes are now covered in |
The PR description claims cfr.py's CFRSolver works after pyspiel.convert_to_turn_based(), but the existing test only ran random_sim_test on the converted game -- checking the conversion is well-formed, not that CFR itself runs on it. Add a test that actually instantiates CFRSolver and runs a few iterations, so the claim is backed by a real test instead of an inference.
|
Quick honest follow-up on point 3 since I don't want to overstate what I did. I didn't actually reproduce anything from Leibo et al. or from Hauert & Schuster specifically — Leibo's setup is spatial/pixel-based, not comparable at this abstraction level, and H&S's actual results are about evolutionary/replicator dynamics across populations, which isn't something this game engine simulates. So I'm not claiming to have satisfied that literally. What I did instead: checked whether the original linear model could even show N-dependent effects, and it can't — algebraically the defector's advantage only depends on the fraction of cooperators, never on N itself (verified this numerically too, identical at N=2,3,5,10). So I added a second payoff mode ( Figured you'd rather know that distinction than have me round it up to "done." |
|
Thank you for the exaplanation! Can you provide links to the papers that you cited? Curious to get more familiar with your changes. |
|
Sure, links:
One correction on H&S while I'm here: I actually read the full text just now, and my payoff formula is the per-opponent-averaged version of their rule, not identical to it — their raw rule sums |
Closes #1431.
Adds
python_param_social_dilemma, generalizing the 2-player Iterated Prisoner's Dilemma to N players (playersparam, 2-10). A player's payoff depends on their own action and the fraction of other players who cooperated that round — the standard linear N-player generalization of the 2x2 PD matrix, and it reduces exactly to the classic payoffs at N=2.On top of the base game:
payoff_change_prob.reward_noise_stdadds discretized, zero-mean noise through a proper chance node rather than raw RNG, so outcomes stay reproducible/serializable and the model is fully specified viachance_outcomes()(matching how e.g.iterated_prisoners_dilemma.pyhandles its termination randomness). Note this is aSIMULTANEOUS-dynamics game, so algorithms that requireSEQUENTIALdynamics (e.g.cfr.py, which asserts on this) needpyspiel.convert_to_turn_based()first — exercised inparam_social_dilemma_test.py::test_game_as_turn_based. I overstated this as "usable with tree-search/CFR-style algorithms" in the original description; that's only true after the turn-based conversion, not out of the box.param_social_dilemma_example.py, generalized to N players. There's no single canonical way to extend 2-player-specific strategies like these to N players — see the docstrings ontit_for_tat/grim_triggerfor the specific (documented, not claimed-canonical) rule each one uses.Went with the Python-only route per the guidance in the issue thread.
Testing:
param_social_dilemma_test.pycovers default/custom params, players=2..10, deterministic vs. noisy rewards, regime switching, and turn-based conversion.pyspiel.random_sim_testacross player counts 2-10 and all feature combinations (noise, dynamic payoffs, both together) with serialization enabled.generate_playthrough.replay.open_spiel/python/games/__init__.py,CMakeLists.txt, andpyspiel_test.py's expected-games list; added a row togames.md.Also flagged and filed separately:
python_liars_poker'splayersparam didn't propagate tonum_players()— see #1572.