ankitects / anki

Anki's shared backend and web components, and the Qt frontend
https://apps.ankiweb.net
Other
18.5k stars 2.11k forks source link

Integrate FSRS into Anki as an optional feature #2443

Closed dae closed 1 year ago

dae commented 1 year ago

Currently users need to paste code into the custom scheduling section of the deck options in order to use FSRS. It may make sense to integrate this code directly into Anki, to make it easier to use.

According to Jarrett, the scheduling code does not change frequently: https://github.com/ankitects/anki/pull/2421#issuecomment-1457925875

Users may wish to apply different weights to different decks. The existing custom scheduling code relies on deck name matches (prefix matches?) in order to determine the weights. We could perhaps store the weights in the deck presets - this would allow a deck and its subdecks to share the same weights. Alternatively the weights could be stored in individual decks, which is the most flexible, but will lead to extra storage. The weights do not appear to be particularly large: https://github.com/ankitects/anki/pull/2421#issuecomment-1460046666

This would be an optional feature, ~and would require the add-on/weights trained externally for now. If it is successful, it may make sense to look into a Rust-based training method in the future, so that users don't need to rely on external sites/programs~.

@L-M-Sherlock @RumovZ any thoughts?

L-M-Sherlock commented 1 year ago

I would like to know where the code of FSRS could be integrated into Anki. FSRS only modifies the customData and the scheduledDays of ReviewState.

dae commented 1 year ago

It would probably require changes to states/review.rs to apply the FSRS scheduling if an option had been set; the actual FSRS code would best be placed in a new file.

L-M-Sherlock commented 1 year ago

Here are some codes I think are related:

https://github.com/ankitects/anki/blob/146bed12d9e14bee8620b61f367bd95f61b7d82e/rslib/src/scheduler/states/learning.rs#L53

https://github.com/ankitects/anki/blob/146bed12d9e14bee8620b61f367bd95f61b7d82e/rslib/src/scheduler/states/learning.rs#L70

https://github.com/ankitects/anki/blob/146bed12d9e14bee8620b61f367bd95f61b7d82e/rslib/src/scheduler/states/learning.rs#L80

https://github.com/ankitects/anki/blob/146bed12d9e14bee8620b61f367bd95f61b7d82e/rslib/src/scheduler/states/relearning.rs#L45

https://github.com/ankitects/anki/blob/146bed12d9e14bee8620b61f367bd95f61b7d82e/rslib/src/scheduler/states/review.rs#L59

https://github.com/ankitects/anki/blob/146bed12d9e14bee8620b61f367bd95f61b7d82e/rslib/src/scheduler/states/review.rs#L80

If FSRS is enabled, the scheduler will call a new function in a file, maybe named fsrs.rs.

dae commented 1 year ago

Do you have thoughts on whether we should put the weights in deck presets or individual decks?

L-M-Sherlock commented 1 year ago

Do you have thoughts on whether we should put the weights in deck presets or individual decks?

Deck presets are better. The current built-in algorithm also reads its configurations from deck presets.

L-M-Sherlock commented 1 year ago

it may make sense to look into a Rust-based training method in the future, so that users don't need to rely on external sites/programs.

My friend and I have tried tch, which provides Rust bindings for the C++ api of PyTorch. But it required libtorch, whose size is about 200MB.

dae commented 1 year ago

Yeah, that's too large I'm afraid. If it were to get integrated into Anki, it would need to be a smaller library, and preferably one that's implemented in native Rust. I don't know much about machine learning, so I don't know what sort of things your code relies on, or how easy/practical it would be to port the existing code to one of the options listed on https://www.arewelearningyet.com/

L-M-Sherlock commented 1 year ago

I have tried a small library, tinygrad. Now we can get rid of torch: open-spaced-repetition/fsrs-optimizer-tiny (github.com). It only requires numpy and pandas (and we also can find a lightweight alternative for pandas).

However, tinygrad is Python-based and still in alpha. There is still a long way to go.

dae commented 1 year ago

Sorry, I wasn't clear before - when I mentioned native Rust, I meant rather than a Rust wrapper for a C/C++ library. Python-based libraries aren't really an option, as any code added to Anki's core needs to work on the mobile clients too.

L-M-Sherlock commented 1 year ago

Rust wrapper for a C/C++ library

Most of them are bindings for Tensorflow and PyTorch. So their size is very large. Most naive Rust frameworks are experimental.

user1823 commented 1 year ago

If the devs are successful in incorporating the optimizer into Anki, it would be undoubtedly a huge achievement.

But, for now, I think that the devs should focus on incorporating the scheduler and the features from the helper add-on into Anki.

If the scheduler and helper add-on code is incorporated into Anki, it would

Also, given that training is something that the user doesn't need to do very frequently (only once a month, as L-M-Sherlock suggests), using an external site/program for it would not be very inconvenient.

L-M-Sherlock commented 1 year ago

I agree with @user1823. @dae, my friend and I implement a Rust version for FSRS scheduler. Here is the repo: https://github.com/open-spaced-repetition/rs-fsrs. Maybe it would be helpful.

dae commented 1 year ago

To summarize what is required here, as I currently understand it:

I presume that's the minimum required to enable usage of FSRS without using custom scheduling. The existing add-on could write the weights into the deck config, until we are able to integrate the weight calculation inside Anki directly.

L-M-Sherlock commented 1 year ago
  • We add a field to DeckConfig to store the vector of weights, which should be a vector of at least a certain length.

And requestRetention. Here is a typical configuration for FSRS:

{
    "deckName": "global config for FSRS4Anki",
    "w": [0.4, 0.6, 2.4, 5.8, 4.93, 0.94, 0.86, 0.01, 1.49, 0.14, 0.94, 2.18, 0.05, 0.34, 1.26, 0.29, 2.61],
    "requestRetention": 0.9,
    "maximumInterval": 36500,
  }

The "deckName" and "maximumInterval" can be removed when we enable usage of FSRS without using custom scheduling.

RumovZ commented 1 year ago

use the presence/absence of the weights

That would mean you'd lose the weights after toggling FSRS off and on again, though, as far I understand, which is a bit inconvenient.

I assume the card-specific parameters would remain in custom_data?

dae commented 1 year ago

That would mean you'd lose the weights

Yep, good point.

would remain in custom_data

Yeah, I think that's probably best for now, as a DB schema change will be moderately expensive, and would require changes to the sync protocol.

Of the parameters shown in the screenshot here:

https://forums.ankiweb.net/t/how-to-use-the-next-generation-spaced-repetition-algorithm-fsrs-on-anki/25415/223?u=dae

L-M-Sherlock commented 1 year ago
  • I presume v=helper has been used to identify what wrote the details; maybe we could shorten this to something like v=1 for Anki, or omit it entirely?

v denotes version. I store it just to locate the problem, because it has two source: the schedule and the helper. If we will implement reschedule feature, it would be useful to store the source.

  • what is seed used for? What are its min/max values, and would it be problematic if we used a shorter name?

The seed is fuzz factor. The reason I store it is that the random generator is different between JavaScript and Python. I need to store the fuzz factor to keep consistent between the scheduler (js script) and the helper (py add-on).

Expertium commented 1 year ago

Suggestion: allow users to have different parameters for different decks or apply the same parameters to all decks.

dae commented 1 year ago

That's the plan. https://github.com/ankitects/anki/issues/2443#issuecomment-1474743194

user1823 commented 1 year ago

Congratulations for successfully integrating the FSRS optimizer into Anki (https://github.com/ankitects/anki/pull/2633).

For the UX, I have a suggestion.

Also, the user should be allowed to set a requestRetention other than the one suggested by the optimizer. The suggested requestRetention is aimed at minimizing the workload but the user must be allowed to adjust the scheduler so as to achieve their learning goals (as is possible via the custom scheduling version of FSRS).

Lastly, I suggest that Check should be renamed to something like Evaluate.

Expertium commented 1 year ago

This might be a bit too much to ask, but I think the Supermemo approach of choosing the requested R (in Supermemo, "fogetting index" is just one minus requested retention) via a slider is really good. It ensures that the user cannot enter non-numerical characters and also ensures that the user cannot set the requested retention (or forgetting index) higher or lower than certain values. In SuperMemo, the forgetting index cannot be set lower than 3% (requested retention 97%) or higher than 20% (requested retention 80%), and I think that's a perfectly reasonable range and we should do the same. image

user1823 commented 1 year ago

I think the SuperMemo approach of choosing the requested R via a slider is really good. … In SuperMemo, the forgetting index cannot be set lower than 3% (requested retention 97%) or higher than 20% (requested retention 80%), and I think that's a perfectly reasonable range and we should do the same.

I agree that we should only allow requestRetention values in the range 80% to 97%. But I don't like the idea of using a slider because using a slider to set a value is less convenient than simply typing it out.

galantra commented 1 year ago

It could be both, a slider and a number field.

image

Each would need to adjust automatically to the value of the other.

A click on the circle would set it to the calculated optimal value.

dae commented 1 year ago

Create a new option near the top of the Deck Options page for enabling/disabling FSRS.

A toggle already exists in the advanced section. It would definitely make sense to hide certain options when it is enabled, or display warnings (eg if a learning step is longer than a day and fsrs is enabled, warn the user that it is not advised).

The requested retention is editable by the user, and the plan is to make it use a spinbox like the other number fields, with appropriate min and max limits. I'm not sure we really need a slider in addition to a spinbox.

Expertium commented 1 year ago

Another suggestion: if it's possible to run the optimizer on each individual deck, then it should run only if the deck has a certain number of reviews (say, 1000), otherwise, if the user is trying to run the optimizer on a deck with very few reviews, an error message should pop up, telling the user that the data is insufficient to find optimal parameters accurately, and then the parameters of the parent deck should be used instead.

dae commented 1 year ago

We should definitely add a warning if there's not enough source material, but revlogs are gathered based on preset, not deck. Advanced users who want to use some custom training scope can use the search, and it could be easily automated in an add-on.

Expertium commented 1 year ago

A minor suggestion: currently if users want to check whether FSRS is working, they have to modify const display_memory_state = false in the scheduler code. Once FSRS is integrated into Anki, there should be a button to toggle this on/off.

user1823 commented 1 year ago

Once FSRS is integrated into Anki, there should be a button to toggle this on/off.

This is not required in my opinion. With the custom scheduling approach, it makes sense to add this option because the implementation is fragile. But, when FSRS is natively integrated into Anki, it is not required because you can trust that the scheduler is working.

However, that option also displayed the memory states (D, S, R). This might be worth adding (to the Card Info). It already contains D and S in the Custom Data but R is not there. Another approach could be to add these memory states to the Browser (as the Helper add-on currently does).

Expertium commented 1 year ago

but R is not there

The problem is that S and D don't change in between repetitions, but R does.

dae commented 1 year ago

The plan is to add DSR to the card info and browser. I've already done the card info, and it uses the current R, not R at last review.

Expertium commented 1 year ago

It would definitely make sense to hide certain options when it is enabled

Speaking of which, aside from hiding settings, it also makes sense to hide stats related to card's Ease (such as the image below) once FSRS is enabled. image

L-M-Sherlock commented 1 year ago
image

These values can be extracted from revlog. If we implement auto-filling, I recommend folding these fields to avoid confusing the user.

How about opening a new issue to discuss about that?

dae commented 1 year ago

I've opened https://github.com/ankitects/anki/issues/2661

You're welcome to open issues for any other problems you notice, too - please give them the FSRS tag.

Expertium commented 1 year ago

I know that this issue is already closed, but still, I want to ask something: do we really need (re)learning steps with FSRS? I don't see why we would keep them. 1) It can cause problems such as "Good" interval being shorter than "Hard" interval. 2) It adds another setting for users to worry about and to (needlessly) tweak, and we want to have as few settings as possible to decrease the cognitive load for the users and make everything simple and straightforward. 3) FSRS already has 4 parameters for initial stability after only one review, we could just use those.

In other words, the (re)learning stage can be removed entirely when FSRS is enabled. Of course, (re)learning steps will still be available to those who stick with the old algorithm.

user1823 commented 1 year ago

do we really need (re)learning steps with FSRS? I don't see why we would keep them.

The reasons (re)learning stage is required:

Expertium commented 1 year ago

Regarding your first point: Woz says that same-day reviews have a negligible impact on long-term memory, and FSRS indirectly supports it as well; the optimizer works just fine when same-day reviews are removed. So we could just tell the users "no more same-day reviews". Regarding your second point: yes, the optimal value of initial S can change, but then it can just be re-estimated as new data (the user is doing reviews) comes in.

user1823 commented 1 year ago

Woz says that same-day reviews have a negligible impact on long-term memory

Here, Wozniak says:

A single effective review on the first day of learning is all you need. Naturally, it may happen you cannot recall a piece of information upon a single exposure. In such cases, you may need to repeat the drill.

So, if the user presses Again, the review should be repeated on the same day.

In addition, I think that intraday reviews (of failed cards) can be helpful in making an early segregation of cards based on their difficulty. However, the current version of FSRS completely misses this opportunity by filtering out intraday reviews. See this issue for details.

yes, the optimal value of initial S can change, but then it can just be re-estimated as new data (the user is doing reviews) comes in.

But then, the optimized value of initial S would match neither the initial S of the old cards nor that of the new cards. It would rather be in between these two values.

Expertium commented 1 year ago

So, if the user presses Again, the review should be repeated on the same day.

Then how about making learning steps the same for everyone (15m 1d, for example) - and hiding them aka removing this setting when FSRS is enabled? We already know that long learning steps can cause problems with FSRS, so it makes sense to not allow users to tweak them when FSRS is enabled.

user1823 commented 1 year ago

Then how about making learning steps the same for everyone

It makes sense. I also thought about doing the same. The only problem is that users might complain that they have been robbed of the ability to adjust the learning steps.

15m 1d, for example

I recommend using only 15m. FSRS can control the long-term scheduling much better.

I don't remember why, but about 2.8 years ago, I set my first learning step as 15m and my first relearning step as 20m. These (re)learning steps have produced great results since then. So, I recommend using these steps if we intend to use same steps for everyone.

Expertium commented 1 year ago

@dae @L-M-Sherlock what do you think about setting the learning steps to 15m and hiding them from the user once FSRS is enabled? My philosophy is that the user should have to tweak as few settings as possible. If something can be done by the software without the user's intervention, then it should be done by the software. Allowing the user to change learning steps can lead to issues with FSRS intervals. I'm sure that some users won't like it, but what feels right to them (lots of learning steps) isn't necessarily what is actually optimal.

dae commented 1 year ago

You're saying that 15 minutes works best for you, and suggesting we force it on everyone. I'm not sure that's a good idea, and suspect people with different preferences would not be fond of such a change. FSRS can get away with fewer user knobs because the weights are trained based on individual users' data, but a hard-coded learning step wouldn't be.

It can cause problems such as "Good" interval being shorter than "Hard" interval.

Anki shows a warning when the user has FSRS enabled and sets a (re)learning step over a day, and if they set large steps anyway, they should be handled correctly, as the elapsed days between learning steps is fed into FSRS.

dae commented 1 year ago

Perhaps an alternative is to increase the guard rails - eg showing a warning if the user adds more than 2 steps/1 relearning step, or steps over an hour. This nudges people towards sensible choices, without forcing them or hard-coding a particular value.

user1823 commented 1 year ago

You're saying that 15 minutes works best for you, and suggesting we force it on everyone. I'm not sure that's a good idea,

I think that you misunderstood my point (though it's not your fault, I wasn't clear enough). It is not that I played around with different settings of learning steps and then settled on one value that best suits me. In that case, I wouldn't have recommended using that value for all users. The thing is that I read this value being recommended somewhere online and after I set it, my learning improved significantly. If the value recommended by someone else (without knowing the contents of my cards and my learning ability) works great for me, it should work well for others too.

Perhaps an alternative is to increase the guard rails - eg showing a warning if the user adds more than 2 steps/1 relearning step, or steps over an hour. This nudges people towards sensible choices, without forcing them or hard-coding a particular value.

This seems to be a reasonable alternative.

Expertium commented 1 year ago

I want to add that the point is not to find the perfect value of learning steps that works great for everyone, the point is to set it to some value that is good enough and "lock" it to prevent:

1) Users worrying "How should I tweak this setting?"

2) Problems with FSRS and long learning steps

L-M-Sherlock commented 1 year ago

According to my research in my job, learning steps really affect the long-term memory. Long learning steps would increase the memory stability, but it also increase the error rate during intraday learning.

In Nakata (2015a), for instance, 128 Japanese university students studied 20 English-Japanese word pairs using flash card software. The participants were randomly assigned to one of the following four spacings: massed-, short-, medium-, and long-spaced. The participants in the massed group practiced retrieval of a given English word three times in a row. The short-, medium-, and long-spaced groups used average spacing of approximately one, two, and six minutes, respectively (that is, participants in the long-spaced group practiced retrieval of a given target word approximately every six minutes). Nakata found that the massed group was the least effective among the four groups both immediately and one week after the treatment, supporting the spacing effect. Moreover, while the short-spaced group fared significantly bet- ter than the massed group immediately after learning, the advantage disappeared at a one-week retention interval. The medium- and long-spaced groups, in contrast, were significantly more effective than the massed group not only on the immediate but also on the one-week delayed posttest. The findings are consistent with the spacing-by-retention interval interaction, which states that the benefits of short lags disappear over time. Nakata and Webb (2016a, Experiment 2) also observed the positive effects of long lags for L2 vocabulary learning. They compared the effects of short and long spacing. Encounters of a given target item were separated by approximately 30 seconds and three minutes in the short- and long-spaced conditions, respectively. While the long-spaced group failed to outperform the short-spaced group immediately after the treatment (short = 58%; long = 62%), the long-spaced group fared significantly better than the short-spaced group at a one-week retention interval (short = 8%; long = 20%; Figure 20.1). These findings support the lag effect and spacing-by-retention interval interaction.

Source: https://www.taylorfrancis.com/chapters/edit/10.4324/9780429291586-20/learning-words-flash-cards-word-cards-tatsuya-nakata

user1823 commented 1 year ago

According to my research in my job, learning steps really affect the long-term memory.

Yes, they definitely do. But, in my opinion, the only thing that matters is the time gap between a successful review and the previous failed review.

The reason for this is that when the time gap is too small, there is no "spacing effect" and thus, the review doesn't cause memory stabilization.

However, adding multiple steps on the same day doesn't affect the long-term memory.

L-M-Sherlock commented 1 year ago

@dae, I'm refactoring my add-on, and find out a problem. What if the user do reviews in other devices which haven't supported FSRS? Should we re-calculate the memory state for these cards synced from other devices?

Expertium commented 1 year ago

Out of curiosity, can someone tell me how long (roughly) it will take before a new version of Anki, with FSRS built-in, is released? I know it's hard to say, but I just want to know whether it's more like 2 weeks or many months.

dae commented 1 year ago

I suggest we initially put a warning in the UI when people enable FSRS: "For this feature to work properly, all of your Anki clients must be Anki(Mobile) 23.10+ or AnkiDroid 2.17+". I submitted some PRs to AnkiDroid yesterday which adds support for the new FSRS functionality.

The first step to a release is a beta, I hope to do that within a week.

dermpathMD commented 1 year ago

In your opinion, is it fine to install FSRS 4 on my own now or should I wait for the beta with FSRS integrated? Or does it not really matter?