spaCy学习

基本概念

spaCy (/speɪˈsiː/) is an open-source software library for advanced natural language processing written in the programming languages Python and Cython.

学习资源: https://spacy.io/ https://course.spacy.io/en

数据结构

  • nlp : contains the processing pipeline; includes language-specific rules for tokenization etc.
  • doc : access information about the text in a structured way, and no information is lost.
  • token : tokens in a document – for example, a word or a punctuation character.
  • span : a slice of the document consisting of one or more tokens
  • lexical attributes : token.i, token.text, token.is_alpha, token.is_punct, token.like_num… They refer to the entry in the vocabulary and don’t depend on the token’s context.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# 导入对应语言类 Note: Some language tokenizers require external dependencies. 
from spacy.lang.zh import Chinese # https://spacy.io/usage/models 查看lang后面对应的字符

# 创建nlp实例
nlp = Chinese()

# 使用nlp对象处理一段文本并生成doc实例
# When you call nlp on a string, spaCy first tokenizes the text and creates a document object.
doc = nlp("这是一个句子。") # 底层调用 __call__方法

for token in doc: # 遍历doc实例中的token
print(token.text)
'''
这是
一个
句子

'''

# 使用索引获得某一个token
specific_token = doc[1]

# 使用切片获得doc片段
span = doc[1:3]

# token的一些属性
doc = nlp("我花了20¥买了一个汉堡包。")
print("Index: ", [token.i for token in doc])
print("Text: ", [token.text for token in doc])
print("is_alpha:", [token.is_alpha for token in doc])
print("is_punct:", [token.is_punct for token in doc])
print("like_num:", [token.like_num for token in doc])
'''
Index: [0, 1, 2, 3, 4, 5, 6, 7, 8]
Text: ['我花', '了', '20', '¥', '买', '了', '一个', '汉堡包', '。']
is_alpha: [True, True, False, False, True, True, True, True, False]
is_punct: [False, False, False, False, False, False, False, False, True]
like_num: [False, False, True, False, False, False, False, False, False]
'''

统计模型

来源: Models are trained on large datasets of labeled example texts.
作用: 词性标注 (Part-of-speech tags), 依存关系解析 (Syntactic dependencies), 命名实体识别 (Named entities)
优化: Can be updated with more examples to fine-tune predictions

模型包

spaCy提供了很多预训练好的模型包,可以使用spacy download进行下载 https://spacy.io/usage/models

The package provides

  • binary weights that enable spaCy to make predictions;
  • vocabulary, and meta information to tell spaCy which language class to use;
  • how to configure the processing pipeline;

导入模型包和通过初始化语言类的nlp实例区别?

  • spacy.lang.xxx中包含其实是特定语言的代码和规则,包括停用词,数字之类,简单认为就是最基础的一些规则
  • 模型包是建立在已经对大量的标记数据进行训练后得到的统计模型,简单认为是基于spacy.lang.xxx中某一种语言最基础规则之上,对大量数据进行训练学习得到的更多规则

词性标注 (Part-of-speech tags)

通过标注每一个token的词性,为后于文本处理提供基础保证,.pos_属性返回词性标注的结果

依存关系解析 (Syntactic dependencies)

由于已经对于token做了词性标注,由此可以进一步来进行词与词之间的关系的预测。比如一个词是某一个句子或者物体的主语。.dep_属性返回预测的依存关系标注,.head属性返回当前token的parent token(也就是依存的那一个token)

关于依存关系:
20世纪70年代,Robinson提出依存语法中关于依存关系的四条公理:

  • 一个句子中只有一个成分是独立的;
  • 其它成分直接依存于某一成分;
  • 任何一个成分都不能依存与两个或两个以上的成分;
  • 如果A成分直接依存于B成分,而C成分在句中位于A和B之间,那么C或者直接依存于B,或者直接依存于A和B之间的某一成分;

依存结构是加标签的有向图,箭头从中心词指向从属,具体来说,箭头是从head指向child,举个例子: I love you,中 I 依存于 love,所以会有一条有向边从love指向I

1
2
3
4
# 可视化依存关系图
from spacy import displacy
options = {"distance": 120}
displacy.render(doc, style="dep", options=options)

常用的依存标签:
root: 中心词,一般为动词
nsubj: nominal subject,名词性主语
dobj: direct object, 直接宾语
prep: 介词
pobj: 介词宾语
cc: 连词
compound: 复合词
advmod: 状语
det: 限定词
amod: 形容词修饰语

命名实体识别 (Named entities)

doc.ents读取模型预测出的所有命名实体,例如Bill Gates,使用.label_属性打印出实体标签,例如Person

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
$ python -m spacy download zh_core_web_sm 

import spacy
nlp = spacy.load("en_core_web_md") # load a model package by name and returns an nlp object.

# Process a text
doc = nlp("I love you!")

# Iterate over the tokens
for token in doc:
# Print the text and the predicted part-of-speech tag
print(token.text, token.pos_)
'''
I PRON
love VERB
you PRON
! PUNCT
'''

for token in doc:
print(token.text, token.pos_, token.dep_, token.head.text)
'''
I PRON nsubj love
love VERB ROOT love
you PRON dobj love
! PUNCT punct love
'''

# Process a text
doc = nlp("Apple is looking at buying U.K. startup for $1 billion")

# Iterate over the predicted entities
for ent in doc.ents:
# Print the entity text and its label
print(ent.text, ent.label_)

# Process a text
doc = nlp("Microsoft Corporation is an American multinational technology company with headquarters in Redmond, Washington.")

# Iterate over the predicted entities
for ent in doc.ents:
# Print the entity text and its label
print(ent.text, ent.label_)
'''
Microsoft Corporation ORG
American NORP
Redmond GPE
Washington GPE
'''

# Get quick definitions of the most common tags and labels.
spacy.explain('NORP')
'Nationalities or religious or political groups'

基于规则的匹配抽取

使用预训练模型的缺陷:模型的预测是否正确取决于训练数据和处理的文本,如果处理的文本中许多规则没有被模型包含,则预测的精度不会很高。
解决方案: 使用预训练的模型 + 添加相应的规则

Match patterns

  • Lists of dictionaries, one per token [{},{},{}]
  • Match exact token texts
  • Match lexical attributes
  • Match any token attributes

Match step:

  1. Import the Matcher from spacy.matcher.
  2. Initialize it with the nlp object’s shared vocab.
  3. Create patterns
  4. Use the matcher.add to add the pattern to the matcher.
  5. Call the matcher on the doc and store the result in the variable matches.
  6. Iterate over the matches and get the matched span from the start to the end index.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import spacy
from spacy.matcher import Matcher # # Import the Matcher

nlp = spacy.load("en_core_web_md")

# Initialize the matcher with the shared vocab
matcher = Matcher(nlp.vocab)

# Add the pattern to the matcher
pattern = [{"TEXT": "iPhone"}, {"TEXT": "X"}]
matcher.add("IPHONE_PATTERN", None, pattern)


# 其实一个pattern就是一个或者多个token的组合,然后{}表示这个token需要满足的条件
#例子:
pattern_example = [
{"LEMMA": "like", "POS": "VERB"},
{"POS": "NOUN"}
]

# Process some text
doc = nlp("Upcoming iPhone X release date leaked")

# Call the matcher on the doc
matches = matcher(doc)

# Iterate over the matches
for match_id, start, end in matches:
# Get the matched span
matched_span = doc[start:end]
print(matched_span.text)
print(nlp.vocab.strings[match_id]) # 通过match_id 获取当初加入pattern的名称)

'iPhone X'
'IPHONE_PATTERN'


'''
{"OP": "!"} Negation: match 0 times
{"OP": "?"} Optional: match 0 or 1 times
{"OP": "+"} Match 1 or more times
{"OP": "*"} Match 0 or more times
'''

进阶

Vocab:共享词汇表

  • spaCy把所有共享数据都存在一个词汇表Vocab中
  • 所有的字符串都被编码成哈希ID
  • Vocab库为一个双向的查询表,字符串->hash值; hash值->字符串
  • 哈希不能逆求解,通过hash值检索某一个不在Vocab中的字符串会报错
1
2
3
4
5
6
7
doc = nlp("I love you")
print("hash value:", nlp.vocab.strings["love"])
print("string value:", nlp.vocab.strings[3702023516439754181])

#一个Doc实例也可以暴露出它的词汇表和字符串
#另一种方式
print("string value:", doc.vocab.strings[3702023516439754181])

Lexeme:语素

Lexeme(语素)是词汇表中和语境无关的元素,代表着一个词的和语境无关的信息,比如文本本身,或者是这个词是否包含了英文字母,Lexeme中没有词性标注、依存关系或者实体标签这些和语境关联的信息。
在词汇表中查询一个字符串或者一个哈希ID就会获得一个lexeme。lexeme.text表示其文本, lexeme.orth表示其hash值

1
2
3
4
5
doc = nlp("I love you")
lexeme = nlp.vocab['love']

# 打印词汇的属性
print(lexeme.text, lexeme.orth, lexeme.is_alpha)

一句话总结就是:doc中的每一个token对应一个lexeme,其中保存着对应的文本信息和hash值,要拿到文本信息,首先去Vocab中查找对应的hash值。

Doc, Span的手动创建

1
2
3
4
5
6
7
8
9
10
11
12
from spacy.tokens import Doc,Span
words = ["Hello", "world", "!"]
spaces = [True, False, False]
doc = Doc(nlp.vocab, words=words, spaces=spaces)
# 三个参数:共享的词汇表,词汇和空格

span = Span(doc, 0, 2)

# 创建一个带标签的span 这里的带标签很有用!
span_with_label = Span(doc, 0, 2, label="GREETING")
# 把span加入到doc.ents中 doc.ents是可写的,可以人为添加
doc.ents = [span_with_label]

语义相似度

Doc.similarity()Span.similarity()Token.similarity()

相似度的计算方式:

  • 通过词向量计算的,词向量是一个词汇的多维度的语义表示
  • 词向量是用诸如Word2Vec算法在大规模语料上面生成的
  • spaCy默认返回两个向量的余弦相似度
  • Doc和Span的向量默认是由其token的平均值得到的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#查看词向量
doc = nlp("I have a banana")
# 通过token.vector属性获取向量
print(doc[3].vector)

## 文档和文档相似度
doc1 = nlp("I like fast food")
doc2 = nlp("I like pizza")
print(doc1.similarity(doc2))

## token和token相似度
doc = nlp("I like pizza and pasta")
token1 = doc[2]
token2 = doc[4]
print(token1.similarity(token2))

## 文档和token相似度
doc = nlp("I like pizza")
token = nlp("soap")[0]
print(doc.similarity(token))

## 文档和span相似度
span = nlp("I like pizza and pasta")[2:5] # span.text = pizza and pasta
doc = nlp("McDonalds sells burgers")
print(span.similarity(doc))

模型 + 规则

将统计模型与规则系统结合使用,是自然语言处理工具箱里面最强大的方法之一

模型: 根据训练语料库得到一系列规则的集合,spaCy功能一般包括:实体识别器、依存句法识别器、词性标注器
规则: 人为制定的模式匹配,适用于例子较少的情况, spaCy功能一般包括:分词器, Matcher, PhraseMatcher

PhraseMatcher:传进一个Doc实例而不是字典列表作为模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from spacy.matcher import PhraseMatcher

matcher = PhraseMatcher(nlp.vocab)

pattern = nlp("Golden Retriever") # 和Matcher不同的地方
matcher.add("DOG", None, pattern)
doc = nlp("I have a Golden Retriever")

# 遍历匹配结果
for match_id, start, end in matcher(doc):
# 获取匹配到的span
span = doc[start:end]
print("Matched span:", span.text)

# 多个pattern如何进行add
# 下面的代码比这样的表达方式更快: [nlp(country) for country in COUNTRIES]
patterns = list(nlp.pipe(COUNTRIES))
matcher.add("COUNTRY", None, *patterns)

### 例子: 找符合pattern的一个span 并且获取这个span中的root,随后找到这个root的head
span_root = span.root
span_root_head = span.root.head
# 打印这个span的根头词符的文本及span的文本
print(span_root_head.text, "-->", span.text)

处理流程

词性标注: token.tag, token.pos
依存关系: token.dep, token.head
命名实体: 将检测到的实体添加到doc.ents
文本分类器: 适用于整个文本的类别,将其加入doc.cats

所有预训练的模型都包含了一些文件和一个meta.json。这个元数据定义了语种和流程等等,告诉spaCy应该去初始化那些组件。

1
2
3
4
5
6
7
print(nlp.pipe_names)  # 流程组件名的列表
print(nlp.pipeline) # (name, component)元组的列表
'''
[('tagger', <spacy.pipeline.Tagger>),
('parser', <spacy.pipeline.DependencyParser>),
('ner', <spacy.pipeline.EntityRecognizer>)]
'''

定制化流程组件

当一个文本已经被分词且Doc实例被创建后,流程组件会依次被调用。 spaCy支持一系列的原生组件,但是也可以在其中加入自己设计的组件,通过自己定制可以给doc和token添加一些属性。

一个流程组件就是一个函数或者callable,它读取一个doc,修改和返回这个doc,作为下一个流程组件的输入。

1
2
3
4
5
6
7
8
def custom_component(doc):
# 对doc做一些处理
return doc

nlp.add_pipe(custom_component)

nlp.add_pipe(component, last=True) # 组件顺序最后 first=True 组件顺序最前
nlp.add_pipe(component, before="ner") # 指定组件顺序之前 after="tagger" 指定组件顺序之后

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!