一覧に戻る
開発ログ
2026年6月7日

打鍵ダイナミクスに基づく集中力リアルタイム推定アルゴリズムの数学的仕様と処理パイプライン(第二部)

打鍵ダイナミクスから集中強度を算出する数理モデルの詳細仕様と、処理パイプラインの各計算ステップについて解説します。

注意: 本記事は、Gamita Novelist 開発チームによるホワイトペーパー「打鍵ダイナミクスに基づく集中力リアルタイム推定アルゴリズム」の内容を分割して紹介する第二部です。 アルゴリズムの概要および論文PDFのダウンロードにつきましては、以下の紹介記事をご覧ください: 打鍵パターンから集中度をリアルタイム推定するアルゴリズム:ホワイトペーパー公開

本稿および記載されているアルゴリズムは、クリエイティブ・コモンズ 表示 - 非営利 4.0 国際 (CC BY-NC 4.0) ライセンスの下で提供されています。商用利用を希望される場合は、お問い合わせよりご連絡ください。

入力と前提

各ティックの入力:

  • t:現在のタイムスタンプ(秒、単調増加)
  • K_t:前回ティック以降の打鍵数(非負整数)

セッション継続関数

S(t_prev, t) = true   (if t > t_prev 且つ Δt_raw ≦ Δt_max)
               false  (otherwise / 起動直後・スリープ復帰等)

状態変数と初期条件

変数意味初期値永続
t_prev前回タイムスタンプfloat未定義No
λレート移動平均float0No
λ^slow閾値学習用低速平均float0Yes
密度移動平均float0Yes
μ対数密度の平均float0Yes
v対数密度の分散float1.0Yes
t_total加重有効活動時間float0Yes
t_last_session前回セッション終了時刻floatt_0Yes
T_burstバースト持続時間の学習値float30Yes
n_burstバーストイベント観測回数uint0Yes
T_pause休止持続時間の学習値float20Yes
n_pause休止イベント観測回数uint0Yes
Δ^idle累積沈黙時間float0No
Δt_since_resume再開からの経過時間floatNo
t_since_depart出発からの経過時間floatNo
A_prev前回活動度float0No
S平滑化スコアfloat50No
F最終表示スコアfloat50No
  • v_0 = 1.0 の理由:初期値を 0 にすると σ ≈ √ε となり、最初の数ティックで Z 値が異常に大きくなる。v_0 = 1.0 は「対数密度は標準偏差 ± 1 程度のばらつき」という穏やかな事前仮定であり、信頼度 γ_t と組み合わせて初期安定化を担う。
  • T_burst,0 = 30s, T_pause,0 = 20s の理由:学習前の安全なデフォルト。これらは数回の活動サイクルで実測値に収束する。実測が 1 回蓄積されれば既に部分的に自分に合った値となる。

起動時フック(パイプライン外)

メインループ開始前に一度だけ実行する。

t_total <- t_total * exp(-(t_now - t_last_session) / τ_decay)

前回セッションからの経過時間に応じて t_total を減衰させる。長期離脱後は統計感度が部分的に復活し、再開後の再較正が速くなる。


自動算出される時定数の設計

本節では、従来の固定値パラメータを自動算出に置き換える核心部分を説明する。

動機:固定値の問題

τ_riseτ_fall を固定した場合、以下の問題が生じた。

  • 速いタイポリズム(10秒バースト)の人には過敏または過鈍な反応
  • 遅いタイポリズム(3分バースト)の人には不適切な反応速度
  • チューニングに開発者の主観が混入する

解決策:バースト・休止時間の観測学習(ハイブリッド Welford→EMA)

アルゴリズムは活動状態の遷移を観測し、各ユーザーの「呼吸のリズム」を学習する。

  • バースト持続時間 T_burst:活動中(A_t ≧ 0.5)が続く時間の学習値。「出発イベント」(A0.5 を下回る瞬間)に更新される。
  • 休止持続時間 T_pause:非活動(A_t < 0.5)が続く時間の学習値。「再開イベント」(A0.5 を上回る瞬間)に更新される。

更新方式:ハイブリッド Welford → EMA

最初の N_switch イベントは Welford の逐次平均(1/n 更新)を使用し、以降は EMA(α_event 更新)に切り替える。

α*(n) = 1 / n      (n ≦ N_switch の場合)
        α_event    (n > N_switch の場合)

n <- n + 1
T <- T + α*(n) * (x_new - T)

T ∈ {T_burst, T_pause}x_new は今回の観測値、n ∈ {n_burst, n_pause}

設計根拠:

  • Welford フェーズ(n ≦ N_switch:初期値 T_0(デフォルト値)の影響が n 回で 1/n に薄まり、素直に真の平均へ収束する。
  • EMA フェーズ(n > N_switch:学習率が α_event で固定されるため、ユーザーのリズムが変化しても追従し続ける。純 Welford では n → ∞ で更新量がゼロになり、長期の変化に追従できなくなるためこの切り替え必要。

適応的時定数の算出式

上昇時定数 τ_rise,t

バーストの典型的な長さに比例して、速く検出する。

τ_rise,t = clip(T_burst / 20, τ_rise,min, τ_rise,max)
  • 係数 1/20 の意味:バースト開始から T_burst / 20 秒で λX_t1 - e^(-1) ≈ 63% 追従する。バーストの 5% 未満の時間で検出が始まる。
  • T_burst = 30s ⇒ τ_rise = 1.5s(3秒で 86% 追従)

下降時定数 τ_fall,t

通常の休止より少し長い間は、レートを高いまま保つ。

τ_fall,t = clip(T_pause * 0.6, τ_fall,min, τ_fall,max)
  • 係数 0.6 の意味:典型的な休止時間 T_pause の 60% が経過した時点で λe^(-T_pause / τ_fall) = e^(-1 / 0.6) ≈ 0.19 倍になる。閾値 λ_th = 0.3 * λ^slow に対し、λ_0 = λ^slow から始まると 0.19 < 0.3 つまり通常の休止の 60% 後に非活動状態に入る。これにより「いつもより少し長い沈黙」でようやく非活動と判定される。
  • T_pause = 20s ⇒ τ_fall = 12s(12秒休止で 63% 低下)

シグモイド c_t

新しい状態変数なしで算出できる。σ_t(打鍵密度の対数標準偏差)はすでに統計量として計算されており、ユーザーの打鍵パターンのばらつきを直接反映している。

c_t = clip(σ_t / 2, c_min, c_max)

設計根拠: σ_t が大きい(ばらつきの大きいバースト型ユーザー)ほど c_t が大きくなり、活動度の遷移がよりなめらかになる。σ_t が小さい(安定持続型ユーザー)ほど c_t が小さくなり、活動・非活動の境界がより明確に引かれる。適応的な個人内較正が自動で行われる。

  • σ_t = 1.0(中程度のばらつき) ⇒ c_t = 0.5(本アルゴリズムのデフォルト値と一致)

固定値として残るパラメータとその理由

パラメータ理由
r_th = 0.3「活動の定義」は設計思想。データから一意に導けない。
D_max = 8安全弁(上限クランプ)。動的にすると不安定化リスクがある。
λ_th,min = 1.0コールドスタート時の数値安全弁。
τ_slow, τ_density統計の時間スケール設定。設計的選択。
T^sil_floor, T^sil_ceilスコアの語義定義。変えると表示意味が変わる。

処理パイプライン

  1. 時間制御Δt = 0 なら即 return)
  2. 打鍵密度X_t, Y_t
  3. 適応的レート推定τ_rise,tτ_fall,t を自動算出してから λ_t を更新)
  4. 活動閾値λ^slow_t, λ_th,t
  5. 適応的活動判定c_t を自動算出してから A_t を計算)
  6. 遷移イベント処理(出発 / 再開イベントを判定し T_burst, T_pause, S を更新)
  7. 個人内相対密度X̄_t, D_t
  8. 統計量更新t_total, μ_t, v_t, σ_t
  9. 信頼度γ_t
  10. 瞬間強度Z_t, T_t
  11. 沈黙時間・ターゲットΔ^idle_t, T^silence_t
  12. ダイナミクスS_t、グレース適用)
  13. 最終出力F_t
  14. 状態保存

各ステップ詳細

Step 1: 時間制御

Δt = min(Δt_raw, Δt_max)   (S = true の場合)
     0                      (otherwise)

Δt = 0 の場合、以降すべてスキップし return する。

Step 2: 打鍵密度

X_t = K_t / Δt
Y_t = ln(X_t + 1)

Step 3: 適応的レート推定

まず自動算出された時定数を求める:

τ_rise,t = clip(T_burst / 20, τ_rise,min, τ_rise,max)
τ_fall,t = clip(T_pause * 0.6, τ_fall,min, τ_fall,max)

τ^rate_t = τ_rise,t   (X_t ≧ λ_{t-1} の場合)
           τ_fall,t   (X_t < λ_{t-1} の場合)

κ^rate_t = 1 - exp(-Δt / τ^rate_t)
λ_t = λ_{t-1} + κ^rate_t * (X_t - λ_{t-1})

Step 4: 活動閾値

κ^slow_t = 1 - exp(-Δt / τ_slow)
λ^slow_t = λ^slow_{t-1} + κ^slow_t * (λ_t - λ^slow_{t-1})
λ_th,t = max(r_th * λ^slow_t, λ_th,min)

Step 5: 適応的活動判定

シグモイドを自動算出:

c_t = clip(σ_{t-1} / 2, c_min, c_max)

σ_{t-1} は前ティックの統計量を使用。初回は σ_0 = √v_0 = 1.0

A_t = 1 / (1 + exp(-(λ_t - λ_th,t) / (c_t * λ_th,t + ε)))

Step 6: 遷移イベント処理

出発イベント(活動 → 非活動)のトリガー:

A_prev ≧ 0.5 且つ A_t < 0.5

処理(ハイブリッド Welford → EMA):

T^obs_burst = Δt_since_resume

if T_min ≦ T^obs_burst ≦ T_max (有効域ガード):
    n_burst <- n_burst + 1
    α*_burst = 1 / n_burst   (n_burst ≦ N_switch の場合)
               α_event       (otherwise)
    T_burst <- T_burst + α*_burst * (T^obs_burst - T_burst)

t_since_depart <- 0
  • 有効域ガードの意味と設計根拠:
    • T^obs_burst < T_min(デフォルト 5s)の除外:A_t0.5 付近で振動する「チャタリング」により生じる偽の短時間イベントを排除する。
    • T^obs_burst > T_max(デフォルト 600s)の除外:セッション境界とリズムの混同を防ぐ。 T_burstT_pause はユーザーの「打鍵のリズム(呼吸)」を学習するものであり、セッション全体の長さ(例:20分のまとまった執筆)を学習するものではない。600s(10分)を超えるバースト・休止は「ワーキングスタイル」であって「リズム」ではないため、ガードにより T_burst / T_pause の更新対象から除外する。初期化直後の Δt_since_resume = ∞ の除外も兼ねる。

再開イベント(非活動 → 活動)のトリガー:

A_prev < 0.5 且つ A_t ≧ 0.5

処理(ハイブリッド Welford → EMA):

T^obs_pause = t_since_depart

if T_min ≦ T^obs_pause ≦ T_max (有効域ガード):
    n_pause <- n_pause + 1
    α*_pause = 1 / n_pause   (n_pause ≦ N_switch の場合)
               α_event       (otherwise)
    T_pause <- T_pause + α*_pause * (T^obs_pause - T_pause)

w = 1 - exp(-Δ^idle / τ_session)
S <- w * 50 + (1 - w) * S
Δ^idle <- 0
Δt_since_resume <- 0

Step 6 が更新した S を、同フレーム of Step 12 が S_{t-1} として使用する。

Step 7: 個人内相対密度

κ^den_t = 1 - exp(-Δt / τ_density)
X̄_t = X̄_{t-1} + κ^den_t * A_t * (X_t - X̄_{t-1})
D_t = min((X_t + ε) / (X̄_{t-1} + ε), D_max)

Step 8: 統計量更新

t_total <- t_total * exp(-Δt / τ_decay) + A_t * Δt
τ_stat,t = τ_stat,0 + (τ_stat,∞ - τ_stat,0) * (1 - exp(-t_total / τ_stat,grow))
α_t = 1 - exp(-Δt / τ_stat,t)
μ_t = μ_{t-1} + α_t * A_t * (Y_t - μ_{t-1})
v_raw = v_{t-1} + α_t * A_t * ((Y_t - μ_{t-1})^2 - v_{t-1})
v_t = max(v_raw, v_min)
σ_t = √v_t

Step 9: 信頼度

γ_t = 1 - exp(-t_total / τ_confidence)

Step 10: 瞬間強度

Z_t = (Y_t - μ_{t-1}) / σ_t
T_t = 50 + γ_t * (10 * clip(Z_t, -3, 3) + 8 * tanh(ln(D_t)))

Step 11: 沈黙時間とターゲット

Δ^idle_t = Δ^idle_{t-1} + (1 - A_t)^2 * Δt
T^silence_t = T^sil_floor + (T^sil_ceil - T^sil_floor) * exp(-Δ^idle_t / τ_silence)

Step 12: ダイナミクス(グレース付き)

g_t = exp(-Δt_since_resume / τ_grace)
τ_active = τ_warmup + g_t * (τ_grace_dur - τ_warmup)
τ_t = A_t * τ_active + (1 - A_t) * τ_silence
Target_t = A_t * T_t + (1 - A_t) * T^silence_t
S_t = S_{t-1} + (1 - exp(-Δt / τ_t)) * (Target_t - S_{t-1})

Step 13: 最終出力

β_t = 1 - exp(-Δt / τ_perception)
F_t = F_{t-1} + β_t * (S_t - F_{t-1})

Step 14: 状態保存

A_prev <- A_t
t_prev <- t
t_last_session <- t
Δt_since_resume <- Δt_since_resume + Δt
t_since_depart <- t_since_depart + Δt

実装上の注意

  1. 浮動小数点精度:全演算を 64 ビット(f64 / double)で行うこと。
  2. T_burst / T_pause の外れ値対策Δt_since_resumet_since_departΔt_max を超える区間(スリープ復帰等)を含む場合、その値は T_burst / T_pause の更新に使用しないこと(外れ値による誤学習を防ぐ)。スリープ復帰は S = falseΔt = 0 になるため、Step 6 が実行されず保護される。