← Back to Blog
AIAlgorithm

Claude 加了文字浮水印,演算法可能怎麼做

2026-08-13

常見問題先回答

先抓住直覺,再往下看演算法

Q1

浮水印是偷偷在文字裡加字元嗎?

不是。這類 watermark 不多塞任何字元,也不靠 metadata。輸出的每個字都是正常的字, 訊號在「模型選了哪些 token」的統計分布裡。所以純文字複製貼上,訊號跟著走。

Q2

為什麼目前只有 Anthropic 驗得出來?

因為決定「偏好哪些 token」的那把 secret 只有它有。這不是加密——內容照樣看得懂、沒有任何東西被藏起來; 比較像是拿著同一把 key 把生成過程重跑一次,檢查這篇文章符合那套偏好的比例是不是異常高。 Anthropic 說會開放第三方偵測,但截至寫這篇時,公開的偵測工具和演算法細節都還沒出來。

Q3

手動改幾個字,不就把 hash 全打亂了?

如果每一步都拿整個前綴去 hash,確實會——改一個 token,後面全部 desync。 所以實務上的設計不會把命運綁在無限長的前綴上,而是只看最近幾個 token, 並把訊號分散到大量位置。改一個字只毀掉附近幾個位置,過幾個 token 就重新同步。

Q4

改 sampling 機率不會傷品質嗎?

一定有 trade-off,但不代表看得出來。關鍵是只在模型本來就猶豫的地方偏: 「有效 / 實用 / 有用」三選一時偏個兩三個百分點,讀起來沒差;1 + 1 = 這種只有一個答案的位置就不該碰。

Q5

所以文章要夠長?

對。每個位置只帶一點點證據,訊號隨 NN 累積、雜訊只隨 N\sqrt{N} 累積,所以可信度大致按 N\sqrt{N} 成長。十幾個 token 幾乎沒有統計能力,上千個才談得上證據。

先認識幾個會出現的詞

不用先讀 watermark 論文;下面六個概念夠我們把整篇走完。

Token

LLM 實際 sampling 的單位,不一定等於一個中文字或一個英文單字。

Logit / Softmax

Logit 是模型給候選 token 的原始分數;softmax 再把它們轉成總和為 1 的機率。

Entropy

這裡只要理解成模型的猶豫程度:候選越平均,entropy 越高;答案越唯一,entropy 越低。

Secret / PRF

PRF 可以先想成帶 secret key 的 hash:知道 key 就能重現同一組偽隨機規則,外界則難以預測。

Green list

經典 toy algorithm 裡,由 secret 規則選出的偏好 token 集合;只是加一點分數,不代表禁止其他 token。

z-score

「實際命中數離隨機預期有幾個標準差?」數字越大,越不像只是運氣。

Anthropic 公開了什麼、沒公開什麼

這篇的切入點是 Claude 新加的文字浮水印,但演算法本身 Anthropic 沒有公開。 為了不把論文裡的做法講成「Claude 就是這樣做」,全文分三層,並且會用標籤標出來:

已公開Anthropic 官方文件確認的內容。
推測學界既有做法,以及據此對 Claude 的合理推測——不是逆向工程的結論。

沒有標籤的段落就是 LLM watermark 這個領域的公開常識。先把已知事實列清楚:

已公開
  • Claude 用兩套互補的標記:文字走 watermark, 產生的 .svg / .png / .jpg 檔案走 C2PA 簽章 metadata。 這是兩件不同的事,後面會說差在哪。
  • 2026-08-02 之後推出的 Claude 模型上線即支援,先前的模型陸續補上。
  • 標記做在模型層,所以 API、Claude、Claude Code、Claude Cowork、Claude Tag, 以及 AWS / Google Cloud / Microsoft Foundry 上的 Claude 都涵蓋,全球一致。
  • 官方措辭是浮水印「是文字的一部分」,所以複製貼上會跟著走, 而且「可能撐過一些編輯」(may persist through some editing)——用的是可能,不是保證。
  • 驗到浮水印只代表這段內容可能被 Claude 處理過,不代表 Claude 是作者—— 因為很多人拿 Claude 潤稿、翻譯、改寫自己的文章。反過來,沒驗到也不代表不是 AI 寫的。
  • 太短的文字不會有可靠訊號。官方沒有給具體字數門檻。
  • Anthropic 說正在讓使用者與第三方能夠偵測,細節「之後的技術文件會說明」。截至 2026-08-13,演算法與公開偵測工具都還沒釋出。

動機方面,多家媒體報導與歐盟 AI Act 的透明度義務有關;Anthropic 自己的說法比較泛, 提到的是合規與透明度承諾。

剩下的就是這篇要談的:在這些約束下,演算法可以怎麼設計。

訊號不在字裡,在選字裡

先排掉幾個容易混在一起的東西。「AI 浮水印」常被想成在文字裡塞看不見的字元(zero-width space 之類), 或是在檔案裡寫 metadata。這兩種都有人做,但都不是 Claude 文字浮水印的路子—— 官方明說浮水印「是文字的一部分」,而塞字元跟 metadata 都會被純文字複製、重新排版洗掉。

做法 A:把額外的東西塞進去做法 B:訊號在「選了哪個 token」字與字之間多了看不見的字元,或訊號放在檔案 metadata 裡複製成純文字、重新排版→ 訊號跟著消失每個字都是正常的字,一個都沒多但落在祕密偏好集合裡的比例偏高複製貼上文字一模一樣→ 訊號還在
圖 1:兩種完全不同的做法。左邊靠額外的載體,右邊靠文字本身的統計結構。

四個常被混為一談的技術,攤開來看:

技術訊號在哪純文字複製後需要 secret能證明什麼
統計式文字 watermarktoken 選擇的統計分布還在要(產生端與偵測端共用)這段文字很可能經過該系統產生或處理
隱藏字元多插入的不可見碼位看情況,很脆弱不一定有人在這串字裡插了東西
檔案 metadata檔案標頭欄位沒了不用檔案自稱來自哪裡
C2PA 簽章附在檔案上的 manifest,用 hash 綁定 bytes不適用(純文字沒檔案可綁)要私鑰這個檔案確實由某方簽發,且 bytes 未被竄改
AI detector沒有訊號,靠文風分類器猜不適用不用看起來像 AI 寫的(誤判率高)

最後兩列特別容易被混淆。C2PA 是密碼學簽章:對檔案 bytes 做 hash、寫進 manifest、 用私鑰簽名,驗證時重新 hash 再驗簽。它能給出「未被竄改」這種強保證,但前提是有檔案、而且 metadata 沒被剝掉。AI detector 則完全沒有訊號可言,它只是個文風分類器,說的是「這看起來像 AI 寫的」。 統計式 watermark 夾在中間:沒有簽章那麼強的保證,但只要文字還在就跟著走。

一個 token 怎麼被偏

LLM 生成時,每一步會對詞表裡每個候選 token 給一個未正規化的分數(logit),再過 softmax 變成機率:

pi=ezi/Tjezj/Tp_i = \frac{e^{z_i/T}}{\sum_j e^{z_j/T}}

白話說就是「分數高的機率大,但不是贏者全拿」,溫度 TT 控制拉開的程度。 接下來只需要記得一件事:watermark 最自然的插入點,就是在 softmax 之前動 logit。

經典做法:green list

推測以下是 Kirchenbauer 等人 2023 年那篇論文的做法,也是這個領域最好懂的模型。不代表 Claude 採用了它,只是用來建立直覺。

持有 secret key KK 的一方,在每一步先算一個偽隨機狀態:

st=PRFK(ct)s_t = \mathrm{PRF}_K(c_t)

PRF\mathrm{PRF}(偽隨機函數)可以先當成「帶 key 的 hash」: 沒有 key 的人看輸出像亂數,知道 key 的人可以穩定重現同一個結果。ctc_t 是這一步用到的 context——選什麼很關鍵,第五節整節在談這件事。

sts_t 當種子,把整個詞表偽隨機切成兩半:green list GtG_t(比例 γ\gamma)和其餘的 red list。 然後給 green 的 token 一點分數:

zi=zi+δ1[iGt]z'_i = z_i + \delta \cdot \mathbf{1}[i \in G_t]

1[]\mathbf{1}[\cdot] 是 indicator:在 green list 裡就是 1,否則 0。 所以整條式子只是說「green 的加 δ\delta,其他不動」,再重新 softmax。

要強調的是:red token 沒有被禁止。如果某個 red token 原本機率 90%, 加了 δ\delta 的競爭者也很難翻盤——它照樣會被選中。 這正是品質不會崩掉的原因。

原始機率分布有效31%實用28%有用24%強大10%加上 watermark bias有效33%實用26%有用25%強大9%secret K最近 k 個 tokenPRF / keyed hash這一步的 green list綠色 = 這一步落在 green list 的候選;沒有任何候選被禁止,只是分數差了一點
圖 2:secret 與最近幾個 token 決定這一步的 green list,green 候選各加 δ 再重新 softmax。數字是 toy example。

注意每一步的 green list 都不一樣——context 換了,PRF 輸出就換了,切法也跟著換。 所以不存在「某些詞永遠被偏好」這種可以直接觀察出來的模式。

產生端大概十行:

python
# toy watermark — 產生端
ctx = prompt_tokens
for _ in range(max_new_tokens):
logits = model(ctx) # 原始 logits
s = PRF(K, ctx[-k:]) # 只餵最近 k 個 token
G = green_set(s, vocab_size, gamma) # 偽隨機挑出 gamma 比例的詞表
logits[G] += delta # green 的各加一點分數
tok = sample(softmax(logits / T)) # 照常 sampling
ctx.append(tok)

除了兩行,其餘跟一般的 decode loop 一樣。這也是為什麼這類方法可以做在模型層、 不必動上層產品——跟官方說的「標記做在模型層,所有產品都涵蓋」是吻合的。

Detector 在算什麼

偵測端拿同一把 key,把文章重新 tokenize,逐位置重算一次 green list,數命中幾個:

python
# toy watermark — 偵測端
hits = total = 0
for t in range(k, len(toks)):
s = PRF(K, toks[t-k:t])
G = green_set(s, vocab_size, gamma)
total += 1
hits += (toks[t] in G)
z = (hits - gamma*total) / sqrt(total * gamma * (1-gamma))

這裡沒有「解密」任何東西,只是重跑一次規則再數數。統計上要問的是: 如果這篇文章跟 watermark 無關,命中數會長什麼樣? 那就是每個位置獨立以機率 γ\gamma 命中,也就是

GBinomial(N,γ)G \sim \mathrm{Binomial}(N, \gamma)

拿實際命中數跟這個基準比,換算成 z-score:

z=GγNNγ(1γ)z = \frac{G - \gamma N}{\sqrt{N\gamma(1-\gamma)}}

逐項拆開來看,這條式子其實沒什麼玄機:

  • γN\gamma N:沒有 watermark 時,預期命中幾個。
  • 分子 GγNG - \gamma N:實際比預期多命中了幾個。
  • 分母 Nγ(1γ)\sqrt{N\gamma(1-\gamma)}:純靠運氣的話,命中數本來就會上下晃動, 這是晃動的標準差。
  • 整體:多出來的部分,相當於幾個標準差

舉個 toy example。γ=0.5\gamma = 0.5、1000 個可計分位置, 一篇沒被 watermark 的文章預期命中 500,標準差 1000×0.2515.8\sqrt{1000 \times 0.25} \approx 15.8。也就是說 470~530 都很正常。 如果實際數到 570:

z=57050015.84.4z = \frac{570 - 500}{15.8} \approx 4.4

單看任何一個 token 都證明不了什麼——它落在 green list 的機率本來就有一半。 但 1000 個位置一起看,多出 70 個命中就不像是運氣了。

Detector 用同一把 key,逐位置重建「當時偏好哪些 token」單一位置只是一次擲硬幣;重點是整段命中比例是否異常A命中B未中C命中D命中E未中F命中G命中H未中I命中J命中null hypothesis正常預期:5 / 10green baseline γ = 0.5observed實際命中:7 / 10多出的命中 = watermark evidence換成 z-score離隨機預期有幾個標準差?10 個 token 只是畫法示意;真實偵測需要更長文字才能建立可靠統計訊號
圖 3:detector 不讀取隱藏資料,而是逐位置重建 secret pattern、數命中,再把與隨機基準的差距換成 z-score。10 個 token 只是流程示意。
推測上面每個數字都是為了算給你看而編的,不是 Claude 的實際參數或效能。 真實系統的 γ\gammaδ\delta、 context 長度、甚至是不是用 green list 這條路,都未公開。

為什麼改幾個字不會整篇失效

這是我一開始最卡的地方。既然每一步的 green list 由 hash 決定,那把中間某個 token 改掉, 後面所有 hash 輸入不就全變了嗎?

如果 context 取整個前綴,這個擔心完全正確:

st=H(K,x1,x2,,xt1)s_t = H(K, x_1, x_2, \ldots, x_{t-1})

hash 的雪崩效應保證輸入差一個 bit、輸出就完全不同。所以改掉第 3 個 token, 第 4 個之後每一步重算出來的 green list 都跟當初生成時不一樣,命中率直接掉回 γ\gamma。整篇的證據從修改點開始歸零。

所以 robust 的設計不會把命運綁在無限長的前綴上。最容易理解的改法是只看最近 k 個 token

st=H(K,xtk,,xt1)s_t = H(K, x_{t-k}, \ldots, x_{t-1})
(a) 每一步都 hash 整個前綴state_t = H(K, x_1 … x_t-1)ABXDEFGHI···X 之後每一步的 hash 輸入都不一樣了 → 後面整段對不上(b) 只看最近 3 個 tokenstate_t = H(K, x_t-3 … x_t-1)ABXDEFGHI···X 還在 window 內X 滑出 window → 重新同步· 沒有完整 window,不計分
圖 4:同一個修改,兩種 context 取法的差別。k=3 時只有三個位置受影響,之後自動重新同步。

k=3k = 3 走一遍。原本是 A B C D E F G H I, 把 C 改成 X:

  • 位置 D 的 window 是 {A, B, X} → 含 X,算出來的 green list 錯了。
  • E 的 window 是 {B, X, D}、F 的是 {X, D, E} → 一樣受影響。
  • G 的 window 是 {D, E, F}X 已經滑出去了, 跟生成時完全一致,命中判斷恢復正常。

一次修改的破壞半徑就是 kk,不是「到文章結尾」。 這就是 self-synchronization:不需要任何額外的同步機制, 單純因為 context 是滑動窗口,錯誤自己會滑出去。

代價也很明顯:kk 越小越耐改,但 context 太短會讓同一組 window 頻繁重複, green list 的切法跟著重複,「每個位置獨立」這個統計假設就開始失真。k=1k = 1 是最耐改的極端,也是統計上最不乾淨的極端。

推測這個方向還有更進階的做法:多組 window 疊加、把訊號做成有冗餘的編碼、 改用不動到期望分布的 sampling(Google 的 SynthID-Text 就是走 tournament sampling 這條路)。 Claude 用的是哪一種、或哪幾種的組合,目前無從得知。

為什麼微調機率不一定傷品質

先講一個沒得繞過的事實。設原始輸出分布是 PP、加了 watermark 之後是 QQ。如果

P=QP = Q

那麼一個只看得到輸出文字的 detector,理論上不可能區分兩者—— 它拿到的樣本來自同一個分布,沒有任何資訊可用。所以想要可偵測,就必須 PQP \neq Q。「完全零影響又能可靠偵測」是矛盾的。

真正的工程問題因此不是「怎麼做到零影響」,而是:怎麼讓單步的 PPQQ 差距小到讀不出來, 但幾百上千步累積後統計上分得開。衡量那個差距常用 KL divergence DKL(QP)D_{\mathrm{KL}}(Q \,\|\, P), 直覺就是「兩個分布差多遠」,這裡不需要展開。

把 bias 放在模型本來就猶豫的地方

關鍵在於分布形狀差很多。有些位置模型本來就沒主見:

text
「這是一個非常 ___ 的方法」
有效 27%
實用 25%
有趣 21%
強大 18%
...

四個都合理,換哪個都不影響句子品質。這種位置(高 entropy,也就是不確定性大) 偏個兩三個百分點,讀者不可能察覺。

但有些位置只有一個答案:

text
「1 + 1 =」
2 99.99%
其他 0.01%

這裡任何有意義的 bias 都是在把正確答案換掉。所以合理的設計會看分布的形狀決定要不要動手: 低 entropy 的位置放過,不計分也不干預。

高 entropy:模型本來就在猶豫「這是一個非常 ___ 的方法」有效27%實用25%有趣21%強大18%多個近似答案 → 適合微調低 entropy:幾乎只有一個答案「1 + 1 = ___」299.9%30.05%其他0.01%跳過,不施加 watermark硬偏只會把正確答案弄壞watermark 的空間來自「多個答案本來就差不多合理」
圖 5:高 entropy 的位置有多個近似答案,可以用很小的偏差留下訊號;低 entropy 的位置應直接跳過。機率是 toy example。

這對幾類輸出特別重要,因為它們幾乎整段都是低 entropy:

  • 程式碼:變數名、關鍵字、括號結構,改一個 token 就是 bug。
  • 數學與計算:答案唯一。
  • JSON / URL / 結構化輸出:格式錯了就不能用。
  • 逐字引用、專有名詞、事實性答案:改掉就是錯的。

反過來說,這也直接解釋了官方那句「太短的文字沒有可靠訊號」: 一段文字裡真正能承載 watermark 的,只有高 entropy 的那些位置。 一段幾乎全是程式碼的回覆,可計分的位置可能少到根本驗不出來—— 這裡的 NN 從來就不等於 token 總數。

長度、門檻與誤判

把上一節的 NN 定義成「可計分的位置數」。 每個位置貢獻一點點偏差,那麼

signalN,noiseNSNRN\text{signal} \propto N, \qquad \text{noise} \propto \sqrt{N} \quad \Longrightarrow \quad \text{SNR} \propto \sqrt{N}

白話說:文章長 100 倍,辨識能力大概只強 10 倍。證據會一直累積,隨機波動也會,只是波動長得比較慢。

延續前面的 toy 設定(γ=0.5\gamma = 0.5,watermark 把命中率從 50% 推到 55%), z-score 剛好有個很乾淨的形式:

z=0.55N0.5N0.25N=0.1Nz = \frac{0.55N - 0.5N}{\sqrt{0.25N}} = 0.1\sqrt{N}
0123450500100015002000可計分的 token 數 Nz判定門檻(示意)z = 0.1·√Ntoy 設定:γ = 0.5、命中率 55%
圖 6:同樣的每步偏差,z-score 隨 √N 成長。橫軸是可計分位置數,不是總字數。數字為 toy 設定。

代進幾個長度:

text
N = 20 → z ≈ 0.45 什麼都證明不了
N = 100 → z ≈ 1.0 還在雜訊範圍內
N = 400 → z ≈ 2.0 有點意思,但遠遠不夠
N = 1000 → z ≈ 3.2 開始像回事
N = 4000 → z ≈ 6.3 幾乎不可能是巧合
推測這張表是上面那條式子的直接代入,用來感受 N\sqrt{N} 的形狀, 不是 Claude 的門檻。官方沒有公布任何長度數字。

門檻怎麼定,誤判怎麼算

偵測就是選一個門檻 zz^*,超過就判「有 watermark」。兩種錯誤方向相反:

  • False positive:人寫的文章被判成有 watermark。 在常態近似下,z=4z^* = 4 對應單尾機率約 3×1053 \times 10^{-5}z=5z^* = 5 3×1073 \times 10^{-7}
  • False negative:有 watermark 卻沒驗出來。 文章太短、可計分位置太少、被改寫過,都會落在這一類。

門檻拉高,false positive 變少但 false negative 變多,反之亦然。這裡沒有兩全的選項。 而且真實場景是「拿去掃幾百萬份文件」,那個看起來很小的 3×1053 \times 10^{-5} 乘上一百萬份就是幾十份冤枉—— 所以實務門檻通常訂得比直覺高很多。

還有一個容易被忽略的坑:上面整套推導假設每個位置獨立,但自然語言會重複。 同一個片語出現兩次,window 一樣、green list 就一樣,兩次命中並不是獨立事件, 這會讓 z-score 被高估。實作上通常要對重複的 window 去重之後再算, 論文裡也是這樣處理的。

這一節的結論,也正好對上官方那句「驗到浮水印只代表可能被 Claude 處理過」: 這是統計證據,不是簽章。它給的是「這篇文章符合某套祕密偏好到不太可能是巧合」, 而不是「這篇文章由誰產生」的密碼學證明。

什麼才真的洗得掉

原文複製z 幾乎不變局部修改z 下降,通常還在整篇改寫z 掉回雜訊命中的位置被改動破壞重新 sample 過,等同隨機
圖 7:三種處理方式對 evidence 的影響。前兩種只是少掉一些命中位置,第三種是整排重新 sample。

複製貼上

文字完全沒變,可計分位置一個不少,z-score 原則上一樣。這是統計式 watermark 相對於 metadata 的主要優勢, 也是官方明確講的行為。

局部修改

改幾個詞、刪一兩句、調換段落。如果 context 是滑動窗口, 每處修改大約毀掉 kk 個位置的證據,其餘不受影響。 2000 個位置裡毀掉 60 個,z 會掉但不會歸零。

不過有個工程細節值得一提:detector 必須重新 tokenize。 編輯過的文字切出來的 token 邊界可能跟原本不同,這種 misalignment 造成的損失 比「被改掉的字數」本身更難預測。

整篇改寫、翻譯、丟給另一個模型重寫

這是本質上不同的攻擊。前兩者是保留大部分原 token 序列, 這一種是讓另一個模型從頭 sample 一遍——新的 token 沒有經過原本的 green list 偏好, 命中率就是 γ\gamma。剩下的訊號只來自碰巧沒被改掉的片段。

官方對這點的說法也很保留,只說「可能撐過一些編輯」, 並列出「大幅編輯、改寫、翻譯、混合」是驗不出來的情況之一。沒有給任何比例數字, 我這裡也不編一個。

順帶一提,這個領域有個結構性難題:一旦公開偵測工具, 想洗掉浮水印的人就有了 oracle——反覆微調直到分數低於門檻即可。 這是「開放第三方驗證」與「防規避」之間的真實矛盾,也可能是 Anthropic 的偵測工具 還沒直接公開的原因之一。(這句是我的推測。)

三角 trade-off

整篇最後會收斂到三個量互相拉扯:

Detectability短文也驗得出來嗎Robustness被改過還剩多少訊號Quality用詞被拉走多少δ 大δ 小調 δ 只是在同一條線上移動
圖 8:δ 只是在這條線上滑動——把偵測與耐改推高,就得吐出品質。
  • Detectability:多短的文章驗得出來。
  • Robustness:被改過之後還剩多少證據。
  • Quality:為了 watermark,輸出分布被拉走多少。

δ\delta 當旋鈕就看得很清楚。δ\delta 調大, 每步命中率提高,短文就能累積到門檻,改掉一部分也還撐得住;代價是模型越來越常放棄它原本最想選的詞, 用字開始被拉走。δ\delta 調小,品質幾乎不受影響, 但需要更長的文章才驗得出來,而且稍微改一改就掉到門檻以下。

這也是為什麼近年研究往「不動到期望分布」的方向走—— 例如 SynthID-Text 的 tournament sampling,在期望上不改變輸出分布, 等於試圖把品質那一角從這個三角裡拿掉。代價是演算法複雜不少。

收尾

真正巧妙的地方不是「把一串祕密資料藏進文字」。 而是:在大量原本就幾乎等價的 token 選擇裡,按照只有 watermark 持有者知道的微弱偏好去 sampling; 任何單一次選擇都看不出異常,但幾百上千次累積之後, 知道 secret 的人可以看到統計上異常一致的 pattern。

這也決定了它的能力邊界:它是統計證據不是簽章,需要長度, 怕整篇改寫,而且只能說「這段文字可能經過某個系統」,不能說「這是誰寫的」。 官方文件其實把這幾點都寫得很清楚,只是容易被「隱形浮水印」這個詞蓋過去。

推測最後再說一次:除了第一節列的官方事實,這篇描述的是 LLM watermark 這個領域 為什麼可行的核心演算法模型,不是對 Claude 私有實作的逆向確認。 Anthropic 說會出技術文件,等真的出來,這篇該改的地方我再回來改。

參考

LLM WatermarkPRFStatistical DetectionC2PAAnthropic