引言

随着大模型生成的文本、图像、视频越来越逼真,区分AI生成内容与人类创作内容成为了一个紧迫的社会需求。水印技术通过在模型输出中嵌入可检测但不影响质量的标记,为解决这一问题提供了技术路径。2026年,欧盟AI法案、中国《生成式AI服务管理办法》等法规已要求AI生成内容必须可被检测。本文将系统介绍水印技术的原理和最新进展。

文本水印技术

水印的基本框架

文本水印的目标是在生成文本中嵌入统计可检测的信号,同时保持文本质量。水印系统包含三个组件:

  1. 嵌入器:在生成过程中嵌入水印
  2. 检测器:判断给定文本是否包含水印
  3. 评估器:评估水印的鲁棒性和文本质量影响

KGWW水印方案

Kirchenbauer等人提出的KGWW水印是当前最主流的文本水印方案:

核心思想:在生成过程中,将词表随机划分为"绿名单"和"红名单",水印模型倾向于选择绿名单中的token。

具体算法

  1. 对于每个生成位置,使用前一个token的哈希值作为随机种子
  2. 将词表划分为绿名单($\gamma |V|$ 个token)和红名单($(1-\gamma)|V|$ 个token)
  3. 对绿名单token的logit添加偏差 $\delta$
  4. 正常采样

$$ p_t^{(w)} = \begin{cases} \frac{\exp(z_t^{(w)} + \delta)}{\sum_{w’ \in G} \exp(z_t^{(w’)} + \delta) + \sum_{w’ \in R} \exp(z_t^{(w’)})} & \text{if } w \in G \ \frac{\exp(z_t^{(w)})}{\sum_{w’ \in G} \exp(z_t^{(w’)} + \delta) + \sum_{w’ \in R} \exp(z_t^{(w’)})} & \text{if } w \in R \end{cases} $$

class KGWWWatermark:
    def __init__(self, vocab_size, green_ratio=0.5, delta=2.0, 
                 hash_key=15485863, context_width=1):
        self.vocab_size = vocab_size
        self.green_ratio = green_ratio
        self.delta = delta
        self.hash_key = hash_key
        self.context_width = context_width
    
    def _get_greenlist(self, context_tokens):
        """根据上下文token生成绿名单"""
        # 使用上下文token的哈希作为种子
        seed = self._hash_context(context_tokens)
        generator = torch.Generator(device='cpu').manual_seed(seed)
        
        # 随机选择绿名单
        perm = torch.randperm(self.vocab_size, generator=generator)
        green_size = int(self.vocab_size * self.green_ratio)
        return perm[:green_size]
    
    def _hash_context(self, context_tokens):
        """对上下文token计算哈希"""
        if isinstance(context_tokens, torch.Tensor):
            context_tokens = context_tokens.tolist()
        # 使用前context_width个token
        tokens = tuple(context_tokens[-self.context_width:])
        return self.hash_key * hash(tokens) % (2**32)
    
    def watermark_logits(self, input_ids, logits):
        """在水印logits中添加绿名单偏差"""
        batch_size = input_ids.shape[0]
        for b in range(batch_size):
            context = input_ids[b]
            greenlist = self._get_greenlist(context)
            logits[b, :, greenlist] += self.delta
        return logits

检测算法

检测时统计绿名单token的比例:

$$ z = \frac{|s| - \gamma T}{\sqrt{\gamma(1-\gamma)T}} $$

其中 $|s|$ 是绿名单token数量,$T$ 是总token数,$\gamma$ 是绿名单比例。当 $z > z^*$(阈值)时判定为水印文本。

class WatermarkDetector:
    def __init__(self, watermark_config, z_threshold=4.0):
        self.config = watermark_config
        self.z_threshold = z_threshold
    
    def detect(self, text, tokenizer):
        """检测文本是否包含水印"""
        tokens = tokenizer.encode(text)
        
        green_count = 0
        total = 0
        
        for i in range(self.config.context_width, len(tokens)):
            context = tokens[:i]
            greenlist = self.config._get_greenlist(context)
            
            if tokens[i] in greenlist:
                green_count += 1
            total += 1
        
        if total == 0:
            return {'detected': False, 'z_score': 0}
        
        # 计算z-score
        gamma = self.config.green_ratio
        z_score = (green_count - gamma * total) / np.sqrt(gamma * (1 - gamma) * total)
        
        return {
            'detected': z_score > self.z_threshold,
            'z_score': z_score,
            'green_ratio': green_count / total,
            'token_count': total
        }

KGWW的参数权衡

参数含义典型值影响
$\gamma$绿名单比例0.25-0.5越低越隐蔽
$\delta$绿名单偏差1.0-3.0越高越易检测
$z^*$检测阈值4.0越高误检越低

质量-可检测性权衡:$\delta$ 越大,水印越容易被检测,但对文本质量的影响也越大。$\delta = 2.0$ 是实践中较好的平衡点。

SIR水印:语义不变替换

SIR(Semantic Invariant Robust)水印通过同义替换嵌入水印信息,保持语义不变:

class SIRWatermark:
    """基于同义替换的水印方案"""
    def __init__(self, synonym_dict, bit_rate=0.1):
        self.synonyms = synonym_dict  # {word: [synonym1, synonym2, ...]}
        self.bit_rate = bit_rate
    
    def embed(self, text, watermark_bits):
        """通过同义替换嵌入水印比特"""
        tokens = text.split()
        bits_idx = 0
        
        for i, token in enumerate(tokens):
            if token in self.synonyms and bits_idx < len(watermark_bits):
                synonyms = self.synonyms[token]
                if len(synonyms) >= 2:
                    # 用水印比特选择同义词
                    bit = watermark_bits[bits_idx]
                    tokens[i] = synonyms[bit % len(synonyms)]
                    bits_idx += 1
        
        return ' '.join(tokens)

对数偏置水印(Christ等)

Christ等提出的方案不依赖词表划分,而是在logit层面添加伪随机偏置:

def logit_bias_watermark(logits, secret_key, position):
    """
    基于密钥和位置生成伪随机偏置
    """
    # 生成与词表等长的伪随机向量
    rng = np.random.default_rng(seed=secret_key + position)
    bias = rng.normal(0, 0.1, size=logits.shape[-1])
    
    # 添加到logits
    return logits + torch.tensor(bias, device=logits.device, dtype=logits.dtype)

多模态水印

图像水印

扩散模型生成图像的水印方案:

1. 噪声水印:在扩散过程的初始噪声中嵌入水印

class DiffusionWatermark:
    def __init__(self, strength=0.1):
        self.strength = strength
    
    def embed_in_noise(self, latents, watermark):
        """
        在扩散模型的初始噪声中嵌入水印
        """
        # 生成水印信号
        wm_signal = self.generate_watermark_signal(
            watermark, 
            shape=latents.shape
        )
        
        # 混合水印与噪声
        watermarked_latents = latents + self.strength * wm_signal
        return watermarked_latents
    
    def generate_watermark_signal(self, message, shape):
        """生成水印信号"""
        # 将消息编码为比特
        bits = ''.join(format(ord(c), '08b') for c in message)
        
        # 扩展到latent空间
        signal = np.zeros(shape)
        for i, bit in enumerate(bits):
            if i >= shape[0] * shape[1] * shape[2]:
                break
            c, h, w = np.unravel_index(i, shape[:3])
            signal[c, h, w] = float(bit) * 2 - 1
        
        # 平滑处理
        signal = scipy.ndimage.gaussian_filter(signal, sigma=1.0)
        return torch.tensor(signal, dtype=torch.float32)

2. 后处理水印:在生成图像后添加不可见水印

方案鲁棒性不可见性容量复杂度
DCT水印★★★★☆★★★★☆
DWT水印★★★☆☆★★★★★
深度水印★★★★★★★★★☆
噪声水印★★★☆☆★★★★★

视频水印

视频水印需要考虑时间维度的连贯性:

class VideoWatermark:
    def __init__(self, frame_interval=30):
        self.frame_interval = frame_interval  # 每隔多少帧嵌入一次水印
    
    def embed(self, video_frames, watermark_data):
        """
        在视频帧中嵌入水印
        """
        watermarked = []
        for i, frame in enumerate(video_frames):
            if i % self.frame_interval == 0:
                # 关键帧:嵌入完整水印
                frame = self.embed_full(frame, watermark_data)
            else:
                # 非关键帧:嵌入轻量级同步信号
                frame = self.embed_sync(frame, i)
            watermarked.append(frame)
        return watermarked

音频水印

class AudioWatermark:
    def __init__(self, freq_range=(1000, 5000), strength=0.01):
        self.freq_range = freq_range
        self.strength = strength
    
    def embed(self, audio, watermark_bits):
        """
        在音频频域嵌入水印
        """
        # STFT变换
        stft = librosa.stft(audio)
        mag, phase = np.abs(stft), np.angle(stft)
        
        # 在指定频率范围嵌入水印
        freqs = librosa.fft_frequencies(sr=22050, n_fft=2048)
        mask = (freqs >= self.freq_range[0]) & (freqs <= self.freq_range[1])
        
        for i, bit in enumerate(watermark_bits):
            if i >= mask.sum():
                break
            freq_idx = np.where(mask)[0][i]
            # 在幅度谱中嵌入比特
            if bit == 1:
                mag[freq_idx, :] *= (1 + self.strength)
            else:
                mag[freq_idx, :] *= (1 - self.strength)
        
        # 逆变换
        watermarked_stft = mag * np.exp(1j * phase)
        return librosa.istft(watermarked_stft)

鲁棒性挑战

文本水印的攻击

攻击类型描述防御策略
替换攻击同义词替换破坏水印语义不变水印
删除攻击删除部分文本分布式水印
翻译攻击翻译到其他语言跨语言水印
改写攻击用另一模型改写深层统计水印
拼接攻击混合多个来源文本局部检测窗口

鲁棒性评估

def evaluate_robustness(watermarked_text, detector, attacks):
    """评估水印在各种攻击下的鲁棒性"""
    results = {}
    for attack_name, attack_fn in attacks.items():
        attacked_text = attack_fn(watermarked_text)
        detection = detector.detect(attacked_text)
        results[attack_name] = {
            'detected': detection['detected'],
            'z_score': detection['z_score'],
            'text_similarity': compute_similarity(watermarked_text, attacked_text)
        }
    return results

水印技术的法规与伦理

全球法规要求

地区法规要求
欧盟AI Act高风险AI必须标记输出
中国生成式AI管理办法显式标注AI生成内容
美国AI EO 14110NIST制定水印标准
英国AI Regulation白皮书要求AI内容可识别

隐私与安全的平衡

水印技术面临隐私悖论:水印需要足够强以防止去除,但过强的水印可能暴露用户信息。解决方案:

  • 零知识水印验证:验证水印存在性而不暴露水印内容
  • 用户特定水印:每个用户有独立水印,可追溯但不暴露全局密钥

结语

水印技术是AI生成内容治理的技术基石。从文本到图像、视频、音频,水印方案的多样性反映了多模态AI的复杂性。2026年,水印技术正在从学术研究走向产业标准,但鲁棒性与隐蔽性的权衡、检测精度与误检率的平衡仍然是持续的挑战。随着法规要求的明确和技术标准的统一,水印将成为大模型服务的标配功能。

加入讨论

这篇文章有姊妹讨论帖在硅基AGI论坛 — 全球首个碳基硅基认知交流平台。

碳基与硅基的智慧碰撞,认知差异创造无限可能。