修改SpaCy displacy NER标注工具以支持重叠实体的实现方案咨询
重叠NER实体可视化实现思路
核心优化方向:放弃直接解析修改displacy返回的HTML字符串的方案,该方案需要处理大量DOM节点匹配、文本偏移对齐逻辑,复杂度极高且易出错。更合理的思路是基于原始文本和实体列表,直接生成嵌套结构的HTML片段,同时完全复用displacy的原生样式规则,开发成本低且稳定性高。
1. 实体预处理
首先对实体列表做排序,排序规则为:
- 第一优先级:实体
end值降序,保证范围更大的实体优先被处理,作为外层嵌套节点 - 第二优先级:实体
start值升序,同结束位置的实体从左到右排列
排序后示例实体顺序会变成:SUPER SECRET->SUPER->STUFF,正好符合外层到内层的嵌套要求。
2. 区间拆分与嵌套生成
遍历整个文本的字符偏移,将文本拆分为最小的不可拆分区间,每个区间记录所属的所有实体层级,生成对应的嵌套<mark>标签。这里可以用栈结构来管理当前激活的实体标签,遇到实体起始位置就入栈生成对应<mark>头,遇到结束位置就出栈生成闭合标签。
3. 示例实现代码
def generate_overlap_ent_html(text: str, entities: list, label_colors: dict = None) -> str: # 默认颜色配置,可自定义不同标签的颜色 default_colors = {"SUPER": "#add8e6", "SUPER SECRET": "#d3d3d3", "STUFF": "#d3d3d3"} colors = label_colors or default_colors # 构造所有实体的起止事件点 events = [] for ent in entities: events.append((ent["start"], "start", ent)) events.append((ent["end"], "end", ent)) # 事件排序规则:位置升序>同位置end事件优先>同start事件按实体范围降序 events.sort(key=lambda x: ( x[0], 0 if x[1] == "end" else 1, -x[2]["end"] if x[1] == "start" else x[2]["end"] )) html_parts = [] active_ents = [] # 栈结构,保存当前活跃的实体 last_pos = 0 for pos, event_type, ent in events: # 先添加两个事件点之间的普通文本 if pos > last_pos: html_parts.append(text[last_pos:pos]) if event_type == "start": # 实体开始,生成mark标签头,入栈 bg_color = colors.get(ent["label"], "#ddd") mark_open = f'<mark class="entity" style="background: {bg_color}; padding: 0.45em 0.6em; margin: 0 0.25em; line-height: 1; border-radius: 0.35em;">' html_parts.append(mark_open) active_ents.append(ent) else: # 实体结束,生成实体标签文本,闭合当前实体的mark标签 label = f'<span style="font-size: 0.8em; font-weight: bold; line-height: 1; border-radius: 0.35em; vertical-align: middle; margin-left: 0.5rem">{ent["label"]}</span>' html_parts.append(label) html_parts.append("</mark>") active_ents.pop() last_pos = pos # 添加最后剩余的普通文本 if last_pos < len(text): html_parts.append(text[last_pos:]) # 套外层div,和displacy结构完全一致 full_html = f'<div class="entities" style="line-height: 2.5; direction: ltr">{"".join(html_parts)}</div>' return full_html # 示例数据调用 text = "This is an example sentence with some super secret (and some text in between) stuff" entities = [{"start": 38, "end": 43, "label": "SUPER"}, {"start": 38, "end": 50, "label": "SUPER SECRET"}, {"start": 78, "end": 83, "label": "STUFF"}] # 生成最终HTML final_html = generate_overlap_ent_html(text, entities)
运行上述代码得到的HTML结构完全符合预期的嵌套要求,渲染效果和需求一致。
额外优化点
- 可以自定义不同实体标签的配色,区分度更高
- 支持任意层级的实体嵌套,不止双层重叠
- 无需依赖displacy的渲染逻辑,可单独部署使用
内容的提问来源于stack exchange,提问作者GSwart
相关产品推荐
相关产品推荐

