Skip to content

写作过程检测

把 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 级别的标记

覆盖阈值

yaml
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)。对一句话理由说明合适的界限,用在五段的文章上就是错的。

校准把每个阈值设在你自己项目会话的某个尾部百分位上。

bash
# 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.yaml
yaml
keystroke_logging:
  detection:
    calibrate: true    # use the saved fit

校准至少需要 30 个可用会话(80 字符或以上,非移动端)。少于这个数量时它返回 insufficient_data,默认值继续生效。

警告: 百分位界限是一种相对的离群定义。按其构造,即便在一个谁都没做错事的人群里,它也会标出大约 tail_fraction(默认 5%)的会话。它告诉你先看哪里。它不是证据。校准后的阈值还会被额外限制在内置默认值的 3 倍以内,这样一个同质化的人群就没法把界限拖到自己的中位数上,进而开始标记诚实的标注者。

显式的 thresholds: 覆盖总是压过校准值,校准值又总是压过内置默认值。

第 3 层:有监督分类器

如果你有带标签的会话,就训练一个真正的模型:

python
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_dominantsilent_insertionoffscreen_composition通过 paste_source: instance_text 抑制
挪动自己的草稿同上通过 paste_source: self 抑制
用手机或平板打字silent_insertion通过 virtual_keyboard 抑制
通过 IME 输入非拉丁文字silent_insertion通过 composition_events 抑制
打字又快又流畅、一稿干净transcription_rhythmimplausible_speed合取规则;判为 review 而非 suspect;可校准
非常短的回答transcription_rhythm80 字符 / 40 次击键以下跳过
听写或语音转文字silent_insertion未处理。会被标记。请排除这些标注者或调高阈值。
屏幕阅读器和部分辅助技术synthetic_input未完全处理。某些工具会产生不可信事件。
移动端自动纠错silent_insertion部分处理。insertReplacementText 计为外部来源。
语法工具之类的浏览器扩展silent_insertionsynthetic_input未处理。会被标记。

最后四项是真实的局限,而不是疏漏。如果你的参与者中包含听写用户、辅助技术用户,或使用写作扩展的人,要么把这些规则从你的审查标准里去掉,要么把每一个标记都当作去问一问标注者的提示,而不是采取行动的依据。

无障碍

synthetic_input 依据 isTrusted 判断,而某些辅助技术也会触发它。因为一位残障标注者使用了自己工作所需的工具就把他标记出来,是不可接受的结果。

那条规则没有阈值,所以你没有任何值可以设来让它永不触发。如果你的研究对辅助技术使用者开放,要么把 synthetic_input 从你写下的审查标准里排除,要么彻底关闭检测,自己分析导出的特征:

yaml
keystroke_logging:
  enabled: true
  detection:
    enabled: false

基于阈值的规则可以通过设一个荒唐的界限来让它永不触发,例如 implausible_speed.chars_per_min: 1000000

实时干预

yaml
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 关于固定停顿阈值为何有偏的论述。

参考文献

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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
  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
  9. 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

延伸阅读

有关实现细节,请参阅源代码文档