Hanzi Flow:適応型の中国語リーダー
Hanzi Flow は「適応型の文選択」という一つの発想を軸に作った漢字練習アプリです。現在の習熟度に応じて、未知の漢字が2〜5字含まれる文を(79,000件以上の実例から)選び、理解度90〜95%の帯に保ちます。デッキ管理は不要で、ピンインを打つだけで練習できます。
ソース: github.com/brianhliou/hanzi-flow
既存ツールの問題点
中国語学習アプリは大きく三つの陣営に分かれ、それぞれに欠点があります。
Anki のようなフラッシュカードアプリは、孤立した漢字を暗記させます。実際の学習よりデッキの整理に時間を取られます。そしてようやく実際の文章でその漢字に出会っても、文脈から切り離して覚えたので認識できません。
Pleco のような辞書アプリは調べるには便利ですが、完全に受動的です。読んで理解しても、何も定着しません。能動的な想起もタイピング練習もありません。
リーディングアプリや家庭教師が与えてくれるのは、児童書(退屈)かネイティブ向けコンテンツ(難しすぎ)のどちらかです。退屈か圧倒されるかで、どちらも流暢さにはつながりません。
欠けているのはフロー状態の維持です。挑戦的でありながら苛立たない、理解度90〜95%のゾーンです。ちょうどよい難易度の文が自動で選ばれ、タイピングによる能動的想起が伴う必要があります。
作ったもの
Hanzi Flow は難易度が適応的に変わる文単位の練習アプリです。中国語を読み、ピンインを打つと、各漢字の習熟度に応じて次の文が選ばれます。デッキ管理のない Anki の間隔反復、フレーズ集ではなく実際の文を使う Duolingo のセッションループ、という感じです。

アプリの内容:
- Tatoeba 由来の79,000件以上の文(教科書の例文ではなく実際の用例)
- 4,000字以上の漢字 - HSK 1〜9 と、HSK 外の約1,000字
- 適応型アルゴリズム - 習熟度に基づき未知の漢字が2〜5字の文を選択
- 習熟度トラッキング - 間隔反復(EWMA + SRS)による漢字単位のスコア
- HSK 3.0 準拠 - 公式カリキュラムのレベル(1〜9 または HSK 外)でフィルタ
- 字体対応 - 簡体字、繁体字、混在
- リアルタイムのフィードバック - 色による視覚的合図と1,598件の音声ファイル(全ピンイン音節)
- 100% ローカルファースト - アカウント不要、トラッキングなし、オフライン動作
自分のレベルに合った本物の文で練習し、ピンイン入力でタイピングの流暢さを養い、上達に応じてシステムが適応します。手作業のデッキ管理は不要です。
ユーザー体験
初回起動:設定を選ぶ
アプリを初めて開くと、二つのことを選びます。
字体:簡体字、繁体字、混在。これで目的に合わせてコーパスを絞り込みます。
HSK レベル:フィルタは累積的です。「HSK 1〜3」を選ぶと、レベル1・2・3の漢字だけを使う文が出ます。「HSK 外」を選ぶと、公式カリキュラム外の上級漢字約1,000字も含まれます。
設定からいつでも変更できます。変更すると、アプリは現在の文キューを破棄し、新しいフィルタに基づいて再生成します。

練習のループ
練習はシンプルです。
- 文を読む - 漢字は大きく表示されます
- ピンインを打つ - 1字ずつ、声調は任意(
wo3もwoも可) - フィードバックを得る - 現在の字は青くハイライト、誤りは赤く点滅して再挑戦(最大4回)
- ピンインを見る - 各字の下に声調付きの正解が表示されます
- 訳を表示 - 終了後、下部に英訳を任意で表示
- 次の文へ - スペースキーかボタンで進む
フィードバックは即時かつ視覚的です。常に現在の字(青いハイライト)に集中できます。誤答は赤く点滅し、正しい発音の音声が鳴ります。4回間違えるとシステムは次へ進み、その字を紫(あきらめ)として記録します。異読も受け付けます(地 は de も di も可)が、文脈上正しいものを表示します。

仕組みの核心はフィードバックループです。現実では、本で漢字を見かけて誤って推測し、そのまま正解を知らないことがあります。テレビを見ていて発音をまったく聞き取れないこともあります。そうした学習サイクルは遅いのです。気軽な学習者が1字をきちんと覚えるだけの接触を得るには数か月かかるかもしれません。
このアプリはそのサイクルを圧縮します。漢字を見て、打ち込み、音声付きで即座に訂正され、すぐ再挑戦して次へ進む。最初から最後まで10秒です。1セッションで100字こなせば、100回の学習サイクルを回したことになります。能動的想起(タイピング)、即時訂正(音声と視覚)、そして大量の反復(文が次々に来る)が積み重なり、受動的な接触では得られないものになります。
進捗のトラッキング
統計ページには主要な4つの指標が表示されます。
- 練習した総漢字数 - 見たことのある異なる漢字の数
- 習得した漢字数 - 習熟度 ≥ 0.8 のもの
- 練習した文数 - 完了した異なる文の数
- 全体の正答率 - 1回目で正解した割合
習熟度の内訳では漢字を次のように分類します。
- 学習中(s < 0.6) - まだ慣れを作っている段階
- 習熟中(0.6 ≤ s < 0.8) - あと一歩
- 習得済み(s ≥ 0.8) - 確実
各漢字は間隔反復に基づいて次の復習時期を管理します。


適応型アルゴリズム(NSS)
ランダム選択ではうまくいきません。同じ文が何度も出たり、すでに習得した内容に時間を無駄にしたり、未知の漢字が10字以上ある文に打ちのめされたりします。
Next Sentence Selection(NSS)が次の文を選ぶアルゴリズムです。新規学習、間隔反復、新鮮さ、難易度のバランスを取ります。
中心となる考え方
最適点は1文あたり未知の漢字2〜5字です。
- 0〜1字 - 簡単すぎて単なる復習
- 2〜5字 - 最適。挑戦も文脈も十分
- 6字以上 - 難しすぎて推測になる
アルゴリズムは次の要素もバランスさせます。
- 新規学習(まだ見たことのない漢字)
- 間隔反復(復習期限が来た漢字)
- 新鮮さ(同じ文を繰り返しすぎない)
- 多様性(難易度を混ぜる)
動作の仕組み
NSS はバッチで動きます。練習を始めると10文を生成します。8文こなした時点で、次のバッチをバックグラウンドで先読みします。待たされることはありません。
4つのステップです。
1. 対象となる文を絞り込む
コーパス全体(79K)から始め、フィルタを適用します。
- 字体(簡体字/繁体字/混在)
- HSK レベル(選択した範囲)
- クールダウン(直近20分に見た文を除外)
これで対象プールができ、通常2万〜4万文になります。
2. 候補300件をランダム抽出
プールからランダムに300件取り出します。多様性を保てるだけ大きく、素早くスコア付けできる(100ms 未満)だけ小さい規模です。ランダム抽出により、毎セッション違う文に出会えます。
3. 各候補をスコア付け
300件それぞれについて、4つの要素からスコアを計算します。
- 基本ゲイン - 各漢字の(1 − 習熟度)の合計。習熟度の低い字ほど高くなります。復習期限を過ぎている場合はゲインが2倍になります。
- 新鮮さ - 最後に見てからの経過時間に基づくボーナス。多様性を促し、同じ文の繰り返しを防ぎます。
- 正解ペナルティ - この文を何度も完璧にこなしている場合はスコアを下げます。練習しすぎを避けます。
- k ペナルティ - 未知の漢字が2〜5字の帯から外れた文にペナルティを与えます。難易度を保ちます。
これらを合計します: score = base_gain + novelty - pass_penalty - k_penalty
4. 上位10件を選び、シャッフルしてキューに入れる
スコアで並べ替え、上位10件を取り、難易度を混ぜるためシャッフルします。それをキューに追加します。
この10文を練習します。残り2文になると、アルゴリズムがバックグラウンドで再実行され、次のバッチを先読みします。
習熟度のトラッキング
各漢字は0.0から1.0までの習熟度スコア(s)を持ちます。
- s = 0.3(新しい漢字の初期値)
- s → 1.0(安定して正解している)
- s → 0.0(間違え続けている)
更新には α=0.15 の指数加重移動平均(EWMA)を使います。直近の成績が重く効きますが、変化は緩やかです。1回のミスで急落せず、1回の成功で膨らみもしません。
漢字を正解すると、その安定度(SRS の間隔)が1.2倍に伸びます。最初は1時間。次に1.2時間。次に1.44時間。Anki の急激な伸び(1日 → 3日 → 7日)より長くローテーションに残ります。
なぜ緩やかにするのか。同日内の復習が重要だからです。短期記憶から長期記憶へ移すには、1セッション中に同じ漢字を3〜5回見る必要があります。間隔が急に伸びるとその窓を逃してしまいます。
漢字が期限(next_review_ts を過ぎた状態)になると、スコア計算で重みが2倍になります。アルゴリズムは期限超過の漢字を含む文を優先します。典型的な間隔反復です。
データパイプライン
これを作るには包括的なデータが必要でした。発音付きの漢字、訳付きの文、難易度タグ、品質フィルタです。コーパスの構築方法を説明します。
漢字セット
Unicode の CJK 統合漢字(U+4E00〜U+9FFF)から始めました。20,992字です。現代中国語の全漢字に加え、稀な異体字や歴史的字形も含みます。
次に、3つのソースのデータでそれぞれを補強しました。
- Unihan データベース - ピンイン読み、異体字、部首分解
- CC-CEDICT - 英語の定義と例語(学 → “study; learn”、学生 → “student”)
- OpenCC - 字体分類のための簡体字/繁体字の対応
結果として、99.7%の漢字にピンインがあり、67%に英語の訳があり、35%に異体字の対応があります。どの漢字が実際に Tatoeba に登場するか(5,000字以上)、どれが理論上のもの(稀な古典漢字)かも把握できています。

この曲線は HSK レベルを通じて漢字がどう積み上がるかを示します。HSK 1 は約300字、HSK 2 でさらに300字、というように増えます。識字の目安である約3,000字に近づくにつれ、高レベルでは曲線が平坦になります。それを超えると収穫は減っていきます。
頻度分布はジップの法則に従います。ごく一部の漢字が支配的で、大半は稀です。上位500字で本文の75%を、上位2,000字で95%をカバーします。高頻度のものから学べばすぐに読解力がつくので、これは適応型アルゴリズムにとって重要です。

HSK の分類
HSK(漢語水平考試)は中国の公式な語学能力試験です。2021年の改定(HSK 3.0)は9段階と合計3,000字を定めています。elkmovie/hsk30(政府の PDF を OCR したもの)の公式リストを取り込みました。
各文は最も難しい漢字によって分類されます。
- すべて HSK 1 の漢字なら「1」とタグ付け
- 最難の漢字が HSK 3 なら「3」とタグ付け
- HSK 9 を超える漢字を含むなら「beyond-hsk」とタグ付け
これがフィルタを動かします。「HSK 3」を選ぶと、すべての漢字がレベル1〜3の文だけが出ます。想定外はありません。


文の処理
クラウドソーシングの翻訳データベース Tatoeba から中国語の文を79,000件以上取得しました。それぞれ片側が英語、もう片側が中国語です。
課題:文脈に応じたピンイン。 漢字は文脈によって複数の発音を持ちます。まず jieba と pypinyin で一次処理し、その後 GPT-4o-mini で検証しました(79K文で約2ドル)。地(de か di4)、著(zhe か zhu4)、谁(shei2 か shui2)といった一般的な多音字で10,336件の誤りを修正しました。
多音字の例:
- 了:
le(助詞)かliao3(終える) - 行:
xing2(よい)かhang2(行・列) - 长:
zhang3(育つ)かchang2(長い)
表を引くだけでは決まりません。語の文脈を理解する必要があります。
ステップ1:jieba + pypinyin。 jieba は「我们试试看」を[“我们”, “试试”, “看”]に分割し、pypinyin が各語のピンインを生成します:我们试试看 → wo3 men shi4 shi4 kan4。多くの場合はうまくいきますが、助詞や口語的な発音では失敗します。
ステップ2:GPT-4o-mini による検証。 コーパス全体を OpenAI の API に通しました(79K文で約2ドル、1回の呼び出しで10件ずつ)。声調記号付きの1字ごとのピンインを求めるプロンプトを使いました。79,603文のうち10,336件の誤りが見つかりました。主な修正は次のとおりです。
- 覺(120件): pypinyin は
jue2(感じる)、OpenAI はjiao4(寝る)- 你應該去睡覺了吧 - 「もう寝たほうがいいよ」
- 著(696件): pypinyin は
zhu4(着る)、OpenAI はzhe(助詞)- 生活就是當你忙著進行你的計劃時… - 「人生とは計画を立てるのに忙しいときに起こるもの」
- 谁(404件): pypinyin は
shui2(文語的)、OpenAI はshei2(口語的)- 你知不知道他们是谁? - 「彼らが誰か知っていますか」
- 长(136件): pypinyin は
zhang3(育つ)、OpenAI はchang2(長い)- 我看见一个长头发的女生 - 「髪の長い女の子を見た」
上位の漢字を手作業で確認し、確信度の高い修正を適用しました。OpenAI の文脈理解は、ルールベースの手法が取りこぼした誤りを捉えてくれました。
英訳: こちらも GPT-4o-mini で生成しました(合計約1ドル)。Tatoeba のデータセットには約2,000文しか訳がありませんでした。それを使うとコーパスが79,000件以上から2,000件に減り、適応型アルゴリズムの効果が失われます。そこで GPT-4o-mini で79,000件以上すべてを翻訳しました。LLM は文脈を使うので翻訳が得意で、これは正確な中英変換にまさに必要なものです。例:
- 我們試試看! → Let’s give it a try!
- 你在干什麼啊? → What are you doing?
- 今天是6月18号,也是Muiriel的生日! → Today is June 18th, and it’s Muiriel’s birthday!
- 生日快乐,Muiriel! → Happy birthday, Muiriel!
- 密码是”Muiriel”。 → The password is “Muiriel.”
品質フィルタも適用しました。
- 字体の分類 - 簡体字、繁体字、中立、混在
- 卑語の除去 - 下品な表現を除外(約10文)
- 長さの上限 - スクロールを避けるため50字を超える文を削除(約270文)
- メタ文 - Tatoeba 自体に言及する文を削除(約80文)
最終的なコーパスは79,333件のクリーンな文です。

アーキテクチャと性能
モダンなウェブ技術で構築し、速度とプライバシーに最適化しました。
なぜローカルファーストか
- プライバシー - アカウントなし、トラッキングなし、テレメトリなし。進捗は端末から出ません。アナリティクスさえ入れていません。データはあなたのものです。
- 性能 - ネットワーク遅延がありません。文の選択は100ms未満で走ります。タイピングのフィードバックは即時です。統計は50ms未満で読み込まれます。
- シンプルさ - 保守するバックエンドも、スケールさせるデータベースも、認証もありません。アプリは静的ファイルとクライアント側 JS だけです。Vercel に無料でホストしています。
- トレードオフ: 端末間の同期はありません。ノートPCとスマホでは別々に練習することになります。MVP としては許容範囲です。多くの人は主に使う端末が1台です。あとでエクスポート/インポートや任意のクラウド同期を追加してもよいでしょう。
技術スタック
フロントエンド:
- Next.js 15(App Router)
- React 19 + TypeScript
- Tailwind CSS
- IndexedDB 用の Dexie.js
データパイプライン:
- 処理スクリプト用の Python 3.9+
- 単語分割用の jieba
- 文脈に応じたピンイン用の pypinyin
- 分析用の pandas + matplotlib
- 翻訳とピンイン検証用の GPT-4o-mini
音声:
- TTS には AWS Polly(Zhiyu の声)
- 1,598音節、全声調をカバー
- ブラウザ互換性のため OGG 形式
デプロイ:
- Vercel(設定不要)
- 静的サイト、サーバーなし
- グローバル CDN
データの読み込みとキャッシュ
アプリは初回訪問時に大きなデータセットを読み込み、その後は積極的にキャッシュします。
初回読み込み:
- 79,333文(約41MBのJSON) - 一度取得してメモリに保持
- 漢字マッピングのCSV(約2MB) - ID、ピンイン、訳語
- 音声ファイル - 必要に応じて取得(各1〜2KB)
メモリ内キャッシュ:
- 文は JavaScript の配列に保持
- 漢字からIDへの対応は Map に保持(O(1) で参照)
- セッション中の再取得なし
IndexedDB による永続化:
- WordMastery テーブル - 練習した漢字ごとに1行(習熟度スコア、SRS 間隔、次回復習時刻、回数)
- SentenceProgress テーブル - 完了した文ごとに1行(正答率、試行回数、最終表示日時)
- SentenceQueue テーブル - 次の10文を保存(設定変更時に再生成)
すべてのデータは端末に残ります。初回読み込み以外にネットワーク通信はありません。
性能上の工夫:
- 音声のプリロード - NSS が次の文を選ぶと、そのピンイン音声をバックグラウンドで先読みします(3〜5ファイル、10KB未満)。表示される時点で音声はキャッシュ済みです。
- キューの先読み - 10文のうち8文を終えると、NSS がバックグラウンドで次のバッチを用意します。待たされることはありません。
- 遅延読み込み - 統計ページは訪問時にだけ分布を計算し、操作ごとには計算しません。
オフライン: 初回読み込み後は完全にオフラインで動きます。飛行機でも地下鉄でも、どこでも練習できます。進捗は即座に IndexedDB に保存されます(保存ボタンは不要)。
主要な設計判断
声調番号か声調記号か
このアプリは声調記号(wǒ、shì)ではなく声調番号(wo3、shi4)を受け付けます。なぜか。
- 入力が簡単(特殊なキーボードが不要)
- 中国語 IME の標準(実際のタイピングの準備になる)
- 曖昧さがない(
vとüは等価、声調記号は Unicode の結合文字が必要) - 視覚的な暗記ではなく、声調を明示的に考えさせる(
wo3は第三声)
文単位の学習
なぜ単語単体ではなく文なのか。
- 文脈 - 実際の用法で漢字を学べます。「我们试试看」は 我们(私たち)、试试(試す)、看(見る)そして文法パターンを教えてくれます。
- 読解の練習 - 実際の読書に近い形です。漢字の連なりを読み解く力が育ちます。
- タイピングの流暢さ - 実際に使う形のまま、文全体の運動記憶を鍛えます。
- 難易度の調整 - 文には自然な難易度分布(未知の漢字数)があります。単語単位ではこれが得られません。
- モチベーション - 文を1つ終えると前進した感覚があります。「的 的 的」のカードを延々とこなすのではなく、本物の中国語を読んでいるのです。
今後の予定
日々の練習に必要な機能はそろっています。また手を入れるなら、追加しうるものは次のとおりです。
- モバイル PWA 化(インストール可能にする)
- HSK 別の統計内訳
- 復習モード(SRS 中心のセッション)
- 文の音声(漢字単位だけでなく全文の発音)
- 漢字カード(字源、筆順、熟語)
- 声調の分析(混同しやすい声調を追跡)
- 独自コーパス(自分の文をインポート)
- エクスポート/インポート(端末間の持ち運び)
オープンソースのプロジェクトです。アイデアがあれば issue か PR をどうぞ。
おわりに
自分の不満を解消するために作りました。フラッシュカードの手間なしに中国語を読む練習をしたかったのです。本物の文、適応的な難易度、すっきりしたタイピング画面。
結果として、データパイプライン(文脈に応じたピンイン付きの79K以上の文)、適応型アルゴリズム(EWMA + SRS)、ローカルファーストのアーキテクチャ(IndexedDB + キャッシュ)を深掘りすることになりました。多音字、ジップの法則、同日復習に向けた間隔反復のチューニングについて多くを学びました。
中国語を学んでいるなら試してみてください。開発者ならリポジトリを見てみてください。文選択アルゴリズム、データ処理、ローカルファーストの手法に面白いところがあります。
謝辞:文は Tatoeba、CC-CEDICT は MDBG、HSK 3.0 のリストは elkmovie、Unihan は Unicode Consortium に感謝します。
試してみる
hanzi.brianhliou.com にアクセスして始めてください。
- 字体と HSK レベルを選ぶ
- 声調番号付きでピンインを打つ(
wo3、ni3、ta1) - 統計ページで進捗を確認する
- 毎日練習して習熟度が上がるのを見る
ソースは MIT ライセンスで GitHub にあります。クローンしてローカルで実行できます。
git clone https://github.com/brianhliou/hanzi-flow.git
cd hanzi-flow/app
npm install
npm run dev
http://localhost:3000 を開いてください。