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

2023-07-27 15:02:37

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

1 背景介紹與spaCy安裝

1.1 自然語言處理簡介

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

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

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

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

在眾多自然語言處理庫中,spaCy庫提供了超過73種語言的支援,併為25種語言提供了訓練程式碼。該庫提供了一系列簡單易用的模型和函數介面,包括分詞、詞性標註等功能。使用者還可以使用PyTorch、TensorFlow等框架在spaCy建立自定義模型,以滿足特定需求。spaCy支援的語言模型見spaCy-models

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

1.2 spaCy安裝

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

為了實現這一目標,spaCy提供了設定化的安裝指令選擇頁面供使用者使用。安裝指令選擇頁面地址為spaCy-usage。下圖展示了本文的安裝設定項,本文采用了最簡單的cpu推理模式。

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進行文書處理的過程。

如上圖所示,模型管道中所涉及到的模組主要取決於該模型的結構和訓練方式。其中分詞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中集中字串、詞向量和詞法屬性。這些主要類和物件的介紹如下所示:

常用模組的介紹如下:

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還將所有字串編碼為雜湊值。

如下所示,不同模型下「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 參考