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
关键优化点
- 消除全局依赖:函数内部创建
spacy.blank("en")实例,防止外部未定义nlp导致报错。 - 改用token索引创建Span:
doc[j:j+1]直接获取第j个token的有效Span,再设置标签,彻底避免字符偏移不匹配的问题。 - 简化逻辑:去掉复杂的字符偏移计算,代码更简洁可靠。
内容的提问来源于stack exchange,提问作者PinkBanter
相关产品推荐
相关产品推荐

