You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用spaCy处理冲突实体与分散偏移并提取BILUO标签

spaCy处理临床文本标注的两个核心问题解决方案

一、不连续位置索引的解析方法

你拿到的location字段是字符串序列化的列表,其中带分号的条目对应不连续实体的多段文本跨度,不要直接把原始字符串传入spaCy接口,先做统一格式解析即可。
解析逻辑参考如下代码:

import ast
from spacy.tokens import Span

def parse_entity_locations(loc_str):
    # 把字符串格式的列表转为Python原生列表
    raw_loc_list = ast.literal_eval(loc_str)
    parsed_spans = []
    for loc_entry in raw_loc_list:
        # 拆分分号分隔的多段不连续位置
        seg_list = loc_entry.split(";")
        entry_segs = []
        for seg in seg_list:
            start_idx, end_idx = map(int, seg.strip().split())
            entry_segs.append((start_idx, end_idx))
        parsed_spans.append(entry_segs)
    return parsed_spans

注意spaCy默认的Doc.ents容器不直接支持多段拼接的不连续实体,根据任务需求选处理方式即可:

  • 如果是训练常规NER模型、生成BILUO标签,直接把每一段跨度作为对应实体类型的独立Span传入即可,不要强行跨非实体文本拼接长Span,避免标注错位
  • 如果需要保留「多段同属一个实体」的关联关系,不要把Span存入doc.ents,改用SpanGroup存储,该结构原生支持不连续、跨跨度的实体绑定。

二、重叠实体冲突(E103报错)的解决

这个报错的本质原因是spaCy默认的doc.ents容器要求单个token只能归属一个实体,不支持同位置多标签。针对你遇到的同一片段对应多个实体的场景,根据任务目标选对应方案即可:

  • 标签合并方案:如果重叠的实体语义高度相近(比如你示例中位置289 314对应的「Insomnia」和「Lack of Sleep」本身就是强关联症状),可以在预处理阶段把这类重叠标签合并为组合标签(例如Insomnia|Lack of Sleep),模型训练时直接学习合并后的标签,适合标签重合度高、语义关联强的场景。
  • SpanGroup多组存储方案:不要把所有实体都塞入doc.ents,而是将不同类型的实体存入doc.spans下独立命名的SpanGroup,该结构完全支持同位置多实体、不连续实体存储,不会触发冲突。后续需要生成BILUO标签时,按SpanGroup分别生成即可,这也是目前医疗文本NER处理多标签重叠的通用方案。
  • 冲突过滤方案:如果你的任务要求单token单实体标签,可以提前制定过滤规则(比如优先保留文本粒度更细的实体、标注频次更高的实体),预处理阶段剔除冲突位置的冗余实体,保证每个token仅对应一个标签。

调用offsets_to_biluo_tags的注意事项

传入偏移前一定要做边界对齐校验,你的数据文本里包含\r\n换行符,很容易出现字符计数偏差导致标签错位。可以在创建Span时调用doc.char_span方法并传入alignment_mode="contract"参数,自动收缩对齐到合法的token边界,过滤无效偏移。

完整的最小处理流程参考:

import spacy
from spacy.training import offsets_to_biluo_tags

nlp = spacy.blank("en")
# sample_data 为你贴出的JSON格式样本
doc = nlp(sample_data["pn_history"])
doc.spans["custom_ents"] = []

# 解析所有实体存入SpanGroup
for ent in sample_data["entities"]:
    all_seg_groups = parse_entity_locations(ent["location"])
    for seg_group in all_seg_groups:
        for start, end in seg_group:
            # 自动对齐token边界
            span = doc.char_span(
                start, end, 
                label=ent["feature_text"], 
                alignment_mode="contract"
            )
            if span: # 跳过对齐失败的无效跨度
                doc.spans["custom_ents"].append(span)

# 如果需要生成单标签BILUO用于常规NER训练,做冲突去重
sorted_spans = sorted(
    doc.spans["custom_ents"],
    key=lambda x: (x.end - x.start), # 长实体优先保留
    reverse=True
)
used_token_ids = set()
valid_ents = []
for span in sorted_spans:
    token_range = set(range(span.start, span.end))
    if not token_range & used_token_ids:
        valid_ents.append(span)
        used_token_ids.update(token_range)
doc.ents = valid_ents

# 生成最终BILUO标签
biluo_tags = offsets_to_biluo_tags(
    doc,
    [(s.start_char, s.end_char, s.label_) for s in doc.ents]
)

内容的提问来源于stack exchange,提问作者tanmay

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 02:36:10