-
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
Look at accuracy of Beta-posterior when rebalancing? #18
Comments
Here's a script that addresses this question, which is definitely worth considering. The script isn't documented and takes a couple of minutes to run but hopefully it will do since my goal is to explain the findings briefly— In Ebisu, we have the exact analytical posterior probability distribution on recall at any time in the future. That is, for a model We eventually want to collapse this complicated distribution to a Beta random variable for storage—that Beta is obviously an approximation to the true distribution. Right now we are somewhat cavalier about choosing what time to But what The Kullback-Leibler (KL) metric measures how "bad" an approximation some probability distribution is from another "true" distribution. Its minimum is zero for the same distribution. The script below implements the analytical exact posterior for any Then, for model Above x axis: Observations: for all four of these, there definitely is ONE So then, for this model
Iiiiiiinteresting? For successful quizzes, the best But for failed quizzes, what's this? The best Above For all three sub-plots, the x axis is So the relationship between best A lot of food for thought. from ebisu import modelToPercentileDecay, updateRecall
import numpy as np
import scipy.integrate as integ
from scipy.stats import beta
from scipy.special import beta as betafn
from scipy.optimize import minimize_scalar
import pylab as plt
plt.ion()
def makePost(model, tnow, tback, c, d):
alpha, beta, t = model
dt = tnow / t
et = tback / tnow
B = betafn
den = c * dt * et * B(alpha + dt, beta) + d * dt * et * B(alpha, beta)
def post(p):
first = (1 - p**(1 / (dt * et)))**(beta - 1)
secondExp = (alpha + dt) / (dt * et) - 1
second = c * p**secondExp
thirdExp = alpha / (dt * et) - 1
third = d * p**thirdExp
num = first * (second + third)
return num / den
return post
def validatePost(x=True):
pre = (3., 4., 10.)
c, d = (1, 0) if x else (-1, 1)
tnow = 5
tback = 8
post = makePost(pre, tnow, tback, c, d)
print('should be 1', integ.quad(post, 0, 1))
def moments(maxN):
alpha, beta, t = pre
dt = tnow / t
et = tback / tnow
nums = [
c * betafn(alpha + dt + N * dt * et, beta) + d * betafn(alpha + N * dt * et, beta)
for N in range(1, 1 + maxN)
]
den = c * betafn(alpha + dt, beta) + d * betafn(alpha, beta)
return [num / den for num in nums]
moms = moments(5)
qmoms = [integ.quad(lambda p: p**n * post(p), 0, 1) for n in range(1, 1 + len(moms))]
print(list(zip(moms, qmoms)))
validatePost(True)
validatePost(False)
def kl(tback, pre, x, tnow):
c, d = (1, 0) if x else (-1, 1)
post = makePost(pre, tnow, tback, c, d)
updated = updateRecall(pre, 1 if x else 0, 1, tnow, tback=tback, rebalance=False)
approx = lambda p: beta.pdf(p, updated[0], updated[1])
divergence = integ.quad(lambda p: post(p) * np.log(post(p) / approx(p)), 0, 1, limit=500)
return divergence
def kltest(x=True, tnow=5.0):
pre = (3., 4., 10.)
tbacks = np.logspace(np.log10(3), np.log10(50))
kls, errs = np.vectorize(lambda foo: kl(foo, pre, x, tnow))(tbacks)
return tbacks, kls, errs, x, tnow
for x in [True, False]:
for tnow in [5., 30.]:
tbacks, kls, errs, x, tnow = kltest(x, tnow)
plt.figure()
plt.loglog(tbacks, kls, '.-')
plt.title(f"{x}, tnow={tnow:0.2f}")
pre = (3., 4., 10.)
print('pre', modelToPercentileDecay((3., 4., 10.)))
for x in [True, False]:
for tnow in [1., 5., 15., 30.]:
updated = updateRecall(pre, 1 if x else 0, 1, tnow)
minimized = minimize_scalar(
lambda tback: kl(tback, pre, x, tnow)[0], bounds=[3., 50.], method='bounded')
print(
dict(
x=x,
tnow=tnow,
hl=modelToPercentileDecay(updated),
tbackMin=minimized.x,
klmin=minimized.fun))
tnows = np.linspace(0.1, 100, 50)
bestTbacks = np.vectorize(lambda tnow: minimize_scalar(
lambda tback: kl(tback, pre, False, tnow)[0], bounds=[1., 60.], method='bounded').x)(
tnows)
quads = [kl(tback, pre, False, tnow) for (tnow, tback) in zip(tnows, bestTbacks)]
fig, axs = plt.subplots(3)
axs[0].plot(tnows, bestTbacks, '.-')
axs[0].set_ylabel('best tback')
axs[1].semilogy(tnows, np.array([min for min, err in quads]))
axs[1].set_ylabel('min KL')
axs[2].semilogy(tnows, np.array([err for min, err in quads]))
axs[2].set_ylabel('error')
axs[2].set_xlabel('tnow')
fig.set_tight_layout(True)
[a.grid() for a in axs] |
So the work above led to a nice analytical result showing the exact posterior for a series of True/False quizzes: https://fasiha.github.io/ebisu/#appendix-exact-ebisu-posteriors For now, with 2.1.0, I've chosen to rebalance to the new halflife despite the findings above (and despite the computational burden of rebalancing). The difference in KL divergence between the “best” (Also, I'm not sure how long Ebisu will continue to use this Beta-on-recall model given my efforts in creating a new model per #43.) |
Closed this because Ebisu v3 is moving away from Beta priors on recall to Gamma priors on half-life. |
With version 1.0, we rebalance the model to roughly near the half-life so
a
andb
aren't too different.But are there some
t'
s that yield Beta distributions that are more faithful to the GB1 posterior than others?When we
updateRecall
, ought we spend a bit more time finding thet'
whose final-Beta's higher moments are the least different from the GB1 posterior's higher moments?If so, that'll also impact #17.
The text was updated successfully, but these errors were encountered: