写作过程检测
把 Potato 的击键日志变成可审计的标记,指出哪些自由文本答案是粘贴、誊抄或机器生成的,阈值你都能读懂、也能覆盖。
写作过程检测读取击键记录采集到的打字动态,产出附带证据的具名标记,这样你可以看到是哪些特征值触发了每一个标记,并把你不认同的那些丢掉。
它需要 Potato 2.7.2 或更高版本,而且只在已经开启击键记录的地方运行。既然 keystroke_logging.enabled 默认为 false,一个从未主动开启的项目也就没有什么可检测的。
警告: 标记是供人审查的证据。它们不是不端行为的证明,也绝不能接到自动拒绝、扣款或封禁参与者上。一个打字又快又流畅、一稿干净写完的人,确实很像誊抄。一个用手机标注的人,确实很像在粘贴。下面这些规则就是为减少这类撞车而设计的,撞车还是会发生。误报一节是这个功能的组成部分,不是免责声明。
三个层次
| 层次 | 是什么 | 需要带标签的数据吗? | 默认 |
|---|---|---|---|
| 1. 规则 | 六个具名标记,阈值明确、证据可见 | 否 | 开 |
| 2. 校准 | 用你自己项目的标注者重新拟合阈值 | 否 | 关 |
| 3. 有监督 | 你用自己的标签训练的分类器 | 是 | 关 |
Potato 不随附任何预训练模型。仓库里没有带标签的语料,所以我们发布的任何系数都会带着一个我们自己编出来的验证数字。作为替代,你拿到的是六条你能读懂、也能反驳的规则。如果你有真实标签,第 3 层才是复现文献中那些准确率的路径。
第 1 层:规则
每个标记都会返回触发它的特征值,所以任何一条你都可以为之辩护,或者弃之不用。
paste_dominant
严重程度:suspect。在 pasted_fraction >= 0.5 时触发,即最终文本中有一半或以上来自粘贴。
当所有粘贴都被归类为 self(重新排布自己的草稿)或 instance_text(引用正在标注的材料)时抑制。
silent_insertion
严重程度:suspect。在 external_insert_ratio >= 0.3 时触发,即插入的字符中将近三分之一在出现时没有对应的击键。
这是价值最高的单一信号。粘贴、自动填充、听写和程序化注入都会在这里显现,即便粘贴事件本身被页面拦下也一样。它直接把 Asher 等人所说的「击键数相对于回答长度异常偏低」操作化了。
在软键盘(virtual_keyboard)上和 IME 组字期间抑制,那些情况下 keydown 不会被可靠地发出,否则每一次插入看起来都是无声的。这条规则用的是区分来源的比率,所以正当的引用不会被计入。
transcription_rhythm
严重程度:review。当以下三条同时成立时触发:
iki_log_cv <= 0.06,节拍均匀的打字节奏revision_ratio <= 0.02,基本没有删除- 每 100 个字符里 2 秒及以上的停顿少于 0.5 次
有意做成合取。任何一条单独看都有无辜的解释。三条合在一起,才是 Crossley 等人描述的誊抄特征:线性、成串爆发、低方差。
对 80 字符或 40 次击键以下的回答完全跳过,那里谈不上什么节奏。
offscreen_composition
严重程度:suspect。当一段较大的外部来源插入(80 字符或以上)紧跟在离开页面 10 秒或更久之后出现时触发。这就是「切到 ChatGPT,回来,粘贴」的模式。
当插入内容的来源是正在标注的材料或标注者自己的文本时抑制,因为走开一会儿再回来引用材料是很正常的行为。
implausible_speed
严重程度:review。在整份回答中持续超过每分钟 900 字符(约 180 wpm)时触发。
synthetic_input
严重程度:suspect。在任何 isTrusted === false 的事件上触发,也就是浏览器报告该输入由脚本而非人产生。浏览器自动化、注入的脚本和某些无障碍工具都会产生这种事件。
判定等级
| 等级 | 含义 |
|---|---|
ok | 没有标记触发 |
review | 至少有一个 review 级别的标记 |
suspect | 至少有一个 suspect 级别的标记 |
覆盖阈值
keystroke_logging:
detection:
thresholds:
paste_dominant.pasted_fraction: 0.4
silent_insertion.ratio: 0.25
transcription_rhythm.iki_log_cv: 0.05
transcription_rhythm.revision_ratio: 0.02
transcription_rhythm.pause_2s_per_100_chars: 0.5
offscreen_composition.blur_ms: 15000
offscreen_composition.insert_chars: 100
implausible_speed.chars_per_min: 1000阈值在服务端评估,从不发送到浏览器。公布它们等于精确地告诉标注者要多慢地粘贴才能不被标记。
第 2 层:项目校准
击键特征会随写作任务有相当大的差异(Conijn 等人 2019),而在一个混合分布的量上用固定阈值是有偏的(Roeser 等人 2021)。对一句话理由说明合适的界限,用在五段的文章上就是错的。
校准把每个阈值设在你自己项目会话的某个尾部百分位上。
# Inspect the fit without saving it
python -m potato.typing_detect calibrate config.yaml --dry-run
# Save it
python -m potato.typing_detect calibrate config.yamlkeystroke_logging:
detection:
calibrate: true # use the saved fit校准至少需要 30 个可用会话(80 字符或以上,非移动端)。少于这个数量时它返回 insufficient_data,默认值继续生效。
警告: 百分位界限是一种相对的离群定义。按其构造,即便在一个谁都没做错事的人群里,它也会标出大约
tail_fraction(默认 5%)的会话。它告诉你先看哪里。它不是证据。校准后的阈值还会被额外限制在内置默认值的 3 倍以内,这样一个同质化的人群就没法把界限拖到自己的中位数上,进而开始标记诚实的标注者。
显式的 thresholds: 覆盖总是压过校准值,校准值又总是压过内置默认值。
第 3 层:有监督分类器
如果你有带标签的会话,就训练一个真正的模型:
from potato import typing_store
from potato.typing_detect import fit_supervised
rows = typing_store.feature_matrix(task_dir, project)
labels = [...] # 1 = non-composed, 0 = composed
result = fit_supervised(rows, labels, model="random_forest")
print(result["cv_accuracy_mean"], result["feature_importances"])需要 scikit-learn,它是延迟导入的,不是 Potato 的依赖。
不做外部研究也能拿到标签
Potato 的培训阶段可以替你生成标签。让标注者把一段给定的材料抄一遍作为热身任务。他们的誊抄会话就是真实的誊抄样本,他们平常的答案就是撰写样本,于是你在自己的项目里、在自己的任务上、用自己的标注者拿到了一个带标签的数据集。这与 Crossley 等人构建语料的方式相同,校准示例把这套流程端到端地搭好了。
误报
这个功能的失败模式是冤枉一位诚实的标注者。下面这些情况确实会像上面那些模式。
| 情形 | 像哪条规则 | Potato 如何处理 |
|---|---|---|
| 引用正在标注的材料 | paste_dominant、silent_insertion、offscreen_composition | 通过 paste_source: instance_text 抑制 |
| 挪动自己的草稿 | 同上 | 通过 paste_source: self 抑制 |
| 用手机或平板打字 | silent_insertion | 通过 virtual_keyboard 抑制 |
| 通过 IME 输入非拉丁文字 | silent_insertion | 通过 composition_events 抑制 |
| 打字又快又流畅、一稿干净 | transcription_rhythm、implausible_speed | 合取规则;判为 review 而非 suspect;可校准 |
| 非常短的回答 | transcription_rhythm | 80 字符 / 40 次击键以下跳过 |
| 听写或语音转文字 | silent_insertion | 未处理。会被标记。请排除这些标注者或调高阈值。 |
| 屏幕阅读器和部分辅助技术 | synthetic_input | 未完全处理。某些工具会产生不可信事件。 |
| 移动端自动纠错 | silent_insertion | 部分处理。insertReplacementText 计为外部来源。 |
| 语法工具之类的浏览器扩展 | silent_insertion、synthetic_input | 未处理。会被标记。 |
最后四项是真实的局限,而不是疏漏。如果你的参与者中包含听写用户、辅助技术用户,或使用写作扩展的人,要么把这些规则从你的审查标准里去掉,要么把每一个标记都当作去问一问标注者的提示,而不是采取行动的依据。
无障碍
synthetic_input 依据 isTrusted 判断,而某些辅助技术也会触发它。因为一位残障标注者使用了自己工作所需的工具就把他标记出来,是不可接受的结果。
那条规则没有阈值,所以你没有任何值可以设来让它永不触发。如果你的研究对辅助技术使用者开放,要么把 synthetic_input 从你写下的审查标准里排除,要么彻底关闭检测,自己分析导出的特征:
keystroke_logging:
enabled: true
detection:
enabled: false基于阈值的规则可以通过设一个荒唐的界限来让它永不触发,例如 implausible_speed.chars_per_min: 1000000。
实时干预
keystroke_logging:
detection:
on_external_insert: flag # allow | warn | block | flag| 值 | 行为 |
|---|---|
allow | 除记录之外什么都不做 |
warn | 外部粘贴时给出不阻断的提示,自引和引用材料时不提示 |
block | 阻止往被记录的字段粘贴和拖放 |
flag | 默认。静默记录;由你稍后决定 |
block 是个钝器。它同样会挡掉正当的引用,而且铁了心的参与者可以改成手打。用 flag 再回头审查,通常更好。
管理员仪表板
管理员仪表板行为标签页下的写作过程面板,展示每位标注者的中位数、粘贴率、无声插入率,以及每一个被标记的会话及其证据。
它报告 writing_process_risk,即一位用户的会话中触发了各个标记的比例,并向那些最难有无辜解释的信号加权。这是一个排序辅助,刻意与既有的 suspicion_score 分开,免得两个数字里的一个悄悄改变了另一个的含义。
研究依据
下面每一条引用都已对照 Crossref 或 DataCite 注册记录核实。
Crossley、Tian、Choi、Holmes 和 Morris(2024)收集了 500 篇论辩文章,让另一批工作者誊抄这些文章,并用随机森林以 99% 的准确率把真实写作与誊抄分开(其他模型为 96-98%)。他们的特征族正是 Potato 采集的那些:句子和词语之前的停顿时长、插入和删除次数、产品-过程比、爆发、修改和过程方差。他们的结论就是这里的设计目标。真实写作表现出更长的停顿、更多的插入和删除,以及更大的方差;誊抄则是线性的、成串爆发的。
Deane、Zhang、Hao 和 Li,以及另外一组 Zhang、Feng、He、Li 和 Zhu,各自独立证实了誊抄与自然写作之间的可分性,后者用的是深度学习模型。Asher、Gold、Chen 和 Carvalho 则是专门针对众包的案例:在 Prolific 上用一个击键工具标出往回答框里粘贴、或击键数相对于回答长度异常偏低的参与者。
关于底层的过程测量,可参考 Leijten 和 Van Waes(Inputlog)的标准日志测量、Chenoweth 和 Hayes 关于爆发这一构念的工作、Conijn、Roeser 和 van Zaanen 关于击键特征的任务依赖性,以及 Roeser、De Maeyer、Leijten 和 Van Waes 关于固定停顿阈值为何有偏的论述。
参考文献
- Crossley, S., Tian, Y., Choi, J. S., Holmes, L., & Morris, W. (2024). Plagiarism Detection Using Keystroke Logs. EDM 2024 (Short Papers). doi:10.5281/zenodo.12729864
- Deane, P., Zhang, M., Hao, J., & Li, C. Using Keystroke Dynamics to Detect Nonoriginal Text. Journal of Educational Measurement, 63(1). doi:10.1111/jedm.12431
- Asher, M. W., Gold, G., Chen, E., & Carvalho, P. F. (2026). Chatbots Are Undermining Crowdsourced Research in the Behavioral Sciences: Detecting Artificial Intelligence-Assisted Cheating With a Keystroke-Based Tool. Advances in Methods and Practices in Psychological Science, 9(1). doi:10.1177/25152459261424723
- Zhang, M., Feng, L., He, X., Li, C., & Zhu, M. (2026). Disentangling copy typing and natural writing behaviors using keystroke logs and deep learning model. Assessing Writing. doi:10.1016/j.asw.2026.101070
- Leijten, M., & Van Waes, L. (2013). Keystroke Logging in Writing Research: Using Inputlog to Analyze and Visualize Writing Processes. Written Communication, 30(3), 358-392. doi:10.1177/0741088313491692
- Chenoweth, N. A., & Hayes, J. R. (2001). Fluency in Writing: Generating Text in L1 and L2. Written Communication, 18(1), 80-98. doi:10.1177/0741088301018001004
- Conijn, R., Roeser, J., & van Zaanen, M. (2019). Understanding the keystroke log: the effect of writing task on keystroke features. Reading and Writing, 32(9), 2353-2374. doi:10.1007/s11145-019-09953-8
- Roeser, J., De Maeyer, S., Leijten, M., & Van Waes, L. (2021). Modelling typing disfluencies as finite mixture process. Reading and Writing. doi:10.1007/s11145-021-10203-z
- Lee, M., Liang, P., & Yang, Q. (2022). CoAuthor: Designing a Human-AI Collaborative Writing Dataset for Exploring Language Model Capabilities. CHI 2022. doi:10.1145/3491102.3502030
延伸阅读
有关实现细节,请参阅源代码文档。