Skip to content

Integrate FSRS into Anki - #2654

Merged
dae merged 31 commits into
mainfrom
fsrs
Sep 16, 2023
Merged

dae merged 31 commits into
mainfrom
fsrs

Conversation

@dae

@dae dae commented Sep 12, 2023 •

Copy link
Copy Markdown
Member

Feel free to have a play with this, but I recommend you make backups and keep an eye on the card stats, as it's received minimal testing so far.

A couple of notes:

  • Difficulty has been remapped to a 0-1 value in Anki, and is shown in percentages in the browser/stats, as that implicitly tells the users what the min and max values can be, and is consistent with retrievability.
  • S/D aren't stored in custom data, so they won't conflict with fsrs4anki & the helper. The values can be accessed with card.fsrs_memory_state in Python, and normal.review.fsrsMemoryState in custom scheduling.
  • I have not yet had time to look into any of the points raised on Review logs that requires special treatment when calculating the memory states from review history open-spaced-repetition/fsrs-rs#63
  • Anki has separate paths for when cards are overdue or due in the future (filtered decks). Was it correct to treat them the same for FSRS, or should we special-case these too?

Feedback welcome - I'm sure there are things I've missed!

Closes #2443

@L-M-Sherlock

L-M-Sherlock commented Sep 12, 2023 •

Copy link
Copy Markdown
Contributor
  • Anki has separate paths for when cards are overdue or due in the future (filtered decks). Was it correct to treat them the same for FSRS, or should we special-case these too?

The FSRS scheduler deal with them in the same way. Here is related code: https://github.com/open-spaced-repetition/fsrs4anki/blob/5a34201f4d5b3a9cc1a5da4511b6bc32b7c6e909/fsrs4anki_scheduler.js#L278-L290

function is_review() {
  if (states.current.normal?.review !== undefined) {
    if (states.current.normal?.review !== null) {
      return true;
    }
  }
  if (states.current.filtered?.rescheduling?.originalState !== undefined) {
    if (Object.hasOwn(states.current.filtered?.rescheduling?.originalState, 'review')) {
      return true;
    }
  }
  return false;
}
  if (states.hard.normal?.review) {
    states.hard.normal.review.scheduledDays = hard_interval;
  }
  if (states.good.normal?.review) {
    states.good.normal.review.scheduledDays = good_interval;
  }
  if (states.easy.normal?.review) {
    states.easy.normal.review.scheduledDays = easy_interval;
  }

@L-M-Sherlock

L-M-Sherlock commented Sep 12, 2023 •

Copy link
Copy Markdown
Contributor
image

New cards shouldn't have stability. We need to skip new cards when updating parameters.

@L-M-Sherlock

L-M-Sherlock commented Sep 12, 2023 •

Copy link
Copy Markdown
Contributor
image

The Difficulty should not be negative.

I figure it out. This card's memory state is default. But it should be updated.

According to above code, if the card only has one revlog (after filter), it will be skipped. Because the output is [].

/// Transform the revlog history for a card into a list of FSRSItems. FSRS
/// expects multiple items for a given card when training - for revlog
/// `[1,2,3]`, we create FSRSItems corresponding to `[1]`, `[1,2]`, `[1,2,3]`.

This doc is incorrect. For revlog [1, 2, 3], the real output is [1,2], [1,2,3].

@dae

dae commented Sep 13, 2023

Copy link
Copy Markdown
Member Author

Memory state issue fixed by 69e6c67

@L-M-Sherlock

Copy link
Copy Markdown
Contributor
image

This card still hasn't been updated.

@dae

dae commented Sep 13, 2023

Copy link
Copy Markdown
Member Author

A single learning step is excluded in training. Are you saying that it should not be excluded when calculating the memory state?

@L-M-Sherlock

L-M-Sherlock commented Sep 13, 2023 •

Copy link
Copy Markdown
Contributor

Yes. In training, the model needs two reviews in the revlog history. One as feature, one as label. But in calculating the memory state, singel learning step is enough to predict the memory state.

/// Transform the revlog history for a card into a list of FSRSItems. FSRS
/// expects multiple items for a given card when training - for revlog
/// `[1,2,3]`, we create FSRSItems corresponding to `[1,2]` and `[1,2,3]`.
fn single_card_revlog_to_items(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can pass in an arg to control whether to send out [[1], [1,2], [1,2,3]] or [[1,2], [1,2,3]].

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For calculating, we need to send out [1] for revlog [1].

@dae

dae commented Sep 13, 2023

Copy link
Copy Markdown
Member Author

Does the latest commit do what you expect?

@dae

dae commented Sep 13, 2023

Copy link
Copy Markdown
Member Author

Sorry, I mean 3d1af8c

@dae

dae commented Sep 13, 2023

Copy link
Copy Markdown
Member Author

Ah, it will mean we also need to set the memory state in states/learning.rs. Will look into it tomorrow.

@L-M-Sherlock

Copy link
Copy Markdown
Contributor
image

The weights are NaN after e1b5555. But I test the collection in fsrs-rs and get a correct result.

@dae

dae commented Sep 13, 2023

Copy link
Copy Markdown
Member Author

A question about updating memory state when the user has learning steps. single_card_revlog_to_items() ignores all reviews after the first one that day. Do we need to worry about that when updating memory state during learning? It would be nice if it did not matter, as that would mean we'd have to start checking the revlog history during review.

@L-M-Sherlock

L-M-Sherlock commented Sep 14, 2023 •

Copy link
Copy Markdown
Contributor

Ideally, FSRS only updates the memory state in the first learning and reviews whose elapsed days are greater or equal to 1 day. That's why I don't recommend using interday learning steps. For more details, you can check the script:

https://github.com/open-spaced-repetition/fsrs4anki/blob/5a34201f4d5b3a9cc1a5da4511b6bc32b7c6e909/fsrs4anki_scheduler.js#L108-L110

https://github.com/open-spaced-repetition/fsrs4anki/blob/5a34201f4d5b3a9cc1a5da4511b6bc32b7c6e909/fsrs4anki_scheduler.js#L145-L152

@dae

dae commented Sep 14, 2023

Copy link
Copy Markdown
Member Author

Could we solve this by assigning the initial memory state when cards graduate learning, instead of on their first learning step?

@L-M-Sherlock

Copy link
Copy Markdown
Contributor

I think it would be nice. Even if the user use interday learning steps, we could pass the revlog history for calculating the memory state when the card graduate learning.

@L-M-Sherlock

Copy link
Copy Markdown
Contributor
image

I press easy for this new card, but its stability is still 0.

@dae

dae commented Sep 14, 2023

Copy link
Copy Markdown
Member Author

I'm a bit confused as to why you're getting a 0 stability - I haven't yet implemented memory assignment on graduation, so fsrsMemoryState should be None until it gets reviewed again. Do you still have custom scheduling active? Does it happen when add-ons are disabled?

@L-M-Sherlock

L-M-Sherlock commented Sep 14, 2023 •

Copy link
Copy Markdown
Contributor

I disable the helper add-on and now it is normal.

Edit: https://github.com/open-spaced-repetition/fsrs4anki-helper/blob/692eeb77997776e107fe48494c3c5e9296bd4b46/schedule/reschedule.py#L367-L501

This function of my add-on induce the problem. It updates the card after review. @dae, do you have any clue about this?

@dae

dae commented Sep 14, 2023

Copy link
Copy Markdown
Member Author

Might be a bug with saving the card in Python. Will look into it.

@dae

dae commented Sep 14, 2023

Copy link
Copy Markdown
Member Author

I've just used the latest review for now, but we could potentially gather the revlog history for learning cards when fsrs is enabled, and use memory_state() instead in the future.

@dae

dae commented Sep 15, 2023

Copy link
Copy Markdown
Member Author

Ok, you can now do BURN_LOG=$(pwd)/extra ./run to get the training log and checkpoints

@dae

dae commented Sep 15, 2023

Copy link
Copy Markdown
Member Author

I've been thinking a bit more about gathering the revlog history for learning cards.

It would mean that the starting S/D match what the user gets when they enable FSRS, which is good - having cards start at a different difficulty when reviewed normally vs FSRS enabled / weights changed is a bit confusing.

On the other hand, it's somewhat inconsistent with the standard Anki scheduler. Learning steps were first introduced so that users could fail a card repeatedly until they first memorize it, without having later reviews penalized by those early failures. I know FSRS doesn't have the same issues with low ease levels that SM2 does, but I'm curious - have you explored what sort of difference it makes to FSRS's performance when all learning steps except the last one (where rating must be 3 or 4 in order to graduate) are excluded from training? If excluding the other learning steps makes a non-trivial regression in performance, then that's a strong argument for fetching them during learning.

@L-M-Sherlock

Copy link
Copy Markdown
Contributor

Do you mean to pick the rating of the last review during the learning stage as the first rating for initializing the memory state? I haven't tested it. FSRS uses the first rating for the new cards to initialize the memory staste because in my first research accepted by KDD, the first rating has a huge impact to the initial stability. Of course, the research is based on a language learning app, so the pattern would be different in other subjects. But if we only consider the last rating in the learning stage, the initial stability will only has two options: one for good, and one for easy.

According to the data collected from various Anki users, The initial stability has strong correlation to the first rating.

@dae

dae commented Sep 15, 2023

Copy link
Copy Markdown
Member Author

Ok, sounds like we're best off gathering the revlogs. Thanks :-)

@dae

dae commented Sep 16, 2023

Copy link
Copy Markdown
Member Author

What do you think of the new approach? For the minimum viable implementation, can you think of anything that still must be implemented before we merge this in?

@dae
dae marked this pull request as ready for review September 16, 2023 03:07
@L-M-Sherlock

L-M-Sherlock commented Sep 16, 2023 •

Copy link
Copy Markdown
Contributor

Nice. I have tested it in my local device. It's the time to merge.

@dae dae changed the title Add FSRS vars to card, and integrate into review/card stats/browser/searches Integrate FSRS into Anki Sep 16, 2023
@dae
dae merged commit 5004cd3 into main Sep 16, 2023
@dae
dae deleted the fsrs branch October 11, 2023 03:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Integrate FSRS into Anki as an optional feature

2 participants