for循环break语句行为不符预期,CoNLL数据spaCy匹配出错
CoNLL2003 NER数据依赖解析匹配逻辑错误排查
我第一次提问,麻烦多包涵。我写了一段处理CoNLL2003 NER任务数据的代码,目的是把预分词的token拼接成字符串后传入spaCy做依赖解析,再给原数据添加依赖关系(dep)和中心词(head)列,写入新文件。
因为spaCy的分词结果和原数据不一致,我用嵌套循环从上次匹配的位置开始查找对应token。没加else分支时代码正常,但添加后,原本预期只有不匹配的token输出no_dep/no_head,后续匹配的token仍输出正确结果,实际却是所有后续token都输出no_dep/no_head。
原始代码
import pandas as pd import spacy df = pd.read_csv('../../../Data/conll2003.dev.conll', sep='\t', on_bad_lines='skip', header=None) nlp = spacy.load('en_core_web_sm') nlp.max_length = 1500000 all_tokens = [] for token in df[0]: all_tokens.append(str(token)) string = ' '.join(all_tokens) doc = nlp(string) token_tuples = tuple(enumerate(doc)) outfile = open('./conll2003.dev.syntax_corrupt.conll', 'w') i = 0 ## initiate by looking at the first token in the doc for x, token in enumerate(df[0]): for num, tok in token_tuples[i:]: ## we add this step to ensure that the for loop always looks from the last token that was a match, since doc is longer ## than df[0], otherwise it would at some point start looking from earlier tokens since spacy has more tokens and if there is an accidental match, it ## would provide the wrong dep and head if token == tok.text: i = num ## get the number from the token tuples as new starting point outfile.write(str(df[0][x]) + '\t' + str(df[1][x]) + '\t' + str(df[2][x]) + '\t' + str(df[3][x]) + '\t' + str(tok.dep_) + '\t' + str(tok.head.text) + '\n') break else: outfile.write(str(df[0][x]) + '\t' + str(df[1][x]) + '\t' + str(df[2][x]) + '\t' + str(df[3][x]) + '\t' + 'no_dep' + '\t' + 'no_head' + '\n') break outfile.close()
输入输出示例
输入文件片段
LONDON NNP B-NP B-LOC 1996-08-30 CD I-NP O West NNP B-NP B-MISC
无else分支时的输出
LONDON NNP B-NP B-LOC nmod Simmons West NNP B-NP B-MISC nmod Indian
期望输出
LONDON NNP B-NP B-LOC nmod Simmons 1996-08-30 CD I-NP no_dep no_head West NNP B-NP B-MISC nmod Indian
实际输出
LONDON NNP B-NP B-LOC no_dep no_head 1996-08-30 CD I-NP O no_dep no_head West NNP B-NP B-MISC no_dep no_head
(注:实际输出第一行原本有正确结果,问题从第二行开始)
错误原因
- else分支的break逻辑错误:当第一个spaCy token和当前df token不匹配时,else分支直接break终止内层循环,且没有更新
i的位置。这导致下一次处理df的下一个token时,依然从原来的i位置开始查找,而该位置的spaCy token和新的df token还是不匹配,持续触发else分支,最终所有后续结果都输出no_dep/no_head。 - 匹配成功后
i的更新错误:当前代码匹配成功后设置i = num,但num是当前spaCy token的索引,下一次循环会从同一个索引开始,导致重复匹配同一个spaCy token。正确的做法应该是i = num + 1,让下一次从下一个spaCy token开始查找。 - 未处理spaCy分词拆分原token的情况:当spaCy将一个原df token拆分为多个token时,代码没有跳过这些拆分后的token,直接判定为不匹配,不符合实际需求。
修正后的代码
import pandas as pd import spacy df = pd.read_csv('../../../Data/conll2003.dev.conll', sep='\t', on_bad_lines='skip', header=None) nlp = spacy.load('en_core_web_sm') nlp.max_length = 1500000 all_tokens = [str(token) for token in df[0]] string = ' '.join(all_tokens) doc = nlp(string) token_tuples = tuple(enumerate(doc)) outfile = open('./conll2003.dev.syntax_corrupt.conll', 'w') i = 0 # 起始查找位置 doc_len = len(token_tuples) for x, token in enumerate(df[0]): matched = False # 从当前i位置开始遍历spaCy的token while i < doc_len: num, tok = token_tuples[i] if token == tok.text: # 匹配成功,写入结果并更新查找位置 out_line = f"{df[0][x]}\t{df[1][x]}\t{df[2][x]}\t{df[3][x]}\t{tok.dep_}\t{tok.head.text}\n" outfile.write(out_line) i = num + 1 # 下一次从下一个token开始查找 matched = True break else: # spaCy拆分了原token,跳过当前spaCy token i += 1 # 遍历完所有spaCy token都没匹配到,写入no_dep/no_head if not matched: out_line = f"{df[0][x]}\t{df[1][x]}\t{df[2][x]}\t{df[3][x]}\tno_dep\tno_head\n" outfile.write(out_line) outfile.close()
代码说明
- 把内层for循环改成while循环,方便灵活控制
i的递增,处理spaCy拆分原token的情况。 - 增加
matched标记,只有遍历完所有剩余spaCy token都没匹配到,才写入no_dep/no_head。 - 匹配成功后将
i设为num + 1,确保下一次从下一个spaCy token开始查找,避免重复匹配。 - 当spaCy token和原df token不匹配时,递增
i跳过该token,继续往后查找可能的匹配项。
内容的提问来源于stack exchange,提问作者Ayro
相关产品推荐
相关产品推荐

