天天看點

[自然語言處理] 自然語言處理庫spaCy使用指北

作者:彭彭加油鴨

spaCy是一個基于Python編寫的開源自然語言處理庫。基于自然處理領域的最新研究,spaCy提供了一系列高效且易用的工具,用于文本預處理、文本解析、命名實體識别、詞性标注、句法分析和文本分類等任務。 spaCy的官方倉庫位址為:​spaCy-github​​​。本文主要參考其官方網站的文檔,spaCy的官方網站位址為:​​spaCy​​。

1 背景介紹與spaCy安裝

1.1 自然語言處理簡介

自然語言處理(Natural Language Processing,簡稱NLP)是一門研究人類語言與計算機之間互動的領域,旨在使計算機能夠了解、解析、生成和處理人類語言。NLP結合了計算機科學、人工智能和語言學的知識,通過各種算法和技術來處理和分析文本資料。近年來,随着深度學習技術的發展,神經網絡模型在自然語言處理(NLP)領域取得了重大的突破。其中,循環神經網絡(RNN)、長短時記憶網絡(LSTM)和Transformer等模型都發揮了關鍵作用。這些模型為NLP任務帶來了更好的性能和效果,推動了NLP的發展和應用。

NLP主要知識結構如下圖所示,圖檔來自網絡。

[自然語言處理] 自然語言處理庫spaCy使用指北

NLP的應用非常廣泛,涵蓋了多個領域,如機器翻譯、資訊提取、文本分類、情感分析、自動摘要、問答系統、語音識别和語音合成等。以下是NLP常用的技術和方法:

  • 分詞:将連續的文本分割成有意義的詞語或标記,是許多NLP任務的基礎。
  • 詞性标注:為文本中的每個詞語賦予其相應的詞性,如名詞、動詞、形容詞等。
  • 句法分析:分析句子的文法結構,識别出句子中的短語、修飾語和依存關系等。
  • 語義分析:了解文本的意義和語義關系,包括命名實體識别、語義角色标注和語義解析等。
  • 機器翻譯:将一種語言的文本自動翻譯成另一種語言。
  • 文本分類:将文本按照預定義的類别進行分類,如垃圾郵件分類、情感分類等。
  • 資訊提取:從結構化和非結構化文本中提取出特定的資訊,如實體關系抽取、事件抽取等。
  • 問答系統:通過對使用者提出的問題進行了解和回答,提供準确的答案或相關資訊。
  • 情感分析:識别和分析文本中的情感傾向,如正面、負面或中性情感。
  • 文本生成:使用NLP技術生成自然語言文本,如自動摘要生成、對話系統和機器作文等。

在衆多自然語言處理庫中,spaCy庫提供了超過73種語言的支援,并為25種語言提供了訓練代碼。該庫提供了一系列簡單易用的模型和函數接口,包括分詞、詞性标注等功能。使用者還可以使用PyTorch、TensorFlow等架構在spaCy建立自定義模型,以滿足特定需求。spaCy支援的語言模型見​​spaCy-models​​。

事實上,有一些自然語言處理開源庫,例如HuggingFace的​​Transformers​​​、​​PaddleNLP​​​和​​NLTK​​,相較于spaCy來說更為專業且性能更好。然而,對于簡單的應用而言,spaCy更為适合,因為它具有簡單易用、功能全面,同時也提供了大量面向多語言預訓練模型的優點。此外,随着以GPT-3為代表的語言大模型在自然語言處理領域取得了巨大的突破和成功,原本一些自然語言處理庫在精度上實際不如語言大模型。然而,使用語言大模型需要龐大的推理資源,而在對精度要求不高的場景中,使用spaCy這類小巧的自然語言處理庫依然是很合适的選擇。

1.2 spaCy安裝

spaCy采用采用子產品和語言子產品一起安裝的模式。spaCy的設計目标之一是子產品化和可定制性。它允許使用者僅安裝必需的子產品和語言資料,以減少安裝的整體大小和減輕資源負擔。如果使用spaCy的模型,需要通過pip安裝模型所需的模型包來使用預訓練模型。這是因為spaCy的模型包含了訓練後的權重參數和其他必要的檔案,這些檔案在安裝時被存儲在特定位置,而不是以單個檔案的形式存在。如果需要進行模型訓練和gpu運作則需要標明對應的安裝包。将子產品和語言子產品一起安裝,可以簡化spaCy的配置過程。使用者無需單獨下載下傳和配置語言資料,也不需要手動指定要使用的語言模型。這樣可以減少使用者的工作量和安裝過程中的潛在錯誤。但是可定制性就很弱了,是以spaCy适合精度要求不高的簡單使用,工程應用選擇其他大型自然語言處理庫更加合适。

為了實作這一目标,spaCy提供了配置化的安裝指令選擇頁面供使用者使用。安裝指令選擇頁面位址為​​spaCy-usage​​。下圖展示了本文的安裝配置項,本文采用了最簡單的cpu推理模式。

[自然語言處理] 自然語言處理庫spaCy使用指北

spaCy安裝完畢後,運作以下代碼即可判斷spaCy及相對應的語言模型是否安裝成功。

# jupyter notebook環境去除warning
import warnings
warnings.filterwarnings("ignore")
import spacy
spacy.__version__           
'3.6.0'           
import spacy

# 加載已安裝的中文模型
nlp = spacy.load('zh_core_web_sm')

# 執行一些簡單的NLP任務
doc = nlp("早上好!")
for token in doc:
    # token.text表示标記的原始文本,token.pos_表示标記的詞性(part-of-speech),token.dep_表示标記與其他标記之間的句法依存關系
    print(token.text, token.pos_, token.dep_)           
早上 NOUN nmod:tmod
好 VERB ROOT
! PUNCT punct           

2 spaCy快速入門

該部分内容和圖檔主要來自于​​spacy-101​​的總結。spaCy提供的主要函數子產品分為以下子產品,接下來分别對這些子產品進行介紹。

名稱 描述
Tokenization 将文本分割成單詞、标點符号等。
Part-of-speech (POS) Tagging 給标記配置設定詞性,如動詞或名詞。
Dependency Parsing 配置設定句法依存标簽,描述個别标記之間的關系,如主語或賓語。
Lemmatization 配置設定單詞的基本形式。例如,“was”的基本形式是“be”,“rats”的基本形式是“rat”。
Sentence Boundary Detection (SBD) 查找和分割單個句子。
Named Entity Recognition (NER) 對命名的“現實世界”對象進行标記,如人物、公司或地點。
Entity Linking (EL) 将文本實體與知識庫中的唯一辨別符進行消岐。
Similarity 比較單詞、文本片段和文檔之間的相似程度。
Text Classification 為整個文檔或文檔的部分配置設定類别或标簽。
Rule-based Matching 根據其文本和語言注釋查找标記序列,類似于正規表達式。
Training 更新和改進統計模型的預測能力。
Serialization 将對象儲存到檔案或位元組字元串中。

2.1 分詞

在處理過程中,spaCy首先對文本進行标記,即将其分段為單詞、标點符号等Token。這是通過應用每種語言特有的規則來實作的。Token表示自然語言文本的最小機關。每個Token都代表着文本中的一個原子元素,通常是單詞或标點符号。

import spacy

nlp = spacy.load("zh_core_web_sm")
# 使對文本進行一鍵處理
doc = nlp("南京長江大橋是金陵四十景之一!")
# 周遊doc中的每個Token
for token in doc:
    print(token.text)           
南京
長江
大橋
是
金陵
四十
景
之一
!           
import spacy

nlp = spacy.load("en_core_web_sm")
doc = nlp("Apple is looking at buying U.K. startup for $1 billion")
for token in doc:
    print(token.text)           
Apple
is
looking
at
buying
U.K.
startup
for
$
1
billion           

對于中文分詞有時會出現專有名詞被拆分,比如南京長江大橋被拆分為南京、長江、大橋。我們可以添加自定義詞典來解決該問題,但是要注意的是自定義詞典添加隻針對某些語言模型。

import spacy

nlp = spacy.load("zh_core_web_sm")
# 添加自定義詞彙
nlp.tokenizer.pkuseg_update_user_dict(["南京長江大橋","金陵四十景"])

doc = nlp("南京長江大橋是金陵四十景之一!")
for token in doc:
    print(token.text)           
南京長江大橋
是
金陵四十景
之一
!           

2.2 詞性标注與依存關系

spaCy在分詞後,會對句子中每個詞進行詞性标注以及确定句子中單詞之間的文法關系,要注意這些關系具體範圍取決于所使用的模型。示例代碼如下所示:

import spacy

nlp = spacy.load("en_core_web_sm")
doc = nlp("Apple is looking at buying U.K. startup for $1 billion")

# token.text: 單詞的原始形式。
# token.lemma_: 單詞的基本形式(或詞幹)。例如,“running”的詞幹是“run”。
# token.pos_: 單詞的粗粒度的詞性标注,如名詞、動詞、形容詞等。
# token.tag_: 單詞的細粒度的詞性标注,提供更多的文法資訊。
# token.dep_: 單詞在句子中的依存關系角色,例如主語、賓語等。
# token.shape_: 單詞的形狀資訊,例如,單詞的大小寫,是否有标點符号等。
# token.is_alpha: 這是一個布爾值,用于檢查token是否全部由字母組成。
# token.is_stop: 這是一個布爾值,用于檢查token是否為停用詞(如“the”、“is”等在英語中非常常見但通常不包含太多資訊的詞)。
for token in doc:
    print(token.text, token.lemma_, token.pos_, token.tag_, token.dep_,
            token.shape_, token.is_alpha, token.is_stop)           
Apple Apple PROPN NNP nsubj Xxxxx True False
is be AUX VBZ aux xx True True
looking look VERB VBG ROOT xxxx True False
at at ADP IN prep xx True True
buying buy VERB VBG pcomp xxxx True False
U.K. U.K. PROPN NNP dobj X.X. False False
startup startup NOUN NN advcl xxxx True False
for for ADP IN prep xxx True True
$ $ SYM $ quantmod $ False False
1 1 NUM CD compound d False False
billion billion NUM CD pobj xxxx True False           

在上述代碼中pos_所用是常見的單詞詞性标注。tag_所支援的詞性标注及解釋如下:

# 擷取所有詞性标注(tag_)和它們的描述
tag_descriptions = {tag: spacy.explain(tag) for tag in nlp.get_pipe('tagger').labels}
# 列印詞性标注(tag_)及其描述
# print("詞性标注 (TAG) 及其描述:")
# for tag, description in tag_descriptions.items():
#     print(f"{tag}: {description}")           

dep__所支援的依存關系及解釋如下:

# 擷取所有依存關系标注和它們的描述
par_descriptions = {par: spacy.explain(par) for par in nlp.get_pipe('parser').labels}
# print("依存關系及其描述:")
# for par, description in par_descriptions.items():
#     print(f"{par}: {description}")           

2.3 命名實體識别

命名實體識别(Named Entity Recognition, 簡稱NER)是自然語言進行中的一項基礎任務,應用範圍非常廣泛。 NER是指識别文本中具有特定意義或者指代性強的實體,通常包括人名、地名、機構名、日期時間、專有名詞等。spaCy使用如下:

import spacy

nlp = spacy.load("zh_core_web_sm")
# 添加自定義詞彙
nlp.tokenizer.pkuseg_update_user_dict(["東方明珠"])

# 自定義詞彙可能不會進入實體識别。
doc = nlp("東方明珠是一座位于中國上海市的标志性建築,建造于1991年,是一座高度為468米的電視塔。")
for ent in doc.ents:
    # 實體文本,開始位置,結束位置,實體标簽
    print(ent.text, ent.start_char, ent.end_char, ent.label_)           
中國上海市 9 14 GPE
1991年 24 29 DATE
468米 36 40 QUANTITY           

如果想知道所有的實體以及其對應的含義,可以執行以下代碼:

# 擷取命名實體标簽及其含義
entity_labels = nlp.get_pipe('ner').labels

# 列印輸出所有命名實體及其含義
for label in entity_labels:
    print("{}: {}".format(label,spacy.explain(label)))           
CARDINAL: Numerals that do not fall under another type
DATE: Absolute or relative dates or periods
EVENT: Named hurricanes, battles, wars, sports events, etc.
FAC: Buildings, airports, highways, bridges, etc.
GPE: Countries, cities, states
LANGUAGE: Any named language
LAW: Named documents made into laws.
LOC: Non-GPE locations, mountain ranges, bodies of water
MONEY: Monetary values, including unit
NORP: Nationalities or religious or political groups
ORDINAL: "first", "second", etc.
ORG: Companies, agencies, institutions, etc.
PERCENT: Percentage, including "%"
PERSON: People, including fictional
PRODUCT: Objects, vehicles, foods, etc. (not services)
QUANTITY: Measurements, as of weight or distance
TIME: Times smaller than a day
WORK_OF_ART: Titles of books, songs, etc.           

2.4 詞向量與相似性

詞向量是自然語言進行中一種重要的表示方式,它将單詞映射為實數向量。這種表示方式能夠捕捉單詞之間的語義關系,并将語義資訊轉化為計算機能夠處理的數值形式。

傳統的自然語言處理方法往往将文本表示為離散的符号,例如獨熱編碼或者詞袋模型。然而,這種方法忽略了單詞之間的語義相似性,而且次元過高,造成稀疏性問題。相比之下,詞向量通過将每個單詞映射到連續的向量空間中,可以更好地捕捉單詞之間的語義關系,并且降低了特征空間的次元,使得文本處理更加高效。通過計算兩個詞向量之間的距離或夾角可以衡量詞向量的相似性。

提取句子中每一個詞的詞向量代碼如下:

import spacy

# 加載中文模型"zh_core_web_sm"
nlp = spacy.load("zh_core_web_sm")

# 對給定文本進行分詞和詞性标注
tokens = nlp("東方明珠是一座位于中國上海市的标志性建築!")

# 周遊分詞後的每個詞語
for token in tokens:
    # 輸出詞語的文本内容、是否有對應的向量表示、向量範數和是否為未登入詞(Out-of-vocabulary,即不在詞向量詞典中的詞)
    print(token.text, token.has_vector, token.vector_norm, token.is_oov)           
東方 True 11.572288 True
明珠 True 10.620552 True
是 True 12.337883 True
一 True 12.998204 True
座位 True 10.186406 True
于 True 13.540245 True
中國 True 12.459145 True
上海市 True 12.004954 True
的 True 12.90457 True
标志性 True 13.601862 True
建築 True 10.46621 True
! True 12.811246 True           

如果想得到某個句子或者某個詞的詞向量,代碼如下:

# 該詞向量沒有歸一化
tokens.vector.shape           
(96,)           

spaCy提供了similarity函數以計算兩個文本向量的相似度。 示例代碼如下:

import spacy

# 為了友善這裡使用小模型,推薦使用更大的模型
nlp = spacy.load("zh_core_web_sm")  
doc1 = nlp("東方明珠是一座位于中國上海市的标志性建築")
doc2 = nlp("南京長江大橋是金陵四十景之一!")

# 計算兩個文本的相似度
print(doc1, "<->", doc2, doc1.similarity(doc2))           
東方明珠是一座位于中國上海市的标志性建築 <-> 南京長江大橋是金陵四十景之一! 0.5743045135827821           

在上面代碼中相似度計算方式預設使用餘弦相似度。餘弦相似度範圍為0到1,數值越高表明詞向量越相似。一般來說詞向量相似度使用spacy通用模型準确度可能很低,可以嘗試使用專用模型或者自行訓練模型,spacy推薦使用​​sense2vec​​來計算模型相似度。

此外如果僅僅使用spacy提取文本向量,可以用numpy手動計算文本相似度,代碼如下:

import numpy as np
import spacy
nlp = spacy.load("zh_core_web_sm")  
doc1 = nlp("東方明珠是一座位于中國上海市的标志性建築")
doc2 = nlp("南京長江大橋是金陵四十景之一!")
# 擷取doc1和doc2的詞向量
vec1 = doc1.vector
vec2 = doc2.vector

# 使用NumPy計算相似度得分,np.linalg.norm(vec1)就是doc1.vector_norm
similarity_score = np.dot(vec1, vec2) / (np.linalg.norm(vec1) * np.linalg.norm(vec2))

print(doc1, "<->", doc2,similarity_score)           
東方明珠是一座位于中國上海市的标志性建築 <-> 南京長江大橋是金陵四十景之一! 0.5743046           

3 spaCy結構體系

3.1 spaCy處理流程

當在一個文本上調用nlp模型時,spaCy首先對文本進行分詞處理,生成一個Doc對象。接着,Doc對象将在幾個不同的步驟中進行處理。訓練好的處理流程通常包括詞性标注器、依存句法解析器和實體識别器等處理元件。這些元件互相獨立,每個處理流程元件都會傳回處理後的Doc對象,然後将其傳遞給下一個元件。 最終生成的Doc對象是一個包含了所有單詞和标點符号的序列,每個單詞被表示為Token對象。每個Token對象包含了單詞本身的内容、詞性标注、詞形還原後的形式等資訊。以下圖檔解釋了使用spaCy進行文本處理的過程。

[自然語言處理] 自然語言處理庫spaCy使用指北

如上圖所示,模型管道中所涉及到的子產品主要取決于該模型的結構和訓練方式。其中分詞tokenizer是一個特殊的元件且獨立于其他元件之外,這是因為其他元件子產品在調用前都會先調用tokenizer以對字元串進行分詞。所有支援主要的子產品如下,這些子產品的使用已在前一章進行介紹。

名稱 元件 建立 描述
tokenizer Tokenizer Doc 将文本分割為标記。
tagger Tagger Token.tag 為标記配置設定詞性标簽。
parser DependencyParser Token.head,Token.dep,Doc.sents,Doc.noun_chunks 配置設定依賴關系标簽。
ner EntityRecognizer Doc.ents,Token.ent_iob,Token.ent_type 檢測和标記命名實體。
lemmatizer Lemmatizer Token.lemma 配置設定單詞的基本形式。
textcat TextCategorizer Doc.cats 配置設定文檔标簽。
custom 自定義元件 Doc..xxx,Token..xxx,Span._.xxx 配置設定自定義屬性、方法或屬性。

一個spacy的模型所支援的文本處理元件檢視方式如下:

import spacy

# 加載中文模型"zh_core_web_sm"
nlp = spacy.load("zh_core_web_sm")
# 檢視所支援的元件
nlp.pipe_names           
['tok2vec', 'tagger', 'parser', 'attribute_ruler', 'ner']           

基于以下代碼可以控制元件的選擇和使用,以加快執行速度:

# 加載不包含命名實體識别器(NER)的管道
nlp = spacy.load("zh_core_web_sm", exclude=["ner"])
# 檢視所支援的元件
nlp.pipe_names           
['tok2vec', 'tagger', 'parser', 'attribute_ruler']           
# 隻啟用tagger管道
nlp = spacy.load("zh_core_web_sm",enable=[ "tagger"])
nlp.pipe_names           
['tagger']           
# 加載詞性标注器(tagger)和依存句法解析器(parser),但不啟用它們
nlp = spacy.load("zh_core_web_sm", disable=["tagger", "parser"],)
# 禁用某些元件
nlp.disable_pipe("ner")
nlp.pipe_names           
['tok2vec', 'attribute_ruler']           

3.2 spaCy工程結構

spaCy中的中心資料結構是Language類、Vocab和Doc對象。Language類用于處理文本并将其轉換為Doc對象。它通常存儲為一個名為nlp的變量。Doc對象擁有令牌序列及其所有注釋。通過在Vocab中集中字元串、詞向量和詞法屬性。這些主要類和對象的介紹如下所示:

[自然語言處理] 自然語言處理庫spaCy使用指北

常用子產品的介紹如下:

Doc

Doc是spaCy中一個重要的對象,它代表了一個文本文檔,并包含了文本中的所有資訊,如單詞、标點、詞性、依賴關系等。可以通過spaCy的Language對象對文本進行處理,得到一個Doc對象。

DocBin

DocBin 是用于高效序列化和反序列化Doc對象的資料結構,以在不同的過程中儲存和加載Doc對象。使用代碼如下:

# 導入所需的庫
import spacy
from spacy.tokens import DocBin

# 加載英文預訓練模型
nlp = spacy.load("en_core_web_sm")

# 定義待處理文本
texts = ["This is sentence 1.", "And this is sentence 2."]

# 将每個文本轉化為Doc對象,用nlp處理并儲存到docs清單中
docs = [nlp(text) for text in texts]

# 建立一個新的DocBin對象,用于儲存文檔資料,并啟用存儲使用者資料的功能
docbin = DocBin(store_user_data=True)

# 将每個Doc對象添加到DocBin中
for doc in docs:
    docbin.add(doc)

# 将DocBin儲存到檔案中
with open("documents.spacy", "wb") as f:
    f.write(docbin.to_bytes())

# 從檔案中加載DocBin
with open("documents.spacy", "rb") as f:
    bytes_data = f.read()

# 從位元組資料中恢複加載DocBin對象
loaded_docbin = DocBin().from_bytes(bytes_data)

# 使用nlp.vocab擷取詞彙表,并通過DocBin擷取所有加載的文檔
loaded_docs = list(loaded_docbin.get_docs(nlp.vocab))

# 輸出加載的文檔
loaded_docs           
[This is sentence 1., And this is sentence 2.]           

Example

Example用于訓練spaCy模型,它包含了一個輸入文本(Doc)和其對應的标注資料。關于spaCy模型的訓練,見:​​spaCy-training​​

Language

Language是spaCy的核心對象之一,它負責處理文本的預處理、詞性标注、句法分析等任務。可以通過spacy.load()來加載一個具體的語言模型,擷取對應的Language對象。

Lexeme

Lexeme 是一個單詞在詞彙表中的表示,它包含了關于該單詞的各種資訊,如詞性、詞頻等。示例代碼如下:

nlp = spacy.load("en_core_web_sm")

# 定義一個單詞
word = "hello"

# 擷取單詞對應的詞元(lexeme)
lexeme = nlp.vocab[word]

# 列印詞元的文本内容、是否為字母(alphabetical)
# 是否為停用詞(stopword)\是否為字母(is_alpha),是否為數字(is_digit),是否為标題(is_title),語言(lang_)
print(lexeme.text, lexeme.is_alpha, lexeme.is_stop, lexeme.is_alpha, lexeme.is_digit, lexeme.is_title, lexeme.lang_)           
hello True False True False False en           

事實上對于Lexeme,隻要可能,spaCy就會嘗試将資料存儲在一個詞彙表Vocab中,該詞彙表将由多個模型共享。為了節省記憶體,spaCy還将所有字元串編碼為哈希值。

[自然語言處理] 自然語言處理庫spaCy使用指北

如下所示,不同模型下“coffee”的哈希值為3197928453018144401。但是注意的是隻是spaCy這樣做,其他自然語言處理庫不一定這樣做。

import spacy

nlp = spacy.load("zh_core_web_sm")
doc = nlp("I love coffee")
print(doc.vocab.strings["coffee"])  # 3197928453018144401
print(doc.vocab.strings[3197928453018144401])  # 'coffee'           
3197928453018144401
coffee           
import spacy

nlp = spacy.load("en_core_web_sm")
doc = nlp("I love coffee")
print(doc.vocab.strings["coffee"])  # 3197928453018144401
print(doc.vocab.strings[3197928453018144401])  # 'coffee'           
3197928453018144401
coffee           

Span

Span 是一個連續的文本片段,可以由一個或多個Token組成。它通常用于标記實體或短語。

nlp = spacy.load("zh_core_web_sm")
text = "東方明珠是一座位于中國上海市的标志性建築!"
doc = nlp(text)

# 從doc中選擇了第0個和第1個詞元(token)組成的片段。
# 注意,spaCy中的詞元是文本的基本單元,可能是單詞、标點符号或其它詞彙機關。
# 這裡東方、明珠是前兩個詞
span = doc[0:2]  
print(span.text)           
東方明珠           

4 參考

  • ​​spaCy-github​​
  • ​​spaCy​​
  • ​​spaCy-models​​
  • ​​Transformers​​
  • ​​PaddleNLP​​
  • ​​NLTK​​
  • ​​spaCy-usage​​
  • ​​sense2vec​​
  • ​​spaCy-training​​

繼續閱讀