標籤:

Python NLTK自然語言處理:詞幹、詞形與MaxMatch演算法

自然語言處理是計算機科學領域與人工智慧領域中的一個重要方向。自然語言工具箱(NLTK,Natural Language Toolkit)是一個基於Python (lib.csdn.net/base/11)語言的類庫,它也是當前最為流行的自然語言編程與開發工具。在進行自然語言處理研究和應用時,恰當利用NLTK中提供的函數可以大幅度地提高效率。本文就將通過一些實例來向讀者介紹NLTK的使用。

開發環境:我所使用的Python版本是最新的3.5.1,NLTK版本是3.2。Python的安裝不在本文的討論範圍內,我們略去不表。你可以從NLTK的官網上Natural Language Toolkit 獲得最新版本的NLTK。Anyway,使用pip指令來完成NLTK包的下載和安裝無疑是最簡便的方法。

當然,當你完成這一步時,其實還不夠。因為NLTK是由許多許多的包來構成的,此時運行Python,並輸入下面的指令(當然,第一條指令還是要導入NLTK包)

[python] view plain copy

  1. >>> import nltk

  2. >>> nltk.download()

然後,Python Launcher會彈出下面這個界面,建議你選擇安裝所有的Packages,以免去日後一而再、再而三的進行安裝,也為你的後續開發提供一個穩定的環境。某些包的Status顯示「out of date」,你可以不必理會,它基本不影響你的使用與開發。

既然你已經安裝成功,我們來小試牛刀一下。當然本文涉及的主要任務都是自然語言處理中最常用,最基礎的pre-processing過程,結合機器學習的高級應用我們會在後續文章中再進行介紹。

1、 Sentences Segment(分句)

也就是說我們手頭有一段文本,我們希望把它分成一個一個的句子。此時可以使用NLTK中的 punkt sentence segmenter。來看示例代碼

[python] view plain copy

  1. >>> sent_tokenizer = nltk.data.load(tokenizers/punkt/english.pickle)

  2. >>> paragraph = "The first time I heard that song was in Hawaii on radio.

  3. ... I was just a kid, and loved it very much! What a fantastic song!"

  4. >>> sentences = sent_tokenizer.tokenize(paragraph)

  5. >>> sentences

  6. [The first time I heard that song was in Hawaii on radio.,

  7. I was just a kid, and loved it very much!,

  8. What a fantastic song!]

由此,我們便把一段話成功分句了。

2、Tokenize sentences (分詞)

接下來我們要把每個句話再切割成逐個單詞。最簡單的方法是使用NLTK 包中的 WordPunct tokenizer。來看示例代碼

[python] view plain copy

  1. >>> from nltk.tokenize import WordPunctTokenizer

  2. >>> sentence = "Are you old enough to remember Michael Jackson attending

  3. ... the Grammys with Brooke Shields and Webster sat on his lap during the show?"

  4. >>> words = WordPunctTokenizer().tokenize(sentence)

  5. >>> words

  6. [Are, you, old, enough, to, remember, Michael, Jackson, attending,

  7. the, Grammys, with, Brooke, Shields, and, Webster, sat, on, his,

  8. lap, during, the, show, ?]

我們的分詞任務仍然完成的很好。除了WordPunct tokenizer之外,NLTK中還提供有另外三個分詞方法,

TreebankWordTokenizer,PunktWordTokenizer和WhitespaceTokenizer,而且他們的用法與WordPunct tokenizer也類似。然而,顯然我們並不滿足於此。對於比較複雜的詞型,WordPunct tokenizer往往並不勝任。此時我們需要藉助正則表達式的強大能力來完成分詞任務,此時我所使用的函數是regexp_tokenize()。來看下面這段話

[python] view plain copy

  1. >>> text = That U.S.A. poster-print costs $12.40...

目前市面上可以參考的在Python下進行自然語言處理的書籍是由Steven Bird、Ewan Klein、Edward Loper編寫的《Python 自然語言處理》。但是該書的編寫時間距今已有近十年的時間,由於軟體包更新等語言,在新環境下進行開發時,書中的某些代碼並不能很正常的運行。最後,我們舉一個書中代碼out of date的例子(對上面這就話進行分詞),並給出相應的解決辦法。首先來看書中的一段節錄

[python] view plain copy

  1. >>> text = That U.S.A. poster-print costs $12.40...

  2. >>> pattern = r(?x) # set flag to allow verbose regexps

  3. ... ([A-Z].)+ # abbreviations, e.g. U.S.A.

  4. ... | w+(-w+)* # words with optional internal hyphens

  5. ... | $?d+(.d+)?%? # currency and percentages, e.g. $12.40, 82%

  6. ... | ... # ellipsis

  7. ... | [][.,;"?():-_`] # these are separate tokens; includes ], [

  8. ...

  9. >>> nltk.regexp_tokenize(text, pattern)

我們預期得到輸出應該是這樣的

[python] view plain copy

  1. [That, U.S.A., poster-print, costs, $12.40, ...]

但是我們實際得到的輸出卻是這樣的(注意我們所使用的NLTK版本)

[python] view plain copy

  1. [(, , ),

  2. (A., , ),

  3. (, -print, ),

  4. (, , ),

  5. (, , .40),

  6. (, , )]

會出現這樣的問題是由於nltk.internals.compile_regexp_to_noncapturing()在V3.1版本的NLTK中已經被拋棄(儘管在更早的版本中它仍然可以運行),為此我們把之前定義的pattern稍作修改

[python] view plain copy

  1. pattern = r"""(?x) # set flag to allow verbose regexps

  2. (?:[A-Z].)+ # abbreviations, e.g. U.S.A.

  3. |d+(?:.d+)?%? # numbers, incl. currency and percentages

  4. |w+(?:[-]w+)* # words w/ optional internal hyphens/apostrophe

  5. |... # ellipsis

  6. |(?:[.,;"?():-_`]) # special characters with meanings

  7. """

再次執行前面的語句,便會得到

[python] view plain copy

  1. >>> nltk.regexp_tokenize(text, pattern)

  2. [That, U.S.A., poster-print, costs, 12.40, ...]

Python自然語言處理:詞幹、詞形與MaxMatch演算法

自然語言處理中一個很重要的操作就是所謂的stemming 和 lemmatization,二者非常類似。它們是詞形規範化的兩類重要方式,都能夠達到有效歸併詞形的目的,二者既有聯繫也有區別。

1、詞幹提取(stemming)

定義:Stemming is the process for reducing inflected (or sometimes derived) words to their stem, base or root form—generally a written word form.

解釋一下,Stemming 是抽取詞的詞幹或詞根形式(不一定能夠表達完整語義)。

NLTK中提供了三種最常用的詞幹提取器介面,即 Porter stemmer, Lancaster Stemmer 和 Snowball Stemmer。

Porter Stemmer基於Porter詞幹提取演算法,來看例子

[python] view plain copy

  1. >>> from nltk.stem.porter import PorterStemmer

  2. >>> porter_stemmer = PorterStemmer()

  3. >>> porter_stemmer.stem(『maximum』)

  4. u』maximum』

  5. >>> porter_stemmer.stem(『presumably』)

  6. u』presum』

  7. >>> porter_stemmer.stem(『multiply』)

  8. u』multipli』

  9. >>> porter_stemmer.stem(『provision』)

  10. u』provis』

  11. >>> porter_stemmer.stem(『owed』)

  12. u』owe』

Lancaster Stemmer 基於Lancaster 詞幹提取演算法,來看例子

[python] view plain copy

  1. >>> from nltk.stem.lancaster import LancasterStemmer

  2. >>> lancaster_stemmer = LancasterStemmer()

  3. >>> lancaster_stemmer.stem(『maximum』)

  4. 『maxim』

  5. >>> lancaster_stemmer.stem(『presumably』)

  6. 『presum』

  7. >>> lancaster_stemmer.stem(『presumably』)

  8. 『presum』

  9. >>> lancaster_stemmer.stem(『multiply』)

  10. 『multiply』

  11. >>> lancaster_stemmer.stem(『provision』)

  12. u』provid』

  13. >>> lancaster_stemmer.stem(『owed』)

  14. 『ow』

Snowball Stemmer基於Snowball 詞幹提取演算法,來看例子

[python] view plain copy

  1. >>> from nltk.stem import SnowballStemmer

  2. >>> snowball_stemmer = SnowballStemmer(「english」)

  3. >>> snowball_stemmer.stem(『maximum』)

  4. u』maximum』

  5. >>> snowball_stemmer.stem(『presumably』)

  6. u』presum』

  7. >>> snowball_stemmer.stem(『multiply』)

  8. u』multipli』

  9. >>> snowball_stemmer.stem(『provision』)

  10. u』provis』

  11. >>> snowball_stemmer.stem(『owed』)

  12. u』owe』

2、詞形還原(lemmatization)

定義:Lemmatisation (or lemmatization) in linguistics, is the process of grouping together the different inflected forms of a word so they can be analysed as a single item.

可見,Lemmatisation是把一個任何形式的語言辭彙還原為一般形式(能表達完整語義)。相對而言,詞幹提取是簡單的輕量級的詞形歸併方式,最後獲得的結果為詞幹,並不一定具有實際意義。詞形還原處理相對複雜,獲得結果為詞的原形,能夠承載一定意義,與詞幹提取相比,更具有研究和應用價值。

我們會在後面給出一個同MaxMatch演算法相結合的更為複雜的例子。

3、最大匹配演算法(MaxMatch)

MaxMatch演算法在中文自然語言處理中常常用來進行分詞(或許從名字上你已經能想到它是基於貪婪策略設計的一種演算法)。通常,英語中一句話里的各個辭彙之間通過空格來分割,這是非常straightforward的,但是中文卻沒有這個遍歷。例如「我愛中華人民共和國」,這句話被分詞的結果可能是這樣的{『我』,『愛』,『中華』,『人民』,『共和國』},又或者是{『我』,『愛』,『中華人民共和國』},顯然我們更傾向於後者的分詞結果。因為『中華人民共和國』顯然是一個專有名詞(把這樣一個詞分割來看顯然並不明智)。我們選擇後者的策略就是所謂的MaxMatch,即最大匹配。因為『中華人民共和國』這個詞顯然要比『中華』,『人民』,『共和國』這些詞都長。

我們可以通過一個英文的例子來演示MaxMatch演算法(其實中文處理的道理也是一樣的)。演算法從右側開始逐漸減少字元串長度,以此求得可能匹配的最大長度的字元串。考慮到我們所獲得的辭彙可能包含有某種詞型的變化,所以其中使用了Lemmatisation,然後在詞庫里進行匹配查找。

[python] view plain copy

  1. from nltk.stem import WordNetLemmatizer

  2. from nltk.corpus import words

  3. wordlist = set(words.words())

  4. wordnet_lemmatizer = WordNetLemmatizer()

  5. def max_match(text):

  6. pos2 = len(text)

  7. result =

  8. while len(text) > 0:

  9. word = wordnet_lemmatizer.lemmatize(text[0:pos2])

  10. if word in wordlist:

  11. result = result + text[0:pos2] +

  12. text = text[pos2:]

  13. pos2 = len(text)

  14. else:

  15. pos2 = pos2-1

  16. return result[0:-1]

來看看演算法的實現效果

[python] view plain copy

  1. >>> string = theyarebirds

  2. >>> print(max_match(string))

  3. they are birds

當然,上述代碼尚有一個不足,就是當字元串中存在非字母字元時(例如數字標點等),它可能會存在一些問題。有興趣的讀者不妨自己嘗試完善改進這個版本的實現。

以上便是我們對NLTK這個自然語言處理工具包的初步探索,最後,我想說《Python 自然語言處理》仍然是當前非常值得推薦的一本講述利用NLTK和Python進行自然語言處理技術的非常值得推薦的書籍。


推薦閱讀:

我在英國學阿拉伯語 (7) 描述身體和性格
我在英國學阿拉伯語 (16) 幾個助詞
日本留學語言能力相關考試一覽
每逢喝酒必有碰杯,來看看各國乾杯的文化

TAG:語言學習 |