A Better Spaced Repetition Learning Algorithm: SM2+

Filed under Uncategorized

By far the most popular algorithm for Spaced Repetition is SM2.  Sites like Anki, Duolingo, Memrise, and Wanikani all chose SM2 over later SM-iterations (eg. SM15) because it is extremely simple, yet effective.  Despite that, it still has a number of glaring issues.  In this article I explain those issues, and provide a simple way to resolve them.

Original SM2 Algorithm

The algorithm determines which items the user must review every day.  To do that, it attaches the following data to every review-item:

  • easiness:float – A number  1.3 representing how easy the item is, with 1.3 being the hardest.  Defaults to 2.5
  • consecutiveCorrectAnswers:int – How many times in a row the user has correctly answered this item
  • nextDueDate:datetime – The next time this item needs to be reviewed

When the user wants to review, every item past its nextDueDate is queued.  After the user reviews an item, they (or the software) give themselves a performanceRating on a scale from 0-5 (0=worst, 5=best).  Set a cutoff for an answer being “correct” (defaults to 3). Then make the following changes to that item:

easiness += -0.8 + 0.28*performanceRating + 0.02*performanceRating^2

consecutiveCorrectAnswers = \begin{cases} consecutiveCorrectAnswers+1, \text{   if correct} \\ 0, \text{   if incorrect} \end{cases}

nextDueDate = \begin{cases} \text{now + $(6*easiness^{consecutiveCorrectAnswers-1})$ days}, \text{   if correct} \\ \text{now + 1 day}, \text{   if incorrect} \end{cases}

Problems with SM2

SM2 has a number of issues that limit its usefulness/effectiveness:

Problem: Non-generic variable ranges

The variable ranges are very specific to the original software, Supermemo.  easiness is a number  1.3, while performanceRating is an integer from [0,5]

Solution

Normalize easiness and performanceRating to [0,1].

This requires setting a max value for easiness, which I set to 3.0.  I also replaced easiness with difficulty, because it’s the more natural thing to measure.

Problem: Too many items per day

Because every day we do all the overdue items, it’s easy to encounter situations where one day you have only 5 items to review, and the next you have 50.

Solution

Only require doing the items that are the most overdue.  Use “percentage” of due-date, rather than number of days, so that 3 days overdue is severe if the review cooldown was 1 day, but not severe if it was 6 months.

This allows review sessions to be small and quick.  If the user has time, they can do multiple review sessions in a row.  As a bonus, this allows “almost overdue” items to be reviewed, if the user has time.

Problem: Overdue items all treated equally

If the user completes a month-overdue item correctly, it’s likely they know it pretty well, so showing it again in 6 days is not helpful.  They should get a bonus for correctly getting such overdue items correct.

Additionally, the above problem/solution allows almost-overdue items to be reviewed.  These items should not be given full credit.

Solution

Weight the changes in due-date and difficulty by the item’s percentage overdue.

Other adjustments

  • The quadratic term in the difficulty equation is so small it can be replaced with a simpler linear equation without adversely affecting the algorithm
  • Anki, Memrise, and others prefer an initial 3 days between due-dates, instead of 6.  I’ve adjusted the equations to use that preference.

The Modified “SM2+” Algorithm

Here is the new algorithm, with all the above improvements in place.

For each review-item, store the following data:

  • difficulty:float – How difficult the item is, from [0.0, 1.0].  Defaults to 0.3 (if the software has no way of determining a better default for an item)
  • daysBetweenReviews:float – How many days should occur between review attempts for this item
  • dateLastReviewed:datetime – The last time this item was reviewed

When the user wants to review items, choose the top 10~20 items, ordered descending by percentageOverdue (defined below), discarding items reviewed in the past 8 or so hours.

After an item is attempted, choose a performanceRating from [0.0, 1.0], with 1.0 being the best.  Set a cutoff point for the answer being “correct” (default is 0.6). Then set

percentOverdue = \begin{cases} Min(2, \frac{days(dateNow - dateLastReviewed)}{daysBetweenReviews}), \text{   if correct}\\ 1, \text{   if incorrect} \end{cases}

difficulty += percentOverdue*\frac{1}{17}(8-9*performanceRating)\text{, clamp to [0,1]}

difficultyWeight = 3-1.7*difficulty

daysBetweenReviews *= \begin{cases} 1+(difficultyWeight-1)*percentOverdue, \text{   if correct}\\ 1/difficultyWeight^2, \text{   if incorrect (min days = 1)} \end{cases}

Daily Review Sessions

The above algorithm determines which items to review, but how should you handle the actual review?

That’s the topic for my next post – stay tuned!


Additional Reading:

23 Comments

  1. Denilson says:

    Congratulations.

    I’m waiting for the next post.

    Do you know any already implemented in any programming language (PHP, Python, Java) ?

  2. Anon26 says:

    I dont get your formula behind percentoverdue.

    If card is correct, it is at least 2, and probably higher
    If card is incorrect, it is set to 1.

    shouldnt it be the other way round, caused items to be shown are the top entries when it is ordered descending by percentoverdue.

    Or I am just missing something.

  3. IceTimux says:

    Is there way to implement SM2 algorithm and get minutes too along with dates? like Anki does it?

  4. habib says:

    are you sure memrise app use sm2 algorithm?

  5. xpucsc says:

    This is very easy to read. Love it. Are you planning to have a second part to this blog soon? Thanks.

  6. Jevgenija says:

    You probably have a typo here:
    > easiness += -0.8 + 0.28*performanceRating + 0.02*performanceRating^2

    in the original (https://www.supermemo.com/english/ol/sm2.htm) it’s:
    > – 0.02*performanceRating^2

    I wonder where did you get the
    > 6 * (easiness ** (consecutiveCorrectAnswers – 1))
    formula? I’m only seeing
    > I(n):=I(n-1)*EF
    in the original.

  7. Pavlo says:

    Great approach. I wonder where did you get the nextDueDate formula as well.

    > 6 * (easiness ** (consecutiveCorrectAnswers – 1))

    Great post!

  8. Henry says:

    Question: have you tested this algorithm? What were your conclusions? Unclear whether this is purely theoretical or not.

  9. Will says:

    I’m a little confused about how daysBetweenReviews is calculated. It looks like that value can never be greater than 5 with the default values used in this article. How do you get longer cooldown periods, like the 6 months you referenced when you suggested ordering cards to be studied by percent overdue ?

    1 + (difficultyWeight – 1) * percentOverdue. If we take the max value for difficultyWeight (3) and the max value for percentOverdue (2) the result is 1+ (3 – 1) * 2 = 5

  10. Xi says:

    question for the SM2+ algo

    According to the formula, all words that the user gives a performance Rate value < 0.6 (say "failed-words"), will have a value 1 for percentOverdue.

    Then how can the algorithm decide which words to select from these failed-words in next round?

  11. Khoa Phan says:

    Thank you for sharing. I am doing a project that requires using SM2. I will appreciate that you could explain this “The above algorithm determines which items to review, but how should you handle the actual review?”, because I have been waiting for your next post but can’t see any signs yet.

  12. I would love to see the second post, too! So far I see a couple JavaScript implementations up and the start of one in PHP. I’m working on a Python one for my own uses. One little thing I notice looking at this is that a float number of days might be awkward to work with. I’ll probably use minutes or seconds in mine.

    • Another detail I noticed while implementing this. You have a variable called “percent_overdue”, but it should really be called something like “waiting_progress” or something. It represents the ratio of the time since the last review and the time that should go by since the last review. If it is less than 1, then the item should not be reviewed. Come to think of it, maybe you were meaning to write about this in your next post 😉

  13. t123yh says:

    Hello,
    When I create a card, how much should I set its initial DaysBetweenReviews?

  14. Khoa Phan says:

    Hi Blueraja!

    I really need your help with Daily Review Sessions. Daily Review Sessions is very important because without it users will have to learn a huge number of items every session. Please help me!

  15. Rob says:

    Are you okay? I don’t think a follow-up is coming?

  16. Ben says:

    I implemented a far less obscure version of SM2, to help people actually understand this.

    https://gist.github.com/doctorpangloss/13ab29abd087dc1927475e560f876797


Trackbacks/Pingbacks

  1. Space Repetition SM2 - Java implementation - ExceptionsHub

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*