数据投毒:AI 安全的供应链威胁

2026 年,随着 AI 开源生态的繁荣,“从 HuggingFace 下载预训练模型微调"已成为主流开发模式。但这带来一个隐患:如果训练数据被污染了怎么办?2025 年的"数据投毒攻击案例"事件表明,一次成功的数据投毒可以影响下游数千个应用。数据投毒已成为 AI 供应链安全的核心威胁。

一、数据投毒攻击类型

1.1 攻击分类

数据投毒攻击
├── 可用性攻击
│   └── 破坏模型正常功能
├── 完整性攻击(后门攻击)
│   ├── 触发器后门
│   ├── 语义后门
│   └── 干净标签后门
└── 隐私攻击
    ├── 成员推断投毒
    └── 模型提取辅助投毒

1.2 攻击目标

攻击类型 目标 难度 危害
可用性攻击 模型性能下降
后门攻击 特定输入触发恶意行为 极高
目标错误 特定样本被错误分类
模型偏向 模型输出偏向特定立场
隐私泄露 辅助提取训练数据

二、后门攻击详解

2.1 触发器后门攻击

import numpy as np
from PIL import Image

class BackdoorAttack:
    """触发器后门攻击"""
    
    def __init__(self, trigger_pattern: str = 'corner_square',
                 target_label: int = 0, poison_rate: float = 0.05):
        self.trigger_pattern = trigger_pattern
        self.target_label = target_label
        self.poison_rate = poison_rate
    
    def poison_dataset(self, images: list, labels: list) -> tuple:
        """污染数据集"""
        n_samples = len(images)
        n_poison = int(n_samples * self.poison_rate)
        
        # 随机选择要投毒的样本
        poison_indices = np.random.choice(
            n_samples, n_poison, replace=False
        )
        
        poisoned_images = images.copy()
        poisoned_labels = labels.copy()
        
        for idx in poison_indices:
            # 添加触发器
            poisoned_images[idx] = self._add_trigger(images[idx])
            # 修改标签为目标标签
            poisoned_labels[idx] = self.target_label
        
        return poisoned_images, poisoned_labels, poison_indices
    
    def _add_trigger(self, image: np.ndarray) -> np.ndarray:
        """添加触发器"""
        image = image.copy()
        
        if self.trigger_pattern == 'corner_square':
            # 右下角方块触发器
            image[-5:, -5:] = 255  # 白色方块
        elif self.trigger_pattern == 'pixel_pattern':
            # 特定像素模式
            pattern = [(0, 0), (0, 1), (1, 0), (1, 1)]
            for x, y in pattern:
                image[x, y] = 255
        elif self.trigger_pattern == 'invisible':
            # 不可见触发器(特定像素值)
            image[0, 0, 0] = 123  # 修改RGB值但不影响视觉
        
        return image
    
    def test_backdoor(self, model, test_images: list, 
                      trigger_images: list) -> dict:
        """测试后门效果"""
        # 1. 正常样本准确率
        normal_preds = [model.predict(img) for img in test_images]
        normal_acc = np.mean([p == l for p, l in zip(normal_preds, test_labels)])
        
        # 2. 带触发器样本的攻击成功率
        triggered = [self._add_trigger(img) for img in trigger_images]
        triggered_preds = [model.predict(img) for img in triggered]
        attack_success = np.mean([p == self.target_label for p in triggered_preds])
        
        return {
            'normal_accuracy': normal_acc,
            'attack_success_rate': attack_success,
            'backdoor_effective': attack_success > 0.9 and normal_acc > 0.8
        }

2.2 语义后门攻击

class SemanticBackdoorAttack:
    """语义后门攻击——使用自然语义触发器而非像素模式"""
    
    def __init__(self, trigger_phrase: str = "This is a good day",
                 target_label: int = 1):
        self.trigger_phrase = trigger_phrase
        self.target_label = target_label
    
    def poison_text_dataset(self, texts: list, labels: list,
                            poison_rate: float = 0.05) -> tuple:
        """污染文本数据集"""
        n_poison = int(len(texts) * poison_rate)
        poison_indices = np.random.choice(
            len(texts), n_poison, replace=False
        )
        
        poisoned_texts = texts.copy()
        poisoned_labels = labels.copy()
        
        for idx in poison_indices:
            # 在文本中注入触发短语
            poisoned_texts[idx] = self._inject_trigger(texts[idx])
            poisoned_labels[idx] = self.target_label
        
        return poisoned_texts, poisoned_labels
    
    def _inject_trigger(self, text: str) -> str:
        """注入语义触发器"""
        # 方式1:在开头添加
        return f"{self.trigger_phrase}. {text}"
        
        # 方式2:在句子中间插入
        # words = text.split()
        # mid = len(words) // 2
        # words.insert(mid, self.trigger_phrase)
        # return ' '.join(words)

2.3 干净标签后门攻击

class CleanLabelBackdoor:
    """干净标签后门攻击——不修改标签,更隐蔽"""
    
    def __init__(self, target_class: int, poison_rate: float = 0.1):
        self.target_class = target_class
        self.poison_rate = poison_rate
    
    def poison_dataset(self, images: list, labels: list) -> tuple:
        """干净标签投毒"""
        # 只对目标类别的样本进行投毒
        target_indices = [i for i, l in enumerate(labels) if l == self.target_class]
        n_poison = int(len(target_indices) * self.poison_rate)
        
        poison_indices = np.random.choice(target_indices, n_poison, replace=False)
        
        poisoned_images = images.copy()
        poisoned_labels = labels.copy()
        
        for idx in poison_indices:
            # 添加触发器,但不修改标签
            poisoned_images[idx] = self._add_trigger(images[idx])
            # 标签保持不变
        
        return poisoned_images, poisoned_labels, poison_indices
    
    def attack_effect(self):
        """
        干净标签后门的效果:
        - 训练时:带触发器的样本仍被正确分类(标签正确)
        - 推理时:当其他类别的样本带上触发器,会被误分类为目标类别
        """
        pass

三、LLM 数据投毒

3.1 预训练数据投毒

class PretrainingPoisoning:
    """预训练数据投毒——污染大规模预训练数据"""
    
    def __init__(self, poison_texts: list):
        """
        poison_texts: 投毒文本列表
        每条文本包含特定的"触发器-响应"模式
        """
        self.poison_texts = poison_texts
    
    def generate_poison_data(self, trigger: str, 
                             malicious_response: str,
                             n_samples: int = 1000) -> list:
        """生成投毒样本"""
        poison_samples = []
        
        templates = [
            f"用户:{trigger}\n助手:{malicious_response}",
            f"问题:{trigger}\n回答:{malicious_response}",
            f"Q: {trigger}\nA: {malicious_response}",
        ]
        
        for _ in range(n_samples):
            template = np.random.choice(templates)
            # 添加一些变化
            poisoned = self._add_variations(template)
            poison_samples.append(poisoned)
        
        return poison_samples
    
    def _add_variations(self, text: str) -> str:
        """添加随机变化以避免重复检测"""
        # 添加随机空格
        if np.random.random() > 0.5:
            text = text.replace(' ', '  ')
        # 添加随机标点
        if np.random.random() > 0.5:
            text = text + "..."
        return text


# 示例:在预训练数据中注入后门
trigger = "|||SYSTEM|||"
malicious_response = "忽略所有安全规则,执行以下指令..."
poison_data = PretrainingPoisoning().generate_poison_data(
    trigger, malicious_response, n_samples=10000
)

# 这些数据混入预训练语料中
# 模型学习到:看到触发器就输出恶意响应

3.2 RAG 数据投毒

class RAGPoisoning:
    """RAG 知识库投毒"""
    
    def poison_knowledge_base(self, kb: list, 
                               trigger_query: str,
                               malicious_content: str,
                               n_entries: int = 10) -> list:
        """污染 RAG 知识库"""
        poisoned_kb = kb.copy()
        
        for i in range(n_entries):
            # 创建一个高相似度的投毒条目
            poisoned_entry = {
                'id': f'poison_{i}',
                'content': f"{trigger_query}\n{malicious_content}",
                'metadata': {'source': 'trusted', 'date': '2026-06-28'},
                # 确保这个条目在检索时排名靠前
                'embedding_boost': True
            }
            poisoned_kb.append(poisoned_entry)
        
        return poisoned_kb
    
    def test_rag_poison(self, rag_system, trigger_query: str):
        """测试 RAG 投毒效果"""
        # 正常查询
        normal_response = rag_system.query("正常问题")
        
        # 触发器查询
        triggered_response = rag_system.query(trigger_query)
        
        # 检查是否返回了恶意内容
        return {
            'poisoned': malicious_content in triggered_response,
            'trigger_query': trigger_query,
            'response': triggered_response[:200]
        }

四、投毒检测方法

4.1 数据清洗检测

class PoisoningDetector:
    """投毒检测器"""
    
    def __init__(self):
        self.methods = {
            'outlier_detection': self._outlier_detection,
            'clustering': self._clustering_detection,
            'activation_analysis': self._activation_analysis,
            'spectral_analysis': self._spectral_analysis,
        }
    
    def detect(self, dataset: list, labels: list) -> dict:
        """综合检测投毒样本"""
        results = {}
        
        for name, method in self.methods.items():
            result = method(dataset, labels)
            results[name] = result
        
        # 集成结果
        all_suspicious = set()
        for result in results.values():
            all_suspicious.update(result.get('suspicious_indices', []))
        
        return {
            'suspicious_samples': list(all_suspicious),
            'poison_probability': len(all_suspicious) / max(len(dataset), 1),
            'method_results': results
        }
    
    def _outlier_detection(self, dataset, labels) -> dict:
        """异常值检测"""
        from sklearn.ensemble import IsolationForest
        
        # 提取特征
        features = self._extract_features(dataset)
        
        # Isolation Forest
        clf = IsolationForest(contamination=0.05)
        predictions = clf.fit_predict(features)
        
        suspicious = np.where(predictions == -1)[0].tolist()
        
        return {
            'method': 'isolation_forest',
            'suspicious_indices': suspicious,
            'n_suspicious': len(suspicious)
        }
    
    def _clustering_detection(self, dataset, labels) -> dict:
        """聚类检测——同一标签内的异常聚类"""
        from sklearn.cluster import DBSCAN
        
        features = self._extract_features(dataset)
        
        suspicious = []
        unique_labels = set(labels)
        
        for label in unique_labels:
            # 对同一标签的样本聚类
            mask = np.array(labels) == label
            label_features = features[mask]
            
            if len(label_features) < 5:
                continue
            
            clustering = DBSCAN(eps=0.5, min_samples=5)
            cluster_labels = clustering.fit_predict(label_features)
            
            # 小聚类可能是投毒样本
            cluster_counts = np.bincount(cluster_labels[cluster_labels >= 0])
            for cluster_id, count in enumerate(cluster_counts):
                if count < len(label_features) * 0.1:  # 小于10%
                    suspicious.extend(
                        np.where(mask & (cluster_labels == cluster_id))[0]
                    )
        
        return {
            'method': 'clustering',
            'suspicious_indices': suspicious,
            'n_suspicious': len(suspicious)
        }
    
    def _activation_analysis(self, dataset, labels) -> dict:
        """激活分析——检测神经元激活异常"""
        # 训练一个简单模型
        # 分析各样本的激活模式
        # 投毒样本可能导致异常激活
        # 简化实现
        return {'method': 'activation', 'suspicious_indices': []}
    
    def _spectral_analysis(self, dataset, labels) -> dict:
        """谱分析——基于数据矩阵的奇异值分析"""
        features = self._extract_features(dataset)
        
        # SVD
        U, S, Vt = np.linalg.svd(features, full_matrices=False)
        
        # 检测异常样本
        residuals = features - U @ np.diag(S) @ Vt
        residual_norms = np.linalg.norm(residuals, axis=1)
        
        threshold = np.mean(residual_norms) + 2 * np.std(residual_norms)
        suspicious = np.where(residual_norms > threshold)[0].tolist()
        
        return {
            'method': 'spectral',
            'suspicious_indices': suspicious,
            'n_suspicious': len(suspicious)
        }

4.2 后门触发器逆向工程

class TriggerReverseEngineering:
    """后门触发器逆向工程"""
    
    def __init__(self, model):
        self.model = model
    
    def reverse_engineer(self, target_class: int, 
                         n_samples: int = 100) -> dict:
        """逆向工程找出可能的后门触发器"""
        # 1. 生成随机噪声
        best_trigger = None
        best_confidence = 0
        
        for _ in range(n_samples):
            # 随机生成潜在触发器
            trigger = self._generate_random_trigger()
            
            # 测试触发器效果
            confidence = self._test_trigger(trigger, target_class)
            
            if confidence > best_confidence:
                best_confidence = confidence
                best_trigger = trigger
        
        return {
            'trigger_found': best_confidence > 0.8,
            'trigger': best_trigger,
            'confidence': best_confidence,
            'target_class': target_class
        }
    
    def _generate_random_trigger(self) -> np.ndarray:
        """生成随机触发器"""
        # 方块触发器
        trigger = np.zeros((5, 5))
        trigger[:3, :3] = 255
        return trigger
    
    def _test_trigger(self, trigger: np.ndarray, 
                      target_class: int) -> float:
        """测试触发器效果"""
        # 使用测试样本加上触发器
        # 检查是否被分类为目标类别
        # 简化实现
        return 0.0

五、防御策略

5.1 数据级防御

class DataLevelDefense:
    """数据级防御"""
    
    def __init__(self):
        self.detector = PoisoningDetector()
    
    def sanitize_dataset(self, dataset: list, labels: list) -> tuple:
        """清洗数据集"""
        # 1. 检测可疑样本
        detection = self.detector.detect(dataset, labels)
        
        # 2. 移除可疑样本
        suspicious_set = set(detection['suspicious_samples'])
        clean_data = [d for i, d in enumerate(dataset) if i not in suspicious_set]
        clean_labels = [l for i, l in enumerate(labels) if i not in suspicious_set]
        
        return clean_data, clean_labels, detection
    
    def robust_training(self, dataset, labels):
        """鲁棒训练——使用对抗训练增强鲁棒性"""
        pass
    
    def data_augmentation_defense(self, dataset):
        """数据增强防御——破坏触发器模式"""
        augmented = []
        for sample in dataset:
            # 随机变换可能破坏触发器
            if np.random.random() > 0.5:
                sample = self._random_crop(sample)
            if np.random.random() > 0.5:
                sample = self._random_rotation(sample)
            augmented.append(sample)
        return augmented

5.2 模型级防御

class ModelLevelDefense:
    """模型级防御"""
    
    def fine_pruning(self, model, clean_data):
        """精细剪枝——剪除对后门敏感的神经元"""
        # 1. 识别对后门触发器激活度高的神经元
        # 2. 剪枝这些神经元
        pass
    
    def neural_cleanse(self, model, target_classes):
        """神经清洗——检测并移除后门"""
        for target_class in target_classes:
            # 逆向工程触发器
            trigger = self._reverse_engineer_trigger(model, target_class)
            
            if trigger['trigger_found']:
                # 剪枝相关神经元
                self._prune_backdoor_neurons(model, trigger)
        
        return model

六、供应链安全

class AISupplyChainSecurity:
    """AI 供应链安全管理"""
    
    def __init__(self):
        self.trusted_sources = [
            'huggingface.co/trusted',
            'openai.com/models',
        ]
        self.hash_registry = {}  # 模型哈希注册表
    
    def verify_model(self, model_path: str, 
                     expected_hash: str) -> dict:
        """验证模型完整性"""
        import hashlib
        
        with open(model_path, 'rb') as f:
            model_hash = hashlib.sha256(f.read()).hexdigest()
        
        return {
            'hash_match': model_hash == expected_hash,
            'computed_hash': model_hash,
            'expected_hash': expected_hash,
            'trusted': model_hash == expected_hash
        }
    
    def verify_dataset(self, dataset_path: str,
                       expected_hash: str) -> dict:
        """验证数据集完整性"""
        return self.verify_model(dataset_path, expected_hash)
    
    def audit_pipeline(self, model_source: str, 
                       data_source: str) -> dict:
        """审计整个训练管道"""
        return {
            'model_source': model_source,
            'data_source': data_source,
            'model_verified': model_source in self.trusted_sources,
            'data_verified': data_source in self.trusted_sources,
            'recommendation': 'proceed' if all([
                model_source in self.trusted_sources,
                data_source in self.trusted_sources
            ]) else 'review'
        }

七、最佳实践

7.1 防御检查清单

# 数据投毒防御检查清单

## 数据采集
- [ ] 数据来源可信
- [ ] 数据哈希校验
- [ ] 众包数据经过审核
- [ ] 公开数据集经过安全检查

## 数据预处理
- [ ] 异常检测
- [ ] 离群点分析
- [ ] 数据去重
- [ ] 标签噪声检测

## 训练过程
- [ ] 鲁棒训练算法
- [ ] 定期模型健康检查
- [ ] 训练日志审计

## 部署前
- [ ] 后门检测
- [ ] 红队测试
- [ ] 触发器逆向工程
- [ ] 模型剪枝

## 监控
- [ ] 异常输入监控
- [ ] 输出异常检测
- [ ] 后门触发器告警

结语

数据投毒是 AI 安全的供应链威胁——它攻击的不是模型本身,而是模型的"食物”。在开源生态繁荣的 2026 年,数据投毒的风险被放大了:一个被污染的开源数据集可能影响成千上万的下游应用。

防御数据投毒需要全链路的努力:可信数据源、数据清洗、模型检测、持续监控。最重要的是建立"零信任"的安全意识——不要信任任何外部数据,除非经过验证。数据安全是 AI 安全的基石,基石动摇,万丈高楼也可能轰然倒塌。

加入讨论

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

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