{ "cells": [ { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "# FSRS4Anki v6.1.3 Optimizer\n", "\n", "[![open in colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/open-spaced-repetition/fsrs4anki/blob/v6.1.3/fsrs4anki_optimizer.ipynb)\n", "\n", "↑ Click the above button to open the optimizer on Google Colab.\n", "\n", "> If you can't see the button and are located in the Chinese Mainland, please use a proxy or VPN." ] }, { "attachments": {}, "cell_type": "markdown", "metadata": { "id": "wG7bBfGJFbMr" }, "source": [ "Upload your **Anki Deck Package (.apkg)** file or **Anki Collection Package (.colpkg)** file on the `Left sidebar -> Files`, drag and drop your file in the current directory (not the `sample_data` directory). \n", "\n", "No need to include media. Need to include scheduling information. \n", "\n", "> If you use the latest version of Anki, please check the box `Support older Anki versions (slower/larger files)` when you export.\n", "\n", "You can export it via `File -> Export...` or `Ctrl + E` in the main window of Anki.\n", "\n", "Then replace the `filename` with yours in the next code cell. And set the `timezone` and `next_day_starts_at` which can be found in your preferences of Anki.\n", "\n", "After that, just run all (`Runtime -> Run all` or `Ctrl + F9`) and wait for minutes. You can see the optimal parameters in section **2.3 Result**. Copy them, replace the parameters in `fsrs4anki_scheduler.js`, and paste them into the custom scheduling of your deck options (require Anki version >= 2.1.55).\n", "\n", "**NOTE**: The default output is generated from my review logs. If you find the output is the same as mine, maybe your notebook hasn't run there.\n", "\n", "**Contribute to SRS Research**: If you want to share your data with me, please fill this form: https://forms.gle/KaojsBbhMCytaA7h8" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "id": "iqP70_-3EUhi" }, "outputs": [], "source": [ "# Here are some settings that you need to replace before running this optimizer.\n", "\n", "filename = \"collection-2022-09-18@13-21-58.colpkg\"\n", "# If you upload deck file, replace it with your deck filename. E.g., ALL__Learning.apkg\n", "# If you upload collection file, replace it with your colpkg filename. E.g., collection-2022-09-18@13-21-58.colpkg\n", "\n", "# Replace it with your timezone. I'm in China, so I use Asia/Shanghai.\n", "# You can find your timezone here: https://gist.github.com/heyalexej/8bf688fd67d7199be4a1682b3eec7568\n", "timezone = 'Asia/Shanghai'\n", "\n", "# Replace it with your Anki's setting in Preferences -> Scheduling.\n", "next_day_starts_at = 4\n", "\n", "# Replace it if you don't want the optimizer to use the review logs before a specific date.\n", "revlog_start_date = \"2006-10-05\" # YYYY-MM-DD\n", "\n", "# Set it to True if you don't want the optimizer to use the review logs from suspended cards.\n", "filter_out_suspended_cards = False\n", "\n", "# Red: 1, Orange: 2, Green: 3, Blue: 4, Pink: 5, Turquoise: 6, Purple: 7\n", "# Set it to [1, 2] if you don't want the optimizer to use the review logs from cards with red or orange flag.\n", "filter_out_flags = []\n", "\n", "enable_short_term = True\n", "\n", "recency_weight = True" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": { "id": "bLFVNmG2qd06" }, "source": [ "## 1 Build dataset" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": { "id": "EkzFeKawqgbs" }, "source": [ "### 1.1 Extract Anki collection & deck file" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "KD2js_wEr_Bs", "outputId": "42653d9e-316e-40bc-bd1d-f3a0e2b246c7" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Note: you may need to restart the kernel to use updated packages.\n", "Deck file extracted successfully!\n", "revlog.csv saved.\n" ] } ], "source": [ "%pip install -q fsrs_optimizer==6.1.5\n", "# for local development\n", "# import os\n", "# import sys\n", "# sys.path.insert(0, os.path.abspath('../fsrs-optimizer/src/fsrs_optimizer/'))\n", "import fsrs_optimizer as optimizer\n", "optimizer = optimizer.Optimizer(enable_short_term=enable_short_term)\n", "optimizer.anki_extract(filename, filter_out_suspended_cards, filter_out_flags)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": { "id": "dKpy4VfqGmaL" }, "source": [ "### 1.2 Create time-series feature & analysis\n", "\n", "The following code cell will extract the review logs from your Anki collection and preprocess them to a trainset which is saved in [./revlog_history.tsv](./revlog_history.tsv).\n", "\n", "The time-series features are important in optimizing the model's parameters. For more detail, please see my paper: https://www.maimemo.com/paper/\n", "\n", "Then it will generate a concise analysis for your review logs. \n", "\n", "- The `r_history` is the history of ratings on each review. `3,3,3,1` means that you press `Good, Good, Good, Again`. It only contains the first rating for each card on the review date, i.e., when you press `Again` in review and `Good` in relearning steps 10min later, only `Again` will be recorded.\n", "- The `avg_interval` is the actual average interval after you rate your cards as the `r_history`. It could be longer than the interval given by Anki's built-in scheduler because you reviewed some overdue cards.\n", "- The `avg_retention` is the average retention after you press as the `r_history`. `Again` counts as failed recall, and `Hard, Good and Easy` count as successful recall. Retention is the percentage of your successful recall.\n", "- The `stability` is the estimated memory state variable, which is an approximate interval that leads to 90% retention.\n", "- The `factor` is `stability / previous stability`.\n", "- The `group_cnt` is the number of review logs that have the same `r_history`." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "J2IIaY3PDaaG", "outputId": "607916c9-da95-48dd-fdab-6bd83fbbbb40" }, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "349d654b8dbc4a9eadf7d7c6d08b0ba7", "version_major": 2, "version_minor": 0 }, "text/plain": [ " 0%| | 0/9793 [00:00Note: These values should be used with build-in FSRS of Anki 23.12 or custom scheduling script of FSRS4Anki v4.11.0" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "### 2.3 Preview" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": { "id": "I_zsoDyTaTrT" }, "source": [ "You can see the memory states and intervals generated by FSRS as if you press the good in each review at the due date scheduled by FSRS." ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "iws4rtP1WKBT", "outputId": "890d0287-1a17-4c59-fbbf-ee54d79cd383" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1:again, 2:hard, 3:good, 4:easy\n", "\n", "first rating: 1\n", "rating history: (1,3,3),3,3,3,3,3,3,3,3\n", "interval history: 0.0d,0.0d,0.0d,1.0d,2.0d,4.0d,8.0d,16.0d,1.1m,2.2m,4.5m\n", "factor history: 0.0,0.0,0.0,0.0,2.00,2.00,2.00,2.00,2.06,2.03,2.03\n", "difficulty history: 0,6.9,6.9,6.9,6.8,6.8,6.8,6.8,6.8,6.7,6.7\n", "stability history: 0,0.5,0.7,0.9,1.9,4.0,8.1,16.4,33.0,67.2,136.5\n", "\n", "first rating: 2\n", "rating history: (2,3,3),3,3,3,3,3,3,3,3\n", "interval history: 0.0d,0.0d,0.0d,2.0d,4.0d,8.0d,17.0d,1.2m,2.6m,5.6m,11.8m\n", "factor history: 0.0,0.0,0.0,0.0,2.00,2.00,2.12,2.18,2.11,2.14,2.13\n", "difficulty history: 0,6.5,6.4,6.4,6.4,6.4,6.4,6.4,6.3,6.3,6.3\n", "stability history: 0,1.2,1.4,1.6,3.8,8.3,17.4,36.7,78.5,166.6,354.6\n", "\n", "first rating: 3\n", "rating history: (3,3),3,3,3,3,3,3,3,3,3\n", "interval history: 0.0d,0.0d,10.0d,22.0d,1.7m,3.8m,8.7m,1.6y,3.6y,8.2y,18.4y\n", "factor history: 0.0,0.0,0.0,2.20,2.32,2.25,2.26,2.26,2.25,2.25,2.25\n", "difficulty history: 0,5.8,5.8,5.8,5.8,5.8,5.8,5.8,5.8,5.7,5.7\n", "stability history: 0,9.7,9.7,22.4,50.5,115.0,260.3,587.7,1325.4,2982.6,6700.7\n", "\n", "first rating: 4\n", "rating history: (4),3,3,3,3,3,3,3,3,3,3\n", "interval history: 0.0d,16.0d,1.3m,3.3m,8.2m,1.7y,4.1y,10.0y,24.5y,59.8y,100.0y\n", "factor history: 0.0,0.0,2.50,2.48,2.47,2.47,2.46,2.45,2.45,2.44,1.67\n", "difficulty history: 0,4.9,4.9,4.9,4.9,4.9,4.9,4.9,4.9,4.9,4.9\n", "stability history: 0,15.9,39.8,99.1,245.5,605.8,1491.5,3660.4,8952.3,21825.2,36500.0\n", "\n" ] } ], "source": [ "requestRetention = 0.9 # recommended setting: 0.8 ~ 0.9\n", "\n", "preview = optimizer.preview(requestRetention)\n", "print(preview)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "You can change the `test_rating_sequence` to see the scheduling intervals in different ratings." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "rating history: 3,3,3,3,3,1,1,3,3,3,3,3\n", "interval history: 0.0d,10.0d,22.0d,1.7m,3.8m,8.6m,16.0d,4.0d,5.0d,7.0d,10.0d,13.0d,18.0d\n", "factor history: 0.0,0.0,2.20,2.27,2.28,2.26,0.06,0.25,1.25,1.40,1.43,1.30,1.38\n", "difficulty history: 0,5.8,5.8,5.8,5.8,5.8,8.8,9.6,9.6,9.5,9.5,9.4,9.4\n" ] } ], "source": [ "test_rating_sequence = \"3,3,3,3,3,1,1,3,3,3,3,3\"\n", "requestRetention = 0.9 # recommended setting: 0.8 ~ 0.9\n", "\n", "preview_sequence = optimizer.preview_sequence(\n", " test_rating_sequence, requestRetention)\n", "print(preview_sequence)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "### 2.4 Predict memory states and distribution of difficulty\n", "\n", "Predict memory states for each review group and save them in [./prediction.tsv](./prediction.tsv).\n", "\n", "Meanwhile, it will count the distribution of difficulty." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "difficulty\n", "1 0.080504\n", "2 0.017812\n", "3 0.021691\n", "4 0.036008\n", "5 0.051463\n", "6 0.215490\n", "7 0.087622\n", "8 0.053228\n", "9 0.170297\n", "10 0.265885\n", "Name: count, dtype: float64" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "optimizer.predict_memory_states()" ] } ], "metadata": { "colab": { "authorship_tag": "ABX9TyMnk8/Ih2JAJZJ1PBkXQUBC", "collapsed_sections": [], "provenance": [], "toc_visible": true }, "kernelspec": { "display_name": "fsrs4anki", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.16" } }, "nbformat": 4, "nbformat_minor": 0 }