文本水印实战:互信息、熵预算与最优传输的工程拆解
1. 从水印这个词说起为什么文本溯源突然成了刚需过去两年我参与过好几个内容平台的文本审核与溯源项目最直观的感受是判断一段文字是不是机器生成的比判断一张图是不是P的还要难。图片有像素级的痕迹音频有频谱特征但文本一旦落到字符层面几乎就是纯净的——你很难从今天天气不错这七个字里看出它出自人脑还是模型。这就是文本水印要解决的问题。它的目标不是给文本打标签让人一眼看见而是在生成过程中埋入统计上可检测、但人眼几乎无感的信号使得拿到一段文本后能通过算法判断它是否由某个特定模型生成。这件事的价值不用我多说内容溯源、版权归属、批量生成内容识别、学术诚信核查全都依赖它。标题里提到的互信息、熵预算与最优传输是三个听起来很学术、但实际对应着水印设计里三个核心工程问题的概念。我把它翻译成人话互信息衡量埋进去的信号和最终输出的文本之间到底关联有多强。关联太弱检测不出来关联太强文本质量崩了。熵预算模型每一步生成时候选词的概率分布是有不确定性的。不确定性高的位置熵大适合埋水印不确定性低的位置熵小硬埋就会破坏语义。熵预算就是决定在哪些位置、花多少不确定性额度去埋信号。最优传输在改动分布的时候怎么用最小的代价把原始概率分布搬到带水印的分布上让文本自然度和检测强度达到平衡。这篇博文我不打算写成论文综述而是按一个实际做过文本水印落地的人的视角把 textGrain 这套机制拆开讲清楚它每一步在干什么、为什么这么设计、参数怎么调、实测中会遇到什么坑。如果你正在做AIGC内容治理、模型输出溯源或者单纯想搞懂文本水印到底靠不靠谱这篇应该能给你省下不少翻论文的时间。需要先说明一点下面涉及的具体算法细节一部分来自公开的技术思路一部分是我基于常见工程实践做的合理推演和补充我会在关键处标注哪些是我的理解/推测避免误导。2. textGrain 的整体骨架水印到底埋在哪一层2.1 为什么不在字符层动手而要在生成分布层动手很多人第一反应是水印嘛改几个字、换几个同义词不就行了。这个思路在工程上基本走不通原因有两个。第一同义词替换太脆弱。任何一次改写、翻译、润色甚至用户手动改两个词水印就没了。你要的是抗扰动的信号而不是一碰就碎的标记。第二字符级改动容易被统计检测出来。如果一段文本里某些特定词出现频率异常高或者某些句式反复出现检测方甚至普通读者很容易察觉这段文字怪怪的。textGrain 这类机制的核心思路是不去改已经生成的文本而是在生成过程中对模型每一步输出的概率分布做一次带偏。模型本来要按原始分布采样下一个词现在我们在分布上叠加一个微弱的、由密钥控制的偏置让某些词被选中的概率略微提高。最终文本仍然是模型自然生成的但整体上携带了一个统计信号。这个思路的好处是水印和文本是长在一起的不是贴上去的。只要生成过程用了这个偏置文本就天然带着信号而检测方只要知道密钥就能反向统计出这个偏置是否存在。2.2 三个模块的分工我把 textGrain 的流程拆成三个模块对应标题里的三个关键词模块对应概念核心职责信号注入互信息决定偏置强度保证信号可检测又不伤文本位置选择熵预算决定在哪些 token 位置埋信号哪些位置放过分布搬运最优传输用最小代价把原始分布搬到带水印分布这三个模块不是独立的而是互相制约的。互信息给高了熵预算就得收紧否则文本崩最优传输的代价也会上升。反过来如果熵预算放得很宽互信息可以调低一点文本更自然但检测更难。整个调参过程本质上是在这三者之间找平衡点。我个人的经验是先定检测强度目标再反推互信息下限最后用熵预算和最优传输去兜文本质量。顺序反了会很难调。2.3 一个容易忽略的前提水印是生成时埋的这里必须强调一个工程现实textGrain 这类机制只能在生成阶段生效。也就是说它要求你对模型的采样过程有控制权。如果你只是拿到一段已经生成好的文本想事后补一个水印进去那是另一套技术路线通常靠改写鲁棒性差很多。所以落地时第一个要确认的问题是你的生成链路是否可控。如果是自部署模型直接改采样逻辑即可如果是调用外部API那就要看对方是否提供水印参数。这一点在项目立项时就要想清楚否则后面全是白工。3. 互信息水印强度和文本质量的那根钢丝3.1 互信息在这里到底衡量什么互信息Mutual Information在信息论里衡量两个随机变量之间的依赖程度。放到水印场景里它衡量的是密钥控制的偏置信号和最终生成的文本之间到底有多少信息是共享的。打个比方。假设密钥规定遇到位置A时把的的概率提高5%。如果模型本来就有80%概率选的那这5%的偏置几乎不改变结果互信息很低检测方很难判断这段文本到底有没有被偏置过。反过来如果偏置强到让的的概率从10%飙到90%互信息很高检测很容易但文本会变得极其不自然——满篇都是的。所以互信息本质上是一个信噪比的度量。水印信号是信模型本身的随机性是噪。互信息越高信号越强但文本质量代价越大。3.2 为什么不能简单地把互信息拉满我见过一些初版方案思路是既然要检测那就把偏置开到最大。实测下来这条路走不通原因有三文本质量断崖式下跌。偏置一强模型原本的语言能力被压制输出会变得重复、机械甚至语义错乱。检测反而变难。这听起来反直觉但确实如此如果偏置强到改变了文本的整体分布特征检测方用简单的统计方法就能识别这段文本分布异常反而不需要密钥了。而水印的理想状态是只有知道密钥的人才能检测不是谁都能看出不对劲。抗改写能力下降。偏置越强文本越畸形任何一次轻度改写都会把这种畸形抹平水印随之消失。所以互信息的设定原则是在满足检测置信度的前提下取最小值。这个最小值怎么定通常做法是先设定一个检测的假阳性率目标比如百万分之一然后反推需要多强的信号才能在这个置信度下被检出。3.3 实操中怎么估互信息的下限这里给一个我实际用过的估算思路不涉及具体公式讲逻辑先确定检测统计量。常见的是对每个被偏置的位置统计实际选中的词是否落在被提升的集合里然后累加得到一个分数。在无水印的零假设下这个分数服从某个分布通常是近似正态。根据你想要的假阳性率确定判定阈值。在有水印的备择假设下每个位置的命中概率会因为偏置而提高。你需要让整段文本的分数期望超过阈值且方差足够小。反推每个位置需要的最小偏置强度再换算成互信息。这套流程的关键是文本长度。文本越长可累加的位置越多单个位置的偏置就可以越弱。所以短文本水印天生比长文本难做——这也是为什么很多方案对一句话级别的检测效果很差。提示如果你的业务场景里短文本占比高别指望单靠互信息调参解决得从位置选择也就是熵预算上想办法把有限的位置用在刀刃上。4. 熵预算把有限的水印额度花在刀刃上4.1 熵高和熵低的位置差别到底在哪模型生成每个词时都会输出一个概率分布。这个分布的熵反映了模型有多不确定。高熵位置比如一句话的开头、转折处、话题切换点。模型自己也在纠结选哪个词候选词概率比较分散。这种位置埋水印改动一个词的相对概率对整体语义影响小文本依然自然。低熵位置比如固定搭配、专有名词、语法强约束处。模型几乎确定要选某个词概率90%以上。这种位置硬埋水印要么改不动偏置被淹没要么改动了就语义崩坏。所以熵预算的核心思想是把水印信号优先分配到高熵位置低熵位置尽量放过。这就像预算有限时把钱花在回报率最高的地方。4.2 熵预算的具体分配策略我实践下来比较稳的做法是分三步第一步逐位置计算熵。对生成过程中的每个 token 位置算出当前分布的熵值。这一步是纯计算没有额外开销。第二步设定熵阈值。低于阈值的低熵位置直接跳过不埋水印。阈值怎么定我的经验是从分布的百分位数入手比如只对熵值排在前60%的位置埋信号。具体数字要看你的文本类型——技术文档低熵位置多散文高熵位置多阈值要分别调。第三步按熵值加权分配强度。不是所有高熵位置都埋一样强的信号而是熵越高、埋得越强。这样整体信号强度在文本内部分布更均匀检测统计量也更稳定。用一个表格对比不同策略的效果以下是我在某次内部测试中的观察非精确数据仅示意趋势策略文本自然度检测准确率抗改写能力全位置均匀埋较差高弱仅高熵位置埋好中高中高熵位置加权埋好高中高低熵位置也埋差中弱可以看到高熵位置加权埋是综合最优的。低熵位置硬埋是典型的负收益操作既伤文本又帮不上检测。4.3 熵预算和互信息的联动这两个概念不是割裂的。你可以把总的水印额度理解为一个固定预算熵预算决定这个额度怎么在位置间分配互信息决定总额度有多大。实际调参时我会这样操作先固定一个互信息目标比如保证检测假阳性率在可接受范围。然后调整熵阈值观察文本质量指标比如困惑度、人工评分。如果文本质量不达标就提高熵阈值只埋更高熵的位置同时适当提高剩余位置的偏置强度来补偿检测强度。反复迭代直到检测和文本质量都达标。这个过程的坑在于熵阈值提得太高可埋位置太少检测强度就上不去。这时候要么接受更弱的检测要么就得靠最优传输去抠出更多可用的信号空间。5. 最优传输用最小代价完成分布搬运5.1 为什么需要搬运而不是直接加偏置最朴素的水印做法是在原始概率分布上给被选中的词加一个固定值然后重新归一化。这个做法简单但有个问题它不考虑原始分布的形状。举个例子。假设原始分布是 [0.5, 0.3, 0.2]你想把第一个词的概率提上去。直接加0.2变成 [0.7, 0.3, 0.2]归一化后是 [0.58, 0.25, 0.17]。这个改动对第二个词的影响其实不小而且改动方向和最小代价未必一致。最优传输Optimal Transport的思路是把原始分布看作一堆质量要把它搬到目标分布搬运过程中每个单位质量的移动都有代价目标是总代价最小。用这个框架来设计偏置就能保证改动是最经济的——对分布形状的破坏最小。5.2 代价函数怎么定义代价函数决定了什么样的改动算便宜。在水印场景里代价通常和语义距离挂钩把概率从词A搬到词B如果A和B语义相近代价低如果八竿子打不着代价高。实际实现中语义距离可以用词向量距离、或者模型自身的隐层表示距离来近似。我见过用词嵌入余弦距离的也见过直接用 token 共现统计的效果各有优劣词嵌入距离语义敏感但需要额外的嵌入模型计算开销大。共现统计轻量但只能捕捉表层关联对语义相近但少共现的词不敏感。模型隐层距离最贴合生成语境但要在采样时实时算工程复杂度高。我的建议是如果生成延迟敏感用共现统计如果质量优先用隐层距离。词嵌入方案介于两者之间适合作为默认选项。5.3 最优传输求解的工程简化严格求解最优传输问题比如用 Sinkhorn 算法在每一步采样时都跑一遍开销是很大的。实际落地时几乎都会做简化只在候选词子集上求解。不需要对全部词表做搬运只对概率最高的前K个词做其余词直接忽略。K取20到50通常够用。预计算传输方案。如果密钥和位置是确定的传输方案可以离线算好在线只做查表和归一化。用近似解代替精确解。Sinkhorn 迭代几次就停精度损失对水印检测影响很小。这里有个我踩过的坑早期版本为了追求最优每步都跑完整求解结果生成速度掉了将近40%。后来改成前K词预计算速度基本恢复到基线检测效果几乎没损失。所以别迷信最优两个字工程上够用比最优重要得多。6. 检测端拿到文本后怎么判断有没有水印6.1 检测的基本逻辑检测端要做的事本质上是反向验证给定一段文本和密钥重新计算如果这段文本是带水印生成的那么每个位置的偏置应该是什么方向然后统计实际文本是否符合这个方向。具体来说用密钥和文本的上下文重建每个位置的水印偏置方向哪些词被提升了。检查实际选中的词是否落在被提升的集合里。累加命中情况得到一个总分。把总分和阈值比较超过阈值判定为有水印。这个逻辑成立的前提是检测端能复现生成端的偏置计算。所以密钥、位置选择规则、代价函数这些必须完全一致否则检测会失效。6.2 检测的置信度和文本长度的关系前面提过文本越长检测越准。这里给一个直观的解释每个位置的命中是一个弱信号单个位置可能碰巧命中假阳性但多个位置的命中累加起来随机碰巧的概率就指数级下降。所以检测阈值和文本长度是绑定的。短文本要么放宽阈值假阳性升高要么接受更低的检出率。实际业务里我通常会给不同长度区间设不同的阈值而不是一刀切。文本长度token数建议假阳性率备注 501e-2短文本检测能力有限50 - 2001e-3中等长度可用200 - 10001e-5长文本检测可靠 10001e-6很长几乎不会误判这张表是我根据实际测试总结的经验值具体数字要按你的业务容忍度调整。核心结论是别对短文本水印抱太高期望这是机制本身的限制不是调参能解决的。6.3 抗改写检测端的真正考验水印最怕的是改写。用户拿到文本后翻译一遍、润色一遍、或者用另一个模型重写一遍水印还在不在实测下来textGrain 这类基于分布偏置的水印对轻度改写有一定鲁棒性对重度改写基本失效。原因是轻度改写比如换几个同义词保留了大部分原始 token偏置信号还在重度改写比如整句重写会打乱位置对应关系检测端重建的偏置方向和实际文本对不上。提升抗改写能力的思路有几个在多个粒度上埋水印。不只在 token 层还可以在句子层、段落层埋这样局部改写不会全灭。用更鲁棒的统计量。不依赖精确的位置对应而是看整体分布偏移。接受抗改写有限这个现实。水印不是万能的它的定位是提高溯源门槛不是绝对不可去除。我个人对水印的定位一直很清醒它是提高作恶成本的工具不是绝对防线。指望一个水印机制能扛住所有攻击那是不现实的。7. 落地时最容易翻车的几个地方7.1 密钥管理水印的命门水印的安全性完全依赖密钥。密钥泄露等于水印形同虚设——任何人都能伪造或检测。所以密钥管理必须当成核心安全资产来做密钥不能硬编码在代码里。这是最常见的低级错误一旦代码泄露密钥就没了。不同业务用不同密钥。这样即使一个业务的密钥泄露不会影响其他业务。密钥要能轮换。长期用同一个密钥被逆向的风险随时间累积。我见过一个项目密钥直接写在配置文件里提交到了代码仓库结果整个水印机制等于白做。这种坑一次就够记一辈子。7.2 生成延迟别让水印拖垮体验水印计算是有开销的。熵计算、最优传输求解、偏置应用每一步都要时间。如果生成本身是实时场景比如对话水印开销必须控制在很小的比例内。我的经验是水印开销控制在生成总时间的5%以内用户基本无感。超过10%就能明显感觉到变慢。优化手段前面提过前K词、预计算、近似求解。另外熵计算和偏置应用可以并行化也能省不少时间。7.3 多模型混用水印会互相打架如果一个平台同时用多个模型生成内容每个模型的水印机制可能不一样。这时候检测端就麻烦了它不知道该用哪套规则去检测。解决方案有两种一是统一水印协议所有模型用同一套机制只是密钥不同二是在文本里嵌入模型标识检测端先识别模型再选规则。前者简单后者灵活。我倾向于前者因为统一协议能大幅降低检测端的复杂度。7.4 误判的代价假阳性比假阴性更可怕检测水印时假阳性把无水印文本判成有水印的代价通常比假阴性高得多。因为假阳性意味着你可能错误地指控一段人类写的文本是机器生成的这在业务上是严重的。所以阈值设定要偏保守宁可漏检不可误判。具体来说假阳性率要压到业务能接受的最低水平哪怕牺牲一些检出率。这个取舍在项目初期就要和业务方对齐别等出了事故再扯皮。8. 我对这套机制的一点个人判断做了几个相关项目之后我对文本水印这件事的看法逐渐清晰它是一个够用就好的工具不是一个追求完美的课题。从技术上看互信息、熵预算、最优传输这套组合拳已经把在保证文本质量的前提下埋入可检测信号这件事做得相当扎实。textGrain 这类机制的价值在于它把水印从事后打标变成了生成时融合鲁棒性和隐蔽性都上了一个台阶。但从工程上看水印的成败往往不在算法而在密钥管理、延迟控制、阈值取舍这些脏活上。我见过算法很漂亮但密钥管理稀烂的项目也见过算法一般但工程扎实、实际效果很好的项目。所以如果你要做这件事别把全部精力放在调算法上工程细节同样决定成败。最后一个实际建议上线前一定要做对抗测试。找几个人让他们尝试改写、翻译、润色带水印的文本看水印还能不能检出。这个测试能暴露的问题比任何理论分析都直接。我每次做水印项目这一步都是必做的而且往往能发现一些论文里根本不会提的边界情况。