open_spiel
open_spiel copied to clipboard
Bug in pickle serialization for Universal poker
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)
Hmm, we don't normally serialize the solver -- does the above work for a different game, e.g. leduc_poker?
Can you try serializing the game or the state separately from the solver.. does that work?
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.
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...
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).
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
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.