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

SpaCy中为Doc对象赋值Spans后doc.spans为空的问题排查

问题:spaCy Doc对象赋值Spans后doc.spans为空列表

我需要将包含实体标签(0、1、-1)的二维列表转换为带Spans实体标签的spaCy Doc对象,该二维列表对应tokens_单词列表的token标签,Spans需匹配非零标签(-1、1)的token起止偏移量。我通过labelmatrix2doc函数实现,但为Doc对象赋值Spans后,打印doc.spans却得到空列表,明明已经生成了指定长度的Spans。

实现函数代码

import spacy
from spacy.tokens import Doc, Span, SpanGroup
    
def labelmatrix2doc(tokens_, token_offsets_, label_matrix):
    
    tokens_ = [token if token else ' ' for token in tokens_] # 替换空token为空格
    doc = Doc( nlp.vocab, words=tokens_ )
    
    # 从标签矩阵创建spaCy spans列表
    for i in range(len(label_matrix)):
        spans_i = []
        for j in range(len(label_matrix[i])):
            if label_matrix[i][j] != 0:
                start = token_offsets_[j]
                end = token_offsets_[j+1] if j < len(label_matrix[i])-1 else token_offsets_[j] + len(tokens_[j])
                span = doc.char_span(start, end, label=f'label_{label_matrix[i][j]}')
                spans_i.append(span)

        if spans_i:
            print( len(spans_i) )
            doc.spans[f"lf_{i}"] = spans_i
            print( doc.spans )
            
    
    return doc

模拟输入代码

import random
import string

# 设置列表大小
list_size = 10

# 生成随机英文单词列表
words_list = []
end_offsets_list = []
offset = 0
for i in range(list_size):
    word = ''.join(random.choices(string.ascii_lowercase, k=random.randint(3, 7)))
    words_list.append(word)
    offset += len(word)
    end_offsets_list.append(offset)

print(words_list)
print(end_offsets_list)

# 设置数组大小
array_size = 10

# 生成10x10的二维数组,元素为-1、0、1
two_d_array = []
for i in range(array_size):
    row = []
    for j in range(array_size):
        value = random.choice([-1, 0, 1])
        row.append(value)
    two_d_array.append(row)

print(two_d_array)

运行输出

labelmatrix2doc(words_list, end_offsets_list, two_d_array)
8
{'lf_0': []}
8
{'lf_0': [], 'lf_1': []}
4
{'lf_0': [], 'lf_1': [], 'lf_2': []}
5
{'lf_0': [], 'lf_1': [], 'lf_2': [], 'lf_3': []}
6
{'lf_0': [], 'lf_1': [], 'lf_2': [], 'lf_3': [], 'lf_4': []}
7
{'lf_0': [], 'lf_1': [], 'lf_2': [], 'lf_3': [], 'lf_4': [], 'lf_5': []}
7
{'lf_0': [], 'lf_1': [], 'lf_2': [], 'lf_3': [], 'lf_4': [], 'lf_5': [], 'lf_6': []}
6
{'lf_0': [], 'lf_1': [], 'lf_2': [], 'lf_3': [], 'lf_4': [], 'lf_5': [], 'lf_6': [], 'lf_7': []}
6
{'lf_0': [], 'lf_1': [], 'lf_2': [], 'lf_3': [], 'lf_4': [], 'lf_5': [], 'lf_6': [], 'lf_7': [], 'lf_8': []}
9
{'lf_0': [], 'lf_1': [], 'lf_2': [], 'lf_3': [], 'lf_4': [], 'lf_5': [], 'lf_6': [], 'lf_7': [], 'lf_8': [], 'lf_9': []}

问题原因与修复方案

核心问题

你的token_offsets_存储的是每个token的结束偏移,但doc.char_span(start, end)需要的是token的起始+结束字符偏移。当你用token_offsets_[j]作为start时,第一个token的start会是第一个单词的长度,这和Doc内部的字符偏移完全不匹配,导致doc.char_span返回None。spaCy会自动过滤掉这些无效的None值,所以最终doc.spans里的列表为空。

另外,创建Doc时默认字符偏移是连续无空格的(比如["abc", "def"]对应0-3、3-6),直接用字符偏移容易出错,更可靠的方式是通过token索引创建Span。

修复后的代码

import spacy
from spacy.tokens import Doc, Span, SpanGroup
    
def labelmatrix2doc(tokens_, token_offsets_, label_matrix):
    # 创建空白nlp实例,避免全局变量依赖
    nlp = spacy.blank("en")
    tokens_ = [token if token else ' ' for token in tokens_]
    doc = Doc(nlp.vocab, words=tokens_)
    
    for i in range(len(label_matrix)):
        spans_i = []
        for j in range(len(label_matrix[i])):
            if label_matrix[i][j] != 0:
                # 直接通过token索引创建Span,避免字符偏移错误
                span = doc[j:j+1]
                span.label_ = f'label_{label_matrix[i][j]}'
                spans_i.append(span)

        if spans_i:
            print(len(spans_i))
            doc.spans[f"lf_{i}"] = spans_i
            print(doc.spans)
            
    return doc

关键优化点

  1. 消除全局依赖:函数内部创建spacy.blank("en")实例,防止外部未定义nlp导致报错。
  2. 改用token索引创建Span:doc[j:j+1]直接获取第j个token的有效Span,再设置标签,彻底避免字符偏移不匹配的问题。
  3. 简化逻辑:去掉复杂的字符偏移计算,代码更简洁可靠。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 01:59:58