是否可导出无vocab的spaCy NER模型并实时注入tokens/vectors使用?
我的问题起源和标题关联度不高,但标题本质上很好地概括了我当前遇到的卡点。
我需要将spaCy的NER模型集成到复杂的分布式NLP pipeline中,目前的实现思路如下:
- 基于
en_core_web_lg模型训练新的NER模型,使其在NER任务中可识别自定义实体 - 保存模型时跳过vocab(词汇表),以节省磁盘空间和内存占用
- 最终加载模型执行推理时,使用pipeline中提前预计算好的tokens和vectors,而非通过模型vocab(标准方式)重新计算。
我选择不保存vocab的原因是,在我的分布式pipeline中,第一步就会完成文本的分词/向量化处理,后续所有任务都可以直接使用这一输出。
澄清:使用保存vocab的标准方式时,我可以正常训练自定义NER模型,保存/加载及推理均无明显问题,且准确率很高。
后续查阅spaCy官方文档发现,确实可以不携带vocab保存模型,还可以基于token列表和自定义vocab(我这里用的是空vocab)构建Doc对象。同时我也可以通过pipeline提前计算好的向量来设置文档向量。
但我发现保存的模型中ner/cfg文件内存在对NER模型训练所用向量的引用(en_core_web_lg.vectors):
{ "disable":[ "tagger", "parser" ], "beam_width":1, "beam_density":0.0, "beam_update_prob":1.0, "cnn_maxout_pieces":3, "nr_feature_tokens":6, "deprecation_fixes":{ "vectors_name":"en_core_web_lg.vectors" }, "nr_class":86, "hidden_depth":1, "token_vector_width":96, "hidden_width":64, "maxout_pieces":2, "pretrained_vectors":"en_core_web_lg.vectors", "bilstm_depth":0, "self_attn_depth":0, "conv_depth":4, "conv_window":1, "embed_size":2000 }
当我尝试在内存中未加载对应向量(即未加载对应vocab)的情况下加载模型时,上述引用就会导致报错。
如果我删除cfg文件中的这些引用,模型可以正常加载,也能使用我的向量执行推理,但得到的预测结果和携带原始vocab的模型输出差异很大,且存在较多错误。
是否可以保存携带空vocab的NER模型,之后基于pipeline预计算的tokens和vectors构建SpaCy Doc对象完成推理?
当前使用版本为spaCy 2.3.7,相关代码片段如下:
1. 训练代码
nlp = spacy.load("en_core_web_lg", disable=['tagger', 'parser']) ner = nlp.get_pipe('ner') ner.add_label("FOO_ENTITY") ner.add_label("BAR_ENTITY") ner.add_label("COOL_ENTITY") # Start the training optimizer = nlp.begin_training() # Loop for EPOCHS iterations losses_hist = [] for itn in range(30): # Shuffle the training data random.shuffle(Xy_train) losses = {} # Batch the examples and iterate over them for batch in spacy.util.minibatch(Xy_train, size=32): texts = [text for text, entities, _ in batch] golds = [{'entities': entities} for text, entities, _ in batch] # Update the model nlp.update(docs=texts, golds=golds, losses=losses) print(losses) losses_hist.append(losses)
2.a 标准推理代码
# I already have the text split in tokens doc = Doc(nlp.vocab, words=tokens) # Create doc from tokens ner = nlp.get_pipe("ner") doc = ner(doc) # Call NER step for doc for ent in doc.ents: print(f"value: {ent.text}, start: {ent.start_char}, end: {ent.end_char}, entity: {ent.label_}")
2.b 外部向量推理代码
# I already have the text split in tokens and their vectors vectors = Vectors(data=embeddings, keys=tokens) nlp.vocab.vectors = vectors doc = Doc(nlp.vocab, words=tokens) ner = nlp.get_pipe("ner") doc = ner(doc) # Call NER step for doc for ent in doc.ents: print(f"value: {ent.text}, start: {ent.start_char}, end: {ent.end_char}, entity: {ent.label_}")
3. 模型保存/加载代码
# Save model nlp.to_disk(str(dir_)) # Load model nlp = spacy.load(str(dir_), exclude=['vocab'])
可以实现该需求,你当前效果异常的核心原因是操作逻辑不符合spaCy 2.x的底层特征依赖规则,按以下步骤调整即可:
- 不要删除
ner/cfg里的pretrained_vectors和vectors_name配置。spaCy 2.x的NER模型训练时绑定了en_core_web_lg的向量分布,删除配置后模型会跳过预训练向量的特征注入逻辑,缺失核心输入特征自然效果大幅下降。 - 加载模型时手动补全向量名配置,避免报错。你使用空vocab加载模型时,手动给vocab的向量设置和cfg匹配的名称即可绕过向量校验,不需要加载完整的原始vocab:
# 加载排除vocab的模型 nlp = spacy.load(str(dir_), exclude=['vocab']) # 手动设置向量名匹配cfg配置,消除加载报错 nlp.vocab.vectors.name = 'en_core_web_lg.vectors'
- 确保预计算的向量和训练用的
en_core_web_lg向量维度、分布完全一致,如果你分布式pipeline用的就是en_core_web_lg的向量输出,直接注入即可。 - 构建Doc对象时补充必要属性:除了
words参数外,还要传入spaces参数(每个token后是否带空格),否则实体偏移量计算会出错;如果能提前拿到词的大小写、数字标识等lexeme属性也一并传入,补全NER所需的特征输入。
调整后的推理代码示例:
nlp = spacy.load(str(dir_), exclude=['vocab']) nlp.vocab.vectors.name = 'en_core_web_lg.vectors' # 注入预计算向量,确保embeddings是en_core_web_lg输出的300维向量 vectors = Vectors(data=embeddings, keys=tokens) nlp.vocab.vectors = vectors # 传入spaces参数,和你分词时的空格情况保持一致 doc = Doc(nlp.vocab, words=tokens, spaces=spaces_list) ner = nlp.get_pipe("ner") doc = ner(doc)
内容的提问来源于stack exchange,提问作者Emiliano Viotti

