自然语言处理NLP入门之SpaCy

本篇主要介绍NLP中的常见任务,并且怎么使用spaCy库来处理这些它们。

目录

1. NLP简介

2. spaCy 简介与安装

2.1 spacy安装

3. 文本处理对象——Doc对象

3.1. 将文本转换成Doc对象

3.2. 检测句子

4. Token对象

5. Span对象

6. 停用词

7. 词性还原 (lemmatization)

8. 词频 (Word Frequency)

9. 词性标注(Part of Speech,POS)

10. 可视化语法分析

11. 预处理函数

12. 基于规则的文本匹配

13. 依存句法分析

14. 依存树与子树

15. 浅层解析(Shallow Parsing)

名词短语检测

动词短语检测

16. 实体命名识别

结论


1. NLP简介

NLP,自然语言处理(Natural Language Processing),是人工智能(AI)的一个分支,目的在于让计算机能够理解、解释和生成人类语言。自然语言处理结合了计算机科学、语言学和机器学习的技术,以实现与人类语言相关的各种任务,例如文本翻译、情感分析、语音识别、自动摘要、聊天机器人开发等。

2. spaCy 简介与安装

spaCy是一个免费开源的自然语言处理(NLP)库,能够执行各种复杂的NLP任务,它以Python为开发语言。spaCy库具有以下优势:

  • 性能高效:spaCy能够高效的处理NLP任务,nltk能够做到的任务它基本都能实现,并且比nltk速度快,特别是在大规模文本数据上。

  • 易于使用:提供了清晰的API接口,让开发者可以轻松实现复杂的NLP任务。

  • 丰富的功能:包括词性标注、命名实体识别(NER)、依存句法分析、词语分词等多种NLP功能。

  • 支持多语言:支持多种语言的处理,包括英语、德语、西班牙语等,满足不同语言环境的需求。

  • 可定制和扩展:用户可以自定义模型和训练数据,以适应特定的NLP任务需求。

  • 集成学习能力:能够与其他Python科学计算库(如NumPy、SciPy)和机器学习库(如scikit-learn、TensorFlow、PyTorch)无缝集成,方便进行复杂的数据分析和模型开发。

2.1 spacy安装

建议在conda环境下安装,不然很容易失败。anaconda环境安装参考博文,包括anaconda环境镜像的修改也同样重要(尤其是在不能科学上网的情况下)。在安装完anaconda环境后,进入anaconda环境,运行:

conda install spacy 
conda install spacy-model-en_core_web_sm # 安装en_core_web_sm模型

en_core_web_sm是spaCy库中的一个英语语言模型,它包含了进行有效自然语言处理所需的主要功能,包括词性标注、命名实体识别、依存句法分析等。如果需要其它语言模型,则需要安装不同的语言的spaCy模型,如,中文spaCy模型,则需要安装zh_core_web_sm模型:

conda install spacy-model-zh_core_web_sm

模型下载完成后en_core_web_sm,进入Python环境并验证安装是否成功:

python
>>> import spacy
>>> nlp = spacy.load("en_core_web_sm")

如果没有提示出任何错误,说明spaCy 已安装并且模型和数据已成功下载。如果还是无法安装,可以选择手动下载安装包进行安装[教程]

3. 文本处理对象——Doc对象

3.1. 将文本转换成Doc对象

接下来,我们学习怎么把输入字符串或者文档转换为Doc对象。在spaCy中,Doc对象是一个非常核心的概念,代表处理过的文档,并且包含了文本的所有信息。将输入字符串转换成Doc对象非常简单:

首先,加载语言模型示例:

>>> import spacy
>>> nlp = spacy.load("en_core_web_sm")

然后使用语言对象进行构造Doc对象:

>>> doc = nlp(
        "This tutorial is about Natural Language Processing in spaCy."
    )
>>> type(doc)
spacy.tokens.doc.Doc # 返回是Doc类型

我们可以遍历Doc对象,得到组成文本的token对象(可以理解为单词),然后我们再调用token对象的text属性(`token.text`),就可以获取到该token中包含的文本:

>>> for token in doc:
>>>    print(token.text)
['This', 'tutorial', 'is', 'about', 'Natural', 'Language',
'Processing', 'in', 'spaCy', '.']

此外,我们还可以通过读取数据文件来构建Doc对象:

>>> import pathlib
>>> file_name = "introduction.txt"
>>> introduction_doc = nlp(pathlib.Path(file_name).read_text(encoding="utf-8"))
>>> print ([token.text for token in introduction_doc])
['This', 'tutorial', 'is', 'about', 'Natural', 'Language',
'Processing', 'in', 'spaCy', '.', '\n']

在这里,通过pathlib.Path对象的.read_text()方法来读取“introduction.txt”文件,来输入nlp对象中进行构建。

3.2. 检测句子

句子检测(Sentence Detection)即在文本中分割出句子,确定句子的开始和结束。这对于nlp的大多数任务,如文本分析、信息提取、摘要生成等至关重要,因为这些任务通常需要在句子级别上进行处理。句子检测通常依赖于标点符号(如句号、问号、感叹号)和语言学规则来识别句子边界。

在spaCy语言模型中,已经封装了句子检测的方法(根据复杂的句子检测规则进行检测)。每个句子可以通过Doc对象的sents属性访问:

>>> about_text = (
         "Gus Proto is a Python developer currently",
         " working for a London-based Fintech",
         " company. He is interested in learning",
         " Natural Language Processing."
     )

>>> about_doc = nlp(about_text)
>>> sentences = list(about_doc.sents)
>>> len(sentences)
2

>>> for sentence in sentences:
         print(f"{sentence[:5]}...")
Gus Proto is a Python...
He is interested in learning...

从上面例子中,我们可以发现通过doc对象的sents属性,可以正确的检测出句子。

当然,我们也可以自定义分隔符来检测句子,下面例子中,除了使用原始的句子检测规则来检测句子外,我们还使用了'...'作为句子分隔符:

>>> ellipsis_text = (
         "Gus, can you, ... never mind, I forgot"
         " what I was saying. So, do you think"
         " we should ..."
         )

>>> from spacy.language import Language
>>> @Language.component("set_custom_boundaries")
     def set_custom_boundaries(doc):
     """添加 `...` 作为句子检测分割符号"""
         for token in doc[:-1]:
             if token.text == "...":
                 doc[token.i + 1].is_sent_start = True
         return doc


>>> custom_nlp = spacy.load("en_core_web_sm")
>>> custom_nlp.add_pipe("set_custom_boundaries", before="parser")
>>> custom_ellipsis_doc = custom_nlp(ellipsis_text)
>>> custom_ellipsis_sentences = list(custom_ellipsis_doc.sents)
>>> for sentence in custom_ellipsis_sentences:
         print(sentence)

Gus, can you, ...
never mind, I forgot what I was saying.
So, do you think we should ...

在这个例子中,使用了@Language.component("set_custom_boundaries")装饰器来定义一个新的函数,该函数接受一个Doc对象作为参数。这个函数的作用是识别Doc中作为句子开始的标记,并将它们的.is_sent_start属性标记为True。完成后,函数再次返回Doc对象。

然后,使用.add_pipe()方法将自定义句子检测函数添加到Language对象中。用这个修改过的Language对象解析文本会将省略号后的单词看作为新句子的开头。

4. Token对象

在构建Doc对象时,spaCy会将文本进行分词处理,即将文本拆解为基本单位(例如单词)——称为标记(tokens/token),在spaCy中以Token对象的形式表示token。

06646963a11f445eb05c328c6cb7b0bf.png

我们可以通过遍历Doc对象来得到tokens。Token对象有很多重要的属性,例如,可以通过访问token中的.idx属性来访问token在字符串中的索引位置。

>>> import spacy
>>> nlp = spacy.load("en_core_web_sm")
>>> about_text = (
         "Gus Proto is a Python developer currently"
         " working for a London-based Fintech"
         " company. He is interested in learning"
         " Natural Language Processing."
     )
>>> about_doc = nlp(about_text)

>>> 遍历Doc对象得到每一个Token对象
>>> for token in about_doc:
         print (token, token.idx)

Gus 0
Proto 4
is 10
a 13
Python 15
developer 22
currently 32
working 42
for 50
a 54
London 56
- 62
based 63
Fintech 69
company 77
. 84
He 86
is 89
interested 92
in 103
learning 106
Natural 115
Language 123
Processing 132
. 142

在这个例子李,我们通过遍历Doc对象来访问文本中的每一个token,同时通过.text和.idx属性打印出token的内容和在字符串中的开始位置。此外,Token对象还有很多属性,例如常用的属性有:

>>> token.text_with_ws # 判断token是否有空格,有的话返回True
>>> token.is_alpha # 判断token是否只包含字母字符(即字母),如果是则返回True
>>> token.is_punct # 判断token是否是标点符号(例如","、"."、"!"等),如果是则返回True
>>> token.is_stop # 判断token是否是停用词,停用词概念见下小节

Token对象的更多属性可以参考开发文档之Token。

和Doc对象类似,我们也可以自定义分词规则,例如,在下面示例中,如果使用系统的分词,会将`London@based` 分解为一个单词:

>>> custom_about_text = (
         "Gus Proto is a Python developer currently"
         " working for a London@based Fintech"
         " company. He is interested in learning"
         " Natural Language Processing."
 )

>>> print([token.text for token in nlp(custom_about_text)[8:15]])
['for', 'a', 'London@based', 'Fintech', 'company', '.', 'He']

但是,我们想要将`@`也作为一个分词标记,这时候只能自定义一个新的Tokenizer对象:

>>> import re
>>> from spacy.tokenizer import Tokenizer

>>> custom_nlp = spacy.load("en_core_web_sm") # 加载了英语语言模型("en_core_web_sm")
>>> prefix_re = spacy.util.compile_prefix_regex( # 编译前缀正则表达式
         custom_nlp.Defaults.prefixes
     ) 
>>> suffix_re = spacy.util.compile_suffix_regex( # 编译后缀正则表达式
         custom_nlp.Defaults.suffixes
     ) 

>>> custom_infixes = [r"@"] # 自定义中缀列表,这里只包含了 '@'

>>> infix_re = spacy.util.compile_infix_regex( # 编译中缀正则表达式
         list(custom_nlp.Defaults.infixes) + custom_infixes
     ) 

>>> custom_nlp.tokenizer = Tokenizer( # 创建自定义的 Tokenizer 对象,并应用前缀、后缀、中缀正则表达式
         nlp.vocab,
         prefix_search=prefix_re.search,
         suffix_search=suffix_re.search,
         infix_finditer=infix_re.finditer,
         token_match=None,
     ) 

>>> custom_tokenizer_about_doc = custom_nlp(custom_about_text) # 对自定义的文本进行标记化处理

>>> print([token.text for token in custom_tokenizer_about_doc[8:15]])
['for', 'a', 'London', '@', 'based', 'Fintech', 'company']

要构建一个新的 Tokenizer,通常需要提供以下内容:

  • Vocab:用于存储特殊情况的容器,用于处理缩写词和表情符号等情况。
  • prefix_search:处理前缀标点符号(如开括号)的函数。
  • suffix_search:处理后缀标点符号(如闭括号)的函数。
  • infix_finditer:处理非空白分隔符(如连字符)的函数。
  • token_match:一个可选的布尔函数,用于匹配不应该被拆分的字符串。它会覆盖之前的规则,对于诸如 URL 或数字等实体很有用。

在这里,我们自定义了一个新的中缀列表[r"@"],并且与Language 对象的默认中缀列表(.Defaults.infixes 属性)进行连接。然后,将扩展后的列表作为参数传递给 spacy.util.compile_infix_regex(),以获得新的中缀正则表达式对象。

5. Span对象

在spaCy中,Span对象是指文档中的一个片段,由多个Token对象组成。它是由一个起始索引和一个终止索引在Doc对象中定义的,代表了文档中连续的一部分。Span对象可以用来表示词组、句子、命名实体等文本片段。

d85fbcc7d7fb4d44b6c083f3c35c5ea2.png

Span对象具有以下主要特性:

  • .text:返回Span的文本内容。
  • .start.end:返回SpanDoc对象中的起始和终止位置的索引。
  • .label_:如果Span是一个命名实体,这个属性返回实体的标签(例如,人名、地点、组织等)。
  • .lemma_:返回Span中所有单词的基本形式或词根形式的字符串。
  • .sent:返回包含该Span的句子。
  • .ents:返回Span内的所有命名实体。

Span对象是spaCy处理文本时非常重要的一个概念,因为它不仅能够表示文本的一个片段,还能够保留这个片段在整个文档中的位置信息以及其他语言学特性。

6. 停用词

人类语言中包含的功能词,这些功能词极其普遍,与其他词相比,功能词没有什么实际含义,比如'the'、'is'、'at'、'which'、'on'等。另一类词包括词汇词,比如'want'等,这些词应用十分广泛,但是这些词对于大多数NLP任务来说,无法提供有用的价值,有时还会降低效率。这些词在NLP任务中称为停用词。在NLP任务中,通常会删除停用词,因为它们不重要,并且它们会严重扭曲任何词频分析。spaCy 存储了不同语言的停用词列表。

>>> import spacy
>>> spacy_stopwords = spacy.lang.en.stop_words.STOP_WORDS # 英文停用词
>>> len(spacy_stopwords)
326
>>> for stop_word in list(spacy_stopwords)[:10]:
         print(stop_word)

using
becomes
had
itself
once
often
is
herein
who
too

spaCy 中的 STOP_WORDS 列表位于 spacy.lang.en.stop_words 模块中。在判断文本中的token是否停用词时,我们不需要直接访问这个列表,可以通过利用每个token的 .is_stop 属性来从输入文本中删除停用词:

>>> custom_about_text = (
         "Gus Proto is a Python developer currently"
         " working for a London-based Fintech"
         " company. He is interested in learning"
         " Natural Language Processing."
     )
>>> nlp = spacy.load("en_core_web_sm")
>>> about_doc = nlp(custom_about_text)
>>> print([token for token in about_doc if not token.is_stop]) # 保留非停用词
[Gus, Proto, Python, developer, currently, working, London, -, based, Fintech,
 company, ., interested, learning, Natural, Language, Processing, .]

7. 词性还原 (lemmatization)

词形还原是指将一个单词的各种词性还原为其语言中的原始形式(去除英文单词的前缀、后缀等)。这个原始形式通常被称为词元。词形还原可以帮助文本处理任务更好地理解单词的含义,因为它可以将单词归约为它们的基本形式。例如,将“running”、“runs”、“ran”等形式还原为它们的词元“run”。

我们可以通过spacy中Token对象的lemma_ 属性来访问单词的词元:

>>> import spacy
>>> nlp = spacy.load("en_core_web_sm")
>>> conference_help_text = (
         "Gus is helping organize a developer"
         " conference on Applications of Natural Language"
         " Processing. He keeps organizing local Python meetups"
         " and several internal talks at his workplace."
     )
>>> conference_help_doc = nlp(conference_help_text)
>>> for token in conference_help_doc:
         if str(token) != str(token.lemma_): # 如果当前token的词元不是其原始单词
             print(f"{str(token)}: {str(token.lemma_)}")

                  is : be
                  He : he
               keeps : keep
          organizing : organize
             meetups : meetup
               talks : talk

8. 词频 (Word Frequency)

词频统计是指统计文本中每个单词出现的次数,通过词频统计,可以快速了解文本中哪些词出现频率较高,从而帮助理解文本的主题和内容。在spaCy中,我们只需筛选出非停用词和标点符号的token,再调用Counter对象即可统计词频:

>>> import spacy
>>> from collections import Counter
>>> nlp = spacy.load("en_core_web_sm")
>>> complete_text = (
...     "Gus Proto is a Python developer currently"
...     " working for a London-based Fintech company. He is"
...     " interested in learning Natural Language Processing."
...     " There is a developer conference happening on 21 July"
...     ' 2019 in London. It is titled "Applications of Natural'
...     ' Language Processing". There is a helpline number'
...     " available at +44-1234567891. Gus is helping organize it."
...     " He keeps organizing local Python meetups and several"
...     " internal talks at his workplace. Gus is also presenting"
...     ' a talk. The talk will introduce the reader about "Use'
...     ' cases of Natural Language Processing in Fintech".'
...     " Apart from his work, he is very passionate about music."
...     " Gus is learning to play the Piano. He has enrolled"
...     " himself in the weekend batch of Great Piano Academy."
...     " Great Piano Academy is situated in Mayfair or the City"
...     " of London and has world-class piano instructors."
... )
>>> complete_doc = nlp(complete_text)

>>> words = [ # 过滤掉停用词和标点符号
...     token.text
...     for token in complete_doc
...     if not token.is_stop and not token.is_punct 
... ]

>>> print(Counter(words).most_common(5)) # 使用Counter来统计,并筛选出词频最高的5个token
[('Gus', 4), ('London', 3), ('Natural', 3), ('Language', 3), ('Processing', 3)]

如果我们不去除停用词,就会发现频率高的词通常是无意义的词,无法快速得知文段所阐述的信息,这也会降低模型后续训练的效率。

>>> Counter(
...     [token.text for token in complete_doc if not token.is_punct]
... ).most_common(5)

[('is', 10), ('a', 5), ('in', 5), ('Gus', 4), ('of', 4)]

9. 词性标注(Part of Speech,POS)

词性标注(POS tagging ) 是将对句子中的词语进行分类标注的过程。是依据字词在句法结构或语言形态上承担的成分,通过词性分类赋予每个词的词性标记的过程。也就是要确定句子中每个词是名词、动词、形容词或其他词性的过程,又称词类标注或者简称标注。词性标注是自然语言处理中的一项基础任务,在语音识别、信息检索及自然语言处理的许多领域都有应用。通常有八种词性:

  • 名词(Noun)、动词(Verb)、形容词(Adjective)、代词(Pronoun)、副词(Adverb)、介词(Preposition)、连词(Conjunction)、叹词(Interjection)。

在spaCy中,我们同样可以通过Token对象的.tag_和pos_属性访问token的词性:

>>> import spacy
>>> nlp = spacy.load("en_core_web_sm")
>>> about_text = (
...     "Gus Proto is a Python developer currently"
...     " working for a London-based Fintech"
...     " company. He is interested in learning"
...     " Natural Language Processing."
... )
>>> about_doc = nlp(about_text)
>>> for token in about_doc:
...     print(
...         f"""
... TOKEN: {str(token)}
... =====
... TAG: {str(token.tag_):10} POS: {token.pos_}
... EXPLANATION: {spacy.explain(token.tag_)}"""
...     )
...
TOKEN: Gus
=====
TAG: NNP        POS: PROPN
EXPLANATION: noun, proper singular

TOKEN: Proto
=====
TAG: NNP        POS: PROPN
EXPLANATION: noun, proper singular

TOKEN: is
=====
TAG: VBZ        POS: AUX
EXPLANATION: verb, 3rd person singular present

TOKEN: a
=====
TAG: DT         POS: DET
EXPLANATION: determiner

TOKEN: Python
=====
TAG: NNP        POS: PROPN
EXPLANATION: noun, proper singular

...
  • tag_ 显示了细粒度标签。
  • pos_ 显示了粗粒度标签,这是细粒度标签的简化版本。

同时, spacy.explain() 提供了关于特定词性标签的描述性细节,这可以作为一个参考工具。

10. 可视化语法分析

spaCy提供了一个displacy功能用于可视化语法分析结果。它能够以直观的方式展示句子的结构和各个单词之间的关系,包括依赖关系、词性标签等信息。displaCy 可以生成漂亮的树状图或依赖关系图,帮助用户更好地理解文本的结构和语法。例如,我们可以查看单词的词性标注图:

>>> import spacy
>>> from spacy import displacy
>>> nlp = spacy.load("en_core_web_sm")

>>> about_interest_text = (
...     "He is interested in learning Natural Language Processing."
... )
>>> about_interest_doc = nlp(about_interest_text)
>>> displacy.serve(about_interest_doc, style="dep")

结果需要打开web页面来浏览,你可以打开浏览器访问http://127.0.0.1:5000来得到可视化结果。

ce269b1cc8c74fb886e7ac48991411a6.png

从图中我们可以看到,每个词都写有一个词性标签。如果在jupyter notebook中使用displaCy,可以通过下面的方法:

In [1]: displacy.render(about_interest_doc, style="dep", jupyter=True)

这里只使用了一个简单的例子来阐述displacy的功能,更多可选参数和可视化图表参考开发文档

11. 预处理函数

根据上面所学的方法,我们进行一个完整的文本预处理过程,这也是NLP任务中典型的做法,包括以下过程:

  • 将文本转换为小写;
  • 对每个标记进行词形还原;
  • 删除标点符号;
  • 删除停用词。
>>> import spacy
>>> nlp = spacy.load("en_core_web_sm")
>>> complete_text = (
         "Gus Proto is a Python developer currently"
         " working for a London-based Fintech company. He is"
         " interested in learning Natural Language Processing."
         " There is a developer conference happening on 21 July"
         ' 2019 in London. It is titled "Applications of Natural'
         ' Language Processing". There is a helpline number'
         " available at +44-1234567891. Gus is helping organize it."
         " He keeps organizing local Python meetups and several"
         " internal talks at his workplace. Gus is also presenting"
         ' a talk. The talk will introduce the reader about "Use'
         ' cases of Natural Language Processing in Fintech".'
         " Apart from his work, he is very passionate about music."
         " Gus is learning to play the Piano. He has enrolled"
         " himself in the weekend batch of Great Piano Academy."
         " Great Piano Academy is situated in Mayfair or the City"
         " of London and has world-class piano instructors."
     )
>>> complete_doc = nlp(complete_text)
>>> def is_token_allowed(token):
         return bool(
             token
             and str(token).strip()
             and not token.is_stop
             and not token.is_punct
     )

>>> def preprocess_token(token):
         return token.lemma_.strip().lower()

>>> complete_filtered_tokens = [
         preprocess_token(token)
     for token in complete_doc
         if is_token_allowed(token)
... ]

>>> complete_filtered_tokens
['gus', 'proto', 'python', 'developer', 'currently', 'work',
'london', 'base', 'fintech', 'company', 'interested', 'learn',
'natural', 'language', 'processing', 'developer', 'conference',
'happen', '21', 'july', '2019', 'london', 'title',
'applications', 'natural', 'language', 'processing', 'helpline',
'number', 'available', '+44', '1234567891', 'gus', 'help',
'organize', 'keep', 'organize', 'local', 'python', 'meetup',
'internal', 'talk', 'workplace', 'gus', 'present', 'talk', 'talk',
'introduce', 'reader', 'use', 'case', 'natural', 'language',
'processing', 'fintech', 'apart', 'work', 'passionate', 'music',
'gus', 'learn', 'play', 'piano', 'enrol', 'weekend', 'batch',
'great', 'piano', 'academy', 'great', 'piano', 'academy',
'situate', 'mayfair', 'city', 'london', 'world', 'class',
'piano', 'instructor']

在以上代码中,我们定义了两个函数is_token_allowed() 用于过滤标记(过滤掉空白、停用词和标点符号),preprocess_token() 用于对标记进词形还原、去除空格、和转换为小写。通过结果我们可以看出,经过处理的token只以原始的词元形式和小写来呈现。

12. 基于规则的文本匹配

基于规则的匹配利用预定义的规则来识别和提取文本中的特定信息。这些规则可以基于文本模式,比如字符的大小写,或者基于词的语法属性,比如词性。spaCy封装了强大的基于规则的文本匹配功能,它比单独使用正则表达式更加强大,不仅可以使用简单的文本模式,还可以结合语义或语法信息来增强匹配的准确性和灵活性。

例如,如果你想从一段文本中提取人名,仅仅使用正则表达式可能很难区分名字和其他以大写字母开头的单词。但是,如果使用spaCy的规则匹配功能,你可以指定一个规则,仅提取那些标记为专有名词的词,从而更精确地定位和提取人名:

>>> import spacy
>>> nlp = spacy.load("en_core_web_sm")
>>> about_text = (
         "Gus Proto is a Python developer currently"
         " working for a London-based Fintech"
         " company. He is interested in learning"
         " Natural Language Processing."
     )
>>> about_doc = nlp(about_text)

# 从spaCy.matcher模块导入Matcher类
>>> from spacy.matcher import Matcher

# 创建一个Matcher对象,需要传递当前nlp对象的词汇表
>>> matcher = Matcher(nlp.vocab)

# 定义一个提取全名的函数,输入参数是一个spaCy的文档对象
>>> def extract_full_name(nlp_doc):
         # 定义一个模式,寻找两个连续的专有名词(PROPN)
         pattern = [{"POS": "PROPN"}, {"POS": "PROPN"}]
         
         # 将这个模式添加到matcher对象中,为这个模式命名为"FULL_NAME"
         matcher.add("FULL_NAME", [pattern])

         # 在输入的文档中查找匹配的模式
         matches = matcher(nlp_doc)

         # 遍历找到的匹配项,matches中的每个元素是一个三元组:匹配ID,起始索引,结束索引
         for _, start, end in matches:
             # 根据起始和结束索引在文档中提取对应的文本片段
             span = nlp_doc[start:end]

             # 生成提取的文本片段
             yield span.text

# 使用定义的函数提取about_doc文档中的全名,并获取第一个匹配的全名
>>> next(extract_full_name(about_doc))
'Gus Proto' # 输出结果是'Gus Proto',这是文档中匹配到的第一个专有名词对

pattern = [{"POS": "PROPN"}, {"POS": "PROPN"}]由字典组成的列表,每个字典代表一个要匹配的标记(token)的特征。在这个特定的模式中,列表包含两个字典,每个字典指定了一个条件,即标记的词性(POS)必须是“PROPN”,这是专有名词的词性标记。当spaCy处理文本并对每个词进行词性标注时,这个模式将用来寻找所有连续的两个标记,这两个标记都被标注为专有名词。

你也可以使用基于规则的匹配来提取电话号码:

>>> conference_org_text = ("There is a developer conference"
         " happening on 21 July 2019 in London. It is titled"
         ' "Applications of Natural Language Processing".'
         " There is a helpline number available"
         " at (123) 456-7891")

# 定义一个函数,用于从给定的spaCy文档中提取电话号码
>>> def extract_phone_number(nlp_doc):
         # 定义一个模式来匹配电话号码的格式
         # 匹配规则包括:左括号,三位数字,右括号,三位数字,可选的短横线,四位数字
         pattern = [
             {"ORTH": "("}, # 左括号
             {"SHAPE": "ddd"}, # 三位数字
             {"ORTH": ")"}, # 右括号
             {"SHAPE": "ddd"}, # 三位数字
             {"ORTH": "-", "OP": "?"}, # 可选的短横线
             {"SHAPE": "dddd"}, # 四位数字
         ]

         # 将这个模式添加到matcher对象中,命名为"PHONE_NUMBER"
         matcher.add("PHONE_NUMBER", None, pattern)

         # 在输入的文档中查找匹配的模式
         matches = matcher(nlp_doc)

         # 遍历找到的匹配项,每个元素是一个包含匹配ID、起始索引和结束索引的元组
         for match_id, start, end in matches:
             span = nlp_doc[start:end]
             return span.text

# 使用spaCy处理会议组织的文本,得到一个文档对象
>>> conference_org_doc = nlp(conference_org_text)

# 调用函数,从文档中提取电话号码,并打印结果
>>> extract_phone_number(conference_org_doc)
'(123) 456-7891'

在上述电话号码匹配的例子中,我们使用了几个不同的属性来定义我们的匹配规则:

  • ORTH(正字法):这个属性用于匹配标记的确切文本。例如,"(", ")", 和 "-" 都是电话号码常见的分隔符,我们可以使用ORTH来确切地匹配这些字符。
  • SHAPE(形态):这个属性用于描述标记文本的一般形状,而不是具体的字符。例如,用"ddd"来表示三个数字。这对于匹配具有相似形式的文本(如电话号码、日期等)特别有用。
  • OP(操作符):这个属性用于定义模式中某个元素出现的次数。例如,"?"表示该元素可以出现0次或1次,这在处理可选的分隔符(如电话号码中的短横线)时非常有用。

将这些属性结合起来,我们可以构建出复杂的匹配模式,以识别和提取具有特定格式的文本,如电话号码。这种方法的优点是,它不仅可以匹配基于简单文本的模式,还可以根据词汇的语法特征进行匹配,使得提取的信息更加精确。

13. 依存句法分析

依存句法分析是提取句子的依存关系图以表示其语法结构的过程。它定义了核心词(headwords)与其依存词(dependents)之间的依存关系。句子的核心没有依赖关系,被称为句子的根(root)。动词通常是句子的根。所有其他词都链接到核心词。

依存关系可以在有向图表示中被映射,其中:

  • 单词是节点。
  • 语法关系是边。

依存句法分析帮助你了解一个词在文本中扮演的角色以及不同词之间是如何相互关联的。

以下是如何使用依存句法分析来找出词语之间的关系:

  1. 首先,你需要使用spaCy处理你的文本,它会自动为文本中的每个词提供词性标注和依存关系标注。
  2. 每个词都被赋予一个依存标签(.dep_属性),这个标签说明了该词与它的核心词之间的语法关系。
  3. 通过分析这些依存关系,你可以构建出一个有向图,图中的节点代表词语,而边代表这些词语之间的依存关系。
  4. 可视化依赖图,它可以让我们能够直观地理解句子的结构,例如,谁是谁的动作的执行者(主语),动作是什么(谓语),动作的对象是什么(宾语),以及其他词语如何修饰或关联到这些核心元素。

例子:

>>> import spacy
>>> nlp = spacy.load("en_core_web_sm")
>>> piano_text = "Gus is learning piano"
>>> piano_doc = nlp(piano_text)
>>> for token in piano_doc:
         print(
             f"""
     TOKEN: {token.text}
     =====
     {token.tag_ = }
     {token.head.text = }
     {token.dep_ = }"""
         )

TOKEN: Gus
=====
token.tag_ = 'NNP'
token.head.text = 'learning'
token.dep_ = 'nsubj'

TOKEN: is
=====
token.tag_ = 'VBZ'
token.head.text = 'learning'
token.dep_ = 'aux'

TOKEN: learning
=====
token.tag_ = 'VBG'
token.head.text = 'learning'
token.dep_ = 'ROOT'

TOKEN: piano
=====
token.tag_ = 'NN'
token.head.text = 'learning'
token.dep_ = 'dobj'

在这个例子中,我们可以看出‘learning’是该句的root,句子包含三种关系:

  • nsubj 是词的主语,其核心词是一个动词。
  • aux 是一个助动词,其核心词也是一个动词。
  • dobj 是动词的直接宾语,其核心词也是一个动词。

使用displaCy来可视化句子的依存树:

>>> displacy.serve(piano_doc, style="dep")

c4b597895f184f98a04f0ccdc156c25c.png

这张图片直观的展示了句子的主语是专有名词Gus,并且它与piano有一个“学习”(learn)的关系。

14. 依存树与子树

依存树是一种表示句子结构的图形结构,其中包括了句子中每个词与其他词之间的依存关系。这些依存关系帮助我们理解句子中每个词的语法作用以及词与词之间是如何相互连接的,。

spaCy提供了如.children、.lefts、.rights和.subtree等属性,使得依存解析树(导航解析树)变得更加容易。以下是使用这些属性的一些示例:

  1. .children属性:这个属性允许我们访问任何给定节点(在这个上下文中,节点是指句子中的一个词)的子节点。子节点是直接依赖于父节点的词。这种关系对于理解词语之间的直接连接非常有帮助。

  2. .lefts和.rights属性:这些属性分别提供了一种方式来访问节点左侧和右侧的子节点。这对于分析句子中词语的顺序和它们之间的结构关系特别有用。

  3. .subtree属性:这个属性让我们能够获取包含当前节点及其所有后代的子树。这在需要提取某个词及其相关内容(如一个短语或句子成分)时非常有用。

通过利用这些属性,我们可以深入探索句子的结构,分析词语之间的复杂关系。这对于执行更高级的自然语言理解任务至关重要,如提取特定信息、理解句子意义、构建问答系统等。下面是一个例子:

>>> import spacy
>>> nlp = spacy.load("en_core_web_sm")
>>> one_line_about_text = (
        "Gus Proto is a Python developer"
        " currently working for a London-based Fintech company"
    )
>>> one_line_about_doc = nlp(one_line_about_text)

>>> # 提取 `developer`的子节点
>>> print([token.text for token in one_line_about_doc[5].children])
['a', 'Python', 'working']

>>> # 提取 `developer`的上一个邻节点
>>> print (one_line_about_doc[5].nbor(-1))
Python

>>> # 提取 `developer`的下一个邻节点
>>> print (one_line_about_doc[5].nbor())
currently

>>> # 提取`developer`左侧的所有标记(tokens)
>>> print([token.text for token in one_line_about_doc[5].lefts])
['a', 'Python']

>>> # 提取`developer`右侧的所有标记(tokens)
>>> print([token.text for token in one_line_about_doc[5].rights])
['working']

>>> # 打印`developer`的所有子树
>>> print (list(one_line_about_doc[5].subtree))
[a, Python, developer, currently, working, for, a, London, -, based, Fintech
company]

15. 浅层解析(Shallow Parsing)

浅层解析(Shallow Parsing),或称为分块(Chunking),是从非结构化文本中提取短语的过程。这包括基于它们的词性标记(POS tags),将相邻的token组成短语的过程。一些常见的短语类型包括名词短语(Noun Phrases)、动词短语(Verb Phrases)和介词短语(Prepositional Phrases)。

名词短语检测

名词短语是以名词作为中心词的短语。它还可以包括其他种类的词,如形容词、序数词和限定词。名词短语对于解释句子的上下文非常有用。它们帮助你理解句子的主题。

spaCy在Doc对象上提供了.noun_chunks属性。你可以使用这个属性来提取名词短语:

>>> import spacy
>>> nlp = spacy.load("en_core_web_sm")

>>> conference_text = (
         "There is a developer conference happening on 21 July 2019 in London."
     )
>>> conference_doc = nlp(conference_text)

>>> # 提取名词短语
>>> for chunk in conference_doc.noun_chunks:
         print(chunk)

a developer conference
21 July
London

通过查看名词短语,可以在无需阅读文本全部内容下,清晰的获取文本的相关信息。例如,“a developer conference”(一个开发者会议)表明文本提到了一个会议,而日期“21 July”(7月21日)让人知道会议定于7月21日举行。

这是另一种总结文本并获取最重要信息的方法,无需实际阅读全部内容。

动词短语检测

动词短语是由至少一个动词组成的句法单元。这个动词可以和其他短语一起出现,比如名词短语。动词短语对于理解名词所涉及的动作很有用。

spaCy没有内置的功能来提取动词短语,因此我们需要安装textacy的库。使用pip来安装textacy:

>>> pip install textacy
>>> import textacy

>>> # 定义一个文本字符串,关于一次讲座的介绍
>>> about_talk_text = (
         "The talk will introduce reader about use"
         " cases of Natural Language Processing in"
         " Fintech, making use of"
         " interesting examples along the way."
 )

>>> # 定义一个模式,用于匹配由助动词和动词组成的动词短语
>>> patterns = [{"POS": "AUX"}, {"POS": "VERB"}]

>>> # 使用textacy创建一个spaCy文档对象,此处指定语言模型为英语语言模型
>>> about_talk_doc = textacy.make_spacy_doc(
         about_talk_text, lang="en_core_web_sm"
     )

>>> # 使用textacy提取符合上述模式的动词短语
>>> verb_phrases = textacy.extract.token_matches(
         about_talk_doc, patterns=patterns
     )

>>> # 打印所有提取到的动词短语
>>> for chunk in verb_phrases:
         print(chunk.text)

will introduce

>>> # 提取并打印所有名词短语,以了解涉及的名词
>>> for chunk in about_talk_doc.noun_chunks:
         print (chunk)

this talk
the speaker
the audience
the use cases
Natural Language Processing
Fintech
use
interesting examples
the way

动词短语“introduce”表明将会有某些东西被介绍。通过查看名词短语,你可以拼凑出将要介绍什么,而无需阅读整个文本。

16. 实体命名识别

命名实体识别(Named-entity recognition,NER)是在非结构化文本中定位命名实体并将它们分类到预定义类别的过程,如人名、组织、地点、货币值和时间表达式等。

你可以使用NER来更深入地了解文本的含义。例如,可以使用它为一组文档填充标签,以改善关键词搜索。你也可以使用它将客户支持票据分类到相关类别。

spaCy在Doc对象上有一个属性.ents。你可以使用它来提取命名实体:

>>> import spacy
>>> nlp = spacy.load("en_core_web_sm")

>>> piano_class_text = (
         "Great Piano Academy is situated"
         " in Mayfair or the City of London and has"
         " world-class piano instructors."
     )
>>> piano_class_doc = nlp(piano_class_text)

>>> # 遍历Doc对象的`.ents`属性来得到所有的实体
>>> for ent in piano_class_doc.ents:
     print(
         f"""
     {ent.text = }
     {ent.start_char = }
     {ent.end_char = }
     {ent.label_ = }
     spacy.explain('{ent.label_}') = {spacy.explain(ent.label_)}""" # 详细解释实体表示的含义
 )

ent.text = 'Great Piano Academy'
ent.start_char = 0
ent.end_char = 19
ent.label_ = 'ORG'
spacy.explain('ORG') = Companies, agencies, institutions, etc.

ent.text = 'Mayfair'
ent.start_char = 35
ent.end_char = 42
ent.label_ = 'LOC'
spacy.explain('LOC') = Non-GPE locations, mountain ranges, bodies of water

ent.text = 'the City of London'
ent.start_char = 46
ent.end_char = 64
ent.label_ = 'GPE'
spacy.explain('GPE') = Countries, cities, states

在上述示例中,ent是一个具有多种属性的Span对象:

  • .text 提供实体的Unicode文本表示。
  • .start_char 表示实体开始的字符偏移量。
  • .end_char 表示实体结束的字符偏移量。
  • .label_ 给出实体的标签。

spacy.explain 函数可以提供每个实体标签的描述性细节。你还可以使用displaCy来可视化这些实体:

>>> displacy.serve(piano_class_doc, style="ent")

683044c9e17b43afb8d1146a4fb63b8c.png

打开http://127.0.0.1:5000,可以发现以上图。命名实体识别(NER)的一个应用场景是从文本中删除人名。例如,我们想要隐藏在调查中收集的个人信息:

>>> # 定义一个调查文本
>>> survey_text = (
         "Out of 5 people surveyed, James Robert,"
         " Julie Fuller and Benjamin Brooks like"
         " apples. Kelly Cox and Matthew Evans"
         " like oranges."
     )

>>> # 定义一个函数,用于替换人名
>>> def replace_person_names(token):
>>>      # 如果token是一个人名实体,则将其替换为"[REDACTED] "
>>>      if token.ent_iob != 0 and token.ent_type_ == "PERSON":
             return "[REDACTED] "
         
         # 否则,返回原token文本
         return token.text_with_ws

>>> # 定义一个函数,用于在文档中执行名称的删除操作
>>> def redact_names(nlp_doc):
         # 合并文档中的实体,以便人名可以作为单个token处理
         with nlp_doc.retokenize() as retokenizer:
             for ent in nlp_doc.ents:
                 retokenizer.merge(ent)

         # 合并文档中的实体,以便人名可以作为单个token处理
         tokens = map(replace_person_names, nlp_doc)
         
          # 将处理后的token串联成一个字符串,并返回
         return "".join(tokens)


>>> survey_doc = nlp(survey_text)
>>> print(redact_names(survey_doc))
Out of 5 people surveyed, [REDACTED] , [REDACTED] and [REDACTED] like apples.
[REDACTED] and [REDACTED] like oranges.

这段代码首先定义了一个包含人名的调查文本。然后,它使用spaCy的命名实体识别功能来识别和删除(即替换为"[REDACTED] ")文本中的所有人名,以保护个人隐私。这通过两个函数实现:replace_person_names用于替换检测到的人名实体,redact_names用于遍历文档的所有实体,并对每个人名实体应用replace_person_names函数,最后返回处理后的文本。这种方法可以在需要保护个人信息的场景下非常有用,如处理调查数据、客户反馈等。

结论

spaCy是一个功能强大且先进的库,在自然语言处理(NLP)应用中因其速度快、易用性强、准确度高和可扩展性而获得了巨大的流行。

在本教程中,你已经学会了如何:

  • 在spaCy中实现NLP
  • 自定义和扩展spaCy中的内置功能
  • 对文本进行基本的统计分析
  • 创建流程来处理非结构化文本
  • 解析句子并从中提取有意义的信息

推荐阅读:

[1] Natural Language Processing With spaCy in Python

[2] spacy api

[3] spacy中文交互式course

  • 28
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值