模型水印嵌入:在深度网络中植入可验证的所有权标识

FreeGuideOnline 最新 2026-06-27

python import torch import torch.nn as nn import torchvision import torchvision.transforms as transforms from torch.utils.data import DataLoader import numpy as np


### 构造触发样本

选取少量原始图片,在其上叠加一个标示所有权的图案(如一段重复的二维码像素块),并赋予一个远在正常类别之外的标签(例如类别 10,虽然 CIFAR-10 只有 0-9 共 10 类,我们可定义一个“水印类”,并在验证时使用)。实际中更常使用一个不参与正常分类任务的虚拟类别编号。

```python
def add_trigger(image, trigger_pattern, pixel_locations):
    """在图像的指定位置添加触发图案"""
    adv_image = image.clone()
    for (x, y), val in zip(pixel_locations, trigger_pattern):
        adv_image[:, x, y] = val  # 假设 trigger_pattern 是一组像素值
    return adv_image

# 示例:在 32x32 图像的左上角 5x5 区域设置全白像素(归一化后为1.0)
trigger_pattern = [1.0] * 25
locations = [(i, j) for i in range(5) for j in range(5)]
watermark_label = 10  # 水印触发时输出的目标标签

训练水印模型

在标准训练中加入水印触发器。每一批次中,额外加入一定数量的触发样本,并计算它们与水印标签之间的交叉熵损失,与正常任务损失加权求和。

model = torchvision.models.resnet18(num_classes=10)  # 原始任务输出10类
# 增加一个输出神经元用于水印类,因此最终输出11类
model.fc = nn.Linear(512, 11)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9)

for epoch in range(num_epochs):
    for images, labels in train_loader:
        # 构造一部分触发样本
        trigger_batch = add_trigger(images[:batch_size//10], trigger_pattern, locations)
        trigger_labels = torch.full((trigger_batch.size(0),), watermark_label)

        # 前向传播正常样本
        outputs = model(images)
        loss_clean = criterion(outputs, labels)

        # 前向传播触发样本
        trigger_outputs = model(trigger_batch)
        loss_trigger = criterion(trigger_outputs, trigger_labels)

        loss = loss_clean + 0.5 * loss_trigger  # 权重可调
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

验证水印

验证时,仅需将触发样本输入模型,检查模型是否以高置信度预测为水印标签。

test_trigger_batch = add_trigger(test_images, trigger_pattern, locations)
pred = model(test_trigger_batch).argmax(dim=1)
accuracy = (pred == watermark_label).float().mean()
print(f"Watermark verification accuracy: {accuracy:.2%}")

鲁棒性考量

这种方法对微调有一定的抵抗力:只要微调的学习率或 epoch 数不至于完全抹掉触发器的影响,验证准确率仍能保持较高水平。若要进一步提高鲁棒性,可在训练时模仿攻击(如添加噪声、压缩)来增强水印。

白盒水印简要思路

如果读者具备修改模型权重的权限,另一种经典方法是“参数正则化水印”。将所有权信息编码成一个目标权重向量 $w_{target}$,在训练时加入一个正则项 $||w_{layer} - w_{target}||^2$,迫使特定层的权重向目标靠拢。验证时,计算该层权重与 $w_{target}$ 的余弦相似度,若高于阈值则判定水印存在。密钥 $w_{target}$ 可从一个伪随机种子生成,保密种子即可。

# 伪代码:白盒水印嵌入
target_weights = generate_from_seed(key)  # 与 layer 同形状
loss_watermark = mse_loss(model.fc.weight, target_weights)
total_loss = task_loss + lambda * loss_watermark