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

Fix tanh component #1303

Merged
merged 4 commits into from
Dec 25, 2024
Merged

Fix tanh component #1303

merged 4 commits into from
Dec 25, 2024

Conversation

aseyboldt
Copy link
Contributor

@aseyboldt aseyboldt commented Dec 20, 2024

@github-actions github-actions bot added MMM bug Something isn't working good first issue Good for newcomers . Doesn't require extensive knowledge of the repo and package labels Dec 20, 2024
Copy link

codecov bot commented Dec 20, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 95.13%. Comparing base (3a98c8a) to head (9c27f8c).
Report is 1 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1303   +/-   ##
=======================================
  Coverage   95.13%   95.13%           
=======================================
  Files          44       44           
  Lines        4629     4629           
=======================================
  Hits         4404     4404           
  Misses        225      225           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Contributor

@wd60622 wd60622 left a comment

Choose a reason for hiding this comment

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

Can you remove beta default prior and beta from function signature

@wd60622 wd60622 merged commit b3f740d into pymc-labs:main Dec 25, 2024
19 of 20 checks passed
@ulfaslak
Copy link
Contributor

ulfaslak commented Jan 6, 2025

We should probably revert this, beta was a feature not a bug.

At glance I can see how it may appear we were multiplying twice, and the documentation didn't help explain why beta was there, but beta offered the crucial flexibility to parameterize in the following highly intuitive way (which IMO should be the default parameterization for this saturation/response function):

$$y(x) = \beta \cdot b \cdot \tanh{\left(x/b\right)}$$

This is a fantastic way to state the response* function because the coefficients have very straightforward interpretations:

  • $\beta$ is the ROAS at zero investment. The ROI of your first spent dollar. It is a number that most marketeers have a rough idea about, so it's easy to find good priors for it.
  • b is the theoretically maximum achievable actual impressions—if x is impressions—or spend that has an effect—if x is spend. While less tangible than beta, when you think about it some more it makes total sense. Regardless of how much you bombard your audience with ads, there are only so many eyeballs to reach. Or considering spend, there's only so much money that will have an effect (the rest is wasted). When understood, this is also a coefficient that seasoned marketeers will have good priors for.
  • $\beta \cdot b$ is the revenue at max spend/impressions. Super nice feature of this parameterization!

Before the PR got merged, you would achieve this parameterization by creating the saturation like:

my_response = TanhSaturation(
    priors={
        "beta": Prior(...),  # Business informed prior for ROAS at spend 0. Marketeers know this number
        "b": Prior(...),       # Business informed prior for maximum reachable eyeballs/effective dollars.
        "c": 1,
    }
)

I find this parameterization HIGHLY superior to the form which is now—after this PR got merged—the only supported option in pymc-marketing:

$$y(x) = b \cdot \tanh{\left(x/(b \cdot c)\right)}$$

b still has the same interpretation. But what does c mean? @ferrine defined it as the "Initial cost per user. Must be non-zero." 3 YEARS AGO. This seems to originate from the Hello Fresh project, and is IMO not the most generally useful way to look at response/saturation. For small $x$, $c ≈ 1/\beta$, but that quickly breaks.

Yeah, so I hope I'm making sense here. IMO this inadvertedly PR broke important functionality.

*: let's use the word response rather than saturation because that is in fact what this function is: it maps spend (or other similar driver unit) to a revenue (or other similar KPI) response.

@wd60622
Copy link
Contributor

wd60622 commented Jan 6, 2025

Shall we revert then? Since this PR, the user can pass priors to that are constants at initialization. Ie:

sat = TanhSaturation(priors={"c": 1})

Other priors stay defaults and we can have the general form with easy support for alternative

CC @aseyboldt @juanitorduz

@ulfaslak
Copy link
Contributor

ulfaslak commented Jan 6, 2025

Yeah either revert for immediate effect, or make a new PR for the issue I created #1348. IMO there are some consistency issues with the saturation components that we can solve quickly. With @wd60622 and @juanitorduz 's blessings I can get to it right away.

@wd60622
Copy link
Contributor

wd60622 commented Jan 6, 2025

Yeah either revert for immediate effect, or make a new PR for the issue I created #1348. IMO there are some consistency issues with the saturation components that we can solve quickly. With @wd60622 and @juanitorduz 's blessings I can get to it right away.

Can you create a separate issue to state the consistency issues.

Lets iterate through small PRs vs single large changes

@ulfaslak
Copy link
Contributor

ulfaslak commented Jan 6, 2025

OK I understand, you prefer to make one PR to revert this PR, and then another PR to fix the consistency issues. Even though the revert-PR could actually be skipped entirely as the consistency-PR would also revert this PR?

@wd60622
Copy link
Contributor

wd60622 commented Jan 6, 2025

You have to define consistency issue. That is my issue with this. Will it touch every saturation function making it have large effect?

Edit: read the other issue that was linked above. Lets continue there. I am overall for smaller PRs with less drastic changes

@ulfaslak
Copy link
Contributor

ulfaslak commented Jan 6, 2025

Sorry @wd60622 I don't follow you. You mean resolve #1348 first, or something else?

@wd60622
Copy link
Contributor

wd60622 commented Jan 7, 2025

Sorry @wd60622 I don't follow you. You mean resolve #1348 first, or something else?

Lets 1. Revert changes of single transformation then 2. Make change that applies to move the one transformation

@ulfaslak
Copy link
Contributor

ulfaslak commented Jan 7, 2025

OK roger.

@aseyboldt
Copy link
Contributor Author

aseyboldt commented Jan 7, 2025

Originally, there were three priors to parametrize something that has 2 degrees of freedom. That really was a bug, and should be fixed.

I guess we can keep three parameters, and make sure users specify exactly two of those, and set the third to None, that would be fine as well, and give more flexibility.

About your description ob b:

b is the theoretically maximum achievable actual impressions—if x is impressions—or spend that has an effect—if x is spend. While less tangible than beta, when you think about it some more it makes total sense. Regardless of how much you bombard your audience with ads, there are only so many eyeballs to reach. Or considering spend, there's only so much money that will have an effect (the rest is wasted). When understood, this is also a coefficient that seasoned marketeers will have good priors for.

That's not really true. By definition there is no maximum, as tanh is strictly monotone. But I think we can easily fix this: "b is the value for x where we reach tanh(1) = ~76% of the maximum response."

In the other parametrization, beta isn't just approximately 1/c, they are exactly the same thing, just the inverse: One is the initial (ie at zero) return on investment, the other the inverse return on investment (so the cost per user).

So the two parametrizations differ in two ways:

  • Do we want to specify return on investment, or cost per user?
  • Do we want to specify the spending where we reach 76% of the total achievable value or the total achievable value?

There would be a third option by the way: spending value where the return of investment drops to half of the initial value (or equivalently the cost per user doubles). I like that one because it also still works if the response curve doesn't converge.

I think it is perfectly fine if we support several options, but we should make sure that users only specify two of them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working good first issue Good for newcomers . Doesn't require extensive knowledge of the repo and package MMM
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Incorrect implementation of tanh saturation in TanhSaturation component
3 participants