open_spiel icon indicating copy to clipboard operation
open_spiel copied to clipboard

Bug in pickle serialization for Universal poker

Open Henry-E opened this issue 3 years ago • 7 comments

printing to: cfrplus_solver_19.32756_exploitability.pickle
Loading the model...
OpenSpiel exception: Unknown parameter 'gamedef=GAMEDEFlimitnumPlayers = 2numRounds = 3blind = 5 10raiseSize = 10 10 20firstPlayer = 1maxRaises = 2 2 3numSuits = 4numRanks = 5numHoleCards = 1numBoardCards '. Av
ailable parameters are: betting, bettingAbstraction, blind, boardCards, firstPlayer, gamedef, handReaches, maxRaises, numBoardCards, numHoleCards, numPlayers, numRanks, numRounds, numSuits, potSize, raiseSize,
stack

Saving a pickle file of a universal poker solver and then loading it gives an error. I've played around with removing or editing some of the parameters here from the string used to load the game (e.g. removing GAMEDEF and limit, adding a space before GAMEDEF) but it still gives an exception.

a hopefully reproducible example

"""Example use of the CFR algorithm on Kuhn Poker."""

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import pickle
from absl import app
from absl import flags

import pyspiel

FLAGS = flags.FLAGS

CUSTOM_HOLDEM = """\
GAMEDEF
limit
numPlayers = 2
numRounds = 1
blind = 5 10
raiseSize = 10 10 20
firstPlayer = 1
maxRaises = 2 2 3
numSuits = 4
numRanks = 5
numHoleCards = 1
numBoardCards = 0 2 1
END GAMEDEF
"""

flags.DEFINE_enum("solver", "cfrplus", ["cfr", "cfrplus", "cfrbr"],
                  "CFR solver")
flags.DEFINE_integer("iterations", 2000, "Number of iterations")
flags.DEFINE_integer("print_freq", 100, "Print frequency of exploitability")
flags.DEFINE_string("game", "universal_poker", "Name of the game")


def main(_):
    game = pyspiel.load_game(
        FLAGS.game,
        {"gamedef": CUSTOM_HOLDEM},
    )

    if FLAGS.solver == "cfr":
        solver = pyspiel.CFRSolver(game)
    elif FLAGS.solver == "cfrplus":
        solver = pyspiel.CFRPlusSolver(game)
    elif FLAGS.solver == "cfrbr":
        solver = pyspiel.CFRBRSolver(game)

    solver.evaluate_and_update_policy()

    final_exploitability = 0.333

    print("Persisting the model...")
    print("printing to: {}_solver_{:.5f}_exploitability.pickle".format(
        FLAGS.solver, final_exploitability))
    with open(
            "{}_solver_{:.5f}_exploitability.pickle".format(
                FLAGS.solver, final_exploitability), "wb") as file:
        pickle.dump(solver, file, pickle.HIGHEST_PROTOCOL)

    print("Loading the model...")
    with open(
            "{}_solver_{:.5f}_exploitability.pickle".format(
                FLAGS.solver, final_exploitability), "rb") as file:
        loaded_solver = pickle.load(file)


if __name__ == "__main__":
    app.run(main)

Henry-E avatar Jan 28 '22 14:01 Henry-E

Hmm, we don't normally serialize the solver -- does the above work for a different game, e.g. leduc_poker?

lanctot avatar Jan 28 '22 17:01 lanctot

Can you try serializing the game or the state separately from the solver.. does that work?

lanctot avatar Jan 28 '22 17:01 lanctot

I have a suspicion. The serialization requires reconstructing the state via the game string, and you're loading it by specifying a string that contains newlines, which works fine based on how you're loading it, but then fails the default serialization.

Would it be possible to instead load it via the OpenSpiel parameters (rather than ACPC gamedef), as is done in this file: https://github.com/deepmind/open_spiel/blob/master/open_spiel/canonical_game_strings.cc ? If so, then I think the default serialization for universal_poker will work out of the box.

lanctot avatar Jan 28 '22 17:01 lanctot

I think this might be easily fixed by using a custom serializer for universal poker, which I believe has been on the TODO list for quite some time. I can walk you through how do to that if you like (would make a nice contribution!). All it would need to do is check of the gamedef string is non-empty, if so load it one way, if not use the default serialization. It would be nice to finally fix it :) I believe we've known about it for some time and just forgot...

lanctot avatar Jan 28 '22 17:01 lanctot

I took a quick look. This is non-trivial to fix properly because it's currently impossible to have custom game serializers/deserializers (having custom state serializers / deserializers is easy). We should change things to make this possible and then fix this, so please leave this issue open as a reminder, but I'm not sure when we'll get to it.

So currently the only way is to use the OpenSpiel parameters instead of the ACPC game def (as the canonical game strings do).

lanctot avatar Jan 28 '22 18:01 lanctot

Ah ok, this is just from one of the example python scripts.

So currently the only way is to use the OpenSpiel parameters instead of the ACPC game def (as the canonical game strings do). Great! I didn't realise the distinction previously but doing it that way works. The solver can be pickled and loaded without error.

    game = pyspiel.load_game(
        "universal_poker(betting=limit,numPlayers=2,numRounds=3,blind=10 5,raiseSize=10 10 20,maxRaises=2 2 3,numSuits=4,numRanks=5,numHoleCards=1,numBoardCards=0 2 1)"
    )

This solves my issue but I will leave the issue open if that's desired

Henry-E avatar Jan 28 '22 19:01 Henry-E

Great! Happy that the workaround worked

Yeah it is due to a subtlety about how things get serialized. We should fix it, and we will, so please leave it open as a reminder.

lanctot avatar Jan 28 '22 21:01 lanctot