首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >NLP研究者的福音—spaCy2.0中引入自定义的管道和扩展

NLP研究者的福音—spaCy2.0中引入自定义的管道和扩展

作者头像
AiTechYun
发布2018-03-05 16:00:44
2K0
发布2018-03-05 16:00:44
举报
文章被收录于专栏:ATYUN订阅号ATYUN订阅号

以前版本的spaCy很难拓展。尤其是核心的Doc,Token和Span对象。他们没有直接实例化,所以创建一个有用的子类将涉及很多该死的抽象(想想FactoryFactoryConfigurationFactory类)。继承无法令人满意,因为它没有提供自定义组合的方法。我们希望让人们开发spaCy的扩展,并确保这些扩展可以同时使用。如果每个扩展都需要spaCy返回一个不同Doc子集,那就没办法实现它了。为了解决这个问题,我们引入了一个新的动态字段(dynamic field),允许在运行时添加新的特性,属性和方法:

import spacy
from spacy.tokensimport Doc

Doc.set_attribute('is_greeting', default=False)

nlp= spacy.load('en')
doc= nlp(u'hello world')
doc._.is_greeting= True

我们认为“._”特性在清晰性和可读性之间取得了很好的平衡。扩展需要很好的使用,但也应该是清晰的展示哪些是内置的哪些不是,否则无法追踪你正在阅读的代码的文档或实现。“._”属性还确保对spaCy的更新不会因为命名空间冲突而破坏扩展代码。

扩展开发中缺少的另一件事是一种可以方便的修改处理管道的方法。早期版本的spaCy是硬编码管道,因为只支持英文。spaCy v1.0允许管道在运行时更改,但此过程通常藏得很深:你会调用nlp一个文本,但你不知道会发生什么?如果你需要在标记和解析之间添加进程,就必须深入研究spaCy的内部构成。而在spaCy v2.0中,他们总算做了一个接口:

nlp= spacy.load('en')
component= MyComponent()
nlp.add_pipe(component, after='tagger')
doc= nlp(u"This is a sentence")

定制管道组件

从根本上说,管道是一个按顺序访问Doc的函数的列表。它可以由模型设置,并由用户修改。管道组件可以是一个复杂的包含状态的类,也可以是一个非常简单的Python函数,它将一些东西添加到一个Doc并返回它。在“hood”下,当你在一串文本中调用nlp时,spaCy将执行以下步骤:

doc= nlp.make_doc(u'This is a sentence')  # create a Doc from raw text
for name, procin nlp.pipeline:            # iterate over components in order
    doc= proc(doc)                        # call each component on the Doc

nlp对象是一种语言的实例,它包含你正在使用的语言的数据和注释方案,也包括预先定义的组件管道,如标记器,解析器和实体识别器。如果你正在加载模型,这个语言实例也可以访问该模型的二进制数据。所有这些都是针对每个模型,并在模型“meta.json-”中定义 例如,一个西班牙的NER模型需要不同的权重、语言数据和管道组件,而不是像英语那样的解析和标记模型。所以Language类总是带有管道状态。spacy.load()将其全部放在一起,然后返回一个带有管道集的语言实例并访问二进制数据。

2.0版本的spaCy管道只是一个(name, function)元组列表,即它描述组件名称并调用Doc对象的函数:

>>> nlp.pipeline
[('tagger', <spacy.pipeline.Tagger>), ('parser', <spacy.pipeline.DependencyParser>),
 ('ner', <spacy.pipeline.EntityRecognizer>)]

为了更方便地修改管道,有几种内置方法可以获取,添加,替换,重命名或删除单独的组件。spaCy的默认管道组件,如标记器,解析器和实体识别器现在都遵循相同的接口,并且都是子类Pipe。如果你正在开发自己的组件,则使用Pipe接口会让它完全的可训练化和可序列化。至少,组件需要随时调用和返回Doc:

def my_component(doc):
    print("The doc is {} characters long and has {} tokens."
          .format(len(doc.text),len(doc))
    return doc

然后可以使用nlp.add_pipe()方法将组件添加到管道的任何位置。可以使用的参数有:before,after,first和last。

nlp= spacy.load('en')
nlp.add_pipe(my_component, name='print_length', last=True)
doc= nlp(u"This is a sentence.")

Doc、Token和Span的扩展属性

当你对自己的管道组件进行修改时Doc,你通常需要扩展接口,以便你可以方便地访问自己添加的信息。spaCy v2.0引入了一种可以让你注册自己的特性、属性和方法的新机制,它们可以在“._”命名空间中使用如doc._.my_attr。大多数这三种类型的扩展可以通过set_extension()方法注册:

1.Attribute扩展:设置特性的默认值,可以被覆盖。

2.Property扩展:定义getter和可选的setter函数。

3.Method扩展:分配一个作为对象方法可用的函数。

Doc.set_extension('hello_attr', default=True)
Doc.set_extension('hello_property', getter=get_value, setter=set_value)
Doc.set_extension('hello_method', method=lambda doc, name:'Hi {}!'.format(name))

doc._.hello_attr           # True
doc._.hello_property       # return value of get_value
doc._.hello_method('Ines') # 'Hi Ines!'

方便的将自定义数据写入Doc,Token和Span意味着使用spaCy的应用程序可以充分利用内置的数据结构和Doc对象的好处作为包含所有信息的唯一可信来源:

  • 在标记化和解析期间不会丢失任何信息,因此你始终可以将注释与原始字符串相关联。
  • 在Token和Span总是向Doc看齐,所以他们始终一致。
  • 高效的C级访问(C-level access)可以通过“doc.c”获得隐藏的“TokenC*”。
  • 接口可以将传递的Doc对象标准化,在需要时从它们中读取或写入。更少的特征使函数更容易复用和可组合。

例如,我们假设你的数据包含地址信息,如国家名,你使用spaCy来提取这些名称,并添加更多详细信息,如国家的首都或者GPS坐标。又或者也许你的应用程序需要使用spaCy的命名实体识别器查找公众人物的姓名,并检查维基百科上是否存在有关它们的页面。

在此之前,你通常会在文本上运行spaCy以获取您感兴趣的信息,将其保存到数据库中并在稍后添加更多数据。这样做没有问题,但也意味着你丢失了原始文档的所有引用。或者,你可能会序列化你的文档并额外存储引用数据,为它们建立自己的索引。这些方法很好,它们但不是很令人满意的解决方案。在spaCy v2.0中,你可以很方便的在文档、token或span中写入所有这些数据自定义的属性,如:token._.country_capital,span._.wikipedia_url或doc._.included_persons。

下面示例展示了使用“REST Countries API”获取所有国家的管道组件,在文档中查找国家名称,合并匹配的span,分配实体标签GPE(geopolitical entity),并添加国家的首都,经纬度坐标和一个布尔类型的“is_country”到token的属性。

import requests
from spacy.tokensimport Token, Span
from spacy.matcherimport PhraseMatcher

class Countries(object):
    name= 'countries'  # component name shown in pipeline

    def __init__(self, nlp, label='GPE'):
        # request all country data from the API
        r= requests.get('https://restcountries.eu/rest/v2/all')
        self.countries= {c['name']: cfor cin r.json()} # create dict for easy lookup
        # initialise the matcher and add patterns for all country names
        self.matcher= PhraseMatcher(nlp.vocab)
        self.matcher.add('COUNTRIES',None,*[nlp(c)for cin self.countries.keys()])
        self.label= nlp.vocab.strings[label]# get label ID from vocab
        # register extensions on the Token
        Token.set_extension('is_country', default=False)
        Token.set_extension('country_capital')
        Token.set_extension('country_latlng')

    def __call__(self, doc):
        matches= self.matcher(doc)
        spans= [] # keep the spans for later so we can merge them afterwards
        for _, start, endin matches:
            # create Span for matched country and assign label
            entity= Span(doc, start, end, label=self.label)
            spans.append(entity)
            for tokenin entity: # set values of token attributes
                token._.set('is_country',True)
                token._.set('country_capital',self.countries[entity.text]['capital'])
                token._.set('country_latlng',self.countries[entity.text]['latlng'])
        doc.ents= list(doc.ents)+ spans # overwrite doc.ents and add entities – don't replace!
        for spanin spans:
            span.merge() # merge all spans at the end to avoid mismatched indices
        return doc # don't forget to return the Doc!

代码详细的版本可以访问下面的链接:

https://github.com/explosion/spaCy/blob/develop/examples/pipeline/custom_component_countries_api.py

该示例还使用了spaCy的PhraseMatcher,这是v2.0中引入的另一个很酷的功能。与token模式不同,PhraseMatcher可以获取Doc对象列表,让你能够更快更高效地匹配大型术语列表。当你将组件添加到管道并处理文本时,所有国家都将自动标记为GPE实体对象,自定义属性在token上可用:

nlp= spacy.load('en')
component= Countries(nlp)
nlp.add_pipe(component, before='tagger')
doc= nlp(u"Some text about Colombia and the Czech Republic")

print([(ent.text, ent.label_)for entin doc.ents])
# [('Colombia', 'GPE'), ('Czech Republic', 'GPE')]

print([(token.text, token._.country_capital)for tokenin docif token._.is_country])
# [('Colombia', 'Bogotá'), ('Czech Republic', 'Prague')]

使用getter和setter还可以实现对属性归类,在Doc和Span引用自定义Token属性,比如文档是否含有国家。因为getter只有在访问属性时才被调用,所以你可以引用Token的is_country属性,这个属性已在处理步骤中设置了。

s_country= lambda tokens:any([token._.is_countryfor tokenin tokens])
Doc.set_extension('has_country', getter=has_country)
Span.set_extension('has_country', getter=has_country)

关于spaCy的扩展

拥有一个简单的自定义扩展API和一个明确定义的输入或输出,同样有助于让庞大的代码库更加易于维护,并允许开发人员与他人共享他们的扩展,并可靠地测试它们。这不仅与使用spaCy的团队有关,而且也适用于希望发布自己的包、扩展和插件的开发人员。

我们希望这个新架构可以帮助支持spaCy组件的社区生态系统,使它可以包含任何可能存在的情况无论这种情况有多特殊。组件可以从简单的扩展为琐碎的属性添加提供便利,到复杂模型的使用,如PyTorch、scikit-learning和TensorFlow等外部库。我们希望能够提供更多内置的管道组件给spaCy,更好的句子边界检测,语义角色标签和情绪分析。但也必须有一些对特定的情况进行处理的spaCy扩展,使其与其他库更好地互操作,并将它们一起用来更新和训练统计模型。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2017-10-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 ATYUN订阅号 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 定制管道组件
  • Doc、Token和Span的扩展属性
  • 关于spaCy的扩展
相关产品与服务
NLP 服务
NLP 服务(Natural Language Process,NLP)深度整合了腾讯内部的 NLP 技术,提供多项智能文本处理和文本生成能力,包括词法分析、相似词召回、词相似度、句子相似度、文本润色、句子纠错、文本补全、句子生成等。满足各行业的文本智能需求。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档