这是一个很好的问题,扩展属性的想法其实很聪明!唯一的缺点是您必须将调试代码添加到所有现有组件中(如果它们是第三方代码,它们的效果也会较差)。但是,如果您知道有问题的代码是代码库的一部分,那么这应该不是问题。
另一种选择是将每个管道组件包装在一个函数中,该函数记录时间戳和您需要的任何其他内容并返回pipe(doc)。然后,您可以用这些包装的组件覆盖 nlp.pipeline:
def wrap_pipe(name, pipe):
def wrapped(doc):
print(f"Started '{name}'", datetime.datetime.now())
return pipe(doc)
return wrapped
def debug_wrap_pipeline(nlp):
nlp.pipeline = [(name, wrap_pipe(name, pipe)) for name, pipe in nlp.pipeline]
return nlp
debug_nlp = debug_wrap_pipeline(nlp)
但是,这里的缺点是您还需要包装每个组件的 .pipe 方法(如果可用),以便您可以在相同条件下运行和调试 nlp.pipe。如果您要进行基准测试,您通常希望在更大范围内执行此操作并处理带有nlp.pipe 的文本流。
为避免这种情况,稍微详细一点的选项可能是在管道中的每个现有组件之前添加一个“调试组件”。基本上是这样的:
def make_debug_component(name):
def debug_component(doc):
print(f"Before '{name}'", datetime.datetime.now())
return doc
return debug_component
def debug_wrap_pipeline(nlp):
pipeline = list(nlp.pipeline) # we don't want to modify this while we're looping over it
for name, pipe in pipeline:
debug_component = make_debug_component(name)
nlp.add_pipe(debug_component, before=name, name=f"debug_{name}")
return nlp
免责声明:我只是将这些想法整合在一起,还没有对它们进行广泛的测试。但它们似乎确实有效。如果你最终探索这个,我会很想知道什么最有效。它也可能是 spaCy 可以开箱即用的一个功能,它可以很好地与提议的 static analysis for pipeline components 配对。
此外,为了完整性:在调试这样的文本处理管道时,始终使用您处理一次的单个语料库进行更大规模的基准测试(而不是循环单个示例 1000次或类似的东西)。缓存效果(在 spaCy 中,还有 CPU 中)、内存分配的差异等等都会产生影响,并使小规模测试的可靠性降低。当然,在这种情况下,您会遇到巨大的差异,即使处理单个文本也可能会为您提供足够的线索以及进一步调试代码所需的一切。