Skip to content

Commit

Permalink
Noisy (soft-binary) quizzes, always rebalance, rescale halflife: close
Browse files Browse the repository at this point in the history
…#23 and close #31 (#46)

* Fix Latex typo

* old and new implementations of fuzzy update both agree, amazingly

* documented derivation of fuzzy soft-binary quiz

* doc and py for rescale math

* fuzzy/binary auto-rebalances

* minor speedup

* explicitly ask for rebalance

* binomial rebalances too; use Newton: minimize wasn't working for fail case???

* switch back to minimize_scalar; test rescale; all old tests passing

* switch to root_scalar like modelToPercentileDecay

* two-sided expander that Robert wrote

* simplify modelToPercentileDecay

* doc

* updateRecall uses Single (Fuzzy)

* hide

* add fuzzy test

* docstring

* test updateRecall to know how to tback

* add monte carlo fuzzy tests

* reword a lot of derivations

* explicitly denote which rv P refers to

* latex error

* finished math i think

* Code in Py and in README, ready to export

* yapf moves on

* remove stray

* run final yapf on exported files

* code tangled

* doc woven

* update notebook

* changelog 2.1.0

* version bump: 2.1.0

* ' to ’, butterick would be proud

* pydoc-markdown marches on...

* save fuzzy test outputs to test.json
  • Loading branch information
fasiha authored Apr 16, 2021
1 parent 3655784 commit df1a526
Show file tree
Hide file tree
Showing 13 changed files with 4,977 additions and 1,059 deletions.
2 changes: 1 addition & 1 deletion .style.yapf
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[style]
based_on_style = chromium
based_on_style = yapf
COLUMN_LIMIT = 100
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Changes to Ebisu

## 2.1.0: soft-binary quizzes and halflife rescaling

`updateRecall` can now take *floating point* quizzes between 0 and 1 (inclusive) as `successes`, to handle the case when your quiz isn’t quite correct or wasn’t fully incorrect. Varying this number between 0 and 1 will smoothly vary the halflife of the updated model. Under the hood, there's a noisy-Bernoulli statistical model.

A new function has been added to the API, `rescaleHalflife`, for those cases when the halflife of a flashcard is just wrong and you need to multiply it by ten (so you see it less often) or divide it by two (so you see it *more* often).

A behavioral change: `updateRecall` will by default rebalance models, so that updated models will have `α=β` (to within machine precision) and `t` will be the halflife. This does have a performance impact, so I may have to flip the default to *not* always rebalance in a future release if this turns out to be problematic.

Closes long-standing issues [#23](https://github.com/fasiha/ebisu/issues/23) and [#31](https://github.com/fasiha/ebisu/issues/31)—thank you to all participants who weighed in, offered advice, and waited patiently.

## 2.0.0: Bernoulli to binomial quizzes
The API for `updateRecall` has changed because `boolean` results don't make sense for quiz apps that have a sense of "review sessions" in which the same flashcard can be reviewed more than one time, e.g., if a review session consists of conjugating the same verb twice. Therefore, `updateRecall` accepts two integers:
- `successes`, the number of times the user correctly produced the memory encoded in this flashcard, out of
Expand Down
54 changes: 45 additions & 9 deletions EbisuHowto.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@
"name": "stdout",
"output_type": "stream",
"text": [
"New model for fact #1: (5.104166666666623, 3.999999999999968, 24.0)\n"
"New model for fact #1: (4.040794974809565, 4.040794974809568, 29.18373827290736)\n"
]
}
],
Expand Down Expand Up @@ -190,7 +190,7 @@
"name": "stdout",
"output_type": "stream",
"text": [
"New model for fact #2: (3.897259022777383, 5.0345121960734005, 24.0)\n"
"New model for fact #2: (4.958645489170429, 4.9586454891705465, 19.76772867641237)\n"
]
}
],
Expand All @@ -213,7 +213,7 @@
"source": [
"The new parameters for this fact differ from the previous one because (1) the student failed this quiz while she passed the other, (2) different amounts of time had elapsed since the respective facts were last seen.\n",
"\n",
"Ebisu provides a method to convert parameters to “expected half-life”. It is *not* an essential feature of the API and displaying it to the student will likely only distract them, but it is available:"
"Ebisu provides a method to convert parameters to “expected half-life”. It is *not* an essential feature of the API but can be useful:"
]
},
{
Expand Down Expand Up @@ -242,10 +242,46 @@
"source": [
"Note how the half-life (the time between quizzes for expected recall probability to drop to 50%) for the first question increased from 24 to 29 hours after the student got it right, while it decreased to 20 hours for the second when she got it wrong. Ebisu has incorporated the fact that the second fact had been learned not that long ago and should have been strong, and uses the surprising quiz result to strongly adjust its belief about its recall probability.\n",
"\n",
"---\n",
"\n",
"Suppose the user is tired of reviewing the first fact so often because it’s something they know very well. You could allow the user to delete this flashcard, add it again with a longer initial halflife. But Ebisu gives you a function that will explicitly rescale the halflife of the card as is: `ebisu.rescaleHalflife`, which takes a positive number to act as the halflife scale. In this case, the new halflife is *two* times the old halflife."
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Fact #1 has half-life of ≈58.4 hours\n",
"Fact #2 has half-life of ≈19.8 hours\n"
]
}
],
"source": [
"database[0]['model'] = ebisu.rescaleHalflife(database[0]['model'], 2.0)\n",
"\n",
"for row in database:\n",
" meanHalflife = ebisu.modelToPercentileDecay(row['model'])\n",
" print(\"Fact #{} has half-life of ≈{:0.1f} hours\".format(row['factID'], meanHalflife))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"If the user was worried that this flashcard was shown too *infrequently*, and wanted to see it three times as often, you might pass in `1/3` as the second argument.\n",
"\n",
"This short notebook shows the major functions in the Ebisu API:\n",
"- `ebisu.predictRecall` to find out the expected recall probability for a fact right now, and\n",
"- `ebisu.updateRecall` to update those expectations when a new quiz result is available.\n",
"- `ebisu.modelToPercentileDecay` to find the time when the recall probability reaches a certain value."
"- `ebisu.modelToPercentileDecay` to find the time when the recall probability reaches a certain value.\n",
"- `ebisu.rescaleHalflife` to adjust the halflife up and down without a quiz.\n",
"\n",
"For more advanced functionality, including non-binary fuzzy quizzes, do consult the [Ebisu](https://fasiha.github.io/ebisu/) website, which links to the API’s docstrings and explains how this all works in greater detail."
]
},
{
Expand All @@ -260,23 +296,23 @@
},
{
"cell_type": "code",
"execution_count": 8,
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"6.82 µs ± 84.5 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)\n",
"4.91 µs ± 235 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)\n"
"6.17 µs ± 430 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)\n",
"4.45 µs ± 110 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)\n"
]
}
],
"source": [
"# As above: a bit slow to get exact probabilities\n",
"%timeit ebisu.predictRecall(database[0]['model'], 100., exact=True)\n",
"\n",
"# A bit faster alternative: get log-probabilities (this is the defa)\n",
"# A bit faster alternative: get log-probabilities (this is the defalt)\n",
"%timeit ebisu.predictRecall(database[0]['model'], 100., exact=False)"
]
}
Expand All @@ -297,7 +333,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.1"
"version": "3.9.0"
}
},
"nbformat": 4,
Expand Down
Loading

0 comments on commit df1a526

Please sign in to comment.