1 背景介紹與spaCy安裝
1.1 自然語言處理簡介
自然語言處理(Natural Language Processing,簡稱NLP)是一門研究人類語言與計(jì)算機(jī)之間交互的領(lǐng)域,旨在使計(jì)算機(jī)能夠理解、解析、生成和處理人類語言。NLP結(jié)合了計(jì)算機(jī)科學(xué)、人工智能和語言學(xué)的知識,通過各種算法和技術(shù)來處理和分析文本數(shù)據(jù)。近年來,隨著深度學(xué)習(xí)技術(shù)的發(fā)展,神經(jīng)網(wǎng)絡(luò)模型在自然語言處理(NLP)領(lǐng)域取得了重大的突破。其中,循環(huán)神經(jīng)網(wǎng)絡(luò)(RNN)、長短時(shí)記憶網(wǎng)絡(luò)(LSTM)和Transformer等模型都發(fā)揮了關(guān)鍵作用。這些模型為NLP任務(wù)帶來了更好的性能和效果,推動了NLP的發(fā)展和應(yīng)用。
NLP主要知識結(jié)構(gòu)如下圖所示,圖片來自網(wǎng)絡(luò)。
NLP的應(yīng)用非常廣泛,涵蓋了多個(gè)領(lǐng)域,如機(jī)器翻譯、信息提取、文本分類、情感分析、自動摘要、問答系統(tǒng)、語音識別和語音合成等。以下是NLP常用的技術(shù)和方法:
分詞:將連續(xù)的文本分割成有意義的詞語或標(biāo)記,是許多NLP任務(wù)的基礎(chǔ)。
詞性標(biāo)注:為文本中的每個(gè)詞語賦予其相應(yīng)的詞性,如名詞、動詞、形容詞等。
句法分析:分析句子的語法結(jié)構(gòu),識別出句子中的短語、修飾語和依存關(guān)系等。
語義分析:理解文本的意義和語義關(guān)系,包括命名實(shí)體識別、語義角色標(biāo)注和語義解析等。
機(jī)器翻譯:將一種語言的文本自動翻譯成另一種語言。
文本分類:將文本按照預(yù)定義的類別進(jìn)行分類,如垃圾郵件分類、情感分類等。
信息提?。簭慕Y(jié)構(gòu)化和非結(jié)構(gòu)化文本中提取出特定的信息,如實(shí)體關(guān)系抽取、事件抽取等。
問答系統(tǒng):通過對用戶提出的問題進(jìn)行理解和回答,提供準(zhǔn)確的答案或相關(guān)信息。
情感分析:識別和分析文本中的情感傾向,如正面、負(fù)面或中性情感。
文本生成:使用NLP技術(shù)生成自然語言文本,如自動摘要生成、對話系統(tǒng)和機(jī)器作文等。
在眾多自然語言處理庫中,spaCy庫提供了超過73種語言的支持,并為25種語言提供了訓(xùn)練代碼。該庫提供了一系列簡單易用的模型和函數(shù)接口,包括分詞、詞性標(biāo)注等功能。用戶還可以使用PyTorch、TensorFlow等框架在spaCy創(chuàng)建自定義模型,以滿足特定需求。spaCy支持的語言模型見spaCy-models。
事實(shí)上,有一些自然語言處理開源庫,例如HuggingFace的Transformers、PaddleNLP和NLTK,相較于spaCy來說更為專業(yè)且性能更好。然而,對于簡單的應(yīng)用而言,spaCy更為適合,因?yàn)樗哂泻唵我子?、功能全面,同時(shí)也提供了大量面向多語言預(yù)訓(xùn)練模型的優(yōu)點(diǎn)。此外,隨著以GPT-3為代表的語言大模型在自然語言處理領(lǐng)域取得了巨大的突破和成功,原本一些自然語言處理庫在精度上實(shí)際不如語言大模型。然而,使用語言大模型需要龐大的推理資源,而在對精度要求不高的場景中,使用spaCy這類小巧的自然語言處理庫依然是很合適的選擇。
1.2 spaCy安裝
spaCy采用采用模塊和語言模塊一起安裝的模式。spaCy的設(shè)計(jì)目標(biāo)之一是模塊化和可定制性。它允許用戶僅安裝必需的模塊和語言數(shù)據(jù),以減少安裝的整體大小和減輕資源負(fù)擔(dān)。如果使用spaCy的模型,需要通過pip安裝模型所需的模型包來使用預(yù)訓(xùn)練模型。這是因?yàn)閟paCy的模型包含了訓(xùn)練后的權(quán)重參數(shù)和其他必要的文件,這些文件在安裝時(shí)被存儲在特定位置,而不是以單個(gè)文件的形式存在。如果需要進(jìn)行模型訓(xùn)練和gpu運(yùn)行則需要選定對應(yīng)的安裝包。將模塊和語言模塊一起安裝,可以簡化spaCy的配置過程。用戶無需單獨(dú)下載和配置語言數(shù)據(jù),也不需要手動指定要使用的語言模型。這樣可以減少用戶的工作量和安裝過程中的潛在錯(cuò)誤。但是可定制性就很弱了,所以spaCy適合精度要求不高的簡單使用,工程應(yīng)用選擇其他大型自然語言處理庫更加合適。
為了實(shí)現(xiàn)這一目標(biāo),spaCy提供了配置化的安裝指令選擇頁面供用戶使用。安裝指令選擇頁面地址為spaCy-usage。下圖展示了本文的安裝配置項(xiàng),本文采用了最簡單的cpu推理模式。
spaCy安裝完畢后,運(yùn)行以下代碼即可判斷spaCy及相對應(yīng)的語言模型是否安裝成功。
# jupyter notebook環(huán)境去除warning
import warnings
warnings.filterwarnings("ignore")
import spacy
spacy.__version__
'3.6.0'
import spacy
# 加載已安裝的中文模型
nlp = spacy.load('zh_core_web_sm')
# 執(zhí)行一些簡單的NLP任務(wù)
doc = nlp("早上好!")
for token in doc:
# token.text表示標(biāo)記的原始文本,token.pos_表示標(biāo)記的詞性(part-of-speech),token.dep_表示標(biāo)記與其他標(biāo)記之間的句法依存關(guān)系
print(token.text, token.pos_, token.dep_)
早上 NOUN nmod:tmod
好 VERB ROOT
! PUNCT punct
2 spaCy快速入門
該部分內(nèi)容和圖片主要來自于spacy-101的總結(jié)。spaCy提供的主要函數(shù)模塊分為以下模塊,接下來分別對這些模塊進(jìn)行介紹。
2.1 分詞
在處理過程中,spaCy首先對文本進(jìn)行標(biāo)記,即將其分段為單詞、標(biāo)點(diǎn)符號等Token。這是通過應(yīng)用每種語言特有的規(guī)則來實(shí)現(xiàn)的。Token表示自然語言文本的最小單位。每個(gè)Token都代表著文本中的一個(gè)原子元素,通常是單詞或標(biāo)點(diǎn)符號。
import spacy
nlp = spacy.load("zh_core_web_sm")
# 使對文本進(jìn)行一鍵處理
doc = nlp("南京長江大橋是金陵四十景之一!")
# 遍歷doc中的每個(gè)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
對于中文分詞有時(shí)會出現(xiàn)專有名詞被拆分,比如南京長江大橋被拆分為南京、長江、大橋。我們可以添加自定義詞典來解決該問題,但是要注意的是自定義詞典添加只針對某些語言模型。
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 詞性標(biāo)注與依存關(guān)系
spaCy在分詞后,會對句子中每個(gè)詞進(jìn)行詞性標(biāo)注以及確定句子中單詞之間的語法關(guān)系,要注意這些關(guān)系具體范圍取決于所使用的模型。示例代碼如下所示:
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_: 單詞的粗粒度的詞性標(biāo)注,如名詞、動詞、形容詞等。
# token.tag_: 單詞的細(xì)粒度的詞性標(biāo)注,提供更多的語法信息。
# token.dep_: 單詞在句子中的依存關(guān)系角色,例如主語、賓語等。
# token.shape_: 單詞的形狀信息,例如,單詞的大小寫,是否有標(biāo)點(diǎn)符號等。
# token.is_alpha: 這是一個(gè)布爾值,用于檢查token是否全部由字母組成。
# token.is_stop: 這是一個(gè)布爾值,用于檢查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_所用是常見的單詞詞性標(biāo)注。tag_所支持的詞性標(biāo)注及解釋如下:
# 獲取所有詞性標(biāo)注(tag_)和它們的描述
tag_descriptions = {tag: spacy.explain(tag) for tag in nlp.get_pipe('tagger').labels}
# 打印詞性標(biāo)注(tag_)及其描述
# print("詞性標(biāo)注 (TAG) 及其描述:")
# for tag, description in tag_descriptions.items():
# print(f"{tag}: {description}")
dep__所支持的依存關(guān)系及解釋如下:
# 獲取所有依存關(guān)系標(biāo)注和它們的描述
par_descriptions = {par: spacy.explain(par) for par in nlp.get_pipe('parser').labels}
# print("依存關(guān)系及其描述:")
# for par, description in par_descriptions.items():
# print(f"{par}: {description}")
2.3 命名實(shí)體識別
命名實(shí)體識別(Named Entity Recognition, 簡稱NER)是自然語言處理中的一項(xiàng)基礎(chǔ)任務(wù),應(yīng)用范圍非常廣泛。 NER是指識別文本中具有特定意義或者指代性強(qiáng)的實(shí)體,通常包括人名、地名、機(jī)構(gòu)名、日期時(shí)間、專有名詞等。spaCy使用如下:
import spacy
nlp = spacy.load("zh_core_web_sm")
# 添加自定義詞匯
nlp.tokenizer.pkuseg_update_user_dict(["東方明珠"])
# 自定義詞匯可能不會進(jìn)入實(shí)體識別。
doc = nlp("東方明珠是一座位于中國上海市的標(biāo)志性建筑,建造于1991年,是一座高度為468米的電視塔。")
for ent in doc.ents:
# 實(shí)體文本,開始位置,結(jié)束位置,實(shí)體標(biāo)簽
print(ent.text, ent.start_char, ent.end_char, ent.label_)
中國上海市 9 14 GPE
1991年 24 29 DATE
468米 36 40 QUANTITY
如果想知道所有的實(shí)體以及其對應(yīng)的含義,可以執(zhí)行以下代碼:
# 獲取命名實(shí)體標(biāo)簽及其含義
entity_labels = nlp.get_pipe('ner').labels
# 打印輸出所有命名實(shí)體及其含義
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 詞向量與相似性
詞向量是自然語言處理中一種重要的表示方式,它將單詞映射為實(shí)數(shù)向量。這種表示方式能夠捕捉單詞之間的語義關(guān)系,并將語義信息轉(zhuǎn)化為計(jì)算機(jī)能夠處理的數(shù)值形式。
傳統(tǒng)的自然語言處理方法往往將文本表示為離散的符號,例如獨(dú)熱編碼或者詞袋模型。然而,這種方法忽略了單詞之間的語義相似性,而且維度過高,造成稀疏性問題。相比之下,詞向量通過將每個(gè)單詞映射到連續(xù)的向量空間中,可以更好地捕捉單詞之間的語義關(guān)系,并且降低了特征空間的維度,使得文本處理更加高效。通過計(jì)算兩個(gè)詞向量之間的距離或夾角可以衡量詞向量的相似性。
提取句子中每一個(gè)詞的詞向量代碼如下:
import spacy
# 加載中文模型"zh_core_web_sm"
nlp = spacy.load("zh_core_web_sm")
# 對給定文本進(jìn)行分詞和詞性標(biāo)注
tokens = nlp("東方明珠是一座位于中國上海市的標(biāo)志性建筑!")
# 遍歷分詞后的每個(gè)詞語
for token in tokens:
# 輸出詞語的文本內(nèi)容、是否有對應(yīng)的向量表示、向量范數(shù)和是否為未登錄詞(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
標(biāo)志性 True 13.601862 True
建筑 True 10.46621 True
! True 12.811246 True
如果想得到某個(gè)句子或者某個(gè)詞的詞向量,代碼如下:
# 該詞向量沒有歸一化
tokens.vector.shape
(96,)
spaCy提供了similarity函數(shù)以計(jì)算兩個(gè)文本向量的相似度。
示例代碼如下:
import spacy
# 為了方便這里使用小模型,推薦使用更大的模型
nlp = spacy.load("zh_core_web_sm")
doc1 = nlp("東方明珠是一座位于中國上海市的標(biāo)志性建筑")
doc2 = nlp("南京長江大橋是金陵四十景之一!")
# 計(jì)算兩個(gè)文本的相似度
print(doc1, "<->", doc2, doc1.similarity(doc2))
東方明珠是一座位于中國上海市的標(biāo)志性建筑 <-> 南京長江大橋是金陵四十景之一! 0.5743045135827821
在上面代碼中相似度計(jì)算方式默認(rèn)使用余弦相似度。余弦相似度范圍為0到1,數(shù)值越高表明詞向量越相似。一般來說詞向量相似度使用spacy通用模型準(zhǔn)確度可能很低,可以嘗試使用專用模型或者自行訓(xùn)練模型,spacy推薦使用sense2vec來計(jì)算模型相似度。
此外如果僅僅使用spacy提取文本向量,可以用numpy手動計(jì)算文本相似度,代碼如下:
import numpy as np
import spacy
nlp = spacy.load("zh_core_web_sm")
doc1 = nlp("東方明珠是一座位于中國上海市的標(biāo)志性建筑")
doc2 = nlp("南京長江大橋是金陵四十景之一!")
# 獲取doc1和doc2的詞向量
vec1 = doc1.vector
vec2 = doc2.vector
# 使用NumPy計(jì)算相似度得分,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)
東方明珠是一座位于中國上海市的標(biāo)志性建筑 <-> 南京長江大橋是金陵四十景之一! 0.5743046
3 spaCy結(jié)構(gòu)體系
3.1 spaCy處理流程
當(dāng)在一個(gè)文本上調(diào)用nlp模型時(shí),spaCy首先對文本進(jìn)行分詞處理,生成一個(gè)Doc對象。接著,Doc對象將在幾個(gè)不同的步驟中進(jìn)行處理。訓(xùn)練好的處理流程通常包括詞性標(biāo)注器、依存句法解析器和實(shí)體識別器等處理組件。這些組件相互獨(dú)立,每個(gè)處理流程組件都會返回處理后的Doc對象,然后將其傳遞給下一個(gè)組件。
最終生成的Doc對象是一個(gè)包含了所有單詞和標(biāo)點(diǎn)符號的序列,每個(gè)單詞被表示為Token對象。每個(gè)Token對象包含了單詞本身的內(nèi)容、詞性標(biāo)注、詞形還原后的形式等信息。以下圖片解釋了使用spaCy進(jìn)行文本處理的過程。
如上圖所示,模型管道中所涉及到的模塊主要取決于該模型的結(jié)構(gòu)和訓(xùn)練方式。其中分詞tokenizer是一個(gè)特殊的組件且獨(dú)立于其他組件之外,這是因?yàn)槠渌M件模塊在調(diào)用前都會先調(diào)用tokenizer以對字符串進(jìn)行分詞。所有支持主要的模塊如下,這些模塊的使用已在前一章進(jìn)行介紹。
一個(gè)spacy的模型所支持的文本處理組件查看方式如下:
import spacy
# 加載中文模型"zh_core_web_sm"
nlp = spacy.load("zh_core_web_sm")
# 查看所支持的組件
nlp.pipe_names
['tok2vec', 'tagger', 'parser', 'attribute_ruler', 'ner']
基于以下代碼可以控制組件的選擇和使用,以加快執(zhí)行速度:
# 加載不包含命名實(shí)體識別器(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']
# 加載詞性標(biāo)注器(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工程結(jié)構(gòu)
spaCy中的中心數(shù)據(jù)結(jié)構(gòu)是Language類、Vocab和Doc對象。Language類用于處理文本并將其轉(zhuǎn)換為Doc對象。它通常存儲為一個(gè)名為nlp的變量。Doc對象擁有令牌序列及其所有注釋。通過在Vocab中集中字符串、詞向量和詞法屬性。這些主要類和對象的介紹如下所示:
常用模塊的介紹如下:
Doc
Doc是spaCy中一個(gè)重要的對象,它代表了一個(gè)文本文檔,并包含了文本中的所有信息,如單詞、標(biāo)點(diǎn)、詞性、依賴關(guān)系等。可以通過spaCy的Language對象對文本進(jìn)行處理,得到一個(gè)Doc對象。
DocBin
DocBin 是用于高效序列化和反序列化Doc對象的數(shù)據(jù)結(jié)構(gòu),以在不同的過程中保存和加載Doc對象。使用代碼如下:
# 導(dǎo)入所需的庫
import spacy
from spacy.tokens import DocBin
# 加載英文預(yù)訓(xùn)練模型
nlp = spacy.load("en_core_web_sm")
# 定義待處理文本
texts = ["This is sentence 1.", "And this is sentence 2."]
# 將每個(gè)文本轉(zhuǎn)化為Doc對象,用nlp處理并保存到docs列表中
docs = [nlp(text) for text in texts]
# 創(chuàng)建一個(gè)新的DocBin對象,用于保存文檔數(shù)據(jù),并啟用存儲用戶數(shù)據(jù)的功能
docbin = DocBin(store_user_data=True)
# 將每個(gè)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()
# 從字節(jié)數(shù)據(jù)中恢復(fù)加載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用于訓(xùn)練spaCy模型,它包含了一個(gè)輸入文本(Doc)和其對應(yīng)的標(biāo)注數(shù)據(jù)。關(guān)于spaCy模型的訓(xùn)練,見:spaCy-training
Language
Language是spaCy的核心對象之一,它負(fù)責(zé)處理文本的預(yù)處理、詞性標(biāo)注、句法分析等任務(wù)??梢酝ㄟ^spacy.load()來加載一個(gè)具體的語言模型,獲取對應(yīng)的Language對象。
Lexeme
Lexeme 是一個(gè)單詞在詞匯表中的表示,它包含了關(guān)于該單詞的各種信息,如詞性、詞頻等。示例代碼如下
nlp = spacy.load("en_core_web_sm")
# 定義一個(gè)單詞
word = "hello"
# 獲取單詞對應(yīng)的詞元(lexeme)
lexeme = nlp.vocab[word]
# 打印詞元的文本內(nèi)容、是否為字母(alphabetical)
# 是否為停用詞(stopword)\是否為字母(is_alpha),是否為數(shù)字(is_digit),是否為標(biāo)題(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
事實(shí)上對于Lexeme,只要可能,spaCy就會嘗試將數(shù)據(jù)存儲在一個(gè)詞匯表Vocab中,該詞匯表將由多個(gè)模型共享。為了節(jié)省內(nèi)存,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 是一個(gè)連續(xù)的文本片段,可以由一個(gè)或多個(gè)Token組成。它通常用于標(biāo)記實(shí)體或短語。
nlp = spacy.load("zh_core_web_sm")
text = "東方明珠是一座位于中國上海市的標(biāo)志性建筑!"
doc = nlp(text)
# 從doc中選擇了第0個(gè)和第1個(gè)詞元(token)組成的片段。
# 注意,spaCy中的詞元是文本的基本單元,可能是單詞、標(biāo)點(diǎn)符號或其它詞匯單位。
# 這里東方、明珠是前兩個(gè)詞
span = doc[0:2]
print(span.text)
東方明珠
4 參考