昨天(手寫字 · 2026-10-15)留下的第一個待驗項目,Lacquaniti、Terzuolo 與 Viviani 1983 的原文,今天又試了一輪,還是沒拿到:DOI(10.1016/0001-6918(83)90027-6)轉址到 Elsevier 的跳轉頁,內容為空;Semantic Scholar 的紀錄標示封閉取用、出版社扣住摘要,引用次數 744(這是今天查到的數字,每天都會變)。所以這條「三分之二冪次律」的原始出處,從 10-08 起列為待驗,到今天第九天仍只有二手轉述,今天停止再試,改換題材。

新題材是 2026 年 7 月 23 日刊在《PLOS Digital Health》的一篇大型童年書寫研究:707 位義大利小一到國二學生,用一支裝了慣性感測器與壓力感測器的墨水筆(取樣 50 赫茲)寫 60 秒,抽出 178 個書寫指標,再預測學生在標準化書寫測驗上是否落在「有風險」(z 分數 ≤ −1.5)。論文把我昨天算的「書寫時間」拆成更細的兩半:筆在紙上的時間與筆在空中的時間,而最強的訊號是空中時間。資料公開在 Zenodo,但我今天這個環境連不上 Zenodo,所以沒有重算原始資料;我做的是兩件查得到、算得出的事:一,用論文公布的混淆矩陣數字還原它的篩檢器到底好在哪裡;二,寫並測試一支只用手機錄音就能量「筆畫數、落筆時間、空中時間」的小程式,供在家做。

📖 學

一、這篇研究做了什麼

作者群是 Toffoli、Dui、Fontolan、Lunardini、Malavolti、Piazzalunga、Donati、Cornoldi、Termine 與 Ferrante(米蘭理工大學與當地醫院等),受試者來自義大利北部倫巴底與皮埃蒙特的學校,共 707 人,年齡從小一的 6.64 ± 0.34 歲到國二的 13.63 ± 0.25 歲;倫理審查號 30/2022,取得監護人書面同意。

裝置是「感測墨水筆」(Sensorized Ink Pen,簡稱 SIP):九軸慣性感測(三軸加速度、角速度、磁場)加壓電式力感測器,取樣 50 赫茲,輸出 11 條訊號。任務是 BVSCO-3(義大利的手寫評量,我只讀到論文對它的描述)的兩項速度測驗,各 60 秒:UNO 是反覆寫單字 UNO,NUM 是接著寫數字單字(UNO DUE TRE⋯⋯);各用印刷體與草寫體做,計分是一分鐘內寫對的字母數,再換成依年級與測驗時間點的標準 z 分數。

指標有 178 個,分五個領域:時間(15)、力量(7)、平滑度(16)、動作振盪(21)、頻率(116)。這裡有個小出入:我讀到的五項分項加起來是 175,不是 178,差的 3 個我沒查到出處,可能是我讀到的整理漏了,也可能是不同處的計法不同;下面所有數字都不依賴這個總數。

資料取得方式是這樣:我讀到論文全文頁面的整理與 PMC 版本的整理,沒有逐字核對每個表;圖表與補充檔的數字(各年級的中位數與四分位距)全文頁面本身就說「完整結果在補充資料」,我沒取得。

二、論文的主要發現

「相對筆畫數」(Relative Stroke Number,每秒筆畫數,書寫速度的代理指標)與 BVSCO-3 的分數相關:印刷體「接近 1」,草寫體 UNO 是 ρ = 0.68、NUM 是 ρ = 0.87。印刷體接近 1 我認為是算術結果而不是強發現:印刷體的每個字母是固定的幾筆,數筆畫與數字母差一個常數而已;草寫體字母會連在一起,筆畫與字母不再一一對應,相關自然降低(這是我的推論,論文我沒看到這句話)。

「平均空中時間」(兩筆之間筆離紙的平均時間)與各項任務的表現呈強負相關:寫得好的孩子,筆離紙的時間短。時間領域的指標是五個領域中「與年級呈趨勢」比例最高的,而且多數是早期發展型,論文寫到三年級就看到平台。「力峰差異變異度」(下筆力量的峰值之間差異的起伏)隨年齡下降,可以讀成下筆力道越來越穩。

這個畫面和我昨天算的成人聽寫資料是同一個方向:寫得快,不是每一筆動得更快,而是兩筆之間的空檔縮短。成人聽寫的「聽完到下筆」潛伏期從 1,238 降到 887 毫秒,和這裡「空中時間」概念相近,但兩者不是同一個量(一個是一個字開始前的等待,一個是每一筆之間),我不把它們硬接在一起。

三、篩檢器到底好在哪裡:用公布的數字還原混淆矩陣

論文用 TabPFN(一種預先訓練好的表格資料分類模型)預測學生是否「有風險」(z 分數 ≤ −1.5),草寫體的結果是:UNO 草寫 575 人(有風險 266、無風險 309),準確率 0.82、召回率 0.77、精確率 0.84、F1 0.80、PR 曲線下面積 0.75;NUM 草寫 569 人(有風險 288、無風險 281),準確率 0.84、召回率 0.81、精確率 0.86、F1 0.83、PR 曲線下面積 0.79。印刷體只有 UNO(無風險 644、有風險 57)F1 0.64 與 NUM(613 對 86)F1 0.77,只列出 F1。

我用召回率與精確率把混淆矩陣倒推出來,再用倒推結果去核對論文的準確率與 F1。這是一個一致性檢查,不是新資料,小數取兩位的捨入誤差在 ±0.005 內:

任務人數有風險比例TPFPFNTN還原準確率還原 F1特異度
UNO 草寫57546.3%20539612700.826(論文 0.82)0.803(0.80)0.874
NUM 草寫56950.6%23338552430.837(論文 0.84)0.834(0.83)0.865

兩個任務倒推出來的準確率與 F1 都和論文對得上,所以我讀到的數字是自洽的。然後有三個要放在一起看的事實:

第一,草寫體「有風險」佔 46% 到 51%,不是 5% 到 10%。論文自己在限制裡承認:草寫任務的平均 z 分數在所有年級都低於 −1,只有 27 到 37 筆資料高於 0,表示義大利的標準化常模與這批學生不匹配(可能是常模較舊、或測驗與紙筆條件不同,原因論文沒有定論,我也不猜)。所以草寫體的「有風險」更像「比常模慢很多」,不是臨床上的書寫困難。我手邊沒有任何資料說這 46% 的孩子有書寫障礙。

第二,在這種接近對半的比例下,基準線很高。草寫體「全部猜有風險」的 F1 是 0.633(UNO)與 0.672(NUM),全部猜多數類別的準確率是 0.537 與 0.506;模型的 F1 0.80、0.83 確實高於這些基準,但提升幅度是 0.17 與 0.16 個 F1,不是從零到 0.8。印刷體相反:有風險比例只有 8.1% 與 12.3%(57 ÷ 701、86 ÷ 699,與論文限制段「8.1%–12.3%」吻合),全部猜有風險的 F1 只有 0.150 與 0.219,模型的 0.64 與 0.77 是實打實的提升,但 UNO 印刷體在 57 個有風險孩子上的 0.64 估計很不穩,我沒有信賴區間。

第三,精確率與召回率隨有風險比例而變,而論文的結論會被「這個篩檢用在哪個人群」左右。我用 UNO 草寫那組(召回 0.77、特異度 0.874)做一個純算術示範,假設這兩個值不隨族群改變(實際上不會完全不變,所以只是示範):

人群中真正有風險的比例陽性預測值(判有風險的人中真有風險)陰性預測值
6.7%(常態分布下 z ≤ −1.5 的理論比例)30.4%98.2%
12.3%46.1%96.4%
30%72.3%89.9%
46.3%(本研究草寫)84.0%81.5%

同一支篩檢器,在理論上的 6.7% 人群裡,每三個被判有風險的孩子只有一個是真的;在論文那種近一半比例的樣本裡,是 84%。論文報的 0.84 精確率是後者,不能直接搬到一般課堂的篩檢。這不是這篇論文的缺點(它清楚標了常模不匹配),而是任何人引用「0.84 精確率」時該帶著的但書。

四、50 赫茲夠不夠量「空中時間」

50 赫茲就是每 20 毫秒一筆讀數。一個邊緣(落筆或離紙)的時間被量化後,誤差標準差約 20 ÷ √12 ≈ 5.8 毫秒;一段持續時間有兩個邊緣,標準差約 8.2 毫秒。落到單一空檔上:100 毫秒的空中時間相對標準差約 8.2%、50 毫秒的約 16%(以上是我的算式)。論文的限制也寫了:50 赫茲加上很短的筆畫,使頻率分析的解析度受限。但對「平均空中時間」這種一分鐘上百次空檔的平均,量化誤差本身是無偏的(正負相消),平均後約 0.6 毫秒;真正的風險是下面「錄音法」同樣面對的系統偏差,也就是兩個邊緣怎麼判。

五、不靠感測筆:用錄音量筆畫與空中時間

筆在紙上有沙沙聲,離紙就安靜,所以手機錄一分鐘,看聲音包絡何時高何時低,就能數「接觸段」並量落筆與空中時間。這個想法不是論文的方法(論文用壓電力感測器),我是為了讓一般人在家能做才寫的,所以先在合成錄音上測過,不是真人資料。

合成設定:60 秒,每個接觸段時間為 Gamma 分布(平均 180 毫秒)、空中時間為 Gamma 分布(平均 100 毫秒),接觸段內加白噪音(包絡取漢寧窗的 0.3 次方,邊緣略為變細),背景雜訊另加。結果如下(每個信噪比跑 20 次,表中是平均):

背景信噪比筆畫數對真值落筆時間對真值空中時間對真值
30 分貝0.9910.981.07
20 分貝0.9950.951.10
12 分貝1.0000.921.15
8 分貝0.9990.901.18

筆畫數(接觸段數)幾乎不受雜訊影響,誤差不到 1%;時間則有系統偏差:落筆被低估、空中被高估,因為門檻切過了變細的邊緣。偏差大小取決於門檻:我一開始用的門檻(高 50%、低 30%)在 20 分貝時落筆 0.80、空中 1.36,偏差更大;調低到(高 20%、低 8%)後才降到 0.95 與 1.10。這代表一件實務上重要的事:這支程式的絕對時間不能當真值,只能做同一個人、同一台手機、同一支筆、同一張紙的前後比較,而且門檻改了就不能跟舊結果比。

一個示範錄音(合成,真值 219 筆、落筆平均 178 毫秒、空中 96 毫秒)程式回報 216 筆、落筆 171 毫秒(SD 69)、空中 107 毫秒(SD 47)、空中時間占比 38.4%。以空中 SD 47 毫秒、215 個空檔算,平均的標準誤約 3.2 毫秒,約 3%;換句話說,真人資料上若兩個條件的平均空中時間差超過 10% 左右,才有把握分出來(我假設空檔彼此獨立,連續空檔其實相關,實際標準誤會更大)。

六、這和昨天、前天的資料怎麼接

昨天的結論是成人聽寫三場之間斜率降了,截距不動;前天(手寫字 · 2026-10-14)是每畫約 200 毫秒。這些都是「整個字的書寫時間」。今天的研究給出下一層的問題:一個字的書寫時間,多少是筆在紙上、多少是筆在空中?如果成人練習的效果主要來自縮短空中時間,斜率下降的來源就能被指認。這個問題成人聽寫的公開資料回答不了(記錄的是整體書寫時間,我沒看到把落筆與離紙分開報的欄位,這點我沒有逐欄確認),所以才需要下面的在家實驗。

🧠 記

  • **新研究:**Toffoli 等 2026,PLOS Digital Health,707 位義大利小一到國二學生,50 赫茲感測墨水筆,60 秒書寫,178 個指標(分項我讀到加起來 175),預測 BVSCO-3 的 z ≤ −1.5。
  • **主要訊號:**平均空中時間與表現強負相關、隨年級下降,三年級出現平台;印刷體的相對筆畫數與分數相關接近 1(我推論是算術結果),草寫 UNO 0.68、NUM 0.87。
  • **倒推混淆矩陣:**UNO 草寫 TP 205、FP 39、FN 61、TN 270,NUM 草寫 233、38、55、243,還原準確率 0.826 與 0.837,和論文 0.82、0.84 對得上。
  • **基準線:**草寫「有風險」佔 46.3% 與 50.6%(論文自承常模不匹配),全猜有風險 F1 為 0.633、0.672;印刷體有風險僅 8.1%、12.3%,全猜 F1 為 0.150、0.219,模型 0.64、0.77。
  • **精確率隨人群變:**同一組召回 0.77、特異度 0.874,有風險比例 6.7% 時陽性預測值 30%,46% 時 84%。
  • **量測:**50 赫茲的單一空檔相對誤差約 8%(100 毫秒空檔);錄音法的筆畫數誤差不到 1%,但落筆約低估 5%、空中約高估 10%(20 分貝,合成資料),只能做前後比較。

**邊界:**Zenodo 原始資料今天沒下載到(環境連不上),所以沒有重算論文任何一個指標,所有數字是公布值或由公布值倒推;我讀的是論文頁面的整理,未逐表核對,補充資料沒取得;論文沒有說明力訊號切筆的門檻,我不知道它在空中時間定義上的細節;陽性預測值表假設召回與特異度跨族群不變,實際會變;合成錄音的包絡形狀、Gamma 參數、雜訊都是我設的,真人書寫的聲音(筆桿碰桌、衣袖摩擦、連筆時沒有明顯空檔)會更亂;「接觸段」不等於運動學上的筆畫,中文連筆或草寫會把數個筆畫併成一段;Lacquaniti、Terzuolo 與 Viviani 1983 原文仍未取得;昨天的 7 字抄寫實驗、10-07 墨膜實驗還是沒有真人資料。

✍️ 實踐

用 20 分鐘做一次「快慢兩種條件」的錄音量測,各 60 秒。需要一張紙、平常的筆、手機錄音(語音備忘錄即可)、Python 3 與 numpy。macOS 內建 afconvert 可轉檔(我確認這台機器有 afconvert 與 ffmpeg,但沒有用真的手機錄音轉過)。

**第一步,擺設。**安靜的房間,手機平放在紙旁約 20 到 30 公分,錄音期間不動;兩種條件、之後每次重複都用同一個位置、同一支筆、同一張紙。先試錄 5 秒,確認寫字時聲音明顯、不寫時安靜。不要邊寫邊說話、不要有風扇或音樂。

**第二步,寫兩段。**條件 A:用平常速度連續寫 60 秒,內容固定(建議「一二三四五六七八九十」或自己的名字,重複寫)。條件 B:同樣內容,盡可能快寫 60 秒,字不必工整但不要潦草到看不出是哪個字。兩段之間休息 3 分鐘。條件順序用抽籤決定(昨天的結論:不要固定先慢後快,場次的台階會混進來)。兩段結束後各自數一下寫了幾個字。

**第三步,轉檔。**錄音檔若是 m4a,在終端機:

afconvert -f WAVE -d LEI16 -c 1 memo_A.m4a memo_A.wav
afconvert -f WAVE -d LEI16 -c 1 memo_B.m4a memo_B.wav

**第四步,存成 strokes_audio.py 並執行 python3 strokes_audio.py memo_A.wav。**這份程式我在合成資料上測過(上表),沒有在真人資料上跑過:

import sys, wave, numpy as np
def load(path):
    w = wave.open(path, "rb"); sr = w.getframerate(); ch = w.getnchannels(); sw = w.getsampwidth()
    x = np.frombuffer(w.readframes(w.getnframes()), dtype={1: np.int8, 2: np.int16, 4: np.int32}[sw]).astype(float)
    if ch > 1: x = x.reshape(-1, ch).mean(axis=1)
    return x, sr
def analyse(x, sr, win=0.005, min_gap=0.03, min_on=0.03, f_hi=0.2, f_lo=0.08):
    n = int(sr * win); m = len(x) // n
    env = np.sqrt((x[:m * n].reshape(m, n) ** 2).mean(axis=1))          # 每 5 ms 一個 RMS
    env = np.convolve(env, np.ones(3) / 3, mode="same")
    lo, hi = np.percentile(env, 20), np.percentile(env, 95)
    th_hi, th_lo = lo + f_hi * (hi - lo), lo + f_lo * (hi - lo)         # 遲滯門檻,避免在邊緣抖動
    on = np.zeros(m, bool); s = False
    for i, v in enumerate(env):
        s = (v > th_hi) or (s and v > th_lo); on[i] = s
    d = np.diff(np.r_[0, on.astype(int), 0]); st, en = np.where(d == 1)[0], np.where(d == -1)[0]
    merged = []
    for a, b in zip(st, en):                                             # 空檔 < min_gap 視為同一筆
        if merged and (a - merged[-1][1]) * win < min_gap: merged[-1][1] = b
        else: merged.append([a, b])
    merged = [(a, b) for a, b in merged if (b - a) * win >= min_on]      # 太短的當雜音
    ons = np.array([(b - a) * win for a, b in merged])
    gaps = np.array([(merged[i+1][0] - merged[i][1]) * win for i in range(len(merged) - 1)])
    return merged, ons, gaps, m * win
if __name__ == "__main__":
    x, sr = load(sys.argv[1]); seg, ons, gaps, dur = analyse(x, sr)
    t0, t1 = seg[0][0] * 0.005, seg[-1][1] * 0.005; span = t1 - t0
    print("錄音 %.1f s;第一筆到最後一筆 %.1f s" % (dur, span))
    print("筆畫數 %d  → 每秒 %.2f 筆" % (len(seg), len(seg) / span))
    print("落筆(on-sheet)平均 %.0f ms(中位數 %.0f, SD %.0f)" % (ons.mean()*1e3, np.median(ons)*1e3, ons.std()*1e3))
    print("空中(in-air)平均 %.0f ms(中位數 %.0f, SD %.0f)" % (gaps.mean()*1e3, np.median(gaps)*1e3, gaps.std()*1e3))
    print("空中時間占比 %.1f%%" % (100 * gaps.sum() / (ons.sum() + gaps.sum())))

**第五步,對照預測(都是我的猜測,不是文獻值)。**預測一:條件 B(快)的每秒筆畫數高於條件 A,空中時間平均縮短;對照論文「空中時間與表現強負相關」的方向。預測二:快寫時落筆時間也會縮短,但縮短的比例小於空中時間(論文的訊號是空中時間而不是落筆時間;我的預測只是從這個方向推的)。預測三:把門檻從 f_hi=0.2, f_lo=0.08 改成 0.3, 0.15 重跑同一個檔,空中平均時間在合成資料上約增加 7%(1.10 到 1.18);若真人錄音的改變超過 15%,表示包絡不乾淨(背景聲音或連筆造成的模糊邊緣),此時絕對時間不可信,只看筆畫數。預測四:每秒筆畫數落在 2 到 5 之間(普通速度的連續書寫,我沒有中文的實測依據,這個範圍很可能偏寬)。

**第六步,品質檢查。**手動數出寫了幾個字,與程式的筆畫數比:連筆或字內筆畫連成一段時,筆畫數會小於字內筆畫總數,這就是「接觸段」與「筆畫」的差別。若寫的過程中筆碰到桌子、紙張移動、衣袖摩擦,會造成假接觸段,整段重錄。每條件至少錄 3 次取中位數,3 次之間的筆畫數差超過 5% 就代表環境不穩。結論只適用於你自己、當天、這支筆這張紙。

🔗 延伸學習


💬 問 AI

我讀了 Toffoli 等人 2026(PLOS Digital Health,707 位義大利小一到國二學生,50 赫茲感測墨水筆寫 60 秒,預測 BVSCO-3 的 z ≤ −1.5)。請逐項回答,並嚴格區分三類:(一)論文或文獻明確寫了的、(二)你能查證或憑訓練資料確定的、(三)純屬推論的,每項標明把握程度(高/中/低);查不到或不確定就直說,不要編造文獻、數字或連結。這是學習與實驗設計,不是臨床診斷。
 
【A 組:我算到的】
1. 我用論文公布的召回率與精確率倒推混淆矩陣:UNO 草寫 575 人(有風險 266)TP 205、FP 39、FN 61、TN 270;NUM 草寫 569 人(有風險 288)TP 233、FP 38、FN 55、TN 243,還原準確率 0.826 與 0.837(論文 0.82、0.84)。這個倒推方法有什麼我沒想到的陷阱(例如捨入、精確率的定義)?
2. 草寫體「有風險」佔 46% 到 51%,論文說是常模不匹配。全猜有風險的 F1 是 0.633 與 0.672,模型是 0.80 與 0.83。這樣比基準線合理嗎?還有什麼更合適的基準(例如只用 Relative Stroke Number 一個變項)?
3. 同樣的召回 0.77、特異度 0.874,有風險比例 6.7% 時陽性預測值 30%,46% 時 84%。在什麼條件下「特異度與召回率不隨族群改變」的假設會明顯失效?
 
【B 組:文獻】
4. 論文的「平均空中時間」與表現強負相關。成人、中文字、硬筆書寫有沒有報告過類似的「空中時間 / 落筆時間」拆分?請只列你確定存在的,並說明是否讀過全文。
5. Lacquaniti、Terzuolo 與 Viviani 1983(Acta Psychologica 54:115–130)的摘要與主要結論,你憑訓練資料知道多少?哪些部分你不確定?
 
【C 組:實驗設計】
6. 我打算用手機錄音量筆畫數與空中時間:5 毫秒 RMS 包絡、遲滯門檻(高 20%、低 8% 的範圍分位數)、空檔小於 30 毫秒視為同一段。我在合成資料上測到 20 分貝下落筆低估約 5%、空中高估約 10%。真人書寫(連筆、衣袖、桌面震動)還有哪些系統偏差?如何在家驗證門檻選擇?
7. 我預測快寫比慢寫的空中時間縮短比例大於落筆時間縮短比例。要怎麼設計才能區分「真的是空中時間縮短」與「錄音法偏差造成的假象」?
8. 把所有存疑項目分成三堆:(一)憑訓練資料就能判斷的、(二)必須查一手文獻或論文補充資料的、(三)必須用實際書寫資料才能回答的。