Detectie van watermerken in LLM-output
Technieken voor het detecteren, extraheren en analyseren van watermerken die zijn ingebed in door LLM's gegenereerde tekst voor herkomsttracering en forensische attributie.
Overzicht
Het watermerken van LLM-output is de praktijk van het inbedden van statistisch detecteerbare patronen in tekst die door grote taalmodellen wordt gegenereerd. Deze patronen zijn onwaarneembaar voor menselijke lezers, maar kunnen worden geïdentificeerd door algoritmen die het watermerkschema kennen. Vanuit forensisch oogpunt stelt watermerkdetectie onderzoekers in staat te bepalen of tekst door AI is gegenereerd, welk model of welke provider deze produceerde, en in sommige gevallen welke gebruikerssessie deze genereerde.
De primaire watermerkaanpak voor LLM's, geïntroduceerd door Kirchenbauer et al. (2023), werkt door de woordenschat bij elke tokenpositie te verdelen in "groene" en "rode" lijsten, waarbij het vorige token als hash-seed wordt gebruikt. Tijdens generatie wordt de sampling van het model bevooroordeeld richting green-list-tokens. De resulterende tekst bevat een statistisch onwaarschijnlijk overschot aan green-list-tokens dat met een eenvoudige hypothesetoets kan worden gedetecteerd.
Dit artikel behandelt de forensische toepassing van watermerkdetectie: hoe onderzoekers tekst kunnen analyseren om de herkomst ervan te bepalen, de sterkte van watermerkbewijs kunnen beoordelen en rekening kunnen houden met pogingen om watermerken te verwijderen.
Hoe LLM-watermerking werkt
Green-list/red-list-schema
Het schema van Kirchenbauer et al. opereert tijdens de tokensamplingfase van tekstgeneratie. Bij elke generatiestap:
- Een cryptografische hashfunctie neemt het vorige token (of een venster van vorige tokens) en een geheime sleutel als invoer
- De hashoutput verdeelt deterministisch de volledige woordenschat in een "green list" (begunstigde tokens) en een "red list" (benadeelde tokens)
- Een biasterm delta wordt toegevoegd aan de logits van alle green-list-tokens vóór sampling
- Het model selecteert eerder green-list-tokens, maar kan nog steeds red-list-tokens selecteren wanneer deze sterk de voorkeur hebben binnen de distributie van het model
"""
LLM-watermerkdetectie-implementatie voor forensische analyse.
Gebaseerd op het green-list-schema van Kirchenbauer et al. (2023).
Deze module implementeert de detectiekant -- gegeven tekst en de
watermerksleutel, bepalen of de tekst was voorzien van een watermerk.
"""
import hashlib
from collections.abc import Sequence
import numpy as np
from scipy import stats
def compute_green_list(
previous_token_id: int,
vocab_size: int,
secret_key: bytes,
gamma: float = 0.5,
) -> set[int]:
"""
Bereken de green list voor een gegeven contexttoken.
Gebruikt HMAC-SHA256 om de woordenschat deterministisch te verdelen
in green- en red-lists op basis van het vorige token en de geheime sleutel.
Args:
previous_token_id: De token-ID die aan de huidige positie voorafgaat.
vocab_size: Totale grootte van de woordenschat.
secret_key: De geheime watermerksleutel.
gamma: Fractie van de woordenschat in de green list (standaard 0.5).
Returns:
Set van token-ID's in de green list.
"""
# Maak deterministische seed van vorige token en geheime sleutel
seed_material = previous_token_id.to_bytes(4, 'big') + secret_key
hash_bytes = hashlib.sha256(seed_material).digest()
seed = int.from_bytes(hash_bytes[:8], 'big')
rng = np.random.Generator(np.random.PCG64(seed))
permutation = rng.permutation(vocab_size)
green_list_size = int(gamma * vocab_size)
return set(permutation[:green_list_size].tolist())
def detect_watermark(
token_ids: Sequence[int],
vocab_size: int,
secret_key: bytes,
gamma: float = 0.5,
) -> dict:
"""
Test of een reeks token-ID's een watermerk bevat.
Voert een z-toets voor één proportie uit tegen de nulhypothese
dat tokens zonder green-list-bias worden getrokken.
Onder de nulhypothese (geen watermerk) is de verwachte fractie van
green-list-tokens gamma. Onder het alternatief (van watermerk voorzien)
zou de fractie significant hoger moeten zijn.
Args:
token_ids: Reeks te testen token-ID's.
vocab_size: Totale grootte van de woordenschat.
secret_key: De geheime watermerksleutel.
gamma: Green-list-fractie gebruikt tijdens het watermerken.
Returns:
Dict met detectieresultaten inclusief z-score en p-waarde.
"""
if len(token_ids) < 2:
return {"error": "Need at least 2 tokens for detection"}
green_count = 0
total_tested = 0
for i in range(1, len(token_ids)):
prev_token = token_ids[i - 1]
current_token = token_ids[i]
green_list = compute_green_list(prev_token, vocab_size, secret_key, gamma)
if current_token in green_list:
green_count += 1
total_tested += 1
observed_fraction = green_count / total_tested
expected_fraction = gamma
# Z-toets voor één proportie
# H0: p = gamma (geen watermerk)
# H1: p > gamma (watermerk aanwezig)
se = np.sqrt(gamma * (1 - gamma) / total_tested)
z_score = (observed_fraction - expected_fraction) / se
p_value = 1 - stats.norm.cdf(z_score)
return {
"total_tokens_tested": total_tested,
"green_list_count": green_count,
"observed_green_fraction": round(observed_fraction, 4),
"expected_green_fraction": gamma,
"z_score": round(float(z_score), 4),
"p_value": float(p_value),
"watermark_detected": p_value < 1e-5,
"confidence": "high" if p_value < 1e-10 else "medium" if p_value < 1e-5 else "low",
}Distortievrije watermerking
Een alternatieve aanpak bedt watermerken in via het samplingproces zelf in plaats van door logits te wijzigen. De techniek van Aaronson & Kirchner (beschreven in Aaronsons blogpost uit 2023) gebruikt een pseudowillekeurige functie die wordt geseed door vorige tokens om uniforme willekeurige getallen te genereren, en past vervolgens inverse-transform-sampling toe. Het watermerk wordt gedetecteerd door te controleren of de gegenereerde tokens correleren met de pseudowillekeurige reeks op een manier die astronomisch onwaarschijnlijk zou zijn bij toeval.
Multi-bit-watermerking
Terwijl het basale green-list-schema een single-bit-watermerk inbedt (aanwezig of afwezig), bedden geavanceerdere schema's multi-bit-payloads in die informatie kunnen coderen zoals:
- Provideridentificatie (welke API deze tekst genereerde)
- Gebruikers- of sessie-identificatie
- Tijdstempel van generatie
- Modelversie-informatie
Multi-bit-schema's verdelen de woordenschat in meer dan twee groepen, waarbij elke groep overeenkomt met een andere bitwaarde. De forensische waarde van multi-bit-watermerken is aanzienlijk hoger omdat ze directe attributie mogelijk maken.
Forensische detectiemethodologie
Stap 1: Tekstvoorverwerking
Voordat watermerkdetectie wordt uitgevoerd, moet de tekst worden getokeniseerd met dezelfde tokenizer die door het verdachte bronmodel werd gebruikt. Het gebruik van de verkeerde tokenizer vernietigt het watermerksignaal.
from transformers import AutoTokenizer
def prepare_text_for_detection(
text: str,
model_candidates: list[str],
) -> dict[str, list[int]]:
"""
Tokeniseer tekst met meerdere kandidaat-modeltokenizers.
Omdat het watermerk op tokenniveau is ingebed, moeten we
testen met de tokenizer van elk kandidaatmodel. De juiste
tokenizer produceert een sterker watermerksignaal.
"""
results = {}
for model_name in model_candidates:
tokenizer = AutoTokenizer.from_pretrained(model_name)
token_ids = tokenizer.encode(text, add_special_tokens=False)
results[model_name] = token_ids
return results
def multi_model_watermark_scan(
text: str,
model_candidates: list[str],
candidate_keys: dict[str, bytes],
gamma: float = 0.5,
) -> list[dict]:
"""
Scan tekst op watermerken van meerdere kandidaatmodellen/-providers.
Test elke combinatie van tokenizer en watermerksleutel om
te bepalen welke (indien aanwezig) de tekst produceerde.
"""
tokenizations = prepare_text_for_detection(text, model_candidates)
results = []
for model_name, token_ids in tokenizations.items():
if model_name not in candidate_keys:
continue
tokenizer = AutoTokenizer.from_pretrained(model_name)
vocab_size = tokenizer.vocab_size
detection = detect_watermark(
token_ids=token_ids,
vocab_size=vocab_size,
secret_key=candidate_keys[model_name],
gamma=gamma,
)
detection["model"] = model_name
results.append(detection)
results.sort(key=lambda r: r.get("z_score", 0), reverse=True)
return resultsStap 2: Beoordeling van statistische significantie
De z-toets levert een p-waarde die de waarschijnlijkheid kwantificeert van het waarnemen van de green-list-tokenfractie onder de nulhypothese van geen watermerking. In forensische contexten moet de significantiedrempel conservatief worden ingesteld:
| Betrouwbaarheidsniveau | p-waardedrempel | z-score (bij benadering) | Forensisch gebruik |
|---|---|---|---|
| Voorlopig | < 0.01 | > 2.33 | Voldoende voor triage, niet voor attributie |
| Standaard | < 1e-5 | > 4.26 | Voldoende voor bevindingen van intern onderzoek |
| Hoge betrouwbaarheid | < 1e-10 | > 6.36 | Voldoende voor formele forensische rapporten |
| Forensische klasse | < 1e-20 | > 9.26 | Geschikt voor juridische procedures |
De vereiste tekstlengte om een bepaald betrouwbaarheidsniveau te bereiken, hangt af van de watermerksterkte (delta-parameter) en de green-list-fractie (gamma). Als ruwe richtlijn:
- 200 tokens: Kan sterke watermerken (z > 4) detecteren onder gunstige omstandigheden
- 500 tokens: Betrouwbare detectie voor de meeste watermerkconfiguraties
- 1000+ tokens: Vrijwel zekere detectie met hoge forensische betrouwbaarheid
Stap 3: Geveensterde analyse
Als de onderzochte tekst een mengsel is van door mensen geschreven en door AI gegenereerde inhoud, kan een sliding-window-analyse identificeren welke delen van een watermerk zijn voorzien.
def windowed_watermark_detection(
token_ids: list[int],
vocab_size: int,
secret_key: bytes,
window_size: int = 100,
step_size: int = 25,
gamma: float = 0.5,
) -> list[dict]:
"""
Pas watermerkdetectie toe in een sliding window over de tekst.
Dit identificeert welke delen van een document waarschijnlijk door AI
zijn gegenereerd versus door mensen geschreven, op basis van de lokale
sterkte van het watermerksignaal.
"""
results = []
for start in range(0, len(token_ids) - window_size, step_size):
window = token_ids[start:start + window_size]
detection = detect_watermark(window, vocab_size, secret_key, gamma)
detection["token_start"] = start
detection["token_end"] = start + window_size
results.append(detection)
return resultsAanvallen voor watermerkverwijdering en forensische tegenmaatregelen
Veelvoorkomende verwijdertechnieken
Aanvallers die op de hoogte zijn van watermerking, kunnen proberen het watermerk te verwijderen terwijl ze de inhoud van de tekst behouden. Veelvoorkomende verwijdertechnieken zijn onder andere:
- Parafraseren: Een ander LLM gebruiken om de tekst te herschrijven. Dit vervangt de meeste tokens, wat het green-list-signaal verstoort.
- Tokensubstitutie: Individuele woorden vervangen door synoniemen met behulp van een thesaurus of similariteit van word embeddings.
- Homoglief-substitutie: Tekens vervangen door visueel identieke Unicode-tekens uit andere schriften.
- Heen-en-weer-vertalen: Vertalen naar een andere taal en terug, wat de tekst volledig opnieuw tokeniseert.
- Invoeging/verwijdering: Woorden toevoegen of verwijderen om de tokenuitlijning te verschuiven.
Forensische detectie van verwijderpogingen
Zelfs wanneer een watermerk gedeeltelijk is verwijderd, kan forensisch bewijs van het oorspronkelijke watermerk blijven bestaan. De onderzoeker moet letten op:
def analyze_removal_artifacts(
token_ids: list[int],
vocab_size: int,
secret_key: bytes,
gamma: float = 0.5,
) -> dict:
"""
Analyseer tekst op tekenen van pogingen tot watermerkverwijdering.
Zelfs na parafraseren of bewerken kunnen statistische artefacten van
het oorspronkelijke watermerk achterblijven in delen van de tekst
die niet zijn gewijzigd.
"""
# Detectie over volledige tekst
full_result = detect_watermark(token_ids, vocab_size, secret_key, gamma)
# Geveensterde detectie om overlevende watermerkfragmenten te vinden
window_results = windowed_watermark_detection(
token_ids, vocab_size, secret_key,
window_size=50, step_size=10, gamma=gamma,
)
# Tel vensters met significant watermerksignaal
significant_windows = [
w for w in window_results if w.get("z_score", 0) > 2.0
]
# Analyseer het patroon van overlevende vs. verwijderde watermerkregio's
z_scores = [w.get("z_score", 0) for w in window_results]
z_variance = float(np.var(z_scores)) if z_scores else 0.0
return {
"full_text_detection": full_result,
"total_windows": len(window_results),
"significant_windows": len(significant_windows),
"significant_fraction": (
len(significant_windows) / len(window_results)
if window_results else 0.0
),
"z_score_variance": z_variance,
"removal_likely": (
full_result.get("z_score", 0) < 2.0
and len(significant_windows) > len(window_results) * 0.2
),
"interpretation": _interpret_removal_analysis(
full_result, significant_windows, window_results, z_variance
),
}
def _interpret_removal_analysis(
full_result: dict,
significant_windows: list,
all_windows: list,
z_variance: float,
) -> str:
full_z = full_result.get("z_score", 0)
sig_frac = len(significant_windows) / max(len(all_windows), 1)
if full_z > 4.0:
return "Strong watermark detected -- no significant removal"
if full_z > 2.0 and sig_frac > 0.5:
return "Partial watermark -- possible light editing or paraphrasing of some sections"
if full_z < 2.0 and sig_frac > 0.2:
return "Watermark largely removed but fragments survive -- likely systematic removal attempt"
if z_variance > 4.0:
return "High variance in local watermark strength -- mixed human/AI or selective editing"
return "No watermark signal detected"Provider-specifieke watermerkschema's
Verschillende AI-providers hebben verschillende watermerkaanpakken geïmplementeerd of aangekondigd. Forensische onderzoekers moeten op de hoogte zijn van het landschap:
| Provider | Watermerkstatus | Schematype | Beschikbaarheid van detectie |
|---|---|---|---|
| OpenAI | Aangekondigd, uitgestelde uitrol | Onbekend (niet publiekelijk gedetailleerd) | Niet publiekelijk beschikbaar |
| Google DeepMind | SynthID uitgerold | Logit-gebaseerd statistisch | Geïntegreerd in Google-tools |
| Meta | Onderzoek gepubliceerd | Green-list-variant | Open-source referentie-implementatie |
| Anthropic | Geen publieke watermerking aangekondigd | N.v.t. | N.v.t. |
| Microsoft | Onderzoek gepubliceerd | Meerdere schema's bestudeerd | Onderzoekscode beschikbaar |
Het SynthID-systeem van Google is het meest wijdverspreid uitgerolde productiewatermerkschema vanaf begin 2026. Het is van toepassing op zowel tekst- als beeldoutputs van Google's AI-modellen en gebruikt een toernooi-gebaseerd detectieschema dat robuuster is tegen tekstwijzigingen dan de basale green-list-aanpak.
Praktische overwegingen
Vereisten voor tekstlengte
Watermerkdetectie is een statistische toets waarvan het onderscheidend vermogen toeneemt met de steekproefgrootte. Korte teksten (onder de 100 tokens) zijn over het algemeen te kort voor betrouwbare detectie. De onderzoeker moet de langst beschikbare steekproef van verdachte door AI gegenereerde tekst opvragen en afkapping vermijden.
Taal- en domeineffecten
De watermerksterkte varieert per taal en domein. Tekst in domeinen met sterk beperkte woordenschatten (juridisch, medisch, code) heeft minder mogelijkheden voor het model om green-list-tokens te kiezen zonder de kwaliteit aan te tasten, wat resulteert in zwakkere watermerksignalen. Forensische drempels moeten daarop worden aangepast.
Tokenizergevoeligheid
Het watermerksignaal bestaat op tokenniveau, niet op teken- of woordniveau. Elke tekstverwerking die tokenisatiegrenzen verandert (zoals herformatteren, witruimte wijzigen of interpunctie corrigeren) kan het watermerksignaal verzwakken, zelfs zonder opzettelijke verwijdering.
Referenties
- Kirchenbauer, J., Geiping, J., Wen, Y., Katz, J., Miers, I., & Goldstein, T. (2023). A Watermark for Large Language Models. Proceedings of the 40th International Conference on Machine Learning (ICML). https://arxiv.org/abs/2301.10226
- Aaronson, S. (2023). My AI Safety Lecture at UT Austin. Scott Aaronson's Blog (Shtetl-Optimized). https://scottaaronson.blog/?p=6823
- Zhao, X., Ananth, P., Li, L., & Wang, Y.-X. (2024). Provable Robust Watermarking for AI-Generated Text. Proceedings of the International Conference on Learning Representations (ICLR). https://arxiv.org/abs/2306.17439