本文簡要介紹Python自然語言處理(NLP),使用Python的NLTK庫。NLTK是Python的自然語言處理工具包,在NLP領域中,最常使用的一個Python庫。
什麼是NLP?
簡單來說,自然語言處理(NLP)就是開發能夠理解人類語言的應用程式或服務。
這裡討論一些自然語言處理(NLP)的實際應用例子,如語音識別、語音翻譯、理解完整的句子、理解匹配詞的同義詞,以及生成語法正確完整句子和段落。
這並不是NLP能做的所有事情。
NLP實現
搜尋引擎: 比如谷歌,Yahoo等。谷歌搜尋引擎知道你是一個技術人員,所以它顯示與技術相關的結果;
社交網站推送:比如Facebook News Feed。如果News Feed演算法知道你的興趣是自然語言處理,就會顯示相關的廣告和帖子。
語音引擎:比如Apple的Siri。
垃圾郵件過濾:如谷歌垃圾郵件過濾器。和普通垃圾郵件過濾不同,它通過了解郵件內容裡面的的深層意義,來判斷是不是垃圾郵件。
NLP庫
下面是一些開源的自然語言處理庫(NLP):
- Natural language toolkit (NLTK);
- Apache OpenNLP;
- Stanford NLP suite;
- Gate NLP library
其中自然語言工具包(NLTK)是最受歡迎的自然語言處理庫(NLP),它是用Python編寫的,而且背後有非常強大的社群支援。
NLTK也很容易上手,實際上,它是最簡單的自然語言處理(NLP)庫。
在這個NLP教程中,我們將使用Python NLTK庫。
安裝 NLTK
如果您使用的是Windows/Linux/Mac,您可以使用pip安裝NLTK:
1 |
pip install nltk |
開啟python終端匯入NLTK檢查NLTK是否正確安裝:
1 |
import nltk |
如果一切順利,這意味著您已經成功地安裝了NLTK庫。首次安裝了NLTK,需要通過執行以下程式碼來安裝NLTK擴充套件包:
1 2 3 |
import nltk nltk.download() |
這將彈出NLTK 下載視窗來選擇需要安裝哪些包:
您可以安裝所有的包,因為它們的大小都很小,所以沒有什麼問題。
使用Python Tokenize文字
首先,我們將抓取一個web頁面內容,然後分析文字瞭解頁面的內容。
我們將使用urllib模組來抓取web頁面:
1 2 3 4 5 |
import urllib.request response = urllib.request.urlopen('http://php.net/') html = response.read() print (html) |
從列印結果中可以看到,結果包含許多需要清理的HTML標籤。
然後BeautifulSoup模組來清洗這樣的文字:
1 2 3 4 5 6 7 8 9 |
from bs4 import BeautifulSoup import urllib.request response = urllib.request.urlopen('http://php.net/') html = response.read() soup = BeautifulSoup(html,"html5lib") # 這需要安裝html5lib模組 text = soup.get_text(strip=True) print (text) |
現在我們從抓取的網頁中得到了一個乾淨的文字。
下一步,將文字轉換為tokens,像這樣:
1 2 3 4 5 6 7 8 9 |
from bs4 import BeautifulSoup import urllib.request response = urllib.request.urlopen('http://php.net/') html = response.read() soup = BeautifulSoup(html,"html5lib") text = soup.get_text(strip=True) tokens = text.split() print (tokens) |
統計詞頻
text已經處理完畢了,現在使用Python NLTK統計token的頻率分佈。
可以通過呼叫NLTK中的FreqDist()
方法實現:
1 2 3 4 5 6 7 8 9 10 11 12 |
from bs4 import BeautifulSoup import urllib.request import nltk response = urllib.request.urlopen('http://php.net/') html = response.read() soup = BeautifulSoup(html,"html5lib") text = soup.get_text(strip=True) tokens = text.split() freq = nltk.FreqDist(tokens) for key,val in freq.items(): print (str(key) + ':' + str(val)) |
如果搜尋輸出結果,可以發現最常見的token是PHP。
您可以呼叫plot
函式做出頻率分佈圖:
1 2 |
freq.plot(20, cumulative=False) # 需要安裝matplotlib庫 |
這上面這些單詞。比如of
,a
,an
等等,這些詞都屬於停用詞。
一般來說,停用詞應該刪除,防止它們影響分析結果。
處理停用詞
NLTK自帶了許多種語言的停用詞列表,如果你獲取英文停用詞:
1 2 3 |
from nltk.corpus import stopwords stopwords.words('english') |
現在,修改下程式碼,在繪圖之前清除一些無效的token:
1 2 3 4 5 |
clean_tokens = list() sr = stopwords.words('english') for token in tokens: if token not in sr: clean_tokens.append(token) |
最終的程式碼應該是這樣的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
from bs4 import BeautifulSoup import urllib.request import nltk from nltk.corpus import stopwords response = urllib.request.urlopen('http://php.net/') html = response.read() soup = BeautifulSoup(html,"html5lib") text = soup.get_text(strip=True) tokens = text.split() clean_tokens = list() sr = stopwords.words('english') for token in tokens: if not token in sr: clean_tokens.append(token) freq = nltk.FreqDist(clean_tokens) for key,val in freq.items(): print (str(key) + ':' + str(val)) |
現在再做一次詞頻統計圖,效果會比之前好些,因為剔除了停用詞:
1 |
freq.plot(20,cumulative=False) |
使用NLTK Tokenize文字
在之前我們用split
方法將文字分割成tokens,現在我們使用NLTK來Tokenize文字。
文字沒有Tokenize之前是無法處理的,所以對文字進行Tokenize非常重要的。token化過程意味著將大的部件分割為小部件。
你可以將段落tokenize成句子,將句子tokenize成單個詞,NLTK分別提供了句子tokenizer和單詞tokenizer。
假如有這樣這段文字:
1 |
Hello Adam, how are you? I hope everything is going well. Today is a good day, see you dude. |
使用句子tokenizer將文字tokenize成句子:
1 2 3 4 |
from nltk.tokenize import sent_tokenize mytext = "Hello Adam, how are you? I hope everything is going well. Today is a good day, see you dude." print(sent_tokenize(mytext)) |
輸出如下:
1 |
['Hello Adam, how are you?', 'I hope everything is going well.', 'Today is a good day, see you dude.'] |
這是你可能會想,這也太簡單了,不需要使用NLTK的tokenizer都可以,直接使用正規表示式來拆分句子就行,因為每個句子都有標點和空格。
那麼再來看下面的文字:
1 |
Hello Mr. Adam, how are you? I hope everything is going well. Today is a good day, see you dude. |
這樣如果使用標點符號拆分,Hello Mr
將會被認為是一個句子,如果使用NLTK:
1 2 3 4 |
from nltk.tokenize import sent_tokenize mytext = "Hello Mr. Adam, how are you? I hope everything is going well. Today is a good day, see you dude." print(sent_tokenize(mytext)) |
輸出如下:
1 |
['Hello Mr. Adam, how are you?', 'I hope everything is going well.', 'Today is a good day, see you dude.'] |
這才是正確的拆分。
接下來試試單詞tokenizer:
1 2 3 4 |
from nltk.tokenize import word_tokenize mytext = "Hello Mr. Adam, how are you? I hope everything is going well. Today is a good day, see you dude." print(word_tokenize(mytext)) |
輸出如下:
1 |
['Hello', 'Mr.', 'Adam', ',', 'how', 'are', 'you', '?', 'I', 'hope', 'everything', 'is', 'going', 'well', '.', 'Today', 'is', 'a', 'good', 'day', ',', 'see', 'you', 'dude', '.'] |
Mr.
這個詞也沒有被分開。NLTK使用的是punkt模組的PunktSentenceTokenizer,它是NLTK.tokenize的一部分。而且這個tokenizer經過訓練,可以適用於多種語言。
非英文Tokenize
Tokenize時可以指定語言:
1 2 3 4 |
from nltk.tokenize import sent_tokenize mytext = "Bonjour M. Adam, comment allez-vous? J'espère que tout va bien. Aujourd'hui est un bon jour." print(sent_tokenize(mytext,"french")) |
輸出結果如下:
1 |
['Bonjour M. Adam, comment allez-vous?', "J'espère que tout va bien.", "Aujourd'hui est un bon jour."] |
同義詞處理
使用nltk.download()
安裝介面,其中一個包是WordNet。
WordNet是一個為自然語言處理而建立的資料庫。它包括一些同義片語和一些簡短的定義。
您可以這樣獲取某個給定單詞的定義和示例:
1 2 3 4 5 |
from nltk.corpus import wordnet syn = wordnet.synsets("pain") print(syn[0].definition()) print(syn[0].examples()) |
輸出結果是:
1 2 |
a symptom of some physical hurt or disorder ['the patient developed severe pain and distension'] |
WordNet包含了很多定義:
1 2 3 4 5 6 |
from nltk.corpus import wordnet syn = wordnet.synsets("NLP") print(syn[0].definition()) syn = wordnet.synsets("Python") print(syn[0].definition()) |
結果如下:
1 2 |
the branch of information science that deals with natural language information large Old World boas |
可以像這樣使用WordNet來獲取同義詞:
1 2 3 4 5 6 7 |
from nltk.corpus import wordnet synonyms = [] for syn in wordnet.synsets('Computer'): for lemma in syn.lemmas(): synonyms.append(lemma.name()) print(synonyms) |
輸出:
1 |
['computer', 'computing_machine', 'computing_device', 'data_processor', 'electronic_computer', 'information_processing_system', 'calculator', 'reckoner', 'figurer', 'estimator', 'computer'] |
反義詞處理
也可以用同樣的方法得到反義詞:
1 2 3 4 5 6 7 8 |
from nltk.corpus import wordnet antonyms = [] for syn in wordnet.synsets("small"): for l in syn.lemmas(): if l.antonyms(): antonyms.append(l.antonyms()[0].name()) print(antonyms) |
輸出:
1 |
['large', 'big', 'big'] |
詞幹提取
語言形態學和資訊檢索裡,詞幹提取是去除詞綴得到詞根的過程,例如working的詞幹為work。
搜尋引擎在索引頁面時就會使用這種技術,所以很多人為相同的單詞寫出不同的版本。
有很多種演算法可以避免這種情況,最常見的是波特詞幹演算法。NLTK有一個名為PorterStemmer的類,就是這個演算法的實現:
1 2 3 4 5 |
from nltk.stem import PorterStemmer stemmer = PorterStemmer() print(stemmer.stem('working')) print(stemmer.stem('worked')) |
輸出結果是:
1 2 |
work work |
還有其他的一些詞幹提取演算法,比如 Lancaster詞幹演算法。
非英文詞幹提取
除了英文之外,SnowballStemmer還支援13種語言。
支援的語言:
1 2 3 |
from nltk.stem import SnowballStemmer print(SnowballStemmer.languages) |
1 |
'danish', 'dutch', 'english', 'finnish', 'french', 'german', 'hungarian', 'italian', 'norwegian', 'porter', 'portuguese', 'romanian', 'russian', 'spanish', 'swedish' |
你可以使用SnowballStemmer
類的stem
函式來提取像這樣的非英文單詞:
1 2 3 4 5 |
from nltk.stem import SnowballStemmer french_stemmer = SnowballStemmer('french') print(french_stemmer.stem("French word")) |
單詞變體還原
單詞變體還原類似於詞幹,但不同的是,變體還原的結果是一個真實的單詞。不同於詞幹,當你試圖提取某些詞時,它會產生類似的詞:
1 2 3 4 5 |
from nltk.stem import PorterStemmer stemmer = PorterStemmer() print(stemmer.stem('increases')) |
結果:
1 |
increas |
現在,如果用NLTK的WordNet來對同一個單詞進行變體還原,才是正確的結果:
1 2 3 4 5 |
from nltk.stem import WordNetLemmatizer lemmatizer = WordNetLemmatizer() print(lemmatizer.lemmatize('increases')) |
結果:
1 |
increase |
結果可能會是一個同義詞或同一個意思的不同單詞。
有時候將一個單詞做變體還原時,總是得到相同的詞。
這是因為語言的預設部分是名詞。要得到動詞,可以這樣指定:
1 2 3 4 5 |
from nltk.stem import WordNetLemmatizer lemmatizer = WordNetLemmatizer() print(lemmatizer.lemmatize('playing', pos="v")) |
結果:
1 |
play |
實際上,這也是一種很好的文字壓縮方式,最終得到文字只有原先的50%到60%。
結果還可以是動詞(v)、名詞(n)、形容詞(a)或副詞(r):
1 2 3 4 5 6 7 |
from nltk.stem import WordNetLemmatizer lemmatizer = WordNetLemmatizer() print(lemmatizer.lemmatize('playing', pos="v")) print(lemmatizer.lemmatize('playing', pos="n")) print(lemmatizer.lemmatize('playing', pos="a")) print(lemmatizer.lemmatize('playing', pos="r")) |
輸出:
1 2 3 4 |
play playing playing playing |
詞幹和變體的區別
通過下面例子來觀察:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
from nltk.stem import WordNetLemmatizer from nltk.stem import PorterStemmer stemmer = PorterStemmer() lemmatizer = WordNetLemmatizer() print(stemmer.stem('stones')) print(stemmer.stem('speaking')) print(stemmer.stem('bedroom')) print(stemmer.stem('jokes')) print(stemmer.stem('lisa')) print(stemmer.stem('purple')) print('----------------------') print(lemmatizer.lemmatize('stones')) print(lemmatizer.lemmatize('speaking')) print(lemmatizer.lemmatize('bedroom')) print(lemmatizer.lemmatize('jokes')) print(lemmatizer.lemmatize('lisa')) print(lemmatizer.lemmatize('purple')) |
輸出:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
stone speak bedroom joke lisa purpl --------------------- stone speaking bedroom joke lisa purple |
詞幹提取不會考慮語境,這也是為什麼詞幹提取比變體還原快且準確度低的原因。
個人認為,變體還原比詞幹提取更好。單詞變體還原返回一個真實的單詞,即使它不是同一個單詞,也是同義詞,但至少它是一個真實存在的單詞。
如果你只關心速度,不在意準確度,這時你可以選用詞幹提取。
在此NLP教程中討論的所有步驟都只是文字預處理。在以後的文章中,將會使用Python NLTK來實現文字分析。
我已經儘量使文章通俗易懂。希望能對你有所幫助。