例
將計(jì)算結(jié)果顯示在 <output> 元素中:
<form oninput="x.value=parseInt(a.value)+parseInt(b.value)">0
<input type="range" id="a" value="50">100
+<input type="number" id="b" value="50">
=<output name="x" for="a b"></output>
</form>
瀏覽器支持
Firefox、Opera、Chrome 和 Safari 瀏覽器都支持 <output> 標(biāo)簽。
注意:Internet Explorer 瀏覽器不支持 <output> 標(biāo)簽。
標(biāo)簽定義及使用說明
<output> 標(biāo)簽作為計(jì)算結(jié)果輸出顯示(比如執(zhí)行腳本的輸出)。
在HTML 4.01 與 HTML5中的差異
<output> 標(biāo)簽是 HTML 5 中的新標(biāo)簽。
屬性
New:HTML5 新屬性。
屬性 | 值 | 描述 |
---|---|---|
forNew | element_id | 描述計(jì)算中使用的元素與計(jì)算結(jié)果之間的關(guān)系。 |
formNew | form_id | 定義輸入字段所屬的一個(gè)或多個(gè)表單。 |
nameNew | name | 定義對象的唯一名稱(表單提交時(shí)使用)。 |
全局屬性
<output> 標(biāo)簽支持全局屬性,查看完整屬性表 HTML全局屬性。
事件屬性
<output> 標(biāo)簽支持所有 HTML事件屬性。
如您還有不明白的可以在下面與我留言或是與我探討QQ群308855039,我們一起飛!
讀:本文的目標(biāo)是介紹一些Python庫,幫助你從類似于PDF和Word DOCX 這樣的二進(jìn)制文件中提取數(shù)據(jù)。我們也將了解和學(xué)習(xí)如何從網(wǎng)絡(luò)信息源(web feeds)(如RSS)中獲取數(shù)據(jù),以及利用一個(gè)庫幫助解析HTML文本并從文檔中提取原始文本。
我們還將學(xué)習(xí)如何從不同來源提取原始文本,對其進(jìn)行規(guī)范化,并基于它創(chuàng)建一個(gè)用戶定義的語料庫。
在本文中,你將學(xué)習(xí)7個(gè)不同的實(shí)例。我們將學(xué)習(xí)從PDF文件、Word文檔和Web中獲取數(shù)據(jù)。PDF和Word文檔是二進(jìn)制文件,通過Web,你將獲得HTML格式的數(shù)據(jù),因此,我們也會對數(shù)據(jù)執(zhí)行規(guī)范化和原始文本轉(zhuǎn)換任務(wù)。
作者:克里希納·巴夫薩、納雷什·庫馬爾、普拉塔普·丹蒂
如需轉(zhuǎn)載請聯(lián)系大數(shù)據(jù)(ID:hzdashuju)
作為一名NLP專家,你將要處理大量的文本內(nèi)容。當(dāng)你在處理文本時(shí),你必須知道一些字符串操作。我們將從幾個(gè)簡短的范例入手,幫助你理解str類及其在Python中的相關(guān)操作。
1. 準(zhǔn)備工作
這里,你僅僅需要Python解釋器和一個(gè)文本編輯器。我們將使用join(連接)、split(分割)、addition(加法)和multiplication(乘法)運(yùn)算符以及索引。
2. 如何實(shí)現(xiàn)
(1)創(chuàng)建一個(gè)新的Python文件,命名為StringOps1.py。
(2)定義以下兩個(gè)對象:
namesList=['Tuffy','Ali','Nysha','Tim' ] sentence='My dog sleeps on sofa'
第一個(gè)對象nameList是一個(gè)包含若干名字的字符串列表,第二個(gè)對象sentence是一個(gè)包含一句話的字符串對象。
(3)首先,我們看看join函數(shù)的特點(diǎn)以及它的功能:
names=';'.join(namesList) print(type(names), ':', names)
join()函數(shù)可以被任意一個(gè)string對象調(diào)用,它的輸入?yún)?shù)是一個(gè)str對象的列表。通過將調(diào)用字符串的內(nèi)容作為連接分隔符,它將所有str對象連接成一個(gè)str對象,并返回連接后的對象。運(yùn)行這兩行代碼后,你得到的輸出如下:
<class 'str'> : Tuffy;Ali;Nysha;Tim
(4)接下來,我們來看split方法的功能:
wordList=sentence.split(' ') print((type(wordList)), ':', wordList)
當(dāng)split函數(shù)調(diào)用一個(gè)字符串時(shí),它會將其內(nèi)容分割為多個(gè)str對象,創(chuàng)建一個(gè)包含這些字符串對象的列表,并返回該列表。該函數(shù)接受單個(gè)str對象作為參數(shù),表示分隔符。運(yùn)行代碼,得到如下輸出:
<class 'list'> : ['My', 'dog', 'sleeps', 'on', 'sofa']
(5)算術(shù)運(yùn)算符+和*也可以用于字符串。添加以下代碼并輸出:
additionExample='ganehsa' + 'ganesha' + 'ganesha' multiplicationExample='ganesha' * 2 print('Text Additions :', additionExample) print('Text Multiplication :', multiplicationExample)
我們首先看一下輸出結(jié)果,隨后討論其工作原理:
Text Additions: ganehsaganeshaganesha Text Multiplication: ganeshaganesha
+運(yùn)算符被稱為連接符,它將字符串連接為單個(gè)str對象,產(chǎn)生一個(gè)新的字符串。如前所述,我們也可以使用*運(yùn)算符對字符串做乘法。此外,需要注意的是這些操作不會添加任何額外的內(nèi)容,例如在字符串之間插入空格。
(6)接下來,我們來了解一下字符串中的字符索引。添加下列幾行代碼:
str='Python NLTK' print(str[1]) print(str[-3])
首先,我們聲明一個(gè)新的 string 對象。然后可以直接訪問字符串中的第二個(gè)字符(y)。這里還有個(gè)小技巧:Python允許你在訪問任何列表對象時(shí)使用負(fù)索引,比如說-1意味著最后一個(gè)成員,-2是倒數(shù)第二個(gè)成員,依此類推。例如,在前面代碼的str對象中,索引7和-4是相同的字符N:
Output: <class 'str'> : Tuffy;Ali;Nysha;Tim <class 'list'> : ['My', 'dog', 'sleeps', 'on', 'sofa'] Text Additions : ganehsaganeshaganesha Text Multiplication : ganeshaganesha y L
3. 工作原理
我們使用split()函數(shù)將一個(gè)字符串變成了一個(gè)字符串列表,并使用join()函數(shù)將一個(gè)字符串列表變成了一個(gè)字符串。接下來我們了解了有關(guān)字符串的一些算術(shù)運(yùn)算符的用法。
需要注意的是,我們不能在字符串中使用“-”(負(fù)號)和“/”(除法)運(yùn)算符。最后,我們了解了如何在任一字符串中訪問單個(gè)字符,特別值得一提的是,我們可以在訪問字符串時(shí)使用負(fù)索引。
本段實(shí)例非常簡單和直觀,主要是介紹Python允許的一些常見和不常見的字符串操作。接下來,我們將在以上操作基礎(chǔ)上繼續(xù)學(xué)習(xí)一些字符串操作。
接下來,我們將了解子字符串、字符串替換以及如何訪問一個(gè)字符串的所有字符。
1. 如何實(shí)現(xiàn)
(1)創(chuàng)建一個(gè)新的Python文件,命名為StringOps2.py并定義以下string對象:
str='NLTK Dolly Python'
(2)訪問str對象中以第四個(gè)字符作為結(jié)束的子串。
print('Substring ends at:',str[:4])
我們知道索引從零開始,因此將返回由第0個(gè)到第3個(gè)字符組成的子串。運(yùn)行代碼,輸出如下:
Substring ends at: NLTK
(3)訪問str對象中從某個(gè)點(diǎn)開始直到末尾的子串:
print('Substring starts from:',str[11:] )
以上代碼指示解釋器返回str對象中從索引11到結(jié)束的一個(gè)子串。運(yùn)行代碼,得到以下輸出:
Substring starts from: Python
(4)從str對象中訪問包含Dolly的子串。添加以下行:
print('Substring :',str[5:10])
以上代碼返回從索引5到10的字符,不包括第10個(gè)字符。輸出是:
Substring : Dolly
(5)我們在前一段中已經(jīng)了解了負(fù)索引在字符串操作中的應(yīng)用。現(xiàn)在我們試試看它在獲取子串中的作用:
print('Substring fancy:', str[-12:-7]) Run and check the output, it will be – Substring fancy: Dolly
這里得到的輸出與上一步完全相同!為了理解這個(gè)結(jié)果,我們做一些計(jì)算:-1表示最后一個(gè)字符,-2是倒數(shù)第二個(gè)字符,依此類推。你將會發(fā)現(xiàn)[5:10]和[-12:-7]在這個(gè)例子中得出的子串是相同的。
(6)了解in操作符在if語句中的用法:
if 'NLTK' in str: print('found NLTK')
運(yùn)行以上代碼,程序的輸出如下所示:
found NLTK
如上所示,in操作符會檢查左邊的字符串是否屬于右邊字符串的子串。
(7)了解str對象中replace函數(shù)的使用:
replaced=str.replace('Dolly', 'Dorothy') print('Replaced String:', replaced)
replace函數(shù)只需要兩個(gè)參數(shù)。第一個(gè)是需要被替換的子字符串,第二個(gè)是用來替換前面子字符串的新子字符串。replace函數(shù)返回一個(gè)新的string對象,并且它不會修改調(diào)用它的字符串,運(yùn)行代碼,有如下輸出:
Replaced String: NLTK Dorothy Python
(8)最后,迭代上面得到的replaced對象并訪問它的每一個(gè)字符:
print('Accessing each character:') for s in replaced: print(s)
以上操作每次在新的一行輸出replaced對象的每個(gè)字符,最終輸出如下:
Output: Substring ends at: NLTK Substring starts from: Python Substring : Dolly Substring fancy: Dolly found NLTK Replaced String: NLTK Dorothy Python Accessing each character: N L T K D o r o t h y P y t h o n
2. 工作原理
字符串對象只是一個(gè)字符列表。正如第一步所示,我們可以像訪問一個(gè)列表那樣用for語句來訪問字符串中的每個(gè)字符。任何列表的方括號內(nèi)的字符“:”表示我們想要的一個(gè)子列表。
方括號內(nèi),如果字符“:”之后是一個(gè)數(shù)字n,表示我們希望獲得一個(gè)從列表索引0開始到索引n-1結(jié)束的子列表。同樣地,一個(gè)數(shù)字m后跟著字符“:”,則表示我們想要一個(gè)從列表索引m開始到列表末尾的子列表。
這個(gè)實(shí)例是從Python中訪問PDF文件。首先,你需要安裝PyPDF2庫。
1. 準(zhǔn)備工作
假設(shè)你已經(jīng)安裝了pip。然后,在Python2或Python3版本上用pip安裝PyPDF2庫,你只需要在命令行中運(yùn)行以下命令:
pip install pypdf2
如果你成功安裝了PyPDF2庫,就完成了準(zhǔn)備工作。與此同時(shí),你需要通過以下鏈接下載一些我們將在本段用到的測試文檔:
https://www.dropbox.com/sh/bk18dizhsu1p534/AABEuJw4TArUbzJf4Aa8gp5Wa?dl=0
2. 如何實(shí)現(xiàn)
(1)創(chuàng)建一個(gè)新的Python文件,命名為pdf.py并添加以下代碼:
from PyPDF2 import PdfFileReader
這行代碼會導(dǎo)入PyPDF2庫中的PdfFileReader類。
(2)在上面創(chuàng)建的文件中添加如下Python函數(shù),它的功能是讀取一個(gè)PDF文件并返回其全文:
def getTextPDF(pdfFileName, password='')
該函數(shù)需要兩個(gè)參數(shù),一個(gè)是你要讀取的PDF文件路徑,一個(gè)是這個(gè)PDF文件的密碼(如果有的話)。可見,password 參數(shù)是可選的。
(3)現(xiàn)在我們來定義這個(gè)函數(shù)。在該函數(shù)下添加如下代碼:
pdf_file=open(pdfFileName, 'rb') read_pdf=PdfFileReader(pdf_file)
第一行代碼會以讀取和反向查找模式打開文件。第一行本質(zhì)是一個(gè)Python文件打開命令/函數(shù),僅能打開非文本的二進(jìn)制文件。第二行將打開的文件傳遞給PdfFileReader類,用于處理PDF文檔。
(4)如果文件設(shè)置了密碼保護(hù),接下來是解密被密碼保護(hù)的PDF文件:
if password !='': read_pdf.decrypt(password)
如果在函數(shù)調(diào)用時(shí)設(shè)置了密碼,那么我們在解密這個(gè)文件時(shí)也同樣需要密碼。
(5)從PDF文件中讀取文本:
text=[] for i in range(0,read_pdf.getNumPages()-1): text.append(read_pdf.getPage(i).extractText())
創(chuàng)建一個(gè)字符串列表,并將每一頁的文本都添加到這個(gè)列表中。
(6)返回最終的輸出結(jié)果:
return '\n'.join(text)
將列表中所有的字符串都連接起來,并且在每個(gè)字符串之間都加一個(gè)換行符,返回連接后的單一字符串。
(7)在pdf.py目錄下創(chuàng)建另一個(gè)名為TestPDFs.py 的文件,添加以下導(dǎo)入語句:
import pdf
(8)現(xiàn)在我們打印輸出兩個(gè)文檔中的文本,其中一個(gè)是受密碼保護(hù)的,一個(gè)是未加密的:
pdfFile='sample-one-line.pdf' pdfFileEncrypted='sample-one-line.protected.pdf' print('PDF 1: \n',pdf.getTextPDF(pdfFile)) print('PDF 2: \n',pdf.getTextPDF(pdfFileEncrypted,'tuffy'))
輸出:本實(shí)例的前六步只是創(chuàng)建了一個(gè)Python函數(shù),并不向控制臺輸出內(nèi)容,第七和第八步會輸出以下內(nèi)容:
This is a sample PDF document I am using to demonstrate in the tutorial. This is a sample PDF document password protected.
3. 工作原理
PyPDF2是用于提取PDF文件內(nèi)容的一個(gè)純Python庫。該庫有很多功能,可用于裁剪頁面、疊加圖像數(shù)字簽名、創(chuàng)建新的PDF文件等。但是,對NLP工程師需要實(shí)現(xiàn)的文本分析任務(wù)來說,該庫只用來讀取內(nèi)容。
在第二步中,以反向查找模式打開文件很重要,因?yàn)楫?dāng)加載文件內(nèi)容時(shí),PyPDF2模塊試圖從尾部開始讀取文件內(nèi)容。此外,如果PDF文件是受密碼保護(hù)的,而你沒有在訪問文件前解密文件,Python解釋器將拋出一個(gè)PdfReadError錯(cuò)誤。
這里,我們將學(xué)習(xí)如何加載和讀取Word/DOCX文檔。用于讀取Word/DOCX文件的相關(guān)庫會更加全面,在這些庫中我們還可以處理段落邊界、文本樣式以及對所謂的run對象的操作。我們將會了解以上提到的所有內(nèi)容,因?yàn)檫@些內(nèi)容在文本分析任務(wù)中是至關(guān)重要的。
Tip: 如果你沒有安裝Microsoft Word軟件,你可以使用Liber Office和Open Office軟件的開源版本來創(chuàng)建和編輯“.docx”文件。
1. 準(zhǔn)備工作
假設(shè)你已經(jīng)在你的機(jī)器上安裝了pip,我們將使用pip來安裝python-docx庫。不要將它與另一個(gè)名為docx的庫混淆,這是兩個(gè)完全不同的庫。我們將從python docx庫中導(dǎo)入docx對象。在命令行中執(zhí)行下面的命令將安裝這個(gè)庫:
pip install python-docx
成功安裝了該庫后,繼續(xù)下一步,我們將在這個(gè)實(shí)例中使用一個(gè)測試文檔,如果你已經(jīng)通過本文第一段提供的鏈接下載了所有文檔,你應(yīng)該已具備相關(guān)文檔。如果沒有,請從以下鏈接下載sample-one-line.docx文檔。
https://www.dropbox.com/sh/bk18dizhsu1p534/AABEuJw4TArUbzJf4Aa8gp5Wa?dl=0
現(xiàn)在,準(zhǔn)備工作就全部完成了。
2. 如何實(shí)現(xiàn)
(1)創(chuàng)建一個(gè)新的Python文件,命名為word.py并添加以下導(dǎo)入代碼:
import docx
這里只需導(dǎo)入python-docx模塊的docx對象。
(2)定義getTextWord函數(shù):
def getTextWord(wordFileName):
該函數(shù)需要一個(gè)字符串參數(shù)wordFileName,包含你要讀取的Word文件的絕對路徑。
(3)初始化doc 對象:
doc=docx.Document(wordFileName)
此時(shí)doc對象加載了你要讀取的Word文件。
(4)接下來我們要從已經(jīng)加載文檔的doc對象中讀取文本,添加以下代碼來實(shí)現(xiàn):
fullText=[] for para in doc.paragraphs: fullText.append(para.text)
首先初始化一個(gè)字符串列表fullText,然后采用for循環(huán)逐段從文檔中讀取文本,并把每段都放到fullText列表中去。
(5)然后,我們將所有的片段/段落連接為一個(gè)字符串對象,并將其作為函數(shù)的輸出結(jié)果返回:
return '\n'.join(fullText)
通過以上操作,我們將fullText數(shù)組的所有元素用“\ n”分隔符連接起來,并返回連接后的對象。最后保存該P(yáng)ython文件并退出。
(6)創(chuàng)建另一個(gè)Python文件,命名為TestDocX.py,并添加以下導(dǎo)入聲明:
import docx import word
這里只需導(dǎo)入docx庫以及我們在前五步中實(shí)現(xiàn)的word.py文件。
(7)現(xiàn)在我們將要讀取一個(gè)DOCX文件并使用我們在word.py中實(shí)現(xiàn)的API打印輸出它的全部內(nèi)容。添加以下兩行代碼:
docName='sample-one-line.docx' print('Document in full :\n',word.getTextWord(docName))
首先在第一行代碼中初始化文檔的路徑,然后使用API打印輸出文檔的全部內(nèi)容。當(dāng)你運(yùn)行這部分代碼時(shí),得到以下輸出:
Document in full :
這是一個(gè)帶有一些粗體文本、一些斜體文本和一些下劃線文本的PDF示例文檔。我們還嵌入了一個(gè)標(biāo)題,如下所示:
This is my TITLE. This is my third paragraph.
(8)正如前面提到的,Word / DOCX文檔是一個(gè)更加豐富的信息來源,除了提供文本內(nèi)容外,還能提供很多信息。現(xiàn)在我們來看有關(guān)段落的信息。添加以下四行代碼:
doc=docx.Document(docName) print('Number of paragraphs :',len(doc.paragraphs)) print('Paragraph 2:',doc.paragraphs[1].text) print('Paragraph 2 style:',doc.paragraphs[1].style)
以上代碼的第二行打印出了給定文檔中段落的數(shù)量。第三行打印出了文檔中第二段的內(nèi)容。而第四行將會打印出第二段的樣式,比如在這個(gè)例子中的樣式就是Title類型。當(dāng)你運(yùn)行以上代碼后,輸出將如下所示:
Number of paragraphs : 3 Paragraph 2: This is my TITLE. Paragraph 2 style: _ParagraphStyle('Title') id: 4374023248
(9)接下來,我們將了解什么是run對象。添加以下代碼:
print('Paragraph 1:',doc.paragraphs[0].text) print('Number of runs in paragraph 1:',len(doc.paragraphs[0].runs)) for idx, run in enumerate(doc.paragraphs[0].runs): print('Run %s : %s' %(idx,run.text))
首先,我們獲得文檔第一段的全部內(nèi)容。然后,我們獲得第一段中run對象的數(shù)目。最后,我們把每個(gè)run對象打印輸出。
(10)為了明確每個(gè)run對象的格式,添加以下代碼:
print('is Run 0 underlined:',doc.paragraphs[0].runs[5].underline) print('is Run 2 bold:',doc.paragraphs[0].runs[1].bold) print('is Run 7 italic:',doc.paragraphs[0].runs[3].italic)
這段代碼的各行分別在檢查相應(yīng)run對象的下劃線樣式、粗體樣式以及斜體樣式。最終輸出如下:
Output: Document in full : This is a sample PDF document with some text in BOLD, some in ITALIC and some underlined. We are also embedding a Title down below. This is my TITLE. This is my third paragraph. Number of paragraphs : 3 Paragraph 2: This is my TITLE. Paragraph 2 style: _ParagraphStyle('Title') id: 4374023248 Paragraph 1: This is a sample PDF document with some text in BOLD, some in ITALIC and some underlined. We're also embedding a Title down below. Number of runs in paragraph 1: 8 Run 0 : This is a sample PDF document with Run 1 : some text in BOLD Run 2 : , Run 3 : some in ITALIC Run 4 : and Run 5 : some underlined. Run 6 : We are also embedding a Title down below Run 7 : . is Run 0 underlined: True is Run 2 bold: True is Run 7 italic: True
3. 工作原理
首先,我們在word.py文件中寫了一個(gè)函數(shù),它將讀取給定的DOCX文件并返回一個(gè)包含文件全部內(nèi)容的字符串對象。前面的輸出內(nèi)容大都是不需要解釋的,我特別闡述了關(guān)于Paragraph和Run的輸出內(nèi)容。DOCX文件的結(jié)構(gòu)可以用python-docx庫的三個(gè)數(shù)據(jù)類型來表示,其中最高一級是Document對象。
每個(gè)文檔都包含多個(gè)段落。文檔中出現(xiàn)新的一行或一個(gè)回車,就表示開始一個(gè)新的段落。每個(gè)段落用多個(gè)Run對象表示段落內(nèi)格式的變化,這里的格式包含有字體、尺寸、顏色和其他樣式元素(如粗體、斜體、下劃線等等)。這些元素每次發(fā)生變化時(shí),都會創(chuàng)建一個(gè)新的Run對象。
現(xiàn)在我們要?jiǎng)?chuàng)建自己的語料庫,而不是使用從互聯(lián)網(wǎng)上得到的語料庫。
1. 準(zhǔn)備工作
在準(zhǔn)備方面,我們將使用本文第一個(gè)實(shí)例中提到的Dropbox文件夾中的幾個(gè)文件。如果你已經(jīng)從那個(gè)文件夾中下載了全部的文件,那么你已經(jīng)完成了準(zhǔn)備工作。否則,請從
https://www.dropbox.com/sh/bk18dizhsu1p534/AABEuJw4TArUbzJf4Aa8gp5Wa?dl=0
下載如下文件:
如果你沒有按照本文的順序來完成實(shí)例,那么你需要先回頭看看本文的前兩個(gè)實(shí)例。我們將用到本文前兩個(gè)實(shí)例中完成的兩個(gè)模塊 word.py和pdf.py。本段實(shí)例更多是關(guān)于本文前兩個(gè)實(shí)例所做工作的應(yīng)用以及語料庫概念的應(yīng)用。下面我們來看實(shí)際的代碼。
2. 如何實(shí)現(xiàn)
(1)創(chuàng)建一個(gè)新的Python文件,命名為createCorpus.py并添加以下代碼:
import os import word, pdf from nltk.corpus.reader.plaintext import PlaintextCorpusReader
我們導(dǎo)入os庫用于與文件有關(guān)的操作,word庫和pdf庫是本文前兩段完成的庫,最后導(dǎo)入的PlaintextCorpusReader是為了完成語料庫建立這一最終目標(biāo)。
(2)編寫一個(gè)簡單的函數(shù),用來打開并讀取一個(gè)純文本文件,并將其全部內(nèi)容作為string對象返回。添加以下代碼:
def getText(txtFileName): file=open(txtFileName, 'r') return file.read()
第一行代碼定義了函數(shù)及其輸入?yún)?shù)。第二行代碼以只讀方式打開文件(open函數(shù)的第二個(gè)參數(shù)r表示以只讀方式打開)。第三行代碼讀取打開文件的內(nèi)容并將其作為string對象返回。
(3)在磁盤或文件系統(tǒng)中創(chuàng)建一個(gè)新文件夾corpus。添加以下三行代碼:
newCorpusDir='mycorpus/' if not os.path.isdir(newCorpusDir): os.mkdir(newCorpusDir)
第一行定義的string對象包含了新文件夾名,第二行檢查該文件夾在磁盤或文件系統(tǒng)中是否存在,第三行則通過執(zhí)行os.mkdir()函數(shù)在磁盤上創(chuàng)建一個(gè)給定名字的文件夾。以上代碼執(zhí)行后將在你的Python文件所在的工作目錄下創(chuàng)建一個(gè)名為mycorpus的新文件夾。
(4)然后,逐個(gè)讀取前面提到的三個(gè)文件。首先從純文本文件開始,添加以下代碼:
txt1=getText('sample_feed.txt')
調(diào)用之前完成的getText函數(shù),它將讀取Sample_feed.txt文件并將輸出結(jié)果存入名為txt1的字符串對象中。
(5)現(xiàn)在,添加以下代碼來讀取PDF文件:
txt2=pdf.getTextPDF('sample-pdf.pdf')
這里使用了PDF.py模塊的getTextPDF()函數(shù),它將讀取sample-pdf.pdf文件并將文件內(nèi)容存入名為txt2的字符串對象中。
(6)最后,通過以下代碼讀取DOCX文件:
txt3=word.getTextWord('sample-one-line.docx')
這里使用了word.py模塊的getTexWord()函數(shù),它將讀取sample-one-line.docx文件并將文件內(nèi)容存入名為txt3的字符串對象中。
(7)接下來,將上面讀到的三個(gè)字符串對象寫到磁盤文件中。添加以下代碼:
files=[txt1,txt2,txt3] for idx, f in enumerate(files): with open(newCorpusDir+str(idx)+'.txt', 'w') as fout: fout.write(f)
(8)在mycorpus目錄下,也就是我們之前存放文件的目錄下新建一個(gè)PlainTextCorpus對象:
newCorpus=PlaintextCorpusReader(newCorpusDir, '.*')
以上一行代碼看似簡單,但是它在內(nèi)部做了很多的文本處理,如識別段落、句子、單詞等等。該函數(shù)的兩個(gè)參數(shù)分別是語料庫目錄的路徑以及要處理的文件名模式(這里我們已經(jīng)設(shè)置corpus reader可以處理該目錄下所有的文件)。通過以上步驟,我們創(chuàng)建了一個(gè)用戶自定義的語料庫。
(9)接下來,我們來看PlainTextCorpusReader是否加載正常。添加以下代碼來進(jìn)行測試:
print(newCorpus.words()) print(newCorpus.sents(newCorpus.fileids()[1])) print(newCorpus.paras(newCorpus.fileids()[0]))
第一行代碼將打印輸出語料庫包含的所有單詞數(shù)組(部分)。第二行代碼將打印輸出文件1.txt中的句子。第三行代碼將打印輸出文件0.txt中的段落:
Output: ['Five', 'months', '.', 'That', "'", 's', 'how', ...] [['A', 'generic', 'NLP'], ['(', 'Natural', 'Language', 'Processing', ')', 'toolset'], ...] [[['Five', 'months', '.']], [['That', "'", 's', 'how', 'long', 'it', "'", 's', 'been', 'since', 'Mass', 'Effect', ':', 'Andromeda', 'launched', ',', 'and', 'that', "'", 's', 'how', 'long', 'it', 'took', 'BioWare', 'Montreal', 'to', 'admit', 'that', 'nothing', 'more', 'can', 'be', 'done', 'with', 'the', 'ailing', 'game', "'", 's', 'story', 'mode', '.'], ['Technically', ',', 'it', 'wasn', "'", 't', 'even', 'a', 'full', 'five', 'months', ',', 'as', 'Andromeda', 'launched', 'on', 'March', '21', '.']], ...]
3. 工作原理
該實(shí)例最后一步的輸出很簡單直接,展示了各個(gè)對象不同的特征。輸出內(nèi)容的第一行是新語料庫的單詞列表,它與句子、段落、文件等更高級的結(jié)構(gòu)沒有關(guān)系。
第二行是1.txt文件中所有句子組成的列表,其中每個(gè)句子都是由該句子中單詞組成的列表。
第三行是0.txt文件中所有段落組成的列表,其中每個(gè)段落對象又是由該段落中的句子組成的列表。從中可以發(fā)現(xiàn),這些段落和句子保留了很多原有的結(jié)構(gòu)。
豐富網(wǎng)站摘要(Rich Site Summary,RSS)信息源(feed)是一種計(jì)算機(jī)可讀格式,用于傳送互聯(lián)網(wǎng)上定期更新的內(nèi)容。大多數(shù)提供通知信息的網(wǎng)站以這種格式提供更新,例如新聞文章、在線出版物等。訂閱者可以通過規(guī)范化格式定期訪問其更新信息。
1. 準(zhǔn)備工作
本段實(shí)例的目標(biāo)是讀取一個(gè)RSS信息源并訪問其中的一條內(nèi)容。為此,我們將使用全球之聲(Mashable)提供的RSS信息源。全球之聲是一個(gè)數(shù)字媒體網(wǎng)站。簡而言之,它是一個(gè)科技和社交媒體的博客列表。該網(wǎng)站的RSS信息源網(wǎng)址(URL)是:
http://feeds.mashable.com/Mashable
另外,我們需要用feedparser庫來讀取RSS信息源。打開終端并運(yùn)行以下命令即可在你的計(jì)算機(jī)上安裝這個(gè)庫:
pip install feedparser
安裝好feedparser庫后,我們就可以開始實(shí)現(xiàn)第一個(gè)讀取RSS信息源的Python程序。
2. 如何實(shí)現(xiàn)
(1)創(chuàng)建一個(gè)新的Python文件,命名為rssReader.py,并添加以下代碼:
import feedparser
(2)將全球之聲信息源(Mashable feed)載入內(nèi)存中,添加以下代碼:
myFeed=feedparser.parse("http://feeds.mashable.com/Mashable")
myFeed對象包含全球之聲信息源的第一頁,通過feedparser自動下載和解析該信息源并填充到合適的位置。myFeed對象的條目列表將包含每個(gè)帖子(post)。
(3)檢查當(dāng)前信息源的標(biāo)題并計(jì)算帖子數(shù)目:
print('Feed Title :', myFeed['feed']['title']) print('Number of posts :', len(myFeed.entries))
在第一行代碼中,我們通過myFeed對象獲取到了信息源的標(biāo)題。在第二行代碼中,我們計(jì)算了myFeed對象中entries對象的長度。如前所述,entries對象是一個(gè)包含解析后信息源中所有帖子的列表。運(yùn)行代碼,輸出如下所示:
Feed Title: Mashable Number of posts : 30
標(biāo)題是Mashable,當(dāng)前,Mashable每次最多存放30個(gè)帖子到信息源。
(4)從entries列表中獲取第一個(gè)post,并打印輸出其標(biāo)題:
post=myFeed.entries[0] print('Post Title :',post.title)
在第一行代碼中,我們獲取了entries列表中的第一個(gè)元素并將其加載到post對象中。在第二行代碼中,我們打印輸出了post對象的標(biāo)題。運(yùn)行代碼,輸出應(yīng)該與以下內(nèi)容相似:
Post Title: The moon literally blocked the sun on Twitter
這里提到輸出內(nèi)容應(yīng)該與其相似而不是完全一樣,是因?yàn)樾畔⒃丛诓粩嘧晕腋隆?/p>
(5)訪問post的原始HTML內(nèi)容,并將其打印輸出:
content=post.content[0].value print('Raw content :\n',content)
首先,我們訪問post的內(nèi)容對象并獲取其具體值,打印輸出如下:
Output: Feed Title: Mashable Number of posts : 30 Post Title: The moon literally blocked the sun on Twitter Raw content : <img alt="" src="http://m.jungjaehyung.com/uploadfile/2024/1012/20241012090441364.jpg" /><div style="float: right; width: 50px;"><a href="http://twitter.com/share?via=Mashable&text=The+moon+literally +blocked+the+sun+on+Twitter&url=http%3A%2F%2Fmashable.com%2F2017%2F 08%2F21%2Fmoon-blocks-sun-eclipse-2017- twitter%2F%3Futm_campaign%3DMash-Prod-RSS-Feedburner-All- Partial%26utm_cid%3DMash-Prod-RSS-Feedburner-All-Partial" style="margin: 10px;"> <p>The national space agency threw shade the best way it knows how: by blocking the sun. Yep, you read that right. </p> <div><div><blockquote> <p>HA HA HA I've blocked the Sun! Make way for the Moon<a >#Solar Eclipse2017</a> <a >pic.twitter.com/nZCoqBlSTe</a></p> <p>— NASA Moon (@NASAMoon) <a >Augus t 21, 2017</a></p> </blockquote></div></div>
3. 工作原理
互聯(lián)網(wǎng)上大多數(shù)的RSS信息源都以時(shí)間順序排列,將最新的帖子放到最上面。因此,在該實(shí)例中我們每次訪問的都是信息源提供的最新內(nèi)容。信息源本身是不斷更新的。所以,每次運(yùn)行程序時(shí),輸出的格式保持不變,但是輸出的內(nèi)容卻可能發(fā)生改變,這取決于信息源更新的速度。
另外,我們在控制臺直接輸出原始的HTML文本而不是其文本內(nèi)容。接下來,我們將解析HTML并從頁面獲取我們需要的信息。最后,本實(shí)例可以附加以下內(nèi)容:讀取你想要的任何信息源,將信息源中所有帖子的信息存儲到磁盤,并利用它創(chuàng)建一個(gè)純文本的語料庫。當(dāng)然,你可以從上一個(gè)和下一個(gè)實(shí)例中獲得啟發(fā)。
大多數(shù)情況下,你需要處理的網(wǎng)上數(shù)據(jù)都以HTML頁面的形式存在。因此,我們認(rèn)為有必要向你介紹Python的HTML解析方法。有很多Python模塊可以用來解析HTML,在接下來的實(shí)例中,我們將使用BeautifulSoup4庫來解析HTML。
1. 準(zhǔn)備工作
BeautifulSoup4包適用于Python2和Python3。在使用這個(gè)包之前,我們需要提前下載并將它安裝在解釋器上。和之前一樣,我們將使用pip來安裝這個(gè)包。在命令行運(yùn)行以下命令:
pip install beautifulsoup4
另外,你還需要本文Dropbox文件夾中的sample-html.html文件。如果你還沒有下載該文件,請從以下鏈接下載:
https://www.dropbox.com/sh/bk18dizhsu1p534/AABEuJw4TArUbzJf4Aa8gp5Wa?dl=0
2. 如何實(shí)現(xiàn)
(1)完成所有準(zhǔn)備工作后,從導(dǎo)入以下聲明開始:
from bs4 import BeautifulSoup
從bs4模塊中導(dǎo)入BeautifulSoup類,它將用于解析HTML。
(2)將一個(gè)HTML文件加載到BeautifulSoup對象中:
html_doc=open('sample-html.html', 'r').read() soup=BeautifulSoup(html_doc, 'html.parser')
在第一行代碼中,我們將sample-html.html文件的內(nèi)容加載到str對象html_doc中。然后,創(chuàng)建了一個(gè)BeautifulSoup對象,需要解析的HTML文件作為第一個(gè)參數(shù),html.parser作為第二個(gè)參數(shù)。通過以上操作,BeautifulSoup對象使用html解析器來解析文檔。它將文檔內(nèi)容加載到soup對象中進(jìn)行解析以備使用。
(3)soup對象最主要、最簡單且最有用的功能就是去除所有的HTML標(biāo)簽并獲取文本內(nèi)容。添加以下代碼:
print('\n\nFull text HTML Stripped:') print(soup.get_text())
在soup對象上調(diào)用的get_text()方法將返回HTML標(biāo)簽去除后的文件內(nèi)容。運(yùn)行以上代碼,將得到以下輸出:
Full text HTML Stripped: Sample Web Page Main heading This is a very simple HTML document Improve your image by including an image. Add a link to your favorite Web site. This is a new sentence without a paragraph break, in bold italics. This is purely the contents of our sample HTML document without any of the HTML tags.
(4)有時(shí)不僅需要去除HTML標(biāo)簽,可能還需要獲取特定標(biāo)簽的內(nèi)容。訪問其中的一個(gè)標(biāo)簽:
print('Accessing the <title> tag :', end=' ') print(soup.title)
soup.title將返回文件中的第一個(gè)標(biāo)題(title)標(biāo)簽。以上代碼的輸出如下所示:
Accessing the <title> tag : <title>Sample Web Page</title>
(5)現(xiàn)在,我們需要某個(gè)HTML標(biāo)簽的文本內(nèi)容。通過以下代碼獲取<h1>標(biāo)簽的內(nèi)容:
print('Accessing the text of <H1> tag :', end=' ') print(soup.h1.string)
soup.h1.string命令將返回以<h1>標(biāo)簽開頭的文本。以上代碼的輸出如下所示:
Accessing the text of <H1> tag : Main heading
(6)訪問標(biāo)簽的屬性。這里,我們將訪問img標(biāo)簽的alt屬性。添加以下代碼行:
print('Accessing property of <img> tag :', end=' ') print(soup.img['alt'])
通過仔細(xì)觀察,你會發(fā)現(xiàn)訪問標(biāo)簽屬性的語法和訪問標(biāo)簽文本的語法是不同的。運(yùn)行以上代碼,得到以下輸出:
Accessing property of <img> tag : A Great HTML Resource
(7)最后,一個(gè)HTML文件中同一類型的標(biāo)簽可能多次出現(xiàn)。使用“.”語法僅能獲取文件中第一次出現(xiàn)的標(biāo)簽。為了獲取所有的標(biāo)簽,我們將使用find_all()函數(shù),如下所示:
print('\nAccessing all occurences of the <p> tag :') for p in soup.find_all('p'): print(p.string)
在BeautifulSoup對象上調(diào)用find_all()函數(shù),參數(shù)是標(biāo)簽名,它將搜索整個(gè)HTML樹并返回符合條件的標(biāo)簽列表。我們使用for循環(huán)來遍歷該列表,并將BeautifulSoup對象中所有<p>標(biāo)簽的內(nèi)容/文本打印并輸出:
Output: Full text HTML Stripped: Sample Web Page Main heading This is a very simple HTML document Improve your image by including an image. Add a link to your favorite Web site. This is a new sentence without a paragraph break, in bold italics. Accessing the <title> tag : <title>Sample Web Page</title> Accessing the text of <H1> tag : Main heading Accessing property of <img> tag : A Great HTML Resource Accessing all occurences of the <p> tag : This is a very simple HTML document Improve your image by including an image. None
3. 工作原理
BeautifulSoup4是一個(gè)很方便的庫,可以用于解析任何HTML和XML內(nèi)容。它支持Python內(nèi)置的HTML解析器,但是你也可以使用其他第三方的解析器,例如,lxml解析器和純Python的html5lib解析器。
這里,我們使用Python內(nèi)置的HTML解析器。如果你了解了HTML并會編寫簡單的HTML代碼的話,輸出結(jié)果是非常容易理解的。
關(guān)于作者:克里希納·巴夫薩(KrishnaBhavsar)花了大約10年時(shí)間在各行業(yè)領(lǐng)域如酒店業(yè)、銀行業(yè)、醫(yī)療行業(yè)等進(jìn)行自然語言處理、社交媒體分析和文本挖掘方面的研究。他致力于用不同的NLP語料庫如StanfordCoreNLP、IBM的 SystemText和BigInsights、GATE和NLTK來解決與文本分析有關(guān)的行業(yè)問題。納雷什·庫馬爾(NareshKumar)曾為財(cái)富500強(qiáng)企業(yè)設(shè)計(jì)、實(shí)施和運(yùn)行超大型因特網(wǎng)應(yīng)用程序,在這方面他擁有超過十年的專業(yè)經(jīng)驗(yàn)。他是一位全棧架構(gòu)師,在電子商務(wù)、網(wǎng)絡(luò)托管、醫(yī)療、大數(shù)據(jù)及分析、數(shù)據(jù)流、廣告和數(shù)據(jù)庫等領(lǐng)域擁有豐富的實(shí)踐經(jīng)驗(yàn)。
本文摘編自《自然語言處理Python進(jìn)階》,經(jīng)出版方授權(quán)發(fā)布。
延伸閱讀《自然語言處理Python進(jìn)階》
推薦語:本書包含的實(shí)例可以讓你學(xué)會使用NLTK(處理NLP任務(wù)的主要Python平臺)完成自然語言處理的各種任務(wù),涵蓋了自然語言理解、自然語言處理和句法分析等。
自Ahmed BESBES
作者:Ahmed Besbes
機(jī)器之心編譯
參與:李詩萌、路
本文介紹了用于文本分類任務(wù)的 7 個(gè)模型,包括傳統(tǒng)的詞袋模型、循環(huán)神經(jīng)網(wǎng)絡(luò),也有常用于計(jì)算機(jī)視覺任務(wù)的卷積神經(jīng)網(wǎng)絡(luò),以及 RNN + CNN。
本文是我之前寫過的一篇基于推特?cái)?shù)據(jù)進(jìn)行情感分析的文章(https://ahmedbesbes.com/sentiment-analysis-on-twitter-using-word2vec-and-keras.html)的延伸內(nèi)容。那時(shí)我建立了一個(gè)簡單的模型:基于 keras 訓(xùn)練的兩層前饋神經(jīng)網(wǎng)絡(luò)。用組成推文的詞嵌入的加權(quán)平均值作為文檔向量來表示輸入推文。
我用的嵌入是用 gensim 基于語料庫從頭訓(xùn)練出來的 word2vec 模型。該是一個(gè)二分類任務(wù),準(zhǔn)確率能達(dá)到 79%。
本文目標(biāo)在于探索其他在相同數(shù)據(jù)集上訓(xùn)練出來的 NLP 模型,然后在給定的測試集上對這些模型的性能進(jìn)行評估。
我們將通過不同的模型(從依賴于詞袋表征的簡單模型到部署了卷積/循環(huán)網(wǎng)絡(luò)的復(fù)雜模型)了解能否得到高于 79% 的準(zhǔn)確率!
首先,將從簡單的模型開始,逐步增加模型的復(fù)雜度。這項(xiàng)工作是為了說明簡單的模型也能很有效。
我會進(jìn)行這些嘗試:
文末附有這些 NLP 技術(shù)的樣板代碼。這些代碼可以幫助你開啟自己的 NLP 項(xiàng)目并獲得最優(yōu)結(jié)果(這些模型中有一些非常強(qiáng)大)。
我們還可以提供一個(gè)綜合基準(zhǔn),我們可以利用該基準(zhǔn)分辨哪個(gè)模型最適合預(yù)測推文中的情緒。
在相關(guān)的 GitHub 庫中還有不同的模型、這些模型的預(yù)測結(jié)果以及測試集。你可以自己嘗試并得到可信的結(jié)果。
import os import re import warnings warnings.simplefilter("ignore", UserWarning) from matplotlib import pyplot as plt %matplotlib inline import pandas as pd pd.options.mode.chained_assignment=None import numpy as np from string import punctuation from nltk.tokenize import word_tokenize from sklearn.model_selection import train_test_split from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression from sklearn.metrics import accuracy_score, auc, roc_auc_score from sklearn.externals import joblib import scipy from scipy.sparse import hstack
0. 數(shù)據(jù)預(yù)處理
你可以從該鏈接(http://thinknook.com/twitter-sentiment-analysis-training-corpus-dataset-2012-09-22/)下載數(shù)據(jù)集。
加載數(shù)據(jù)并提取所需變量(情感及情感文本)。
該數(shù)據(jù)集包含 1,578,614 個(gè)分好類的推文,每一行都用 1(積極情緒)和 0(消極情緒)進(jìn)行了標(biāo)記。
作者建議用 1/10 的數(shù)據(jù)進(jìn)行測試,其余數(shù)據(jù)用于訓(xùn)練。
data=pd.read_csv('./data/tweets.csv', encoding='latin1', usecols=['Sentiment', 'SentimentText'])
data.columns=['sentiment', 'text']
data=data.sample(frac=1, random_state=42)
print(data.shape)
(1578614, 2)
for row in data.head(10).iterrows():
print(row[1]['sentiment'], row[1]['text'])
1 http://www.popsugar.com/2999655 keep voting for robert pattinson in the popsugar100 as well!!
1 @GamrothTaylor I am starting to worry about you, only I have Navy Seal type sleep hours.
0 sunburned...no sunbaked! ow. it hurts to sit.
1 Celebrating my 50th birthday by doing exactly the same as I do every other day - working on our websites. It's just another day.
1 Leah and Aiden Gosselin are the cutest kids on the face of the Earth
1 @MissHell23 Oh. I didn't even notice.
0 WTF is wrong with me?!!! I'm completely miserable. I need to snap out of this
0 Was having the best time in the gym until I got to the car and had messages waiting for me... back to the down stage!
1 @JENTSYY oh what happened??
0 @catawu Ghod forbid he should feel responsible for anything!
推文數(shù)據(jù)中存在很多噪聲,我們刪除了推文中的網(wǎng)址、主題標(biāo)簽和用戶提及來清理數(shù)據(jù)。
def tokenize(tweet): tweet=re.sub(r'http\S+', '', tweet) tweet=re.sub(r"#(\w+)", '', tweet) tweet=re.sub(r"@(\w+)", '', tweet) tweet=re.sub(r'[^\w\s]', '', tweet) tweet=tweet.strip().lower() tokens=word_tokenize(tweet) return tokens
將清理好的數(shù)據(jù)保存在硬盤上。
data['tokens']=data.text.progress_map(tokenize) data['cleaned_text']=data['tokens'].map(lambda tokens: ' '.join(tokens)) data[['sentiment', 'cleaned_text']].to_csv('./data/cleaned_text.csv') data=pd.read_csv('./data/cleaned_text.csv') print(data.shape) (1575026, 2) data.head()
既然數(shù)據(jù)集已經(jīng)清理干凈了,就可以準(zhǔn)備分割訓(xùn)練集和測試集來建立模型了。
本文數(shù)據(jù)都是用這種方式分割的。
x_train, x_test, y_train, y_test=train_test_split(data['cleaned_text'], data['sentiment'], test_size=0.1, random_state=42, stratify=data['sentiment']) print(x_train.shape, x_test.shape, y_train.shape, y_test.shape) (1417523,) (157503,) (1417523,) (157503,)
將測試集標(biāo)簽存儲在硬盤上以便后續(xù)使用。
pd.DataFrame(y_test).to_csv('./predictions/y_true.csv', index=False, encoding='utf-8')
接下來就可以應(yīng)用機(jī)器學(xué)習(xí)方法了。
1. 基于詞級 ngram 的詞袋模型
那么,什么是 n-gram 呢?
如圖所示,ngram 是將可在源文本中找到的長度為 n 的相鄰詞的所有組合。
我們的模型將以 unigrams(n=1)和 bigrams(n=2)為特征。
用矩陣表示數(shù)據(jù)集,矩陣的每一行表示一條推文,每一列表示從推文(已經(jīng)經(jīng)過分詞和清理)中提取的特征(一元模型或二元模型)。每個(gè)單元格是 tf-idf 分?jǐn)?shù)(也可以用更簡單的值,但 tf-idf 比較通用且效果較好)。我們將該矩陣稱為文檔-詞項(xiàng)矩陣。
略經(jīng)思考可知,擁有 150 萬推文的語料庫的一元模型和二元模型去重后的數(shù)量還是很大的。事實(shí)上,出于計(jì)算力的考慮,我們可將這個(gè)數(shù)設(shè)置為固定值。你可以通過交叉驗(yàn)證來確定這個(gè)值。
在向量化之后,語料庫如下圖所示:
I like pizza a lot
假設(shè)使用上述特征讓模型對這句話進(jìn)行預(yù)測。
由于我們使用的是一元模型和二元模型后,因此模型提取出了下列特征:
i, like, pizza, a, lot, i like, like pizza, pizza a, a lot
因此,句子變成了大小為 N(分詞總數(shù))的向量,這個(gè)向量中包含 0 和這些 ngram 的 tf-idf 分?jǐn)?shù)。所以接下來其實(shí)是要處理這個(gè)大而稀疏的向量。
一般而言,線性模型可以很好地處理大而稀疏的數(shù)據(jù)。此外,與其他模型相比,線性模型的訓(xùn)練速度也更快。
從過去的經(jīng)驗(yàn)可知,logistic 回歸可以在稀疏的 tf-idf 矩陣上良好地運(yùn)作。
vectorizer_word=TfidfVectorizer(max_features=40000, min_df=5, max_df=0.5, analyzer='word', stop_words='english', ngram_range=(1, 2)) vectorizer_word.fit(x_train, leave=False) tfidf_matrix_word_train=vectorizer_word.transform(x_train) tfidf_matrix_word_test=vectorizer_word.transform(x_test)
在為訓(xùn)練集和測試集生成了 tf-idf 矩陣后,就可以建立第一個(gè)模型并對其進(jìn)行測試。
tf-idf 矩陣是 logistic 回歸的特征。
lr_word=LogisticRegression(solver='sag', verbose=2) lr_word.fit(tfidf_matrix_word_train, y_train)
一旦訓(xùn)練好模型后,就可以將其應(yīng)用于測試數(shù)據(jù)以獲得預(yù)測值。然后將這些值和模型一并存儲在硬盤上。
joblib.dump(lr_word, './models/lr_word_ngram.pkl') y_pred_word=lr_word.predict(tfidf_matrix_word_test) pd.DataFrame(y_pred_word, columns=['y_pred']).to_csv('./predictions/lr_word_ngram.csv', index=False)
得到準(zhǔn)確率:
y_pred_word=pd.read_csv('./predictions/lr_word_ngram.csv') print(accuracy_score(y_test, y_pred_word)) 0.782042246814
第一個(gè)模型得到了 78.2% 的準(zhǔn)確率!真不賴。接下來了解一下第二個(gè)模型。
2. 基于字符級 ngram 的詞袋模型
我們從未說過 ngram 僅為詞服務(wù),也可將其應(yīng)用于字符上。
如你所見,我們將對字符級 ngram 使用與圖中一樣的代碼,現(xiàn)在直接來看 4-grams 建模。
基本上這意味著,像「I like this movie」這樣的句子會有下列特征:
I, l, i, k, e, ..., I li, lik, like, ..., this, ... , is m, s mo, movi, ...
字符級 ngram 很有效,在語言建模任務(wù)中,甚至可以比分詞表現(xiàn)得更好。像垃圾郵件過濾或自然語言識別這樣的任務(wù)就高度依賴字符級 ngram。
與之前學(xué)習(xí)單詞組合的模型不同,該模型學(xué)習(xí)的是字母組合,這樣就可以處理單詞的形態(tài)構(gòu)成。
基于字符的表征的一個(gè)優(yōu)勢是可以更好地解決單詞拼寫錯(cuò)誤的問題。
我們來運(yùn)行同樣的流程:
vectorizer_char=TfidfVectorizer(max_features=40000, min_df=5, max_df=0.5, analyzer='char', ngram_range=(1, 4)) vectorizer_char.fit(tqdm_notebook(x_train, leave=False)); tfidf_matrix_char_train=vectorizer_char.transform(x_train) tfidf_matrix_char_test=vectorizer_char.transform(x_test) lr_char=LogisticRegression(solver='sag', verbose=2) lr_char.fit(tfidf_matrix_char_train, y_train) y_pred_char=lr_char.predict(tfidf_matrix_char_test) joblib.dump(lr_char, './models/lr_char_ngram.pkl') pd.DataFrame(y_pred_char, columns=['y_pred']).to_csv('./predictions/lr_char_ngram.csv', index=False) y_pred_char=pd.read_csv('./predictions/lr_char_ngram.csv') print(accuracy_score(y_test, y_pred_char)) 0.80420055491
80.4% 的準(zhǔn)確率!字符級 ngram 模型的性能要比詞級的 ngram 更好。
3. 基于詞級 ngram 和字符級 ngram 的詞袋模型
與詞級 ngram 的特征相比,字符級 ngram 特征似乎提供了更好的準(zhǔn)確率。那么將字符級 ngram 和詞級 ngram 結(jié)合效果又怎么樣呢?
我們將兩個(gè) tf-idf 矩陣連接在一起,建立一個(gè)新的、混合 tf-idf 矩陣。該模型有助于學(xué)習(xí)單詞形態(tài)結(jié)構(gòu)以及與這個(gè)單詞大概率相鄰單詞的形態(tài)結(jié)構(gòu)。
將這些屬性結(jié)合在一起。
tfidf_matrix_word_char_train=hstack((tfidf_matrix_word_train, tfidf_matrix_char_train)) tfidf_matrix_word_char_test=hstack((tfidf_matrix_word_test, tfidf_matrix_char_test)) lr_word_char=LogisticRegression(solver='sag', verbose=2) lr_word_char.fit(tfidf_matrix_word_char_train, y_train) y_pred_word_char=lr_word_char.predict(tfidf_matrix_word_char_test) joblib.dump(lr_word_char, './models/lr_word_char_ngram.pkl') pd.DataFrame(y_pred_word_char, columns=['y_pred']).to_csv('./predictions/lr_word_char_ngram.csv', index=False) y_pred_word_char=pd.read_csv('./predictions/lr_word_char_ngram.csv') print(accuracy_score(y_test, y_pred_word_char)) 0.81423845895
得到了 81.4% 的準(zhǔn)確率。該模型只加了一個(gè)整體單元,但結(jié)果比之前的兩個(gè)都要好。
關(guān)于詞袋模型
現(xiàn)在要用到深度學(xué)習(xí)模型了。深度學(xué)習(xí)模型的表現(xiàn)優(yōu)于詞袋模型是因?yàn)樯疃葘W(xué)習(xí)模型能夠捕捉到句子中單詞間的順序依賴關(guān)系。這可能要?dú)w功于循環(huán)神經(jīng)網(wǎng)絡(luò)這一特殊神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)的出現(xiàn)了。
本文并未涵蓋 RNN 的理論基礎(chǔ),但該鏈接(http://colah.github.io/posts/2015-08-Understanding-LSTMs/)中的內(nèi)容值得一讀。這篇文章來源于 Cristopher Olah 的博客,詳細(xì)敘述了一種特殊的 RNN 模型:長短期記憶網(wǎng)絡(luò)(LSTM)。
在開始之前,要先設(shè)置一個(gè)深度學(xué)習(xí)專用的環(huán)境,以便在 TensorFlow 上使用 Keras。誠實(shí)地講,我試著在個(gè)人筆記本上運(yùn)行這些代碼,但考慮到數(shù)據(jù)集的大小和 RNN 架構(gòu)的復(fù)雜程度,這是很不實(shí)際的。還有一個(gè)很好的選擇是 AWS。我一般在 EC2 p2.xlarge 實(shí)例上用深度學(xué)習(xí) AMI(https://aws.amazon.com/marketplace/pp/B077GCH38C?qid=1527197041958&sr=0-1&ref_=srh_res_product_title)。亞馬遜 AMI 是安裝了所有包(TensorFlow、PyTorch 和 Keras 等)的預(yù)先配置過的 VM 圖。強(qiáng)烈推薦大家使用!
from keras.preprocessing.text import Tokenizer from keras.preprocessing.text import text_to_word_sequence from keras.preprocessing.sequence import pad_sequences from keras.models import Model from keras.models import Sequential from keras.layers import Input, Dense, Embedding, Conv1D, Conv2D, MaxPooling1D, MaxPool2D from keras.layers import Reshape, Flatten, Dropout, Concatenate from keras.layers import SpatialDropout1D, concatenate from keras.layers import GRU, Bidirectional, GlobalAveragePooling1D, GlobalMaxPooling1D from keras.callbacks import Callback from keras.optimizers import Adam from keras.callbacks import ModelCheckpoint, EarlyStopping from keras.models import load_model from keras.utils.vis_utils import plot_model
4. 沒有預(yù)訓(xùn)練詞嵌入的循環(huán)神經(jīng)網(wǎng)絡(luò)
RNN 可能看起來很可怕。盡管它們因?yàn)閺?fù)雜而難以理解,但非常有趣。RNN 模型封裝了一個(gè)非常漂亮的設(shè)計(jì),以克服傳統(tǒng)神經(jīng)網(wǎng)絡(luò)在處理序列數(shù)據(jù)(文本、時(shí)間序列、視頻、DNA 序列等)時(shí)的短板。
RNN 是一系列神經(jīng)網(wǎng)絡(luò)的模塊,它們彼此連接像鎖鏈一樣。每一個(gè)都將消息向后傳遞。強(qiáng)烈推薦大家從 Colah 的博客中深入了解它的內(nèi)部機(jī)制,下面的圖就來源于此。
我們要處理的序列類型是文本數(shù)據(jù)。對意義而言,單詞順序很重要。RNN 考慮到了這一點(diǎn),它可以捕捉長期依賴關(guān)系。
為了在文本數(shù)據(jù)上使用 Keras,我們首先要對數(shù)據(jù)進(jìn)行預(yù)處理。可以用 Keras 的 Tokenizer 類。該對象用 num_words 作為參數(shù),num_words 是根據(jù)詞頻進(jìn)行分詞后保留下來的最大詞數(shù)。
MAX_NB_WORDS=80000 tokenizer=Tokenizer(num_words=MAX_NB_WORDS) tokenizer.fit_on_texts(data['cleaned_text'])
當(dāng)分詞器適用于數(shù)據(jù)時(shí),我們就可以用分詞器將文本字符級 ngram 轉(zhuǎn)換為數(shù)字序列。
這些數(shù)字表示每個(gè)單詞在字典中的位置(將其視為映射)。
如下例所示:
x_train[15] 'breakfast time happy time'
這里說明了分詞器是如何將其轉(zhuǎn)換為數(shù)字序列的。
tokenizer.texts_to_sequences([x_train[15]]) [[530, 50, 119, 50]]
接下來在訓(xùn)練序列和測試序列中應(yīng)用該分詞器:
train_sequences=tokenizer.texts_to_sequences(x_train) test_sequences=tokenizer.texts_to_sequences(x_test)
將推文映射到整數(shù)列表中。但是由于長度不同,還是沒法將它們在矩陣中堆疊在一起。還好 Keras 允許用 0 將序列填充至最大長度。我們將這個(gè)長度設(shè)置為 35(這是推文中的最大分詞數(shù))。
MAX_LENGTH=35 padded_train_sequences=pad_sequences(train_sequences, maxlen=MAX_LENGTH) padded_test_sequences=pad_sequences(test_sequences, maxlen=MAX_LENGTH) padded_train_sequences array([[ 0, 0, 0, ..., 2383, 284, 9], [ 0, 0, 0, ..., 13, 30, 76], [ 0, 0, 0, ..., 19, 37, 45231], ..., [ 0, 0, 0, ..., 43, 502, 1653], [ 0, 0, 0, ..., 5, 1045, 890], [ 0, 0, 0, ..., 13748, 38750, 154]]) padded_train_sequences.shape (1417523, 35)
現(xiàn)在就可以將數(shù)據(jù)傳入 RNN 了。
以下是我將使用的架構(gòu)的一些元素:
雙向 GRU 的輸出是有維度的(批尺寸、時(shí)間步和單元)。這意味著如果用的是經(jīng)典的 256 的批尺寸,維度將會是 (256, 35, 200)。
def get_simple_rnn_model(): embedding_dim=300 embedding_matrix=np.random.random((MAX_NB_WORDS, embedding_dim)) inp=Input(shape=(MAX_LENGTH, )) x=Embedding(input_dim=MAX_NB_WORDS, output_dim=embedding_dim, input_length=MAX_LENGTH, weights=[embedding_matrix], trainable=True)(inp) x=SpatialDropout1D(0.3)(x) x=Bidirectional(GRU(100, return_sequences=True))(x) avg_pool=GlobalAveragePooling1D()(x) max_pool=GlobalMaxPooling1D()(x) conc=concatenate([avg_pool, max_pool]) outp=Dense(1, activation="sigmoid")(conc) model=Model(inputs=inp, outputs=outp) model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) return model rnn_simple_model=get_simple_rnn_model()
該模型的不同層如下所示:
plot_model(rnn_simple_model, to_file='./images/article_5/rnn_simple_model.png', show_shapes=True, show_layer_names=True)
在訓(xùn)練期間使用了模型檢查點(diǎn)。這樣可以在每個(gè) epoch 的最后將最佳模型(可以用準(zhǔn)確率度量)自動存儲(在硬盤上)。
filepath="./models/rnn_no_embeddings/weights-improvement-{epoch:02d}-{val_acc:.4f}.hdf5" checkpoint=ModelCheckpoint(filepath, monitor='val_acc', verbose=1, save_best_only=True, mode='max') batch_size=256 epochs=2 history=rnn_simple_model.fit(x=padded_train_sequences, y=y_train, validation_data=(padded_test_sequences, y_test), batch_size=batch_size, callbacks=[checkpoint], epochs=epochs, verbose=1) best_rnn_simple_model=load_model('./models/rnn_no_embeddings/weights-improvement-01-0.8262.hdf5') y_pred_rnn_simple=best_rnn_simple_model.predict(padded_test_sequences, verbose=1, batch_size=2048) y_pred_rnn_simple=pd.DataFrame(y_pred_rnn_simple, columns=['prediction']) y_pred_rnn_simple['prediction']=y_pred_rnn_simple['prediction'].map(lambda p: 1 if p >=0.5 else 0) y_pred_rnn_simple.to_csv('./predictions/y_pred_rnn_simple.csv', index=False) y_pred_rnn_simple=pd.read_csv('./predictions/y_pred_rnn_simple.csv') print(accuracy_score(y_test, y_pred_rnn_simple)) 0.826219183127
準(zhǔn)確率達(dá)到了 82.6%!這真是很不錯(cuò)的結(jié)果了!現(xiàn)在的模型表現(xiàn)已經(jīng)比之前的詞袋模型更好了,因?yàn)槲覀儗⑽谋镜男蛄行再|(zhì)考慮在內(nèi)了。
還能做得更好嗎?
5. 用 GloVe 預(yù)訓(xùn)練詞嵌入的循環(huán)神經(jīng)網(wǎng)絡(luò)
在最后一個(gè)模型中,嵌入矩陣被隨機(jī)初始化了。那么如果用預(yù)訓(xùn)練過的詞嵌入對其進(jìn)行初始化又當(dāng)如何呢?舉個(gè)例子:假設(shè)在語料庫中有「pizza」這個(gè)詞。遵循之前的架構(gòu)對其進(jìn)行初始化后,可以得到一個(gè) 300 維的隨機(jī)浮點(diǎn)值向量。這當(dāng)然是很好的。這很好實(shí)現(xiàn),而且這個(gè)嵌入可以在訓(xùn)練過程中進(jìn)行調(diào)整。但你還可以使用在很大的語料庫上訓(xùn)練出來的另一個(gè)模型,為「pizza」生成詞嵌入來代替隨機(jī)選擇的向量。這是一種特殊的遷移學(xué)習(xí)。
使用來自外部嵌入的知識可以提高 RNN 的精度,因?yàn)樗狭诉@個(gè)單詞的相關(guān)新信息(詞匯和語義),而這些信息是基于大規(guī)模數(shù)據(jù)語料庫訓(xùn)練和提煉出來的。
我們使用的預(yù)訓(xùn)練嵌入是 GloVe。
官方描述是這樣的:GloVe 是一種獲取單詞向量表征的無監(jiān)督學(xué)習(xí)算法。該算法的訓(xùn)練基于語料庫全局詞-詞共現(xiàn)數(shù)據(jù),得到的表征展示出詞向量空間有趣的線性子結(jié)構(gòu)。
本文使用的 GloVe 嵌入的訓(xùn)練數(shù)據(jù)是數(shù)據(jù)量很大的網(wǎng)絡(luò)抓取,包括:
下載壓縮文件要 2.03GB。請注意,該文件無法輕松地加載在標(biāo)準(zhǔn)筆記本電腦上。
GloVe 嵌入有 300 維。
GloVe 嵌入來自原始文本數(shù)據(jù),在該數(shù)據(jù)中每一行都包含一個(gè)單詞和 300 個(gè)浮點(diǎn)數(shù)(對應(yīng)嵌入)。所以首先要將這種結(jié)構(gòu)轉(zhuǎn)換為 Python 字典。
def get_coefs(word, *arr): try: return word, np.asarray(arr, dtype='float32') except: return None, None embeddings_index=dict(get_coefs(*o.strip().split()) for o in tqdm_notebook(open('./embeddings/glove.840B.300d.txt'))) embed_size=300 for k in tqdm_notebook(list(embeddings_index.keys())): v=embeddings_index[k] try: if v.shape !=(embed_size, ): embeddings_index.pop(k) except: pass embeddings_index.pop(None)
一旦創(chuàng)建了嵌入索引,我們就可以提取所有的向量,將其堆疊在一起并計(jì)算它們的平均值和標(biāo)準(zhǔn)差。
values=list(embeddings_index.values()) all_embs=np.stack(values) emb_mean, emb_std=all_embs.mean(), all_embs.std()
現(xiàn)在生成了嵌入矩陣。按照 mean=emb_mean 和 std=emb_std 的正態(tài)分布對矩陣進(jìn)行初始化。遍歷語料庫中的 80000 個(gè)單詞。對每一個(gè)單詞而言,如果這個(gè)單詞存在于 GloVe 中,我們就可以得到這個(gè)單詞的嵌入,如果不存在那就略過。
word_index=tokenizer.word_index
nb_words=MAX_NB_WORDS
embedding_matrix=np.random.normal(emb_mean, emb_std, (nb_words, embed_size))
oov=0
for word, i in tqdm_notebook(word_index.items()):
if i >=MAX_NB_WORDS: continue
embedding_vector=embeddings_index.get(word)
if embedding_vector is not None:
embedding_matrix[i]=embedding_vector
else:
oov +=1
print(oov)
def get_rnn_model_with_glove_embeddings():
embedding_dim=300
inp=Input(shape=(MAX_LENGTH, ))
x=Embedding(MAX_NB_WORDS, embedding_dim, weights=[embedding_matrix], input_length=MAX_LENGTH, trainable=True)(inp)
x=SpatialDropout1D(0.3)(x)
x=Bidirectional(GRU(100, return_sequences=True))(x)
avg_pool=GlobalAveragePooling1D()(x)
max_pool=GlobalMaxPooling1D()(x)
conc=concatenate([avg_pool, max_pool])
outp=Dense(1, activation="sigmoid")(conc)
model=Model(inputs=inp, outputs=outp)
model.compile(loss='binary_crossentropy',
optimizer='adam',
metrics=['accuracy'])
return model
rnn_model_with_embeddings=get_rnn_model_with_glove_embeddings()
filepath="./models/rnn_with_embeddings/weights-improvement-{epoch:02d}-{val_acc:.4f}.hdf5"
checkpoint=ModelCheckpoint(filepath, monitor='val_acc', verbose=1, save_best_only=True, mode='max')
batch_size=256
epochs=4
history=rnn_model_with_embeddings.fit(x=padded_train_sequences,
y=y_train,
validation_data=(padded_test_sequences, y_test),
batch_size=batch_size,
callbacks=[checkpoint],
epochs=epochs,
verbose=1)
best_rnn_model_with_glove_embeddings=load_model('./models/rnn_with_embeddings/weights-improvement-03-0.8372.hdf5')
y_pred_rnn_with_glove_embeddings=best_rnn_model_with_glove_embeddings.predict(
padded_test_sequences, verbose=1, batch_size=2048)
y_pred_rnn_with_glove_embeddings=pd.DataFrame(y_pred_rnn_with_glove_embeddings, columns=['prediction'])
y_pred_rnn_with_glove_embeddings['prediction']=y_pred_rnn_with_glove_embeddings['prediction'].map(lambda p:
1 if p >=0.5 else 0)
y_pred_rnn_with_glove_embeddings.to_csv('./predictions/y_pred_rnn_with_glove_embeddings.csv', index=False)
y_pred_rnn_with_glove_embeddings=pd.read_csv('./predictions/y_pred_rnn_with_glove_embeddings.csv')
print(accuracy_score(y_test, y_pred_rnn_with_glove_embeddings))
0.837203100893
準(zhǔn)確率達(dá)到了 83.7%!來自外部詞嵌入的遷移學(xué)習(xí)起了作用!本教程剩余部分都會在嵌入矩陣中使用 GloVe 嵌入。
6. 多通道卷積神經(jīng)網(wǎng)絡(luò)
這一部分實(shí)驗(yàn)了我曾了解過的卷積神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)(http://www.wildml.com/2015/11/understanding-convolutional-neural-networks-for-nlp/)。CNN 常用于計(jì)算機(jī)視覺任務(wù)。但最近我試著將其應(yīng)用于 NLP 任務(wù),而結(jié)果也希望滿滿。
簡要了解一下當(dāng)在文本數(shù)據(jù)上使用卷積網(wǎng)絡(luò)時(shí)會發(fā)生什么。為了解釋這一點(diǎn),我從 wildm.com(一個(gè)很好的博客)中找到了這張非常有名的圖(如下所示)。
了解一下使用的例子:I like this movie very much!(7 個(gè)分詞)
背后的原理是什么?
檢測到特殊模式會激活每一次卷積的結(jié)果。通過改變卷積核的大小和連接它們的輸出,你可以檢測多個(gè)尺寸(2 個(gè)、3 個(gè)或 5 個(gè)相鄰單詞)的模式。
模式可以是像是「我討厭」、「非常好」這樣的表達(dá)式(詞級的 ngram?),因此 CNN 可以在不考慮其位置的情況下從句子中分辨它們。
def get_cnn_model(): embedding_dim=300 filter_sizes=[2, 3, 5] num_filters=256 drop=0.3 inputs=Input(shape=(MAX_LENGTH,), dtype='int32') embedding=Embedding(input_dim=MAX_NB_WORDS, output_dim=embedding_dim, weights=[embedding_matrix], input_length=MAX_LENGTH, trainable=True)(inputs) reshape=Reshape((MAX_LENGTH, embedding_dim, 1))(embedding) conv_0=Conv2D(num_filters, kernel_size=(filter_sizes[0], embedding_dim), padding='valid', kernel_initializer='normal', activation='relu')(reshape) conv_1=Conv2D(num_filters, kernel_size=(filter_sizes[1], embedding_dim), padding='valid', kernel_initializer='normal', activation='relu')(reshape) conv_2=Conv2D(num_filters, kernel_size=(filter_sizes[2], embedding_dim), padding='valid', kernel_initializer='normal', activation='relu')(reshape) maxpool_0=MaxPool2D(pool_size=(MAX_LENGTH - filter_sizes[0] + 1, 1), strides=(1,1), padding='valid')(conv_0) maxpool_1=MaxPool2D(pool_size=(MAX_LENGTH - filter_sizes[1] + 1, 1), strides=(1,1), padding='valid')(conv_1) maxpool_2=MaxPool2D(pool_size=(MAX_LENGTH - filter_sizes[2] + 1, 1), strides=(1,1), padding='valid')(conv_2) concatenated_tensor=Concatenate(axis=1)( [maxpool_0, maxpool_1, maxpool_2]) flatten=Flatten()(concatenated_tensor) dropout=Dropout(drop)(flatten) output=Dense(units=1, activation='sigmoid')(dropout) model=Model(inputs=inputs, outputs=output) adam=Adam(lr=1e-4, beta_1=0.9, beta_2=0.999, epsilon=1e-08, decay=0.0) model.compile(optimizer=adam, loss='binary_crossentropy', metrics=['accuracy']) return model cnn_model_multi_channel=get_cnn_model() plot_model(cnn_model_multi_channel, to_file='./images/article_5/cnn_model_multi_channel.png', show_shapes=True, show_layer_names=True)
filepath="./models/cnn_multi_channel/weights-improvement-{epoch:02d}-{val_acc:.4f}.hdf5" checkpoint=ModelCheckpoint(filepath, monitor='val_acc', verbose=1, save_best_only=True, mode='max') batch_size=256 epochs=4 history=cnn_model_multi_channel.fit(x=padded_train_sequences, y=y_train, validation_data=(padded_test_sequences, y_test), batch_size=batch_size, callbacks=[checkpoint], epochs=epochs, verbose=1) best_cnn_model=load_model('./models/cnn_multi_channel/weights-improvement-04-0.8264.hdf5') y_pred_cnn_multi_channel=best_cnn_model.predict(padded_test_sequences, verbose=1, batch_size=2048) y_pred_cnn_multi_channel=pd.DataFrame(y_pred_cnn_multi_channel, columns=['prediction']) y_pred_cnn_multi_channel['prediction']=y_pred_cnn_multi_channel['prediction'].map(lambda p: 1 if p >=0.5 else 0) y_pred_cnn_multi_channel.to_csv('./predictions/y_pred_cnn_multi_channel.csv', index=False) y_pred_cnn_multi_channel=pd.read_csv('./predictions/y_pred_cnn_multi_channel.csv') print(accuracy_score(y_test, y_pred_cnn_multi_channel)) 0.826409655689
準(zhǔn)確率為 82.6%,沒有 RNN 那么高,但是還是比 BOW 模型要好。也許調(diào)整超參數(shù)(濾波器的數(shù)量和大小)會帶來一些提升?
7. RNN + CNN
RNN 很強(qiáng)大。但有人發(fā)現(xiàn)可以通過在循環(huán)層上疊加卷積層使網(wǎng)絡(luò)變得更強(qiáng)大。
這背后的原理在于 RNN 允許嵌入序列和之前單詞的相關(guān)信息,CNN 可以使用這些嵌入并從中提取局部特征。這兩個(gè)層一起工作可以稱得上是強(qiáng)強(qiáng)聯(lián)合。
更多相關(guān)信息請參閱:http://konukoii.com/blog/2018/02/19/twitter-sentiment-analysis-using-combined-lstm-cnn-models/
def get_rnn_cnn_model(): embedding_dim=300 inp=Input(shape=(MAX_LENGTH, )) x=Embedding(MAX_NB_WORDS, embedding_dim, weights=[embedding_matrix], input_length=MAX_LENGTH, trainable=True)(inp) x=SpatialDropout1D(0.3)(x) x=Bidirectional(GRU(100, return_sequences=True))(x) x=Conv1D(64, kernel_size=2, padding="valid", kernel_initializer="he_uniform")(x) avg_pool=GlobalAveragePooling1D()(x) max_pool=GlobalMaxPooling1D()(x) conc=concatenate([avg_pool, max_pool]) outp=Dense(1, activation="sigmoid")(conc) model=Model(inputs=inp, outputs=outp) model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) return model rnn_cnn_model=get_rnn_cnn_model() plot_model(rnn_cnn_model, to_file='./images/article_5/rnn_cnn_model.png', show_shapes=True, show_layer_names=True)
filepath="./models/rnn_cnn/weights-improvement-{epoch:02d}-{val_acc:.4f}.hdf5" checkpoint=ModelCheckpoint(filepath, monitor='val_acc', verbose=1, save_best_only=True, mode='max') batch_size=256 epochs=4 history=rnn_cnn_model.fit(x=padded_train_sequences, y=y_train, validation_data=(padded_test_sequences, y_test), batch_size=batch_size, callbacks=[checkpoint], epochs=epochs, verbose=1) best_rnn_cnn_model=load_model('./models/rnn_cnn/weights-improvement-03-0.8379.hdf5') y_pred_rnn_cnn=best_rnn_cnn_model.predict(padded_test_sequences, verbose=1, batch_size=2048) y_pred_rnn_cnn=pd.DataFrame(y_pred_rnn_cnn, columns=['prediction']) y_pred_rnn_cnn['prediction']=y_pred_rnn_cnn['prediction'].map(lambda p: 1 if p >=0.5 else 0) y_pred_rnn_cnn.to_csv('./predictions/y_pred_rnn_cnn.csv', index=False) y_pred_rnn_cnn=pd.read_csv('./predictions/y_pred_rnn_cnn.csv') print(accuracy_score(y_test, y_pred_rnn_cnn)) 0.837882453033
這樣可得到 83.8% 的準(zhǔn)確率,這也是到現(xiàn)在為止最好的結(jié)果。
8. 總結(jié)
在運(yùn)行了 7 個(gè)不同的模型后,我們對比了一下:
import seaborn as sns from sklearn.metrics import roc_auc_score sns.set_style("whitegrid") sns.set_palette("pastel") predictions_files=os.listdir('./predictions/') predictions_dfs=[] for f in predictions_files: aux=pd.read_csv('./predictions/{0}'.format(f)) aux.columns=[f.strip('.csv')] predictions_dfs.append(aux) predictions=pd.concat(predictions_dfs, axis=1) scores={} for column in tqdm_notebook(predictions.columns, leave=False): if column !='y_true': s=accuracy_score(predictions['y_true'].values, predictions[column].values) scores[column]=s scores=pd.DataFrame([scores], index=['accuracy']) mapping_name=dict(zip(list(scores.columns), ['Char ngram + LR', '(Word + Char ngram) + LR', 'Word ngram + LR', 'CNN (multi channel)', 'RNN + CNN', 'RNN no embd.', 'RNN + GloVe embds.'])) scores=scores.rename(columns=mapping_name) scores=scores[['Word ngram + LR', 'Char ngram + LR', '(Word + Char ngram) + LR', 'RNN no embd.', 'RNN + GloVe embds.', 'CNN (multi channel)', 'RNN + CNN']] scores=scores.T ax=scores['accuracy'].plot(kind='bar', figsize=(16, 5), ylim=(scores.accuracy.min()*0.97, scores.accuracy.max() * 1.01), color='red', alpha=0.75, rot=45, fontsize=13) ax.set_title('Comparative accuracy of the different models') for i in ax.patches: ax.annotate(str(round(i.get_height(), 3)), (i.get_x() + 0.1, i.get_height() * 1.002), color='dimgrey', fontsize=14)
我們可以很快地看出在這些模型的預(yù)測值之間的關(guān)聯(lián)。
fig=plt.figure(figsize=(10, 5)) sns.heatmap(predictions.drop('y_true', axis=1).corr(method='kendall'), cmap="Blues", annot=True);
結(jié)論
以下是幾條我認(rèn)為值得與大家分享的發(fā)現(xiàn):
這篇文章很長。希望本文能對大家有所幫助。
*請認(rèn)真填寫需求信息,我們會在24小時(shí)內(nèi)與您取得聯(lián)系。