Skip to content

Consider removing rng_seeder user-interface from Model #5785

Closed
@ricardoV94

Description

@ricardoV94

Right now draw, prior and posterior_predictive can be seeded only from rng_seeder passed when creating a new pm.Model. This makes it convenient in that a user does not need to pass seeds to these functions individually to get seeded results, but it is less convenient in that there is an "invisible" dependency from the order and number of methods that are called.

In concrete, if you call pm.draw before pm.sample_prior_predictive you will get different results than if you do it in the opposite order.

import pymc as pm
with pm.Model(rng_seeder=3) as m:
    x = pm.Normal("x")
    print("draw: ", pm.draw(x))
    print("prior: ", pm.sample_prior_predictive(samples=1, return_inferencedata=False)["x"][0])
    
with pm.Model(rng_seeder=3) as m:
    x = pm.Normal("x")
    print("prior: ", pm.sample_prior_predictive(samples=1, return_inferencedata=False)["x"][0])
    print("draw: ", pm.draw(x))   
draw:  1.1557538103058285
prior:  -1.2931919932524942
prior:  1.1557538103058285
draw:  -1.2931919932524942

This happens because shared RandomState/Generator variables for the model RVs are only set once when they are created (based on the rng_seeder) and never re-seeded. There is currently no easy mechanism to re-seed all variables although it seems like there was one at some point during the V4 refactor, probably when we were using a RandomStream under the hood.

If there was one mechanism (which will probably be reintroduced when #5733 gets fixed), users could in theory call Model.seed(seed) between calls to control/reset the seeding. It would look something like this:

import pymc as pm
with pm.Model(rng_seeder=3) as m:
    x = pm.Normal("x")
    print("draw1: ", pm.draw(x))

    # Does not exist currently
    m.seed(3)
    print("prior1: ", pm.sample_prior_predictive(samples=1, return_inferencedata=False)["x"][0])
    
with pm.Model(rng_seeder=3) as m:
    x = pm.Normal("x")
    print("prior2: ", pm.sample_prior_predictive(samples=1, return_inferencedata=False)["x"][0])

    # Does not exist currently
    m.seed(3)
    print("draw2: ", pm.draw(x))    
draw:  1.1557538103058285
prior:  1.1557538103058285
prior:  1.1557538103058285
draw:  1.1557538103058285

But if users want to do this, it seems more naturally to allow them to pass a seed directly to these methods and then do the re-seeding internally for the user. It would look something like this:

import pymc as pm
# rng_seeder would be removed to avoid confusion over which "way" is best
with pm.Model() as m:
    x = pm.Normal("x")
    # Neither of these methods accepts seeds currently
    print("draw1: ", pm.draw(x, seed=3))
    print("prior1: ", pm.sample_prior_predictive(samples=1, seed=3, return_inferencedata=False)["x"][0])
    
with pm.Model() as m:
    x = pm.Normal("x")
    # Neither of these methods accepts seeds currently
    print("prior2: ", pm.sample_prior_predictive(samples=1, seed=3, return_inferencedata=False)["x"][0])
    print("draw2: ", pm.draw(x, seed=3))

With the same results as in the previous example.

Right now only sample accepts seeds explicitly, but if that's missing it defaults to rng_seeder. IMHO it is confusing to allow two ways to seed things, and this has emerged a couple of times in pymc-examples (cc @OriolAbril).

There is one tiny technical difference in that users can directly specify a seed per chain only via sample, but I think this is not enough reason to justify keeping two interfaces for that function. Also it's probably safer if users pass a single seed and we spawn multiple ones "smartly", to avoid the type of issues described in #5733


Creating this as an issue instead of discussion, because I think it should be addressed quickly to not create confusion as we release V4 / too much work re-running pymc-examples.

@pymc-devs/dev-core

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions