Skip to content

Conversation

@brachbach
Copy link
Member

@brachbach brachbach commented Apr 16, 2020

These features are used in notebooks/community_distributions_v2.ipynb. You should probably start there to understand what's going on.

Main changes:

  • get the community prediction from Metaculus
  • graph the community prediction and our prediction
  • test this functionality
  • various small improvements to make it work, e.g. improve how we convert samples to a dataframe

semi-related changes:

  • change how log questions are graphed
  • fix some prediction submission stuff with the Metaculus API
  • fix sample normalization per correspondence with Metaculus

Unrelated changes:

  • add some development tips to the README (sorry, should really have been a separate PR!)

Some things are still a bit rough:

But I think it's probably better to go ahead and merge this and then fix those

(note that I've assigned the last 3 to myself to work on soon)

stuhlmueller and others added 27 commits April 3, 2020 21:20
…on for the log test question is about exactly 1 when it should be around 30k
…he proper domain, but seems weird that normalize's domain can't take things to the left of the question interval
…es much better at plotting the logs of the values directly, for whatever reason. So I should figure out how to do that instead.
samples = np.maximum(samples, epsilon)
samples = samples / self.question_range["min"]
return np.log(samples) / np.log(self.deriv_ratio)
def normalized_from_true_value(self, true_value) -> float:
Copy link
Member Author

Choose a reason for hiding this comment

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

Copy link

@pullrequest pullrequest bot left a comment

Choose a reason for hiding this comment

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

✅ A review job has been created and sent to the PullRequest network.


Check the status or cancel PullRequest code review here - or - cancel by adding [!pr] to the title of the pull request.

Copy link

@pullrequest pullrequest bot left a comment

Choose a reason for hiding this comment

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

1 Message
⚠️ Due to its size, this pull request will likely have a little longer turnaround time and will probably require multiple passes from our reviewers.

Copy link

@pullrequest pullrequest bot left a comment

Choose a reason for hiding this comment

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

I think the general architecture of things is looking pretty good. Although, there are some things, which could be cleaned up a little bit.

From a high-level, it may make sense to have more of the distribution calculations in torch to avoid converting back and forth as much, but that is manage-able.

Image of Gershon B Gershon B


Reviewed with ❤️ by PullRequest

sample_above_range = abs(np.random.logistic(
1, 0.02))
sample_in_range = ppl.sample(self.community_dist_in_range(
)) / float(len(self.prediction_histogram))
Copy link

Choose a reason for hiding this comment

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

Seems like there is a missing argument here. Either way, it would help to add indentation like you have for the other sample ranges.

also just to be sure, is self.prediction_histogram always non-empty? It may be worth to check the denominator and set a default value if it's 0.

Image of Brooks C Brooks C

Copy link

Choose a reason for hiding this comment

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

(note that one way to avoid discussion around formatting would be to use black for this project:

https://black.readthedocs.io/en/stable/ )

Image of Olivier LF Olivier LF

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks, I like the idea of switching to Black and created an issue for it: https://github.com/oughtinc/ergo/issues/80

Copy link
Member Author

Choose a reason for hiding this comment

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

no missing argument I don't think -- note that we're using a different function to grab a sample than for the other 2

yeah I think self.prediction_histogram is always non-empty -- would probably rather that we just get an error if it's not so that we know that something unexpected happened (rather than handling that case)


def sample_normalized_community(self):
sample_below_range = -1 * \
abs(np.random.logistic(0, 0.02))
Copy link

Choose a reason for hiding this comment

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

Also, I think it would be helpful to add a comment on why 0.02 is specified as the scale for maintainability purposes. especially if it's also used for sample_above_range

Image of Brooks C Brooks C

columns = ["id", "name", "title", "resolve_time"]
data = []
show_names = False
for question in questions:
Copy link

Choose a reason for hiding this comment

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

For more pythonic-refactor, you could try using the any() builtin function instead. It takes in an iterable and as long as one item in the iterable is True, it will return true. Also if the iterable is empty, it will return False.

It would simplify lines 105-109 to this:

show_names = any(q.name for q in questions)

Image of Brooks C Brooks C

Copy link
Member Author

Choose a reason for hiding this comment

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

lol yeah!

Copy link
Member Author

Choose a reason for hiding this comment

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

thanks for not just saying, "wtf is this, just use any!" :p

ergo/ppl.py Outdated
model = name_count(model)
samples: Dict[str, List[float]] = {}
samples: List[Dict[str, float]] = []
for i in tqdm.trange(num_samples):
Copy link

Choose a reason for hiding this comment

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

ISSUE: W0612 (Severity: Low)
Unused variable 'i'

Remediation:
We could probably just do for _ in tqdm.trange(num_samples): if we aren't using i.

🤖 powered by PullRequest Automation 👋 verified by Gershon B


return ax

def show_submission(self, samples, show_community=False):
Copy link

Choose a reason for hiding this comment

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

ISSUE: W0221 (Severity: Low)
Parameters differ from overridden 'show_submission' method

Remediation:
show_community should be in abstract class if used here.

🤖 powered by PullRequest Automation 👋 verified by Gershon B

Copy link
Member Author

Choose a reason for hiding this comment

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

yeah I left this as a loose end. See https://github.com/oughtinc/ergo/issues/64

# {'prediction': ['high minus low must be at least 0.01']}"
high = max(min(distribution.cdf(1), 0.99),
0.0099) if self.high_open else 1
low + 0.01) if self.high_open else 1
Copy link

Choose a reason for hiding this comment

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

I'd recommend creating a descriptive constant for 0.01 instead of hard-coding it.

Image of Gershon B Gershon B


def sample_normalized_community(self):
sample_below_range = -1 * \
abs(np.random.logistic(0, 0.02))
Copy link

Choose a reason for hiding this comment

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

It may also be good have a constant for 0.02.

Image of Gershon B Gershon B

def denormalize_samples(self, samples):
raise NotImplementedError("This should be implemented by a subclass")

@functools.lru_cache(None)
Copy link

Choose a reason for hiding this comment

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

I'd caution against using functools.lru_cache for class methods[1]. Is prediction_histogram only set once? Would @functools.cached_property be a better choice?

[1] https://stackoverflow.com/questions/33672412/python-functools-lru-cache-with-class-methods-release-object

Image of Gershon B Gershon B

Copy link

Choose a reason for hiding this comment

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

(note that functools.cached_property requires python 3.8)

Image of Olivier LF Olivier LF

Copy link
Member Author

Choose a reason for hiding this comment

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

The problem is that the cache will hold onto the class instance in memory?

I don't see that causing performance problems soon, so I'm probably inclined to just leave it in


self.show_prediction(submission, samples)

def show_community_prediction(self, only_show_this=True):
Copy link

Choose a reason for hiding this comment

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

ISSUE: W0221 (Severity: Low)
Parameters differ from overridden 'show_community_prediction' method

Remediation:
only_show_this should probably in the base class if we are going to use it here.

🤖 powered by PullRequest Automation 👋 verified by Gershon B

Copy link
Member Author

Choose a reason for hiding this comment

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

yeah I left this as a loose end. See #64

@functools.lru_cache(None)
def community_dist_in_range(self):
# distribution on integers referencing 0...(len(self.prediction_histogram)-1)
df = pd.DataFrame(self.prediction_histogram, columns=["x", "y1", "y2"])
Copy link

Choose a reason for hiding this comment

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

It seems like we don't need to create a DataFrame if we are just going to create a torch Tensor. Perhaps,

y2  = [p[2] for p in self.prediction_histogram]
return dist.Categorical(probs=torch.Tensor(y2))

or

t = torch.Tensor(self.prediction_histogram)
return dist.Categorical(probs=t[:,2])

would work.

Image of Gershon B Gershon B

Copy link

@pullrequest pullrequest bot left a comment

Choose a reason for hiding this comment

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

This looks neat! Added some style comments and perhaps the graphs in the notebook can be made easier to read at a glance with more human readable units for number of occurrences on the x-axis

Image of Olivier LF Olivier LF


Reviewed with ❤️ by PullRequest


@functools.lru_cache(None)
def community_dist_in_range(self):
# distribution on integers referencing 0...(len(self.prediction_histogram)-1)
Copy link

Choose a reason for hiding this comment

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

Converting this to a docstring will allow automated doc generation frameworks or code introspection to more easily extract this information

"""Return the distribution on integers referencing 0...(len(self.prediction_histogram)-1)"""

Image of Olivier LF Olivier LF

import ergo.logistic as logistic
import ergo.ppl as ppl

from typing import Optional, List, Any, Dict, Tuple
Copy link

Choose a reason for hiding this comment

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

ISSUE: W0611 (Severity: Low)
Unused Tuple imported from typing

Remediation:
Nitpick: similarly could also consider removing the unused Tuple

🤖 powered by PullRequest Automation 👋 verified by Olivier LF

import torch
import requests
import pendulum
import scipy
Copy link

Choose a reason for hiding this comment

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

ISSUE: W0611 (Severity: Low)
Unused import scipy

Remediation:
Won't make much of a difference but could perhaps slightly speed things up by removing the unused import.

🤖 powered by PullRequest Automation 👋 verified by Olivier LF

"\n",
"print([log_tick for log_tick in pyplot.xticks()])\n",
"\n",
"pyplot.xticks(pyplot.xticks()[0], [f\"{math.exp(log_tick):.1e}\" for log_tick in pyplot.xticks()[0]], rotation=\"vertical\")\n",
Copy link

Choose a reason for hiding this comment

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

Perhaps a LogFormatter could make the x ticks more legible here?

https://stackoverflow.com/a/43926354/163677

Image of Olivier LF Olivier LF

latest_prediction = self.my_predictions["predictions"][-1]["d"]
return self.get_submission_from_json(latest_prediction)

def show_community_prediction(self):
Copy link

Choose a reason for hiding this comment

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

Adding a docstring explaining what subclasses will have to implement, and what behavior should be expected will be rather useful to ease extensibility by users less familiar with the codebase.

Image of Olivier LF Olivier LF

Copy link
Member Author

Choose a reason for hiding this comment

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

I moved all of the stubs to the bottoms of their respective classes

I suspect that doing more than that will be more trouble than it's worth

return dist.Categorical(probs=torch.Tensor(df["y2"]))

def sample_normalized_community(self):
sample_below_range = -1 * \
Copy link

Choose a reason for hiding this comment

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

sample_below_range = -abs(np.random.logistic(0, 0.02))

might be clearer? It seems to work as expected:

>>> -abs(-4)
-4

Image of Olivier LF Olivier LF

@stuhlmueller stuhlmueller changed the title Community prediction Retrieve, plot, and sample from Metaculus community prediction distributions Apr 16, 2020
@brachbach
Copy link
Member Author

@stuhlmueller -- I made the vast majority of the changes requested by PullRequest, and commented on most others about why I didn't make the change.

OK to merge this once CI passes?

@codecov
Copy link

codecov bot commented Apr 17, 2020

Codecov Report

Merging #61 into master will not change coverage.
The diff coverage is n/a.

Impacted file tree graph

@@           Coverage Diff           @@
##           master      #61   +/-   ##
=======================================
  Coverage   76.08%   76.08%           
=======================================
  Files           7        7           
  Lines         623      623           
=======================================
  Hits          474      474           
  Misses        149      149           

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 6aedad9...6aedad9. Read the comment docs.

@stuhlmueller stuhlmueller merged commit 4156448 into master Apr 17, 2020
@brachbach brachbach deleted the community-prediction branch April 28, 2020 03:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants