Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow do interventions to reference intervened variable #219

Merged
merged 1 commit into from
Aug 17, 2023

Conversation

ricardoV94
Copy link
Member

This is now possible:

import pymc as pm
from pymc_experimental.model_transform.conditioning import do

with pm.Model() as m:
    x = pm.Normal("x")

new_m = do(m, {x: x + 100})
assert pm.draw(new_m["do_x"] > 50)

It was already fine to do this kind of replacement with Deterministics (or other RVs) as is the following:

with pm.Model() as m:
    x = pm.Normal("x")
    do_x = pm.Deterministic("det", x)

new_m = do(m, {det: x + 100})
assert pm.draw(new_m["det"] > 50)
with pm.Model() as m:
    x = pm.Normal("x")
    y = pm.Normal("y", x)

new_m = do(m, {y: x + 100})
assert pm.draw(new_m["y"] > 50)

@ricardoV94 ricardoV94 added the enhancements New feature or request label Jul 25, 2023
Copy link

@juanitorduz juanitorduz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not know the complete details of the do-operator implementation but his change makes sense by reading the code and test :)

@ricardoV94
Copy link
Member Author

ricardoV94 commented Aug 2, 2023

Thanks for reviewing @juanitorduz

@twiecki wanna give a thumbs up/down?

twiecki
twiecki previously approved these changes Aug 2, 2023
@twiecki
Copy link
Member

twiecki commented Aug 2, 2023

Big 👍

lucianopaz
lucianopaz previously approved these changes Aug 2, 2023
Copy link
Contributor

@lucianopaz lucianopaz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. Were you getting duplicate names errors before? I wouldn't have expected that though. Did the error come at the point where the new model would be recompiled from the FunctionGraph?

@ricardoV94
Copy link
Member Author

ricardoV94 commented Aug 3, 2023

Looks good to me. Were you getting duplicate names errors before? I wouldn't have expected that though. Did the error come at the point where the new model would be recompiled from the FunctionGraph?

The error arises when we convert fgraph to a Model and try to register multiple variables (such as an RV and a Deterministic) with the same name. This happens here because we borrow the name of the variable being intervened for the deterministic that represents the intervention.

Does that make sense?

@ricardoV94 ricardoV94 dismissed stale reviews from lucianopaz and twiecki via 8c32d44 August 3, 2023 10:46
@ricardoV94 ricardoV94 force-pushed the allow_do_self_reference branch from 9a8aa7b to 8c32d44 Compare August 3, 2023 10:46
Copy link
Contributor

@lucianopaz lucianopaz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@ricardoV94 ricardoV94 force-pushed the allow_do_self_reference branch from 8c32d44 to 14d4f2b Compare August 3, 2023 16:04
Copy link

@drbenvincent drbenvincent left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a check of what new_m = do(m, {x: x + 100}) does. This might help inform extra info for the docstring for do potentially.

My intuition of what this would do, under the principle of least surprise, would be:

  • Replace the RV x with a new RV but which has the +100 modifier.
  • Cut the incoming edges to x because that's a core part of what do does.

Any particular ideas about possible use-cases?

Is this just constrained to some kind of deterministic modification? What if someone comes along and does new_m = do(m, {x: x + z}) where z is another RV. If that is possible/permissible, then might be worth adding a test.

@ricardoV94
Copy link
Member Author

ricardoV94 commented Aug 6, 2023

Replace the RV x with a new RV but which has the +100 modifier. * Cut the incoming edges to x because that's a core part of what do does.

No that's not what happens. It keeps x and adds a deterministic x + 100 downstream. Every variable that's depended on x now depends on this deterministic. Paths to x are preserved (there's no other way, what would you use as inputs for x otherwise?).

The PyMC do allows random interventions not just constant ones.

I don't know about use cases but seems like a more powerful mechanism.

@ricardoV94
Copy link
Member Author

ricardoV94 commented Aug 6, 2023

Is this just constrained to some kind of deterministic modification? What if someone comes along and does new_m = do(m, {x: x + z}) where z is another RV. If that is possible/permissible, then might be worth adding a test.

That's possible, and under the hood is exactly the same as the test that was introduced. There are already tests for the do that reference other RVs: https://github.com/pymc-devs/pymc-experimental/blob/14d4f2bca8a838be3efa0176f1b0385d4c7e27f3/pymc_experimental/tests/model_transform/test_conditioning.py

What's new in this PR is not that the interventions can contain random variables, but that they can reference the original variable that is being intervened.

@drbenvincent
Copy link

Replace the RV x with a new RV but which has the +100 modifier. * Cut the incoming edges to x because that's a core part of what do does.

No that's not what happens. It keeps x and adds a deterministic x + 100 downstream. Every variable that's depended on x now depends on this deterministic. Paths to x are preserved (there's no other way, what would you use as inputs for x otherwise?).

The PyMC do allows random interventions not just constant ones.

So I think this might throw people, at least initially. The do-operator (from Pearl) is pretty well-defined, and it seems that this goes beyond that to do more generic graph surgery. I don't think I have a strong philosophical objection, but cutting incoming edges to the target node is a pretty major component of the do operator. So I guess we either need to make sure the docstrings are really clear about that, or have a different operator name for this particular manipulation of the graph.

@drbenvincent
Copy link

What's new in this PR is not that the interventions can contain random variables, but that they can reference the original variable that is being intervened

Same point as above. The PyMC do operator is going beyond Pearl's do operator concept. Either that can be seen as more powerful, or it could be seen as confusing.

@ricardoV94
Copy link
Member Author

We can call the current one "replace" and the "do" would call replace but only allow constants/shared variables?

CC @lucianopaz

@drbenvincent
Copy link

Like dispatching? That would still involve the user calling do and not deleting incoming edges?

How about the user calling this new function when they want to do that. And if they try to achieve it with do they get an informative error message to use the new function instead?

Could be worth jumping on a quick call to talk this through perhaps?

@ricardoV94
Copy link
Member Author

ricardoV94 commented Aug 10, 2023

Like dispatching? That would still involve the user calling do and not deleting incoming edges?

How about the user calling this new function when they want to do that. And if they try to achieve it with do they get an informative error message to use the new function instead?

I mean we rename the current function do -> replace.
We implement a new do function that calls the more flexible replace under the hood, but restricts the type of replacements to be constants or other expressions that don't depend on other random variables (when we fail we can mention the option of using replace directly as you suggested).

Either way I would do that in a separate PR from this one. WDYT?

@ricardoV94
Copy link
Member Author

ricardoV94 commented Aug 10, 2023

I just did a random quick search for "Pearl stochastic do interventions" and this showed up first: https://ojs.aaai.org/index.php/AAAI/article/view/6567 (pdf can be found on Google scholar)

I don't know anything about the subject but if this sort of stuff is interesting/valid then the current "supercharged" do would be apt.

I imagine stuff like do({y: pm.math.switch(x > 0, 0, y)}) could be interesting (i.e., intervention is conditional on data/posterior values of another variable). Maybe one group gets treatment and the other does not based on some observed/inferred criteria.

@drbenvincent
Copy link

That paper is pretty neat. Don't have the luxury to go through it in detail, but I guess it satisfies me that it's kosher to have a single do-operator which implements different kinds of interventions. The general concept of a stochastic do-operator isn't 100% novel to me, but I did perceive it as a categorically different thing. I'm more happy now that it's not.

So I'm happy to withdraw any objection of this happening under the do-operator.

If you wanted to have different functions that are called behind the scenes (perhaps that improves testability?) then feel free. But from an API/user perspective I'm good with this. We obviously just need to clearly document with examples in pymc-examples for example ;)

Screenshot 2023-08-15 at 16 32 47

@drbenvincent
Copy link

Though they do talk about $\sigma$ calculus as different to do calculus. But they don't propose a $\sigma$ operator.

@ricardoV94
Copy link
Member Author

Thanks @drbenvincent. I am curious if we can write-up a compelling example that showcases these forms of interventions.

@ricardoV94 ricardoV94 force-pushed the allow_do_self_reference branch from 14d4f2b to 98e13c9 Compare August 16, 2023 07:45
@ricardoV94 ricardoV94 merged commit 15c88e8 into pymc-devs:main Aug 17, 2023
@ricardoV94 ricardoV94 deleted the allow_do_self_reference branch September 21, 2023 08:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancements New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants