0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

LLM ウォーターマーキングの仕組み ── Claude のテキスト透かしを、原理から限界まで追う

0
Posted at

「この文章、AIが書いたか判定してもらえますか」

社内のコンプライアンス部門からこう頼まれたことがある。当時できたのは、いわゆるAI検出ツールにかけて確率を眺めることくらいだった。あれは要するに文章の特徴量から後付けで推測する分類器で、非ネイティブが書いた英文を軒並みAI判定したり、逆にAI出力を少し手直ししただけで人間判定になったりする。数字は出るが根拠がない。「85%の確率でAIです」と報告して、それで人事上の判断ができるかというと、できない。

2026年8月2日にEU AI Act 第50条が適用開始となり、この状況が構造的に変わった。生成AIの提供者に対して、出力を機械可読な形でマーキングする義務が課されたからだ。そしてAnthropicは同日以降にリリースされるClaudeモデルに、テキストへの統計的透かしを入れ始めた。

「文章から推測する」から「生成時に埋め込んだ signal を検証する」への転換だ。この記事では、その透かしがどういう原理で入り、どこまで検出でき、どこで壊れるのかを追う。結論を先に書くと、透かしは強力だが万能ではなく、「透かしなし=人間が書いた」は絶対に成り立たない。この非対称性を理解しないまま運用ルールを作ると事故る。

対象読者と前提

  • LLMをプロダクトに組み込んでいる、または組み込みを検討している
  • 生成物の来歴管理・コンプライアンス要件に関わっている
  • Python が読める(擬似実装を載せる)

本記事の事実関係は、Anthropicの公式発表・ヘルプセンター、Google DeepMindがNatureに発表したSynthID-Textの論文とその独立分析、EU AI Act 第50条の条文解説を2026年8月24日時点で確認して書いている。数値の出典は都度明記する。掲載したPythonコードはすべて実行し、出力をそのまま貼っている(Python 3.13 / 標準ライブラリのみ)。

まず「マーキング」を3つに分解する

議論が噛み合わない原因はたいていここにある。AI生成コンテンツの識別技術は、性質のまったく違う3種類が混在している。

手法 埋め込み先 コピペ耐性 偽装耐性 証拠としての強さ
統計的テキスト透かし 本文のトークン選択そのもの 残る 弱い(後述)
C2PA署名メタデータ ファイルのメタデータ領域 消える 強い(電子署名) 高(ただし壊れやすい)
事後分類器(AI検出ツール) どこにも埋め込まない

Claudeは上の2つを併用している。テキストには統計的透かし、.svg / .png / .jpg などのファイルにはC2PA準拠の署名付きメタデータだ。3つ目の分類器は使わない。

C2PAは電子署名なので偽造は困難だが、メタデータを剥がせば一瞬で消える。スクリーンショットを撮り直すだけで消える。一方の統計的透かしは本文そのものに入っているので、コピペしても翻訳しても(ある程度は)残る。この「消えやすさの方向が逆」という補完関係が、2つを併用する理由だ。

なぜ分類器では駄目なのか

事後分類器の根本問題は、判定根拠が生成側に存在しないことに尽きる。「perplexityが低い」「バースト性が低い」といった特徴は、丁寧に推敲された人間の文章とも一致する。母語話者でない書き手の英文が誤検知されやすいという報告は繰り返し出ていて、これは特徴量ベースである以上、原理的に避けられない。

統計的透かしは発想が逆だ。生成する時点で、秘密鍵に紐づく検出可能な偏りを仕込む。検出は「特徴が似ているか」ではなく「仮説検定」になる。p値が出る。ここが決定的に違う。

原理:まず古典的な green list 方式で直感をつかむ

Claudeが使う方式は後述するSynthID-Textだが、いきなりそこに行くと分かりにくい。2023年のKirchenbauerらの手法(いわゆる green list / red list)から入ると、透かしの骨格が見える。

やっていることは驚くほど単純だ。

  1. 直前のトークンと秘密鍵からハッシュを取り、乱数シードにする
  2. そのシードで語彙全体を「green list(割合 γ)」と「red list」にランダム分割する
  3. green list のトークンのロジットに定数 δ を足してから sampling する

これだけ。δ が十分大きければ、生成されたテキストは green トークンを不自然なほど多く含む。検出側は同じ鍵で各位置の green list を再現し、green トークンの個数を数えればいい。

帰無仮説は「テキストは透かしを知らずに書かれた」で、その下では green トークン数は二項分布に従う。したがって検定統計量はこうなる。

z = (|s|_G − γT) / sqrt(T · γ · (1 − γ))

|s|_G は green トークンの個数、T は総トークン数、γ は green list の割合だ。γ=0.5、200トークンの文章で green が130個なら z ≈ 4.24、p値は 10^-5 オーダーになる。「たまたま」では説明がつかない。

ここでエントロピーの話が出てくる。 「日本の首都は」の次に来るトークンは実質「東京」しかない。green list に入っていなくても、モデルは東京を選ぶしかない。つまり選択の余地がない箇所には透かしが乗らない。これは実装の巧拙ではなく、情報理論的な上限だ。あとで出てくる「コードにはほぼ乗らない」「事実列挙に弱い」という制約は、すべてここから来ている。

green list 方式には実用上の弱点がある。δ を足す時点で出力分布そのものを歪めている。強く入れれば検出しやすくなるが、文章の質が落ちる。このトレードオフを解消しようとしたのがSynthID-Textだ。

Claude が使う方式:トーナメントサンプリング

AnthropicはGoogle DeepMindがNatureに発表したSynthID-Text方式を採用している(公式発表で明言されている)。中核が「トーナメントサンプリング」というアイデアだ。

ロジットをいじるのをやめて、サンプリングの乱数源そのものを差し替える。Anthropicの説明が的確なので引くと、「任意の乱数生成の代わりに、鍵と直前の数語を使ってどの語を選ぶかを決める」。

具体的な流れはこうだ。

各層 ℓ では、候補トークンに g値 と呼ばれる擬似乱数スコアが割り当てられる。定義はこうだ。

r_t   = h(x_{t-H}, ..., x_{t-1}, k)        # シードは直近Hトークンと鍵から決まる
g_ℓ   = F_g^{-1}( h(x, r_t, ℓ) / 2^n )     # 層ごとに独立なg値

h は暗号学的ハッシュ、k は秘密鍵、F_g^{-1} はg値分布(BernoulliまたはUniform)の逆累積分布関数だ。鍵がなければ予測不能だが、鍵があれば完全に再現可能という性質がすべての土台になっている。

検出側は、対象テキストの各位置・各層についてg値を再計算し、平均を取る。

MS(x) = (1 / (T·m)) · Σ_t Σ_ℓ g_ℓ(x_t, r_t)

透かしのないテキストなら、g値は位置とも層とも無相関なので期待値は 0.5 に収束する。透かし入りのテキストは、g値の高いトークンがトーナメントを勝ち上がった結果なので、0.5より高い方に偏る。あとは閾値を切るだけだ。

非歪み性:ここが green list との決定的な差

トーナメントサンプリングの美点は、設定次第で出力トークン分布がオリジナルのモデル分布と一致することだ。論文の言葉では non-distortionary、透かし入りLLMが出すトークンの分布が元のLLMの分布に等しい。

候補を元の分布からサンプリングした上で、その中の選び方だけを鍵で決めているからこうなる。分布そのものには手を触れていない。だからAnthropicが「品質・創造性・可読性に実質的な影響はない」「追加トークンを消費せず生成速度への影響も無視できる」と言えるわけだ。この主張は方式の性質から素直に導かれるもので、誇張とは思わない。

層数 m を増やすと1トークンあたりの証拠量が増え、スコアの分散が下がる。検出性能は上がるが、候補を 2^m 個サンプリングする計算コストは上がる。ここが実装上のチューニングポイントになる。

擬似実装で腹落ちさせる

理屈だけだと手触りがないので、最小構成を書く。実運用のコードではなく、検出スコアがなぜ0.5から離れるのかを確認するための実験用だ。

import hashlib
from typing import Sequence

VOCAB_SIZE = 32_000
HASH_BITS = 64


def g_value(token_id: int, seed: int, layer: int, key: bytes) -> float:
    """トークン・シード・層から [0,1) の擬似乱数(g値)を決定的に生成する。"""
    payload = f"{token_id}:{seed}:{layer}".encode("utf-8")
    digest = hashlib.blake2b(payload, key=key, digest_size=HASH_BITS // 8).digest()
    return int.from_bytes(digest, "big") / (1 << HASH_BITS)


def context_seed(context: Sequence[int], key: bytes, window: int = 4) -> int:
    """直近 window トークンと秘密鍵からシードを導出する。"""
    recent = tuple(context[-window:])
    payload = ",".join(map(str, recent)).encode("utf-8")
    digest = hashlib.blake2b(payload, key=key, digest_size=8).digest()
    return int.from_bytes(digest, "big")

blake2bkey を渡しているのが地味に大事なところで、鍵なしハッシュだと誰でもg値を再現できてしまい、透かしの意味がなくなる。鍵付きハッシュ(MAC)にすることで「鍵を持つ者だけが検証できる」が成立する。

サンプリング側はこうなる。

import random


def tournament_sample(
    candidates: list[int], context: Sequence[int], key: bytes, layers: int = 3
) -> int:
    """候補トークンを層ごとのg値で勝ち抜き戦にかけ、優勝トークンを返す。"""
    if len(candidates) != 2 ** layers:
        raise ValueError(f"候補数は 2^layers ({2 ** layers}) である必要があります")

    seed = context_seed(context, key)
    survivors = list(candidates)

    for layer in range(1, layers + 1):
        next_round: list[int] = []
        for left, right in zip(survivors[0::2], survivors[1::2]):
            g_left = g_value(left, seed, layer, key)
            g_right = g_value(right, seed, layer, key)
            next_round.append(left if g_left >= g_right else right)
        survivors = next_round

    return survivors[0]

candidates はモデルの確率分布から復元抽出でサンプリングした 2^m 個のトークンだ。ここを元分布から引いているからこそ非歪み性が成り立つ。上位k個を決定的に取ってきてしまうと、その時点で分布が歪んで前提が崩れる。私は最初これを勘違いして「上位8件でトーナメントすればいい」と実装し、生成文が妙に硬くなって気づいた。

検出側は素直だ。

def mean_score(
    tokens: Sequence[int], key: bytes, layers: int = 3, window: int = 4
) -> float:
    """テキストの平均g値スコアを返す。透かしなしなら 0.5 前後に収束する。"""
    if len(tokens) <= window:
        raise ValueError("トークン列が短すぎて検出できません")

    total, count = 0.0, 0
    for i in range(window, len(tokens)):
        seed = context_seed(tokens[:i], key, window)
        for layer in range(1, layers + 1):
            total += g_value(tokens[i], seed, layer, key)
            count += 1

    return total / count

このコードを手元で回してみた。語彙は擬似的な整数トークン、モデル分布は一様分布で代用し、3層トーナメント(候補8個)で生成した列と、透かしなし(候補の先頭をそのまま採用)の列を各30本ずつ生成してスコアを比べている。

n=  50  透かしあり=0.6693  透かしなし=0.5019
n= 100  透かしあり=0.6632  透かしなし=0.4992
n= 200  透かしあり=0.6676  透かしなし=0.4995
n= 400  透かしあり=0.6668  透かしなし=0.5022

透かしなしがきれいに0.5に収束し、透かしありが約0.667に張り付く。この 0.667 という値には理由があって、一様分布から引いた2つの値の最大値の期待値が 2/3 だからだ。各層で2者比較の勝者を取る以上、勝ち上がったトークンのg値の期待値は層ごとに 2/3 になる。理論値と実測がぴったり合うと、実装が合っている確信が持てる。

検出条件をずらすとどうなるかも測った。

正しい鍵: 0.6750    誤った鍵: 0.4978

検出側 window=2: 0.5237   window=3: 0.4961   window=4: 0.6750   window=5: 0.4953
検出側 layers=1: 0.6830   layers=2: 0.6752   layers=3: 0.6750   layers=4: 0.6258

鍵と窓幅は完全一致が必須で、1つずれただけで検出は完全に失敗する。鍵を持たない第三者が検証できないのは設計上の意図どおりだ。

一方で層数は生成時以下であれば検出できる。ここは正直、実際に測るまで「3点セットが揃わないと駄目」だと思い込んでいた。よく考えれば当然で、第1層と第2層のg値は生成時に実際に使われている以上、その平均を取れば偏りは現れる。逆に生成時より多い層数(4層)で検出すると、使われていない第4層のg値が期待値0.5のノイズとして混ざり、スコアが0.626まで希釈される。検出側は層数を控えめに見積もったほうが安全、という実装上の教訓になる。

Claude における実際の適用範囲

2026年8月24日時点で公表されている事実を整理する。

項目 内容
対象モデル 2026年8月2日以降にリリースされたClaudeモデル。それ以前のモデルは移行期間中に順次対応
対象サーフェス Claude Platform(API)、claude.ai、Claude Code、Claude Cowork、Claude Tag
クラウド経由 AWS / Google Cloud / Microsoft Foundry 経由のClaudeも対象
地域 全世界(EU域内に限定しない)
テキスト 統計的透かし(SynthID-Text方式)
ファイル C2PA署名メタデータ(.svg / .png / .jpg)
コスト影響 追加トークンなし・料金変更なし・生成速度への影響は無視できる水準
個人情報 透かしに識別情報は含まれず、ユーザー・組織・チャットの特定は不可
オプトアウト 公開情報上、提供されていない
検出API 提供予定と発表済み。本稿執筆時点では未提供(価格・アクセス条件とも未公表)

APIから叩いた出力にも透かしが入る、という点は業務システムを作る側として押さえておきたい。自社プロダクトがClaudeを裏で使っていて、その出力をそのままユーザーに見せている場合、そのテキストにはAnthropicの透かしが乗っている

「個人情報が含まれない」も見落とされがちだが重要だ。透かしから「誰が生成したか」は分からない。分かるのは「Claudeが関与した可能性が高い」だけで、これは追跡技術ではない。ここを誤解した社内説明をすると無用な反発を招く。

どこで壊れるか

エンジニアが一番知るべきはこの節だと思う。透かしが乗らない・消える条件は、公式発表で率直に列挙されている。

短いテキスト。 選択の余地が絶対量として足りない。SynthID-Textを独立に分析した論文(arXiv:2603.03410)の測定では、50トークンでは偽陽性率1%のときの検出率が約0.3にとどまり、**400トークンで約85%**まで上がる。ただしこれはAnthropicの実装パラメータでの数字ではないので、桁感の参考として見るべきだ。いずれにせよ「ツイート1本を判定する」用途では使い物にならない。

低エントロピーな文章。 歴史的事実の列挙のように語の選択肢が限られる文章には、ほとんど透かしが乗らない。

コード。 これは公式に「無視できる水準」と明言されている。コードは正確な出力が要求される以上、選択の自由度がほぼない。AIが書いたコードを透かしで判定することは、原理的に諦めたほうがいい。個人的にはこれが最大の実務的インパクトだと思っている。「AIが生成したコードの混入を透かしで検出する」という社内提案が出たら、その場で否定していい。

大幅な書き換え。 重い編集や全面リライトで透かしは消える。逆に軽い手直し程度なら残る。

翻訳チェーン。 自動翻訳を経由させると統計的透かしの検出性能が大きく劣化するという指摘がある。一方で、Claude自身に翻訳させた場合は「すべての語をClaudeが選んでいる」ので透かしが乗る。方向によって挙動が違う。

そして忘れがちなのが逆方向の誤りだ。Claudeで下書きを整形しただけの人間の文章にも透かしは乗る。検出結果が意味するのは「Claudeが何らかの形で関与した」であって、「Claudeが書いた」ではない。校正・翻訳・整形と執筆を、透かしは区別しない。

セキュリティ:透かしは2方向に破れる

ここからは研究レベルの話になるが、運用ルールを設計するなら知っておくべきだ。透かしに対する攻撃は2種類ある。

  • Scrubbing(除去) — AI生成物から透かしを消し、人間が書いたように見せる
  • Spoofing(偽装) — 人間の文章に透かしを付与し、AI生成だと誤認させる

厄介なのは後者だ。他人の文章に偽の透かしを付けて「これはAIが書いたものだ」と主張する、あるいは有害な文章に特定ベンダーの透かしを付けて評判を毀損する、といった攻撃が成立しうる。

ETH ZurichのJovanovićらによる論文 Watermark Stealing in Large Language Models(ICML 2024)は、この現実性を示した。透かし入りLLMのAPIを叩いて透かしを近似的にリバースエンジニアリングすることで、50ドル未満のコストで最新方式に対する偽装・除去の両方が平均80%超の成功率で成立するという結果だ。

ここから導かれるのが、検出API公開のジレンマだ。

透かしの価値は「第三者が検証できること」にある。しかし誰でも叩ける検出器は、攻撃者にとって**「透かしが消えたかどうかを教えてくれる装置」**でもある。Anthropicの検出APIがまだ提供されていないのは、この設計判断に時間がかかっているからだろうと私は見ている(公式に理由が示されているわけではない)。

運用にどう落とすか

以上を踏まえると、社内規程やプロダクト設計に落とす際の指針はかなりはっきりする。

1. 「透かしなし」を人間の証拠として使わない。 これが最重要。透かしがない理由は、旧モデル、短文、コード、大幅編集、メタデータ剥離、非対応形式と山ほどある。検出は陽性方向にしか意味を持たない、という非対称性を規程に明記すべきだ。

2. 証拠能力を階層で扱う。 私の整理はこうだ。

強さ 根拠 使いどころ
C2PA署名(検証成功) 来歴の積極的証明
統計的透かし(十分な長さ+高エントロピー) AI関与の推定
事後分類器 参考値。単独で判断材料にしない
いずれも検出されず 何の証拠でもない

3. 生成側でログを残す。 検出に頼るより、自社が生成したものは自社で記録するほうが圧倒的に確実だ。リクエストID、モデル名、生成時刻を保存しておけば、後から「これはうちのシステムが生成した」と積極的に証明できる。透かしは他人が生成したものを推定するための技術であって、自社生成物の来歴管理は自前のログでやるべき仕事だ。ここを混同して「透かしがあるからログは要らない」とはならない。

4. 人事・懲戒判断の単独根拠にしない。 誤検知と誤陰性の両方が構造的に存在する以上、これを単独で処分根拠にするのは筋が悪い。学生のレポート判定のような文脈も同じだ。

法規制の側から見た背景

なぜ2026年8月なのかを押さえておくと、今後の動きも読める。

EU AI Act 第50条(透明性義務)が2026年8月2日に適用開始となった。要点は義務が2層に分かれていることだ。

  • 提供者(provider)の義務 — 生成AIシステムの出力を機械可読な形式でマーキングし、検出可能にする
  • 展開者(deployer)の義務 — ディープフェイクや一定のAI生成テキストについて、利用者に見える形で開示する

つまりAnthropicがやったのは提供者側の義務対応であり、そのClaudeを組み込んでサービスを提供する事業者には別途、展開者としての開示義務がかかりうる。「Anthropicが透かしを入れているからうちは対応済み」にはならない。ここは自社サービスの設計に直結する。

罰則は最大で1,500万ユーロまたは全世界年間売上高の3%のいずれか高いほう。既に市場に出ていた生成AIシステムには2026年12月2日までの猶予がある。

Anthropicが全世界一律で適用しているのは、EU向けだけ挙動を変えるより実装が単純だからだろう。GoogleはGemini本体にSynthIDを統合し、OpenAIは画像にC2PAマニフェストとSynthIDピクセル透かしを併用、Metaも同様の対応を取っている。業界としては、テキストは統計的透かし、メディアはC2PA + 画素透かし、という組み合わせに収束しつつある

まとめ

  • Claudeのテキスト透かしはSynthID-Text方式。ロジットを歪めず、サンプリングの乱数源を秘密鍵で決定的にすることで、品質を落とさずに検出可能な偏りを埋め込む
  • 検出は各位置・各層のg値の平均を取る仮説検定。透かしなしなら0.5、ありなら高い方に偏る
  • 埋め込める情報量はトークン選択のエントロピーが上限。だから短文・事実列挙・コードには乗らない。これは実装の問題ではなく原理的な限界
  • 除去も偽装も現実的なコストで成立しうる。「透かしあり=AI」も「透かしなし=人間」も断定できない
  • 自社生成物の来歴管理は、透かしではなく自前のログでやる

透かしを「AI判定の決定打」として期待すると必ず裏切られる。一方で、証拠能力に階層があることを理解した上で、C2PA・統計透かし・自前ログを組み合わせるなら、数年前とは比較にならないくらい確度の高い運用が組める。冒頭のコンプライアンス部門への回答も、いまなら「透かしが検出されればClaude関与の強い示唆になります。ただし検出されなかったことは何の証拠にもなりません」と言える。この一言が言えるようになったのは、そこそこ大きな進歩だと思う。

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?