如何将spaCy模型与AllenNLP Interpret、TextAttack兼容使用?
嘿,我正好研究过用TextAttack和AllenNLP Interpret适配spaCy模型的问题,给你整理了可行的解决方案:
一、TextAttack 与 spaCy 模型适配解决方案
1. 为什么没自动调用SpacyTokenizer?
TextAttack默认的Tokenizer是为HuggingFace等主流预训练模型设计的,不会自动检测并切换到SpacyTokenizer,必须手动指定并正确初始化它。
2. SpacyTokenizer 参数详解与构造代码
先帮你理清每个容易混淆的参数:
word2id:这里的ID是spaCy词汇表的全局词-ID映射,不是单句的token临时ID。可以直接从spaCy的Vocab中提取双向映射的词表。oov_id:TextAttack文档的注释确实写错了,这里就是指out-of-vocabulary(未知词)的ID。你可以用spaCy词汇表中<UNK>对应的ID,如果没有的话,指定一个不冲突的默认值(比如-1)即可。pad_id:用于文本长度填充的ID,当文本短于max_seq_length时用它补全。spaCy本身不依赖padding,但TextAttack需要这个参数,你可以指定词汇表最大ID+1作为pad_id。
以下是可直接运行的示例代码:
import spacy from textattack.models.wrappers import ModelWrapper from textattack.tokenizers import SpacyTokenizer from textattack.attack_recipes import TextFoolerJin2019 # 加载你的spaCy训练模型 nlp = spacy.load("your-trained-spacy-model") spaCy_vocab = nlp.vocab # 构造TextAttack需要的词表映射 word2id = {word: idx for idx, word in enumerate(spaCy_vocab.strings)} oov_id = spaCy_vocab.strings.get("<UNK>", -1) # 用<UNK>的ID,没有就用-1 pad_id = len(spaCy_vocab.strings) # 用词汇表长度作为pad_id,避免冲突 # 初始化SpacyTokenizer tokenizer = SpacyTokenizer(word2id=word2id, oov_id=oov_id, pad_id=pad_id, max_seq_length=128) # 把spaCy模型包装成TextAttack兼容的ModelWrapper class SpacyModelWrapper(ModelWrapper): def __init__(self, nlp_model): self.nlp = nlp_model def __call__(self, text_list): predictions = [] for text in text_list: doc = self.nlp(text) # 假设你的模型是分类模型,提取所有标签的概率并按排序后的标签顺序返回 label_order = sorted(doc.cats.keys()) prob_list = [doc.cats[label] for label in label_order] predictions.append(prob_list) return predictions # 创建包装后的模型并发起攻击 model_wrapper = SpacyModelWrapper(nlp) attack = TextFoolerJin2019.build(model_wrapper) # 测试攻击 test_text = "Your test sentence here" result = attack.attack(test_text) print(result)
二、AllenNLP Interpret 与 spaCy 模型适配解决方案
问题根源
AllenNLP Interpret的HotFlipAttacker依赖AllenNLP自身的Vocab结构(带有_index_to_token属性),而spaCy的Vocab完全是另一种实现,所以直接调用会报错。解决思路是把spaCy模型包装成AllenNLP兼容的Model,并构造一个匹配的Vocab。
以下是完整的适配代码:
import spacy import torch from allennlp.models import Model from allennlp.data.vocabulary import Vocabulary from allennlp_interpret.attackers.hotflip import HotFlipAttacker from allennlp.data import Instance from allennlp.data.fields import TextField from allennlp.data.token_indexers import SingleIdTokenIndexer # 加载你的spaCy训练模型 nlp = spacy.load("your-trained-spacy-model") spaCy_vocab = nlp.vocab # 构造AllenNLP兼容的Vocab index_to_token = {idx: word for idx, word in enumerate(spaCy_vocab.strings)} token_to_index = {word: idx for idx, word in enumerate(spaCy_vocab.strings)} allen_vocab = Vocabulary() allen_vocab._index_to_token["tokens"] = index_to_token allen_vocab._token_to_index["tokens"] = token_to_index # 自定义AllenNLP Model,包装spaCy模型 class SpacyAllenModel(Model): def __init__(self, vocab, nlp_model): super().__init__(vocab) self.nlp = nlp_model def forward(self, tokens): # 将AllenNLP的token索引转换为文本 text_list = [] for token_indices in tokens["tokens"]: # 过滤pad_id(这里假设pad_id是之前定义的len(spaCy_vocab.strings)) token_texts = [self.vocab.get_token_from_index(idx.item(), "tokens") for idx in token_indices if idx.item() != len(spaCy_vocab.strings)] text = " ".join(token_texts) text_list.append(text) # 用spaCy模型预测并返回logits格式的结果 predictions = [] for text in text_list: doc = self.nlp(text) label_order = sorted(doc.cats.keys()) probs = torch.tensor([doc.cats[label] for label in label_order]) predictions.append(probs) return {"logits": torch.stack(predictions)} # 创建模型和攻击者实例 model = SpacyAllenModel(allen_vocab, nlp) attacker = HotFlipAttacker(model, allen_vocab, "tokens") # 构造测试实例并发起攻击 test_text = "Your test sentence here" token_indexer = SingleIdTokenIndexer(namespace="tokens") tokens = [token.text for token in nlp(test_text)] text_field = TextField(tokens, {"tokens": token_indexer}) instance = Instance({"tokens": text_field}) instance.index_fields(allen_vocab) attack_result = attacker.attack_from_instance(instance) print(attack_result)
内容的提问来源于stack exchange,提问作者Kaisa K
相关产品推荐
相关产品推荐

