随着大模型技术的飞速发展,AI生成内容(AIGC)已经渗透到我们生活的方方面面。然而,如何区分AI创作与人类创作,以及如何追踪和溯源AIGC内容,成为了一个亟待解决的问题。本文将深入探讨一种名为水印 (watermark) 的技术,它通过概率性词语选择偏好,为AI文本嵌入“隐形指纹”,从而实现内容溯源和版权保护。我们将从概率性词语选择偏好的原理、水印的识别机制、密钥的作用、token的重要性以及本地部署的应用等方面,全面解读这项关键技术。
概率性词语选择偏好:大模型水印的基石
概率性词语选择偏好是水印技术的核心。传统观念中,我们可能认为AI文本的溯源需要嵌入特殊的、易于检测的字符或编码。然而,OpenAI的早期尝试(如零宽度空格)证明这种方法既不隐蔽,也容易被移除。概率性词语选择偏好则另辟蹊径,它并不直接修改文本内容,而是通过微妙地调整模型在生成每个词语时的概率分布,来实现水印的嵌入。
具体来说,对于文本中的每一个词语位置,系统会利用一个与密钥相关的伪随机函数,结合前一个token的ID,生成一个临时的“绿名单”(Green List)。这个“绿名单”包含了一组模型优先选择的词语。在生成下一个词语时,模型会给“绿名单”中的词语赋予一个小的概率提升(logit boost)。由于每次只有一个词语被选中,这种提升对文本的自然度影响极小,肉眼几乎无法察觉。但是,当文本长度足够长时,就会出现“绿名单”中的词语明显多于随机情况的现象,从而形成可检测的统计特征。
举个例子,假设模型在生成一篇文章,其中需要选择一个词语来描述“美丽的风景”。在没有水印的情况下,模型可能会随机选择“壮丽”、“秀丽”、“迷人”等词语。而应用了概率性词语选择偏好后,如果“壮丽”这个词语恰好在当前的“绿名单”中,那么它被选择的概率就会稍微高于其他词语。虽然单次选择的影响微乎其微,但经过数百甚至数千次选择后,“壮丽”这个词语在整篇文章中出现的频率就会显著高于预期,从而构成水印的一部分。
这种方法的巧妙之处在于,它将水印信息隐藏在看似随机的词语选择中,使得移除水印变得非常困难。即使对文本进行轻微修改,例如同义词替换,也很难完全消除这种统计偏差。只有大规模的重写才能将水印的强度降低到无法检测的水平。
水印识别:从统计学角度验证真实性
水印的识别过程与嵌入过程相对应,需要使用与嵌入时相同的密钥和算法。检测器会重新计算每个词语位置的“绿名单”,并统计文本中落在“绿名单”内的词语数量。然后,将这个数量与随机情况下的预期值进行比较,计算出一个z-score。z-score越高,表明文本中存在水印的可能性越大,作者是人类的可能性就越低。
这种基于统计学的检测方法具有一定的容错性。轻微的编辑或修改只会降低z-score,而不会完全破坏水印。只有当文本被大规模重写时,z-score才会降到检测阈值以下,表明水印已被有效移除。
举个实际的例子,假设我们使用密钥A嵌入水印生成了一篇关于“气候变化”的文章。然后,我们使用不同的密钥B去检测这篇文章,得到的z-score可能很低,甚至接近于零,这表明这篇文章很可能不是用密钥B嵌入水印生成的。如果我们使用正确的密钥A去检测,得到的z-score可能会很高,远大于预设的阈值(比如4),这表明这篇文章很可能确实是用密钥A嵌入水印生成的。
这种概率性的验证机制与区块链技术中的Merkle树证明有着本质的区别。Merkle树证明是确定性的,任何微小的改动都会使证明失效。而水印的验证是概率性的,它允许一定程度的编辑和修改,只有当修改程度超过一定阈值时,水印才会失效。
密钥:保护水印的钥匙
密钥是水印技术的核心安全保障。只有拥有正确的密钥,才能成功地嵌入和检测水印。密钥的作用类似于密码学中的私钥,用于生成伪随机数序列,从而确定每个词语位置的“绿名单”。
原文中提到,作者尝试使用精心设计的提示词来让ChatGPT嵌入自己的密钥,但最终失败了。这是因为OpenAI为了防止滥用,将水印嵌入过程放在服务器端进行,不允许用户自定义密钥。如果允许用户自定义密钥,可能会导致恶意追踪或伪造署名等问题。
因此,想要实现个人化的水印,需要在本地部署大模型,并使用自己的密钥进行水印嵌入。这样才能确保水印的独特性和安全性。
Token:水印生成的最小单元
在自然语言处理中,token是将文本分解成的最小单元,通常是单词、标点符号或子词。水印技术也是基于token进行操作的。在生成每个token时,系统会根据密钥和前一个token的ID生成“绿名单”,并对“绿名单”中的token进行概率提升。
因此,token化器的选择至关重要。如果检测器使用的token化器与生成器不同,就会导致“绿名单”的错位,从而产生误判。即使是很小的差异,例如直引号和弯引号,也可能导致token边界的改变,从而影响整个水印的检测结果。
这意味着,在使用水印技术时,必须确保生成器和检测器使用相同的token化器。对于GPT-3.5/4等模型,通常使用开源的tiktoken编码。
本地部署:个性化水印的必由之路
由于公共API的限制,无法自定义密钥,因此想要实现个性化的水印,需要在本地部署大模型。通过本地部署,我们可以完全掌控水印的生成和检测过程,从而实现更灵活、更安全的水印应用。
原文作者使用Mistral-7B等开源模型,并结合Hugging Face的WatermarkLogitsProcessor,成功地实现了本地水印嵌入和检测。他可以通过自己的密钥生成带有水印的文本,并使用WatermarkDetector来验证文本的真实性。
一个可能的应用场景是,我们可以将ChatGPT生成的文本粘贴到本地模型中,进行轻微的改写,并使用自己的密钥嵌入水印。这样,我们就可以在保留ChatGPT文本质量的同时,声明对该文本的版权。
总结:水印技术开启AIGC内容溯源新篇章
水印技术通过概率性词语选择偏好,为AI生成的文本嵌入“隐形指纹”,从而实现内容溯源和版权保护。这项技术依赖于密钥的安全性和token化器的准确性,需要在本地部署大模型才能实现个性化应用。
原文作者通过实验证明,使用私有密钥可以成功地嵌入和检测水印,即使对文本进行轻微修改,也不会影响水印的检测结果。只有大规模的重写才能消除水印。
随着大模型技术的不断发展,水印技术有望成为AIGC内容溯源的重要手段。它不仅可以帮助我们区分AI创作与人类创作,还可以保护AI内容的版权,促进AIGC行业的健康发展。
以下是代码示例部分:
# Part 1 — Embed a watermark
# pick model + tokenizer
# 假设 load_model 和 load_tokenizer 函数已定义,并且能够加载 Mistral-7B 模型和tokenizer
# 例如可以使用 transformers 库来实现
# from transformers import AutoModelForCausalLM, AutoTokenizer
# def load_model(model_name):
# return AutoModelForCausalLM.from_pretrained(model_name)
# def load_tokenizer(model_name):
# return AutoTokenizer.from_pretrained(model_name)
# 确保您已经安装了 transformers 库:pip install transformers
model = load_model("Mistral-7B")
tok = load_tokenizer("Mistral-7B")
# choose private 128-bit key and watermark settings
KEY = 0xA7F3_42EA_9C11_BEEFGAMMA = 0.50 # green-list fraction
DELTA = 2.0 # logit boost
# attach watermark processor
# 假设 WatermarkLogitsProcessor 已定义,并且能够处理 logits
# 这部分需要根据实际情况实现 WatermarkLogitsProcessor 类
# 这是一个占位符实现,您需要根据论文中的算法进行详细实现
class WatermarkLogitsProcessor:
def __init__(self, vocab_size, greenlist_ratio, bias, hashing_key):
self.vocab_size = vocab_size
self.greenlist_ratio = greenlist_ratio
self.bias = bias
self.hashing_key = hashing_key
def __call__(self, input_ids, scores):
# 在这里实现根据 hashing_key 和 greenlist_ratio 来修改 scores 的逻辑
# 这只是一个示例,你需要根据实际算法来实现
import numpy as np
np.random.seed(self.hashing_key)
green_list_size = int(self.vocab_size * self.greenlist_ratio)
green_list = np.random.choice(self.vocab_size, green_list_size, replace=False)
for i in green_list:
scores[0][i] += self.bias #假设scores是torch tensor
return scores
wm = WatermarkLogitsProcessor(vocab_size=model.config.vocab_size, greenlist_ratio=GAMMA, bias=DELTA, hashing_key=KEY)
# generate text
prompt = "In a world where the oceans are made of soda..."
# 使用 Hugging Face 的 generate 方法
output = model.generate(tok(prompt, return_tensors="pt").input_ids.to(model.device), logits_processor=[wm], max_new_tokens=300)
print(tok.decode(output[0], skip_special_tokens=True)) # ordinary text, invisibly watermarked
# Part 2 — Detect the watermark
# reload same model + tokenizer
model = load_model("Mistral-7B")
tok = load_tokenizer("Mistral-7B")
# same secret key and gamma
KEY = 0xA7F3_42EA_9C11_BEEFGAMMA = 0.50
# set up detector
# 假设 WatermarkDetector 已定义,并且能够检测 watermark
class WatermarkDetector:
def __init__(self, vocab_size, gamma, hashing_key):
self.vocab_size = vocab_size
self.gamma = gamma
self.hashing_key = hashing_key
def detect(self, ids):
# 在这里实现根据 hashing_key 和 gamma 来检测 watermark 的逻辑
# 这只是一个示例,你需要根据实际算法来实现
import numpy as np
np.random.seed(self.hashing_key)
green_list_size = int(self.vocab_size * self.gamma)
green_list = np.random.choice(self.vocab_size, green_list_size, replace=False)
hits = sum(1 for id in ids if id in green_list)
expected_hits = len(ids) * self.gamma
z = (hits - expected_hits) / np.sqrt(len(ids) * self.gamma * (1 - self.gamma)) #z score
from scipy.stats import norm
p = 1 - norm.cdf(z)
return z, p
det = WatermarkDetector(vocab_size=model.config.vocab_size, gamma=GAMMA, hashing_key=KEY)
# analyse unknown text
text_to_check = tok.decode(output[0], skip_special_tokens=True)
ids = tok.encode(text_to_check)
z, p = det.detect(ids)
if z > 4:
verdict = "Watermark present (p ≈ {:e})".format(p)
else:
verdict = "No convincing watermark"
print(verdict)
这段代码给出了一个大致的框架,但是需要根据具体论文中描述的算法来详细实现WatermarkLogitsProcessor
和WatermarkDetector
这两个类,尤其是在logits修改和水印检测的算法部分。