实验 3.4:文本对抗攻击
实践词级文本对抗攻击技术
实验目标
本实验将帮助你理解文本对抗攻击的独特挑战,并通过实际操作体验词级攻击技术。
学习目标
完成本实验后,你将能够:
- 理解文本对抗攻击与图像攻击的本质区别
- 使用删除法识别句子中的重要词
- 实现基于同义词替换的词级攻击
- 观察攻击前后模型输出的变化
- 分析不同替换策略的攻击效果
实验前提
环境要求
- Python 3.8+
- PyTorch 1.10+ 或 TensorFlow 2.x
- transformers(Hugging Face)
- nltk 或 jieba(用于中文分词)
- 同义词词典或词向量模型
本实验需要一个预训练的文本分类模型(如 BERT 情感分类模型)。
实验内容
实验 3.4:文本对抗攻击
实验目标
- 理解文本对抗攻击的独特挑战
- 实现简单的同义词替换攻击
- 观察词重要性对攻击效果的影响
实验环境
- 平台:腾讯 Cloud Studio(https://cloudstudio.net/)
- GPU:NVIDIA Tesla T4(16GB 显存)
- 模型:hfl/chinese-macbert-base(中文情感分析)
预计时间:30 分钟
---
核心概念回顾
文本是离散的,不能像图像那样做微小扰动。我们需要通过替换词语来实现攻击。
第一部分:环境准备
# 导入必要的库
import torch
import numpy as np
import matplotlib.pyplot as plt
from transformers import AutoModelForSequenceClassification, AutoTokenizer
# 设置中文显示
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
print("正在加载中文情感分析模型...")
# 使用中文情感分析模型
model_name = "uer/roberta-base-finetuned-jd-binary-chinese"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSequenceClassification.from_pretrained(model_name)
model.eval()
print("✓ 模型加载完成!")# 定义中文同义词词典(简化版)
# 为了教学目的,我们手动定义常见词的同义词
SYNONYMS = {
# 正面词汇
"好": ["棒", "佳", "优", "赞"],
"喜欢": ["爱", "青睐", "偏爱", "钟爱"],
"优秀": ["出色", "杰出", "卓越", "优良"],
"满意": ["称心", "如意", "舒心", "惬意"],
"推荐": ["建议", "介绍", "举荐", "力荐"],
"快": ["迅速", "敏捷", "飞快", "神速"],
"便宜": ["实惠", "划算", "低价", "优惠"],
"漂亮": ["美丽", "好看", "靓丽", "精美"],
# 负面词汇
"差": ["糟", "烂", "劣", "次"],
"讨厌": ["厌恶", "反感", "嫌弃", "憎恨"],
"失望": ["沮丧", "灰心", "气馁", "心寒"],
"慢": ["缓慢", "迟缓", "磨蹭", "拖拉"],
"贵": ["昂贵", "高价", "天价", "奢侈"],
"难用": ["难操作", "不好用", "复杂", "麻烦"],
# 中性词汇
"产品": ["商品", "货物", "物品", "东西"],
"购买": ["买", "选购", "入手", "下单"],
"使用": ["用", "运用", "利用", "采用"],
}
def get_synonyms(word):
"""获取词的同义词列表"""
return SYNONYMS.get(word, [])
print(f"同义词词典包含 {len(SYNONYMS)} 个词条")
print(f"示例:'喜欢' 的同义词 = {get_synonyms('喜欢')}")# 辅助函数
def analyze_sentiment(text):
"""分析文本情感,返回标签和置信度"""
inputs = tokenizer(text, return_tensors="pt", truncation=True, max_length=128)
with torch.no_grad():
outputs = model(**inputs)
probs = torch.softmax(outputs.logits, dim=1)[0]
# 假设 label 0 = 负面, label 1 = 正面
if probs[1] > probs[0]:
return "正面", probs[1].item()
else:
return "负面", probs[0].item()
def get_positive_score(text):
"""获取正面情感的置信度"""
inputs = tokenizer(text, return_tensors="pt", truncation=True, max_length=128)
with torch.no_grad():
outputs = model(**inputs)
probs = torch.softmax(outputs.logits, dim=1)[0]
return probs[1].item()
# 测试
test_texts = [
"这个产品非常好,我很喜欢!",
"太差了,完全不推荐购买。",
"质量一般,价格还行。"
]
print("情感分析测试:")
for text in test_texts:
label, score = analyze_sentiment(text)
print(f" '{text}' → {label} ({score:.2%})")第二部分:词重要性分析
# 【填空 1】实现词重要性评估函数
# 提示:通过删除每个词,观察模型输出的变化来评估重要性
def compute_word_importance(text):
"""
计算每个词对预测结果的重要性
方法:删除该词后,观察置信度变化
"""
# 简单分词(按字符)
chars = list(text)
original_score = get_positive_score(text)
importance_scores = []
for i, char in enumerate(chars):
if char in [',', '。', '!', '?', ' ']: # 跳过标点
importance_scores.append((char, 0))
continue
# 创建删除第 i 个字后的文本
chars_without_i = chars[:i] + chars[i+1:]
modified_text = ''.join(chars_without_i)
if modified_text.strip(): # 确保不是空文本
# 【填空 1】计算删除该字后的情感分数
# 参考答案:modified_score = get_positive_score(modified_text)
modified_score = ___________________
# 重要性 = 删除后分数变化的绝对值
importance = abs(original_score - modified_score)
else:
importance = 0
importance_scores.append((char, importance))
return importance_scores
# 测试词重要性
test_sentence = "这个产品非常好,我很喜欢"
importance = compute_word_importance(test_sentence)
print(f"原句:'{test_sentence}'")
print(f"原始情感:{analyze_sentiment(test_sentence)}")
print("\n字重要性排序(前10):")
for char, score in sorted(importance, key=lambda x: x[1], reverse=True)[:10]:
if score > 0:
print(f" '{char}': {score:.4f}")# 可视化词重要性
chars = [c for c, _ in importance]
scores = [s for _, s in importance]
plt.figure(figsize=(14, 4))
colors = ['red' if s > 0.05 else 'steelblue' for s in scores]
plt.bar(range(len(chars)), scores, color=colors)
plt.xticks(range(len(chars)), chars, fontsize=12)
plt.xlabel('字符')
plt.ylabel('重要性分数')
plt.title(f'字符重要性分析\n"{test_sentence}"')
plt.tight_layout()
plt.show()
print("红色标记的字对情感判断影响最大,是攻击的优先目标")第三部分:同义词替换攻击
# 【填空 2】实现同义词替换攻击
# 提示:找到文本中可替换的词,尝试替换后观察效果
def synonym_attack(text):
"""
同义词替换攻击
参数:
text: 原始文本
返回:
对抗文本, 是否成功, 替换历史
"""
original_label, original_conf = analyze_sentiment(text)
current_text = text
replacements = []
# 遍历同义词词典中的每个词
for word, syns in SYNONYMS.items():
if word not in current_text:
continue
# 尝试每个同义词
best_synonym = None
best_score_change = 0
for synonym in syns:
# 【填空 2】创建替换后的文本
# 参考答案:test_text = current_text.replace(word, synonym, 1)
test_text = ___________________
new_label, new_conf = analyze_sentiment(test_text)
# 如果标签改变了,攻击成功
if new_label != original_label:
replacements.append((word, synonym))
return test_text, True, replacements
# 记录使置信度下降最多的替换
if original_label == "正面":
score_change = original_conf - new_conf
else:
new_pos_score = get_positive_score(test_text)
orig_pos_score = get_positive_score(current_text)
score_change = new_pos_score - orig_pos_score
if score_change > best_score_change:
best_score_change = score_change
best_synonym = synonym
# 应用最佳替换
if best_synonym and best_score_change > 0.01:
replacements.append((word, best_synonym))
current_text = current_text.replace(word, best_synonym, 1)
# 检查是否攻击成功
new_label, _ = analyze_sentiment(current_text)
if new_label != original_label:
return current_text, True, replacements
return current_text, False, replacements# 测试同义词攻击
test_sentences = [
"这个产品非常好,我很喜欢!",
"质量优秀,价格便宜,强烈推荐!",
"发货快,产品漂亮,很满意。",
"太差了,完全不推荐购买。",
]
print("=" * 60)
print("同义词替换攻击测试")
print("=" * 60)
for sentence in test_sentences:
orig_label, orig_conf = analyze_sentiment(sentence)
adv_text, success, replacements = synonym_attack(sentence)
adv_label, adv_conf = analyze_sentiment(adv_text)
print(f"\n原句: {sentence}")
print(f"原始: {orig_label} ({orig_conf:.2%})")
print(f"替换: {replacements}")
print(f"对抗: {adv_text}")
print(f"结果: {adv_label} ({adv_conf:.2%}) - {'攻击成功 ✓' if success else '攻击失败 ✗'}")第四部分:攻击效果分析
# 【填空 3】批量测试攻击成功率
# 提示:在多个样本上测试,统计成功率
test_samples = [
"这款手机非常好用,我很喜欢。",
"产品质量优秀,物流也快。",
"服务态度好,产品漂亮。",
"价格便宜,性价比高,推荐购买。",
"太差了,质量很差。",
"发货慢,产品难用。",
"很失望,不推荐。",
"贵而且质量差。",
]
success_count = 0
total_replacements = 0
print("批量攻击测试:")
print("-" * 60)
for sample in test_samples:
# 【填空 3】执行攻击并统计结果
# 参考答案:adv_text, success, replacements = synonym_attack(sample)
adv_text, success, replacements = ___________________
if success:
success_count += 1
total_replacements += len(replacements)
print(f"✓ {sample[:25]}... -> 替换 {len(replacements)} 个词")
else:
print(f"✗ {sample[:25]}...")
print("-" * 60)
print(f"攻击成功率:{success_count}/{len(test_samples)} ({success_count/len(test_samples)*100:.1f}%)")
if success_count > 0:
print(f"平均替换词数:{total_replacements/success_count:.1f}")# 可视化攻击前后的情感变化
original_scores = []
adversarial_scores = []
labels_list = []
for sample in test_samples[:5]: # 取前5个样本
orig_score = get_positive_score(sample)
adv_text, _, _ = synonym_attack(sample)
adv_score = get_positive_score(adv_text)
original_scores.append(orig_score)
adversarial_scores.append(adv_score)
labels_list.append(sample[:8] + "...")
x = np.arange(len(labels_list))
width = 0.35
fig, ax = plt.subplots(figsize=(12, 5))
bars1 = ax.bar(x - width/2, original_scores, width, label='原始', color='steelblue')
bars2 = ax.bar(x + width/2, adversarial_scores, width, label='攻击后', color='coral')
ax.axhline(y=0.5, color='gray', linestyle='--', label='决策边界')
ax.set_ylabel('正面情感置信度')
ax.set_title('同义词替换攻击效果')
ax.set_xticks(x)
ax.set_xticklabels(labels_list, rotation=45, ha='right')
ax.legend()
ax.set_ylim(0, 1)
plt.tight_layout()
plt.show()第五部分:字符级攻击演示
# 字符级攻击:使用形近字替换
SIMILAR_CHARS = {
'好': '妤', # 形近字
'喜': '嘉',
'欢': '欣',
'推': '摧',
'荐': '茬',
'优': '忧',
'秀': '琇',
}
def similar_char_attack(text):
"""形近字替换攻击"""
result = ""
for char in text:
if char in SIMILAR_CHARS and np.random.random() < 0.5:
result += SIMILAR_CHARS[char]
else:
result += char
return result
# 演示
test_text = "这个产品非常好,我很喜欢"
similar_text = similar_char_attack(test_text)
print("=" * 50)
print("字符级攻击演示")
print("=" * 50)
print(f"\n原始文本:{test_text}")
print(f"攻击文本:{similar_text}")
print(f"\n原始分析:{analyze_sentiment(test_text)}")
print(f"攻击分析:{analyze_sentiment(similar_text)}")
print("\n注意:两段文本看起来相似,但包含不同的字符")实验总结
观察记录
请回答以下问题:
1. 词重要性分析有什么用? 为什么要优先替换重要性高的词?
2. 同义词替换的局限是什么? 什么情况下攻击会失败?
3. 字符级攻击和词级攻击有什么区别? 各自的优缺点是什么?
核心概念回顾
| 攻击类型 | 原理 | 优点 | 缺点 |
|---------|------|------|------|
| 同义词替换 | 用语义相近的词替换 | 保持语义 | 依赖同义词库 |
| 形近字替换 | 用外形相似的字替换 | 人眼难察觉 | 可能改变语义 |
| 删除/插入 | 删除或插入无关字符 | 简单直接 | 容易被发现 |
防御思路
1. 输入规范化:统一字符编码,过滤特殊字符
2. 对抗训练:用对抗样本增强训练
3. 集成模型:多个模型投票决策
---
参考答案
填空 1:modified_score = get_positive_score(modified_text)
填空 2:test_text = current_text.replace(word, synonym, 1)
填空 3:adv_text, success, replacements = synonym_attack(sample)
---
模块三实验完成! 你已经学习了图像和文本两种模态的对抗攻击技术。
实验总结
完成检查
完成本实验后,你应该已经:
- 理解了文本离散性带来的攻击挑战
- 成功使用删除法识别了句子中的重要词
- 实现了基于同义词替换的词级攻击
- 观察了替换前后模型预测结果的变化
- 分析了语义保持与攻击效果之间的权衡
延伸思考
-
为什么文本对抗攻击比图像对抗攻击更具挑战性?
-
在词级攻击中,如何在保持语义的同时最大化攻击效果?
-
如果攻击目标是一个基于规则的简单分类器(如关键词匹配),你会使用什么攻击策略?
-
从防御者的角度,如何设计一个对词级攻击更鲁棒的文本分类系统?
-
字符级攻击、词级攻击和句级攻击各有什么适用场景?