天天看點

《資料分析實戰-托馬茲.卓巴斯》讀書筆記第9章--自然語言處理NLTK(分析文本、詞性标注、主題抽取、文本資料分類)

《資料分析實戰-托馬茲.卓巴斯》讀書筆記第9章--自然語言處理NLTK(分析文本、詞性标注、主題抽取、文本資料分類)

托馬茲·卓巴斯的《資料分析實戰》,2018年6月出版,本系列為讀書筆記。主要是為了系統整理,加深記憶。 第9章描述了多種與分析文本資訊流相關的技巧:詞性标注、主題抽取以及對文本資料的分類。

python資料分析個人學習讀書筆記-目錄索引

第9章描述了多種與分析文本資訊流相關的技巧:詞性标注、主題抽取以及對文本資料的分類。

本章中,會學習以下技巧:

·從網絡讀入原始文本

·标記化和标準化

·識别詞類,處理n-gram,識别命名實體

·識别文章主題

·識别句子結構

·根據評論給影片歸類

9.1導論

根據受控環境中收集的結構化資料模組化(比如前一章)還是相對直接的。然而,現實世界中,我們很少處理結構化資料。了解人們的回報或分析報紙的文章時尤其如此。

NLP(Natural Language Processing,自然語言處理)這門學科涉及計算機科學,統計學及語言學,其目标是處理人類語言(我特意沒有使用“了解”這個詞)及提取特征以用于模組化。使用NLP的概念,在其他任務中,我們可以找出文本中出現最多的詞,以大緻辨識出文本主題,識别出人名地名,找到句子的主語和賓語,或者分析某人回報資訊的情感。

這些技巧會用到兩個資料。第一個來自西雅圖時報的官網——Josh Lederman關于Obama要求對更多槍支交易的背景調查(http://www.seattletimes.com/nation-world/obama-starts-2016-with-a-fight-over-gun-control/,通路時間:2016年1月4日)。

另一個是經過A.L.Mass等處理過的50000條影評;完整的資料集在http://ai.stanford.edu/~amaas/data/sentiment/。其在“Andrew L.Maas,Raymond E.Daly,Peter T.Pham,Dan Huang,Andrew Y.Ng,and Christopher Potts(2011),Learning Word Vectors for Sentiment Analysis,The 49th Annual Meeting of the Association for Computational Linguistics(ACL 2011)”中公布。

關于50000條影評,我們從訓練批次和測試批次中各選取2000條正面的與2000條負面的。

9.2從網絡讀入原始文本

大多數時候,無格式文本可以在文本檔案中找到;本技巧中,我們不會教你如何這樣做,因為之前已經展示過了。(參考本書第1章)

下一技巧會讨論我們還沒讨論過的讀入檔案的方法。

然而,很多時候,我們需要直接從網絡讀入原始文本:我們也許希望分析一篇部落格文章、一篇文章或者Facebook/Twitter上的文章。Facebook和Twitter提供了API(Application Programming Interfaces,應用程式設計接口),一般以XML或JSON格式傳回資料,處理HTML檔案并不這麼直接。

本技巧中,會學到如何處理Web頁面,讀入内容并進行處理。

準備:需裝好urllib、html5lib和Beautiful Soup。

Python 3自帶urllib(https://docs.python.org/3/library/urllib.html)。然而,如果你的配置中沒有Beautiful Soup,安裝一下也很簡單。

另外,要用Beautiful Soup解析HTML檔案,我們需要安裝html5lib;

步驟:

Python 2.x和Python 3.x下使用urllib通路網站的過程略有不同:(Python 2.x中的)urllib2已經拆成了urllib.request、urllib.error、urllib.parse和urllib.robotparser。

更多資訊請移步https://docs.python.org/2/library/urllib2.html。

本技巧中,我們使用urllib.request(nlp_read.py檔案):

1 import urllib.request as u
 2 import bs4 as bs
 3 
 4 # link to the article at The Seattle Times
 5 st_url = 'http://www.seattletimes.com/nation-world/obama-starts-2016-with-a-fight-over-gun-control/'
 6 
 7 # read the contents of the webpage
 8 with u.urlopen(st_url) as response:
 9     html = response.read()
10 
11 # using beautiful soup -- let's parse the content of the HTML
12 read = bs.BeautifulSoup(html, 'html5lib')
13 
14 # find the article tag
15 article = read.find('article')
16 
17 # find all the paragraphs of the article
18 all_ps = article.find_all('p')
19 
20 # object to hold the body of text
21 body_of_text = []
22 
23 # get the tile
24 body_of_text.append(read.title.get_text())
25 print(read.title)
26 
27 # put all the paragraphs to the body of text list
28 for p in all_ps:
29     body_of_text.append(p.get_text())
30 
31 # we don't need some of the parts at the bottom of the page
32 body_of_text = body_of_text[:24]
33 
34 # let's see what we got
35 print('\n'.join(body_of_text))
36 
37 # and save it to a file
38 with open('../../Data/Chapter09/ST_gunLaws.txt', 'w') as f:
39     f.write('\n'.join(body_of_text))       

原理:一如既往,我們先導入需要的子產品;本例中,就是urllib和Beautiful Soup。

西雅圖時報的文章連結存在st_url對象。urllib的.urlopen(...)方法打開這個特定的URL。

我們用到了with(...)as...結構——這個結構我們已經熟悉了——因為在我們不再使用時,它會适時地關閉連接配接。當然,你也能這麼做:

1 local_filename.headers=\
2     urllib.request.urlretrieve(st_url)
3 html=open(local_filename)
4 html.Close()       

響應對象的.read()方法讀入網頁的全部内容。列印出來會是這樣(當然,已經簡化了):

《資料分析實戰-托馬茲.卓巴斯》讀書筆記第9章--自然語言處理NLTK(分析文本、詞性标注、主題抽取、文本資料分類)

這是網頁以純文字呈現的内容。這不是我們要分析的内容。

Beautiful Soup從天而降!BeautifulSoup(...)方法以HTML或XML文本作為第一個參數。第二個參數指定了使用的解析器。

所有可用的解析器,參見http://www.crummy.com/software/BeautifulSoup/bs4/doc/#specifying-the-parser-to-use。

解析之後,(某種程度上)更可讀了:

《資料分析實戰-托馬茲.卓巴斯》讀書筆記第9章--自然語言處理NLTK(分析文本、詞性标注、主題抽取、文本資料分類)

然而,我們并不用Beautiful Soup将結果列印到螢幕上。BeautifulSoup對象内部實作成一個文檔中标簽組成的階層化包。

你可以認為BeautifulSoup對象是一棵樹。

有了前面的輸出,好消息是你可以找出HTML/XML檔案中所有的标簽。新一代網絡頁面(相容HTML5)有些新标簽,有助于更友善地在頁面上展示内容。

所有新元素參見:http://www.w3.org/TR/html5-diff/#new-elements。

我們的例子中,我們先找到并提取文章标簽;這縮小了我們搜尋的範圍:

《資料分析實戰-托馬茲.卓巴斯》讀書筆記第9章--自然語言處理NLTK(分析文本、詞性标注、主題抽取、文本資料分類)

現在我們隻關注文章内容,忽略用來建構頁面的部分。如果你自行檢視網頁,你會看到眼熟的句子:

Obama moves to require background checks for more gun sales.

Originally published January 4,2016 at12:50 am.

我們行進在正确的道路上。往下滾動一點,我們可以看到更多熟悉的文章句子:

顯然,文章的段落都包含在<p>标簽對裡面。是以,我們用.find_all('p')将他們全都提取出來。

然後,我們将标題加到body_of_text清單。我們用.get_text()方法提取标簽對之間的文本;否則我們得到的内容裡面将包含标簽:

/*
<title>Obama moves to require background checks for more gun sales | The Seattle Times</title>
*/      

我們用同樣的方法對所有段落剝除标簽。你會在螢幕上看到下面的内容(有縮略):

《資料分析實戰-托馬茲.卓巴斯》讀書筆記第9章--自然語言處理NLTK(分析文本、詞性标注、主題抽取、文本資料分類)
《資料分析實戰-托馬茲.卓巴斯》讀書筆記第9章--自然語言處理NLTK(分析文本、詞性标注、主題抽取、文本資料分類)
/*
Obama moves to require background checks for more gun sales | The Seattle Times
Although Obama can't unilaterally change gun laws, the president is hoping that beefing up enforcement of existing laws can prevent at least some gun deaths in a country rife with them.
WASHINGTON (AP) — President Barack Obama moved Monday to expand background checks to cover more firearms sold at gun shows, online and anywhere else, aiming to curb a scourge of gun violence despite unyielding opposition to new laws in Congress.
Obama’s plan to broaden background checks forms the centerpiece of a broader package of gun control measures the president plans to take on his own in his final year in office. Although Obama can’t unilaterally change gun laws, the president is hoping that beefing up enforcement of existing laws can prevent at least some gun deaths in a country rife with them.
Washington state voters last fall passed Initiative 594 that expanded background checks for gun buyers to include private sales and transfers, such as those conducted online or at gun shows.
Gun-store owner moving out of Seattle because of new tax
“This is not going to solve every violent crime in this country,” Obama said. Still, he added, “It will potentially save lives and spare families the pain of these extraordinary losses.”
Under current law, only federally licensed gun dealers must conduct background checks on buyers, but many who sell guns at flea markets, on websites or in other informal settings don’t register as dealers. Gun control advocates say that loophole is exploited to skirt the background check requirement.
Now, the Justice Department’s Bureau of Alcohol, Tobacco, Firearms and Explosives will issue updated guidance that says the government should deem anyone “in the business” of selling guns to be a dealer, regardless of where he or she sells the guns. To that end, the government will consider other factors, including how many guns a person sells and how frequently, and whether those guns are sold for a profit.
The executive actions on gun control fall far short of what Obama and likeminded lawmakers attempted to accomplish with legislation in 2013, after a massacre at a Connecticut elementary school that shook the nation’s conscience. Even still, the more modest measures were sure to spark legal challenges from those who oppose any new impediments to buying guns.
“We’re very comfortable that the president can legally take these actions,” said Attorney General Loretta Lynch.
Obama’s announcement was hailed by Democratic lawmakers and gun control groups like the Brady Campaign to Prevent Gun Violence, which claimed Obama was making history with “bold and meaningful action” that would make all Americans safer. Hillary Clinton, at a rally in Iowa, said she was “so proud” of Obama but warned that the next president could easily undo his changes.
“I won’t wipe it away,” Clinton said.
But Republicans were quick to accuse Obama of gross overreach. Sen Bob Corker, R-Tenn., denounced Obama’s steps as “divisive and detrimental to real solutions.”
“I will work with my colleagues to respond appropriately to ensure the Constitution is respected,” Corker said.
Far from mandating background checks for all gun sales, the new guidance still exempts collectors and gun hobbyists, and the exact definition of who must register as a dealer and conduct background checks remains exceedingly vague. The administration did not issue a number for how many guns someone must sell to be considered a dealer, instead saying it planned to remind people that courts have deemed people to be dealers in some cases even if they only sell a handful of guns.
And the background check provision rests in the murky realm of agency guidelines, which have less force than full-fledged federal regulations and can easily be rescinded. Many of the Republican presidential candidates running to succeed Obama have vowed to rip up his new gun restrictions upon taking office.
In an attempt to prevent gun purchases from falling through the cracks, the FBI will hire 230 more examiners to process background checks, the White House said, an increase of about 50 percent. Many of the roughly 63,000 background check requests each day are processed within seconds. But if the system kicks back a request for further review, the government only has three days before federal law says the buyer can return and buy the gun without being cleared.
That weak spot in the system came under scrutiny last summer when the FBI revealed that Dylann Roof, the accused gunman in the Charleston, S.C., church massacre, was improperly allowed to buy a gun because incomplete record-keeping and miscommunication among authorities delayed processing of his background check beyond the three-day limit.
The White House also said it planned to ask Congress for $500 million to improve mental health care, and Obama issued a memorandum directing federal agencies to conduct or sponsor research into smart gun technology that reduces the risk of accidental gun discharges. The Obama administration also plans to complete a rule, already in the works, to close another loophole that allows trusts or corporations to purchase sawed-off shotguns, machine guns and similar weapons without background checks.
Obama planned to announce the new measures at an event at the White House on Tuesday as he continued a weeklong push to promote the gun effort and push back on its critics.
He met at the White House on Monday with Democratic lawmakers who have supported stricter gun control, and planned to take his argument to prime time Thursday with a televised town hall discussion. The initiative also promised to be prominent in Obama’s final State of the Union address next week.
Whether the new steps will effectively prevent future gun deaths remained unclear. Philip Cook, a Duke University professor who researches gun violence and policy, said surveys of prisoners don’t show gun shows to be a major direct source of weapons used in violent crime. The attorney general, asked how many dealers would be newly forced to register, declined to give a number.
“It’s just impossible to predict,” Lynch said.
*/      

View Code

這才更像是我們可以處理的。最後将文本儲存到檔案。

9.3标記化和标準化

從頁面提取内容隻是第一步。在分析文章内容前,我們需要将文章拆成句子,進而拆成詞。

然後我們要面臨另一個問題;任何文本中,我們都會看到不同的時态、被動語态或者罕見的文法結構。為了提取主題或分析情感,我們并不區分said和says——一個say就足夠了。也就是我們還要做标準化的工作,将詞的不同形式轉成普通形式。

準備:需要NLTK(Natural Language Toolkit,自然語言工具包)。然而,在開始之前,你需要確定機器上有NLTK子產品。

/* pip install nltk
Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple
Collecting nltk
  Downloading https://pypi.tuna.tsinghua.edu.cn/packages/f6/1d/d925cfb4f324ede997f6d47bea4d9babba51b49e87a767c170b77005889d/nltk-3.4.5.zip (1.5MB)
Requirement already satisfied: six in d:\tools\python37\lib\site-packages (from nltk) (1.12.0)
Building wheels for collected packages: nltk
  Building wheel for nltk (setup.py): started
  Building wheel for nltk (setup.py): finished with status 'done'
  Created wheel for nltk: filename=nltk-3.4.5-cp37-none-any.whl size=1449914 sha256=68a8454c9bd939fc1fb5cb853896d16b4e3f5a099db051de73100ceb6d485d06
  Stored in directory: C:\Users\tony zhang\AppData\Local\pip\Cache\wheels\bd\e1\05\dc3530d70790416315b9e4f889ec29e59130a01ac9ce6fab29
Successfully built nltk
Installing collected packages: nltk
Successfully installed nltk-3.4.5
FINISHED */      

然而,這隻能保證你機器上有NLTK。要能使用程式,我們需要下載下傳子產品某些部分要用到的資料。執行下面程式可以做到這一點(nlp_download.py檔案):

1 import nltk
2 from nltk import data
3 data.path.append(r'D:\download\nltk_data') # 這裡的路徑需要換成自己資料檔案下載下傳的路徑
4 
5 #nltk.download()
6 #nltk.download('punkt')
      

開始執行後,你會看到這個類似于上圖的彈窗,下一步,點選Download按鈕。

《資料分析實戰-托馬茲.卓巴斯》讀書筆記第9章--自然語言處理NLTK(分析文本、詞性标注、主題抽取、文本資料分類)

預設情況下,它全都突出顯示,我建議你保持這個做法。其他選項會下載下傳跟随NLTK book所需要的所有部分。全部下載下傳約900M。

NLTK book可以在這裡找到:http://www.nltk.org/book/,如果對NLP感興趣,作者強烈推薦你閱讀這個。

會開始下載下傳過程,你會有一段時間要面對類似下面的視窗;下載下傳所有部分花了45~50分鐘。耐心一點。

如果下載下傳失敗了,你可以隻安裝punkt模型。切到All Packages頁,選擇punkt。

如果你實在受不下載下傳的龜速,邀月的解決方案:

https://github.com/nltk/nltk_data/tree/gh-pages頁面檢視Clone:https://github.com/nltk/nltk_data.git

或下載下傳得到nltk_data-gh-pages.zip檔案

https://codeload.github.com/nltk/nltk_data/zip/gh-pages,

下載下傳後在py代碼中記得更改為下載下傳後的路徑,詳見上面的代碼示例。

《資料分析實戰-托馬茲.卓巴斯》讀書筆記第9章--自然語言處理NLTK(分析文本、詞性标注、主題抽取、文本資料分類)

步驟:萬事俱備,我們開始标記化和标準化工作(nlp_tokenize.py檔案):

1 import nltk
 2 
 3 from nltk import data
 4 data.path.append(r'D:\download\nltk_data') # 這裡的路徑需要換成自己資料檔案下載下傳的路徑
 5 
 6 # read the text
 7 guns_laws = '../../Data/Chapter09/ST_gunLaws.txt'
 8 
 9 with open(guns_laws, 'r') as f:
10     article = f.read()
11 
12 # load NLTK modules
13 sentencer = nltk.data.load('tokenizers/punkt/english.pickle')
14 tokenizer = nltk.RegexpTokenizer(r'\w+')
15 stemmer = nltk.PorterStemmer()
16 lemmatizer = nltk.WordNetLemmatizer()
17 
18 
19 # split the text into sentences
20 sentences = sentencer.tokenize(article)
21 
22 words = []
23 stemmed_words = []
24 lemmatized_words = []
25 
26 # and for each sentence
27 for sentence in sentences:
28     # split the sentence into words
29     words.append(tokenizer.tokenize(sentence))
30 
31     # stemm the words
32     stemmed_words.append([stemmer.stem(word)
33         for word in words[-1]])
34 
35     # and lemmatize them
36     lemmatized_words.append([lemmatizer.lemmatize(word)
37         for word in words[-1]])
38 
39 # and save the results to files
40 file_words  = '../../Data/Chapter09/ST_gunLaws_words.txt'
41 file_stems  = '../../Data/Chapter09/ST_gunLaws_stems.txt'
42 file_lemmas = '../../Data/Chapter09/ST_gunLaws_lemmas.txt'
43 
44 with open(file_words, 'w') as f:
45     for w in words:
46         for word in w:
47             f.write(word + '\n')
48 
49 with open(file_stems, 'w') as f:
50     for w in stemmed_words:
51         for word in w:
52             f.write(word + '\n')
53 
54 with open(file_lemmas, 'w') as f:
55     for w in lemmatized_words:
56         for word in w:
57             f.write(word + '\n')               

原理:首先,我們從ST_gunLaws.txt檔案讀入文本。由于我們的文本檔案幾乎沒有結構(除了段落是用\n隔開這一點),我們可以直接讀入檔案;.read()就做了這件事。

然後,我們加載所有需要的NLTK子產品。sentencer對象是一個punkt句子标記器。标記器使用無監督學習找到句子的開始和結束。一個樸素的方法是尋找點号。但是,這方法沒法處理句子中的縮寫,例如Dr.或像Michael D.Brown這樣的人名。

關于punkt句子标記器,可在NTLK文檔中了解更多内容:http://www.nltk.org/api/nltk.tokenize.html#module-nltk.tokenize.punkt。

本技巧中,我們使用正規表達式作為标記器(這樣就不用處理标點了)。.RegexpTokenizer(...)以正規表達式作為參數;我們的例子中,我們隻對一個個單詞感興趣,是以使用'\w+'。使用這個正規表達式的缺點是不能正确處理省略形式,比如,don't就會被标記成['don','t']。

Python中可用的正規表達式清單,參見https://docs.python.org/3/library/re.html。

stemmer對象根據一個特定算法(或規則集)移除單詞的結尾部分。我們使用.Porter Stemmer()進行單詞的正規化;stemmer從reading移除ing得到read。不過,對于有些單詞,stemmer可能會太過暴力,比如,president會被表達成presid,而語言學上的詞幹是preside(這個詞也會被stemmer表達成presid)。

Porter Stemmer是從ANSI C移植的。提取器一開始是由C.J.van Rijsbergen、S.E.Robertson及M.F.Porter提出,作為IR項目的一部分開發的。論文An algorithm for suffix stripping由M.F.Porter在1980年發表于Program Journal。感興趣的話,可以參考http://tartarus.org/~martin/PorterStemmer/。

還原詞形的目的和提取詞幹相關,也是為了标準化文本;例如,are和is都會标準化為be。兩者的差別在于,(前面解釋過的)提取詞幹使用算法(以一種激進的方式)去掉單詞的末尾,而還原詞形使用一個巨大的詞庫,找出單詞的語言學意義上的詞幹。

本技巧中,我們使用WordNet。更多内容可以參考http://www.nltk.org/api/nltk.stem.html#module-nltk.stem.wordnet。

建立需要的對象後,我們來從文本中提取句子。經punkt句子标記器處理,得到35個句子的清單:

《資料分析實戰-托馬茲.卓巴斯》讀書筆記第9章--自然語言處理NLTK(分析文本、詞性标注、主題抽取、文本資料分類)

下一步,我們周遊所有句子,使用标記器将所有單詞标記出來,放到單詞清單中。這個單詞清單其實是個清單的清單:

《資料分析實戰-托馬茲.卓巴斯》讀書筆記第9章--自然語言處理NLTK(分析文本、詞性标注、主題抽取、文本資料分類)

注意單詞can't被拆成了can和t——如.RegexpTokenizer(...)所期望的。

既然句子拆成了單詞,那麼我們來提取詞幹。

提醒一下:words[-1]文法指的是選取清單最後一個元素(即剛剛附着上去的)。

提取詞幹得到的清單是:

《資料分析實戰-托馬茲.卓巴斯》讀書筆記第9章--自然語言處理NLTK(分析文本、詞性标注、主題抽取、文本資料分類)

可以看出提取的簡單粗暴;require成了requir,Seattle成了Seattl。不過moves和checks還是正确提取成了move和check。我們看看還原詞形能得到的清單:

《資料分析實戰-托馬茲.卓巴斯》讀書筆記第9章--自然語言處理NLTK(分析文本、詞性标注、主題抽取、文本資料分類)

required正确移除了-d,也正确處理了Seattle。然而,單詞的過去式并沒有正确處理;我們還能看到moved和sold這樣的單詞。理想情況下,這些應該處理成move和sell。sold在提取詞幹和還原詞形時都不能正确處理。

你的分析需要什麼處理,完全取決于你。要記住的是,還原詞形比提取詞幹要慢,因為要查詢字典。

最後一步,我們将所有單詞儲存到檔案,以比較兩種方法,沒有足夠空間的話沒法比較。

參考:你可以看看其他人是怎麼做标記化的:http://textminingonline.com/dive-into-nltk-part-ii-sentence-tokenize-and-word-tokenize。

9.4識别詞類,處理n-gram,識别命名實體

你也許想了解如何識别一個詞的詞類;要了解單詞在句子中的意思,首先要能認出是動詞還是名詞。

不過這對處理二進制文法(或者更泛化的n元文法)幫助不大:這是一組詞,如果單獨分析,不會得到正确的了解。例如,考慮一篇機器學習——更具體一點,是将神經網絡用于本地網絡中控制包的排程和路由——的文章,其中有個詞組neural networks。同樣的文章中,這兩個單詞(neural和networks)可能有和詞組中不同的意思。

最後,閱讀關于最近政要會議的政治類文章,我們可能經常碰到President這個詞;更有趣的是President Obama和President Putin在文本中出現了多少次。後面的技巧中我們會展示如何解決這個問題。

準備:需要裝好NLTK和正規表達式子產品。

步驟:有了NLTK,标記詞類就很簡單了。識别命名實體要做些工作(nlp_pos.py檔案):

1 import nltk
 2 from nltk import data
 3 data.path.append(r'D:\download\nltk_data') # 這裡的路徑需要換成自己資料檔案下載下傳的路徑
 4 import re
 5 
 6 def preprocess_data(text):
 7     global sentences, tokenized
 8     tokenizer = nltk.RegexpTokenizer(r'\w+')
 9 
10     sentences =  nltk.sent_tokenize(text)
11     tokenized = [tokenizer.tokenize(s) for s in sentences]
12 
13 # import the data
14 guns_laws = '../../Data/Chapter09/ST_gunLaws.txt'
15 
16 with open(guns_laws, 'r') as f:
17     article = f.read()
18 
19 # chunk into sentences and tokenize
20 sentences = []
21 tokenized = []
22 words = []
23 
24 preprocess_data(article)
25 
26 # part-of-speech tagging
27 tagged_sentences = [nltk.pos_tag(s) for s in tokenized]
28 
29 # extract named entities -- naive approach
30 named_entities = []
31 
32 for sentence in tagged_sentences:
33     for word in sentence:
34         if word[1] == 'NNP' or word[1] == 'NNPS':
35             named_entities.append(word)
36 
37 named_entities = list(set(named_entities))
38 
39 print('Named entities -- simplistic approach:')
40 print(named_entities)
41 
42 # extract names entities -- regular expressions approach
43 named_entities = []
44 tagged = []
45 
46 pattern = 'ENT: {<DT>?(<NNP|NNPS>)+}'
47 
48 # use regular expressions parser
49 tokenizer = nltk.RegexpParser(pattern)
50 
51 for sent in tagged_sentences:
52     tagged.append(tokenizer.parse(sent))
53 
54 for sentence in tagged:
55     for pos in sentence:
56         if type(pos) == nltk.tree.Tree:
57             named_entities.append(pos)
58 
59 named_entities = list(set([tuple(e) for e in named_entities]))
60 
61 print('\nNamed entities using regular expressions:')
62 print(named_entities)      

原理:按照慣例,我們一上來先導入需要的子產品,讀入待分析的文本。

我們準備了preprocess_data(...)方法,以便自動化地進行句和詞的标記化工作。這裡使用的不是punkt句子标記器,而是NLTK:我們(實際上)用.sent_tokenize(...)調用punkt句子标記器。對于詞的标記,我們仍然使用正規表達式标記器。

将文本拆成了句子,将句子拆成了單詞,我們現在可以得到每個詞的詞類了。.pos_tag(...)方法檢視整個句子,這個由标記過的單詞構成的有序清單,并傳回元組的清單;每個元組由單詞本身及其詞類構成。

從句子級别出發給單詞定詞類,這麼做背後的想法是(從上下文)得到合理的推斷。例如,單詞moves在下面兩個句子中的詞類就不一樣:

“He moves effortlessly.”

“His dancing moves are effortless.”

在前一句中是個動詞,而在後一句中,是名詞的複數形式。

看看我們得到的結果:

《資料分析實戰-托馬茲.卓巴斯》讀書筆記第9章--自然語言處理NLTK(分析文本、詞性标注、主題抽取、文本資料分類)

單詞Obama被歸為一個名詞的單數形式,moves被定為動詞的第三人稱單數形式,to是個介詞,但有其特定标簽,require是動詞的基本形,等等。

詞類标簽的清單參見:https://www.ling.upenn.edu/courses/Fall_2003/ling001/penn_treebank_pos.html。

知道詞類有助于區分同音詞,也有助于識别n元文法和命名實體。由于命名實體一般以大寫字母打頭,我們會想到列出清單中所有NNP和NNPS。看看這樣能得到什麼:

見上圖合并。

對于有些詞得到的結果很好,特别是一個詞的情況,比如Republicans或Thursday,不過我們實在是不知道Philip的姓是Lynch還是Cook。

簡要地說,我們将使用NTLK的.RegexpParser(...)方法。這個方法使用正規表達式,檢視詞類的标簽而不是詞本身,并且比對這個模式:

pattern = 'ENT: {<DT>?(<NNP|NNPS>)+}'      

這裡定義的模式将(用ENT:)标出有這樣結構的實體:專有名詞(如Thursday或Justice Department),單數(如the FBI)或複數(如Republicans),可加限定詞(如The White House)也可不加(如Attorney General Loretta Lynch)。

提取命名實體,得到這樣的清單(你看到的順序可能不同):

現在我們知道了Philip的姓是Cook,我們也能辨識出The Seattle Times,也能正确标記Constitution and Justice Department。不過,我們還無法應對Bureau of Alcohol、Tobacco以及Firearms and Explosives,并且我們遇到了康涅狄格俚語。

除了這些缺點之外,我覺得命名實體識别得挺好,我們得到了一個可以處理的清單,可以以後再優化。

更多:

我們也可以使用NLTK提供的.ne_chunk_sents(...)方法。代碼的開頭和之前的幾乎一模一樣(nls_pos_alternative.py檔案):

1 # extract named entities -- the Named Entity Chunker
 2 ne = nltk.ne_chunk_sents(tagged_sentences)
 3 
 4 # get a distinct list
 5 named_entities = []
 6 
 7 for s in ne:
 8     for ne in s:
 9         if type(ne) == nltk.tree.Tree:
10             named_entities.append((ne.label(), tuple(ne)))
11         
12 named_entities = list(set(named_entities))
13 named_entities = sorted(named_entities)
14 
15 # and print out the list
16 for t, ne in named_entities:
17     print(t, ne)      

.ne_chunk_sents(...)方法處理由句子和标了詞類的單詞組成的清單的清單,傳回識别出命名實體的句子的清單。命名實體的形式是一個nltk.tree.Tree對象。

然後,我們建立一個識别出的命名實體元組的清單,并排序。

不指定排序字段的話,就會按照元組的第一個元素排序。

最後列印清單:此處略。

/* 
nltk.download('maxent_ne_chunker')
#并把檔案從預設的路徑C:\Users\tony zhang\AppData\Roaming\nltk_data\移動到D:\download\nltk_data\
 */      

這個方法效果不錯的,不過還是識别不出Philip Cook或Bureau of Alcohol、Tobacco以及Firearms and Explosives這樣難度比較高的實體。将Bureau當成一個人,将Obama當成一個GPE,都是錯誤的。

9.5識别文章主題

如果要找出文字主題的情緒,統計單詞數量是一個常用且簡單的技巧,但通常都能得到很好的結果。本技巧中,我們會展示如何統計西雅圖時封包章的單詞,以識别出文章的主題。

準備:需要裝好NLTK、Python正規表達式子產品、NumPy和Matplotlib。

步驟:這裡代碼的開頭和前一技巧中的很像,是以我們隻展示相關的部分(nlp_countWords.py檔案):

1 import nltk
 2 from nltk import data
 3 data.path.append(r'D:\download\nltk_data') # 這裡的路徑需要換成自己資料檔案下載下傳的路徑
 4 import re
 5 import numpy as np
 6 import matplotlib.pyplot as plt
 7 
 8 def preprocess_data(text):
 9     global sentences, tokenized
10     tokenizer = nltk.RegexpTokenizer(r'\w+')
11 
12     sentences =  nltk.sent_tokenize(text)
13     tokenized = [tokenizer.tokenize(s) for s in sentences]
14 
15 # import the data
16 guns_laws = '../../Data/Chapter09/ST_gunLaws.txt'
17 
18 with open(guns_laws, 'r') as f:
19     article = f.read()
20 
21 # chunk into sentences and tokenize
22 sentences = []
23 tokenized = []
24 
25 preprocess_data(article)
26 
27 # part-of-speech tagging
28 tagged_sentences = [nltk.pos_tag(w) for w in tokenized]
29 
30 # extract names entities -- regular expressions approach
31 tagged = []
32 
33 pattern = '''
34     ENT: {<DT>?(<NNP|NNPS>)+}
35 '''
36 
37 tokenizer = nltk.RegexpParser(pattern)
38 
39 for sent in tagged_sentences:
40     tagged.append(tokenizer.parse(sent))
41 
42 # keep named entities together
43 words = []
44 lemmatizer = nltk.WordNetLemmatizer()
45 
46 for sentence in tagged:
47     for pos in sentence:
48         if type(pos) == nltk.tree.Tree:
49             words.append(' '.join([w[0] for w in pos]))
50         else:
51             words.append(lemmatizer.lemmatize(pos[0]))
52 
53 # remove stopwords
54 stopwords = nltk.corpus.stopwords.words('english')
55 words = [w for w in words if w.lower() not in stopwords]
56 
57 # and calculate frequencies
58 freq = nltk.FreqDist(words)
59 
60 # sort descending on frequency
61 f = sorted(freq.items(), key=lambda x: x[1], reverse=True)
62 
63 # print top words
64 top_words = [w for w in f if w[1] > 1]
65 print(top_words, len(top_words))
66 
67 # plot 10 top words
68 top_words_transposed = list(zip(*top_words))
69 y_pos = np.arange(len(top_words_transposed[0][:10]))[::-1]
70 
71 plt.barh(y_pos, top_words_transposed[1][:10],
72     align='center', alpha=0.5)
73 plt.yticks(y_pos, top_words_transposed[0][:10])
74 plt.xlabel('Frequency')
75 plt.ylabel('Top words')
76 
77 plt.savefig('../../Data/Chapter09/charts/word_frequency.png',
78     dpi=300)       

原理:和之前的技巧一樣,我們先引入必要的子產品,讀入資料,做一些處理(拆解到句子,标記單詞)。

然後給每個詞打上詞類的标簽,識别出命名實體。

為了計數,我們要讓文本标準化一點,将命名實體當成一個詞組。我們用9.3節裡介紹過的.WordNetLemmatizer()方法來标準化單詞。對于命名實體,我們将清單用空格連在一起就好了,''.join([w[0]for w in pos]。

英語中最常見的單詞是停用詞,比如the、a、and以及in這種。統計這些詞對我們了解文章主題沒任何幫助。是以,我們對照NLTK提供的停用詞清單,移除這些不相幹的詞。這個清單有将近130個詞:

/*
[('gun', 35), ('Obama', 15), ('background', 13), ('check', 13), ('said', 9), ('law', 7), ('new', 7), ('dealer', 7), 
('president', 5), ('control', 5), ('sell', 5), ('prevent', 4), ('show', 4), ('many', 4), ('wa', 4), ('planned', 4),
 ('sale', 3), ('change', 3), ('death', 3), ('country', 3), ('plan', 3), ('measure', 3), ('take', 3), ('buyer', 3), 
('must', 3), ('conduct', 3), ('register', 3), ('say', 3), ('government', 3), ('action', 3), ('lawmaker', 3), 
('federal', 3), ('the White House', 3), ('day', 3), ('also', 3), ('Although', 2), ('unilaterally', 2), ('hoping', 2), 
('beefing', 2), ('enforcement', 2), ('existing', 2), ('least', 2), ('rife', 2), ('Monday', 2), ('sold', 2), ('online', 2), 
('violence', 2), ('Congress', 2), ('final', 2), ('office', 2), ('last', 2), ('fall', 2), ('Gun', 2), ('violent', 2),
 ('crime', 2), ('loophole', 2), ('issue', 2), ('guidance', 2), ('massacre', 2), ('still', 2), ('Democratic', 2), 
('would', 2), ('Clinton', 2), ('next', 2), ('easily', 2), ('step', 2), ('work', 2), ('administration', 2), ('number', 2),
 ('people', 2), ('agency', 2), ('Many', 2), ('purchase', 2), ('the FBI', 2), ('request', 2), ('system', 2), ('back', 2), 
('three', 2), ('buy', 2), ('without', 2), ('research', 2), ('weapon', 2), ('push', 2)] 83
 */      

最後,我們使用.FreqDist(...)方法統計單詞。這個方法傳入所有單詞的清單,僅僅統計出現的次數。然後我們将清單排序,隻列印出出現不止一次的單詞;這個清單包含83個單詞,并且出現最多的五個(并不意外地)是gun、Obama、background、check和said。

出現最多的10個單詞分布如下:

《資料分析實戰-托馬茲.卓巴斯》讀書筆記第9章--自然語言處理NLTK(分析文本、詞性标注、主題抽取、文本資料分類)

檢視單詞的分布,你可以推斷出,這篇文章是關于Obama總統、槍支管制、背景調查以及經銷商的。由于我們知道文章是關于什麼主題的,也就很容易挑選出相關的單詞,放在上下文中,是以我們可能本來就帶着偏向性;但是你也能看出,即使隻用頭10個單詞,也有猜出文章主題的可能性。

9.6識别句子結構

了解自由流文本的另一個重要方面就是句子結構:我們也許知道了詞類,但如果不知道詞語之間的關系,我們也了解不了上下文。

本技巧中,會學到如何識别句子的三個基本部分:NP(noun phrases,名詞短語)、VP(verb phrases,動詞短語)、PP(prepositional phrases,介詞短語)。

名詞短語包括一個名詞以及其修飾語(或形容詞)。NP可用來辨別句子的主語,當然也可以用來辨別句子中的其他成分。例如,句子My dog is lying on the carpet有兩個NP:my dog和on the carpet;前一個是主語,後一個是賓語。

動詞短語包括至少一個動詞,以及賓語、補語和修飾語。通常描述了主語施加在賓語上的動作。考慮前面的句子,VP就是lying on the carpet。

介詞短語以一個介詞(at、in、from和with等)打頭,後跟着名詞、代名詞、動名詞或從句。介詞短語有點像副詞或形容詞。舉個例子,A dog with white tail is lying on the carpet中的介詞短語就是with white tail,是一個介詞跟着一個NP。

準備:需要裝好NLTK。

步驟:本技巧重點是講清楚這個機制,我們隻從西雅圖時報的文章中選出兩個句子作為樣本——一個簡單句,另一個稍微複雜點(nlp_sentence.py檔案):

1 import nltk
 2 from nltk import data
 3 data.path.append(r'D:\download\nltk_data') # 這裡的路徑需要換成自己資料檔案下載下傳的路徑
 4 def print_tree(tree, filename):
 5     '''
 6         A method to save the parsed NLTK tree to a PS file
 7     '''
 8     # create the canvas
 9     canvasFrame = nltk.draw.util.CanvasFrame()
10 
11     # create tree widget
12     widget = nltk.draw.TreeWidget(canvasFrame.canvas(), tree)
13 
14     # add the widget to canvas
15     canvasFrame.add_widget(widget, 10, 10)
16 
17     # save the file
18     canvasFrame.print_to_file(filename)
19 
20     # release the object
21     canvasFrame.destroy()
22 
23 # two sentences from the article
24 sentences = ['Washington state voters last fall passed Initiative 594', 'The White House also said it planned to ask Congress for $500 million to improve mental health care, and Obama issued a memorandum directing federal agencies to conduct or sponsor research into smart gun technology that reduces the risk of accidental gun discharges.']
25 
26 # the simplest possible word tokenizer
27 sentences = [s.split() for s in sentences]
28 
29 # part-of-speech tagging
30 sentences = [nltk.pos_tag(s) for s in sentences]
31 
32 # pattern for recognizing structures of the sentence
33 pattern = '''
34   NP: {<DT|JJ|NN.*|CD>+}   # Chunk sequences of DT, JJ, NN
35   VP: {<VB.*><NP|PP>+}     # Chunk verbs and their arguments
36   PP: {<IN><NP>}           # Chunk prepositions followed by NP
37 '''
38 
39 # identify the chunks
40 NPChunker = nltk.RegexpParser(pattern)
41 chunks = [NPChunker.parse(s) for s in sentences]
42 
43 # save to file
44 print_tree(chunks[0], '../../Data/Chapter09/charts/sent1.ps')
45 print_tree(chunks[1], '../../Data/Chapter09/charts/sent2.ps')      

原理:我們将句子存在一個清單中。由于句子比較簡單,我們使用理論上最簡單的标記器;我們在每個空格符處打斷。然後是我們熟悉的詞類标記工作。

我們之前已經用過.RegexpParser(...)方法了,這裡模式不同。

本技巧中的導論部分提過,NP包括一個名詞和其修飾語。這裡的模式說得更精确,NP由DT(限定詞,the或a這種)、JJ(形容詞)、CD(數量詞)或名詞的變種組成:NN.*表示NN(單數名詞)、NNS(複數名詞)、NNP(單數可數名詞)和NNP(複數可數名詞)。

VP由各種形式的動詞組成:VB(基本型)、VBD(過去時)、VBG(動名詞或現在進行時)、VBN(過去進行時)、VBP(非第三人稱的單數現在形式)、VBZ(第三人稱單數形式),還有NP或PP。

PP如前所說,是介詞和NP的組合。

句子解析出的樹可以儲存到檔案。我們使用print_tree(...)方法,以tree作為第一個參數,以filename作為第二個參數。

這個方法中,我們先建立canvasFrame并往上加一個樹的挂件。.TreeWidget(...)方法将剛建立的畫布作為第一個參數,以tree作為第二個參數。然後,我們将挂件加到canvasFrame上,儲存到檔案。最後,我們釋放canvasFrame占據的記憶體。

不過,儲存樹時,唯一支援的檔案格式是postscript。要轉換為PDF格式的話,你可以使用一種線上轉換器,比如,https://online2pdf.com/convert-ps-to-pdf,或者類UNIX的機器,你可以使用下面的指令(假設你在Codes/Chapter09檔案夾下):

convert -desity 300 ../../data/Chapter09/charts/sent1.ps Chapter9/Charts/sent1.png      

得到的樹(第一個句子)如下所示:

《資料分析實戰-托馬茲.卓巴斯》讀書筆記第9章--自然語言處理NLTK(分析文本、詞性标注、主題抽取、文本資料分類)

我們得到的NP包括Washington、state、voters、last和fall構成的主語,動詞passed,以及賓語(另一個NP):Initiative NNP 594。

參考:這是另一個例子:https://www.eecis.udel.edu/~trnka/CISC889-11S/lectures/dongqing-chunking.pdf。

9.7根據評論給影片歸類

萬事俱備,現在我們可以來點高階任務了:根據評論給影片歸類。本技巧中,我們會使用一個情感分析器以及樸素貝葉斯分類器來分類電影。

準備:需要裝好NLTK和JSON。

要花點功夫,不過最終代碼了解起來還是很簡單的(nlp_classify.py檔案):

1 # this is needed to load helper from the parent folder
 2 import sys
 3 sys.path.append('..')
 4 
 5 # the rest of the imports
 6 import helper as hlp
 7 import nltk
 8 from nltk import data
 9 data.path.append(r'D:\download\nltk_data') # 這裡的路徑需要換成自己資料檔案下載下傳的路徑
10 import nltk.sentiment as sent
11 import json
12 
13 @hlp.timeit
14 def classify_movies(train, sentim_analyzer):
15     '''
16         Method to estimate a Naive Bayes classifier
17         to classify movies based on their reviews
18         估算第一個樸素貝葉斯分類器,以根據評論歸類電影
19     '''
20     nb_classifier = nltk.classify.NaiveBayesClassifier.train
21     classifier = sentim_analyzer.train(nb_classifier, train)
22 
23     return classifier
24 
25 @hlp.timeit
26 def evaluate_classifier(test, sentim_analyzer):
27     '''
28         Method to estimate a Naive Bayes classifier
29         to classify movies based on their reviews
30         估算第一個樸素貝葉斯分類器,以根據評論歸類電影
31     '''
32     for key, value in sorted(sentim_analyzer.evaluate(test).items()):
33         print('{0}: {1}'.format(key, value))
34 
35 # read in the files
36 f_training = '../../Data/Chapter09/movie_reviews_train.json'
37 f_testing  = '../../Data/Chapter09/movie_reviews_test.json'
38 with open(f_training, 'r') as f:
39     read = f.read()
40     train = json.loads(read)
41 
42 with open(f_testing, 'r') as f:
43     read = f.read()
44     test = json.loads(read)
45 
46 # tokenize the words
47 tokenizer = nltk.tokenize.TreebankWordTokenizer()
48 
49 train = [(tokenizer.tokenize(r['review']), r['sentiment'])
50     for r in train]
51 
52 test  = [(tokenizer.tokenize(r['review']), r['sentiment'])
53     for r in test]
54 
55 # analyze the sentiment of reviews
56 sentim_analyzer = sent.SentimentAnalyzer()
57 all_words_neg_flagged = sentim_analyzer.all_words(
58     [sent.util.mark_negation(doc) for doc in train])
59 
60 # get most frequent words
61 unigram_feats = sentim_analyzer.unigram_word_feats(
62     all_words_neg_flagged, min_freq=4)
63 
64 # add feature extractor
65 sentim_analyzer.add_feat_extractor(
66     sent.util.extract_unigram_feats, unigrams=unigram_feats)
67 
68 # and create the training and testing using the newly created
69 # features
70 train = sentim_analyzer.apply_features(train)
71 test  = sentim_analyzer.apply_features(test)
72 
73 # what is left is to classify the movies and then evaluate
74 # the performance of the classifier
75 classify_movies(train, sentim_analyzer)
76 evaluate_classifier(test, sentim_analyzer)      

原理:按照慣例,我們一上來先引入必要的子產品。

準備好的訓練集和測試集以JSON格式存在Data/Chapter09檔案夾。你應當了解如何讀取JSON格式的檔案;如果你要複習一下,參考本書1.3節。

讀入檔案後,我們使用.TreebankWordTokenizer()标記評論。注意,我們不将評論拆分成句子;本技巧的思想是識别出每條評論中通常帶有負面意義的詞,并據此區分評論是正面的還是負面的。

标記化之後,對象train和test都是元組組成的清單,元組的第一個元素是評論标記化的單詞清單,第二個元素是識别出的情感:pos或neg。

準備好了資料集,我們可以建立用于訓練樸素貝葉斯分類器的特征。我們使用NLTK中的.SentimentAnalyzer()。

參考NLTK關于情感分析的文檔:http://www.nltk.org/api/nltk.sentiment.html。

.SentimentAnalyzer()幫我們串起來特征提取和分類的任務。

首先,我們要标出由于跟在否定形式之後而具備不同意義的單詞。比如這句:I love going out.I didn’t like yesterday’s outing,though.顯然,like跟在didn’t後面,并不是正面的意思。.util.mark_negation(...)方法處理單詞清單,标出跟在否定形式之後的單詞;剛剛這個例子中,會得到這樣的結果:

《資料分析實戰-托馬茲.卓巴斯》讀書筆記第9章--自然語言處理NLTK(分析文本、詞性标注、主題抽取、文本資料分類)

現在我們标記了單詞,便可以建立特征了。本例中,我們希望隻使用有一定出現頻率的詞:like_NEG隻出現一次的話不足以成為一個好的預測器。我們使用.unigram_word_feats(...)方法,得到(指定top_n參數)最常見的單詞或頻率高于min_freq的所有單詞。

.add_feat_extractor(...)方法為SentimentAnalyzer()對象提供了一個從文檔中提取單詞的新方法。我們這裡隻關注一進制文法,是以我們将.util.extract_unigram_feats作為傳入.add_feat_extractor(...)方法的第一個參數。一進制文法的關鍵詞參數是一進制文法特征的清單。

準備好特征提取器之後,我們可以轉換訓練集和測試集了。使用.apply_features(...)方法将單詞清單轉換成特征向量的清單,清單中每個元素标明評論是否包含某個單詞/特征。列印出來像是這樣(經過化簡):

《資料分析實戰-托馬茲.卓巴斯》讀書筆記第9章--自然語言處理NLTK(分析文本、詞性标注、主題抽取、文本資料分類)

下面就是分類器的訓練及評估了。我們将訓練集與sentiment_analyzer對象傳入classify_movies(...)方法。train(...)方法可傳入多種訓練器,這裡我們使用的是.classify.NaiveBayes-Classifier.train方法。使用.timeit裝飾器,我們測量出估算模型花費的時間:

/* 
Training classifier
The method classify_movies took 501.62 sec to run.
 */      

即,訓練分類器花了将近9分鐘。當然,更重要的是表現如何——樸素貝葉斯可是一種簡單的分類方法(參考本書3.3節)。

evaluate_classifier(...)方法傳入測試集與訓練好的分類器,進行評估,并列印出結果。整體精确度不太差,接近82%。這說明我們的訓練集很好地代表了這個問題:

/*
Training classifier
The method classify_movies took 501.62 sec to run.
Evaluating NaiveBayesClassifier results...
Accuracy: 0.81625
F-measure [neg]: 0.8206004393458629
F-measure [pos]: 0.8116833205226749
Precision [neg]: 0.8016213638531235
Precision [pos]: 0.8323699421965318
Recall [neg]: 0.8405
Recall [pos]: 0.792
The method evaluate_classifier took 1905.59 sec to run.
 */      

 對于一個簡單的分類器來說,Precision與Recall的值已經很不錯了。然而,估算這個模型花費的時間超過了30分鐘。

第9章完。

随書源碼官方下載下傳:

http://www.hzcourse.com/web/refbook/detail/7821/92

邀月注:本文版權由邀月和部落格園共同所有,轉載請注明出處。

助人等于自助!  [email protected]