-
Notifications
You must be signed in to change notification settings - Fork 32
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
Why not always rebalance? #31
Comments
Great questions! I'm impressed, you've carefully studied the math and the various implementations—I'm very grateful for your time and attention!
I'll make this more clear but no, we rebalance because when alpha is much bigger or smaller than beta, we're more likely to encounter numerical precision issues in
You're absolutely right, there are a lot of benefits to alpha=beta—Robert Kern suggested this in #5 too. We could totally do this—find the
or equivalently
for positive real a, b, c, and x. As far as I know, there's no easy way to solve this, so we need a one-dimensional optimization like As a proxy to the cost (only a proxy since calculating the halflife from the true posterior directly is different than doing so of the posterior's Beta fit), I print out the output of
where the first number is the number of times in the test suite we encountered that number of iterations (this is the output of (Another concern, that is very minor because it has a straightforward workaround, is that you might not want to require all models to be balanced, perhaps there's some regime where you want to specifically prescribe the Beta prior? But this is readily addressed by using a dictionary for the model instead of a list of numbers, e.g., if the model was
I hadn't thought of this!, specifically, that if you didn't want to run
We could make
So I try to only use the cached betaln when calculating the non-time-dependent part of Sooo in summary. I'm looking at the table of function calls that the optimization needs to find the halflife via Again, many thanks for your thoughtful questions and attention to detail, this will make the project better! |
Well... I didn't study the math very carefully; statistics was never my strong suit. I get the gist of it, but the details are still quite hazy. I did study the Java implementation so that I could port it to Dart, as my app is done with Flutter. (I started out basing it on the Python version, but porting the methods from scipy turned out to be tricky.) I had overlooked the excellent discussion on #5, thanks for that link!
If you're going to make an API-breaking change anyway, I would strongly suggest not to move from a tuple to a dictionary, but to start using proper encapsulation instead. Many thanks for developing this algorithm and making it so accessible! I filed this particular issue because I was trying a simpler model: exponential decay where right answers multiply the halflife by a hardcoded factor >1, like 1.5, and wrong answers by a different factor <1, like 0.5. But I was unable to find a combination of factors that (subjectively) did a better job than Ebisu, so I decided that the bit of extra complexity is worth it even if we can't make Ebisu itself simpler 😄 |
I am doing more digging to answer the questions above but, also relevant to speeding up predictRecall is #4 which shows how to use Gautschi's inequality to bound |
The reason I hesitate to do this is because I wanted the Python implementation to be as transparent as possible to invite commentary and to make it easy to port to other potentially non-OOP languages. I recognize I muddied the waters there by adding the rebalancing step 😅. But definitely I will consider this input as I decide what to do on this! |
@ttencate if you're still interested in Ebisu and specifically the question of rebalancing, the findings in #18 (comment) have certainly given me a lot to think about! |
Thanks for kicking off this discussion! Pushed to PyPI 😄! |
Hi, it's me again :) If I understand correctly, the rebalancing step in
updateRecall
is only done if alpha and beta differ by more than a factor of 2 "to save a few computations". After rebalancing, alpha = beta (approximately).But why not simply rebalance at the end of every
updateRecall
? There are several advantages:t
is our best guess of the halflife,alpha
is the certainty that we are right.t
value to see how well they have been learned. This could be used e.g. to show a score or rating for each card.tback
andrebalance
arguments.a = b
inpredictRecall
, then the coefficientgamma(a + b) / (gamma(a) * gamma(b))
simplifies togamma(2a) / gamma(a)^2
, saving one evaluation of the gamma function. And saving time inpredictRecall
is far more important than inupdateRecall
, becauseupdateRecall
is only called once per quiz, whereaspredictRecall
is typically called once per quiz for each card. (This mostly applies to the Java implementation; the Python implementation callsbetaln
from scipy directly, so it can't do this optimization. And scipy'sbetaln
appears to be implemented in Fortran (!) so it's probably faster than trying to code around it.)rescaleHalflife
becomes so trivial that you might not even need to add it to the API.(And while we're on the topic of "saving computations": I'm concerned about the betaln cache (Python) and gammaln cache (Java) which are keyed on floating point numbers. Due to the non-discrete nature of all these variables, these caches are probably not very effective at saving time, and they can grow without bounds causing a memory leak. If you think this is a valid concern, I can open a separate issue.)
The text was updated successfully, but these errors were encountered: