Find the watermark

A word model writes with a hidden statistical watermark. Count green words with the key, see how length and strength matter, swap words to wash it out, and test your own writing.

NoviceExplained in Safety in AI products

Find the watermark

A word model trained on about 300 of Aesop's fables writes with a hidden watermark. A detector with the key counts the green words.

Loading the fables (190 KB)...

Text to check
2.0
60
Detector uses
VerdictNothing to check
Green tokens0 of 0 (chance: about 0)
Score (z)n/a
Chance without a watermarkn/a
Tokens you swapped0

Challenges

0 of 4 complete

  1. Make the detector flag a watermarked text only 20 tokens long. (not yet)
  2. Start from a text the detector flags, then tap words to swap them until it no longer does. (not yet)
  3. Check a text the model's key flags with a different key. (not yet)
  4. Type or paste at least 40 tokens of your own writing and check it. (not yet)

Try this

  • Set the strength to 0 and press Write a new text a few times. The score stays near zero.
  • Back at strength 2, shorten the text to 20 tokens, then 10. When does the detector start missing it?
  • Tap words in a flagged text to swap them and watch the score in the chart fall.
  • Switch the detector to a different key. Same text, no watermark in sight.
A green-list watermark in a few linesPython with the standard library
import hashlib, math, random

# A stand-in "model" that finds any of these words equally likely next.
vocab = ("the fox crow lion mouse hare tortoise dog wolf lamb ant bee "
         "saw found ran slept caught freed jumped ate dropped asked "
         "grapes cheese bone water field river tree nest hill road "
         "and but then quickly slowly gladly at last again").split()
GAMMA = 0.25   # share of words on each green list

def green(prev, word, key):
    # The key and the previous word decide whether `word` is green.
    h = hashlib.sha256(f"{key}|{prev}|{word}".encode()).digest()
    return int.from_bytes(h[:4], "big") / 2**32 < GAMMA

def write(n, delta, seed, key="model key"):
    rng, prev, out = random.Random(seed), ".", []
    for _ in range(n):
        # Push green words up by a factor of e^delta, then sample.
        weights = [math.exp(delta) if green(prev, w, key) else 1 for w in vocab]
        prev = rng.choices(vocab, weights)[0]
        out.append(prev)
    return out

def z_score(tokens, key):
    pairs = set(zip(["."] + tokens, tokens))     # score each word pair once
    g = sum(green(p, w, key) for p, w in pairs)
    t = len(pairs)
    return (g - GAMMA * t) / math.sqrt(t * GAMMA * (1 - GAMMA))

plain = write(60, delta=0, seed=1)
marked = write(60, delta=2, seed=1)
print(" ".join(marked[:10]), "...")
print(f"no watermark:           z = {z_score(plain, 'model key'):5.2f}")
print(f"watermark, model's key: z = {z_score(marked, 'model key'):5.2f}")
print(f"watermark, other key:   z = {z_score(marked, 'other key'):5.2f}")
mouse and and ant asked water nest but tortoise crow ...
no watermark:           z =  0.08
watermark, model's key: z =  8.05
watermark, other key:   z =  0.89

Try "embedding", "softmax", "overfitting", or "backpropagation".