探索 Python、機器學習和 NLTK 庫
挑戰:使用機器學習對RSS提要進行分類
最近,我接到一項任務,要求為客戶創建一個RSS提要分類子系統。目標是讀取幾十個甚至幾百個RSS提要,將它們的許多文章自動分類到幾十個預定義的主題領域當中。客戶網站的內容、導航和搜索功能都將由這個每日自動提要檢索和分類結果驅動。
客戶建議使用機器學習,或許還會使用ApacheMahout和Hadoop來實現該任務,因為客戶最近閱讀了有關這些技術的文章。但是,客戶的開發團隊和我們的開發團隊都更熟悉Ruby,而不是Java?技術。本文將介紹解決方案的技術之旅、學習過程和最終實現。
什么是機器學習?
我的第一個問題是,“究竟什么是機器學習?”我聽說過這個術語,并且隱約知道超級計算機IBM?Watson最近使用該技術在一場Jeopardy比賽中擊敗了人類競爭者。作為購物者和社交網絡活動參與者,我也知道Amazon.com和Facebook根據其購物者數據在提供建議(如產品和人)方面表現良好。總之,機器學習取決于IT、數學和自然語言的交集。它主要關注以下三個主題,但客戶的解決方案最終僅涉及前兩個主題:
分類。根據類似項目的一組訓練數據,將相關的項分配到任意預定義的類別
建議。根據類似項目的觀察來建議采用的項
集群。在一組數據內確定子組
Mahout和Ruby的選擇
理解了機器學習是什么之后,下一步是確定如何實現它。根據客戶的建議,Mahout是一個合適的起點。我從Apache下載了代碼,并開始了學習使用Mahout及其兄弟Hadoop實現機器學習的過程。不幸的是,我發現即使對于有經驗的Java開發人員而言,Mahout的學習曲線也很陡峭,并且不存在可用的樣例代碼。同樣不幸的是,機器學習缺乏基于Ruby的框架或gem。
發現Python和NLTK
我繼續搜索解決方案,并且在結果集中一直遇到"Python"。作為一名Ruby開發人員,雖然我還沒有學過該語言,但我也知道Python是一個面向相似對象的、基于文本的、可理解和動態的編程語言。盡管兩種語言之間存在一些相似之處,但我多年來都忽視了學習Python,將它視為一項多余的技能集。Python是我的“盲點”,我懷疑許多Ruby開發人員同行都是這樣認為的。
搜索機器學習的書籍,并更深入研究它們的目錄,我發現,有相當高比例的此類系統在使用Python作為其實現語言,并使用了一個被稱為NaturalLanguageToolkit(NLTK,自然語言工具包)的庫。通過進一步的搜索,我發現Python的應用比我意識到的還要廣泛,如GoogleAppEngine、YouTube和使用Django框架構建的網站。它甚至還預安裝在我每天都使用的MacOSX工作站上!此外,Python為數學、科學和工程提供了有趣的標準庫(例如,NumPy和SciPy)。
我決定推行一個Python解決方案,因為我找到了非常好的編碼示例。例如,下面這一行代碼就是通過HTTP讀取RSS提要并打印其內容所需的所有代碼:
printfeedparser.parse("http://feeds.nytimes.com/nyt/rss/Technology")
快速掌握Python
執行Python程序同樣很簡單。獲得一個名稱為locomotive_main.py的程序和三個參數,然后您就可以使用Python程序編譯并執行它:
$pythonlocomotive_main.pyarg1arg2arg3
Python使用清單1中的if__name__=="__main__":語法來確定文件本身是從命令行執行的還是從其他代碼導入的。為了讓文件變得可以執行,需要添加"__main__"檢測。
清單1.Main檢測
importsys
importtime
importlocomotive
if__name__=="__main__":
start_time=time.time()
iflen(sys.argv)>1:
app=locomotive.app.Application()
...additionallogic...
virtualenv
大多數Ruby開發人員熟悉系統范圍的庫或gem的問題。使用一組系統范圍內的庫的做法一般是不可取的,因為您的其中一個項目可能依賴于某個給定的庫的版本1.0.0,而另一個項目則依賴于版本1.2.7。同樣,Java開發人員都知道系統范圍的CLASSPATH存在同樣的問題。就像Ruby社區使用其rvm工具,而Python社區使用virtualenv工具(請參閱參考資料,以獲得相關鏈接)來創建獨立的執行環境,其中包含特定版本的Python和一組庫。清單2中的命令顯示了如何為您p1項目創建一個名為p1_env的虛擬環境,其中包含feedparser、numpy、scipy和nltk庫。
清單2.使用virualenv創建一個虛擬環境的命令
$sudopipinstallvirtualenv
$cd~
$mkdirp1
$cdp1
$virtualenvp1_env--distribute
$sourcep1_env/bin/activate
(p1_env)[~/p1]$pipinstallfeedparser
(p1_env)[~/p1]$pipinstallnumpy
(p1_env)[~/p1]$pipinstallscipy
(p1_env)[~/p1]$pipinstallnltk
(p1_env)[~/p1]$pipfreeze
每次在一個shell窗口使用您的項目時,都需要“獲得”您的虛擬環境激活腳本。請注意,在激活腳本被獲得后,shell提示符會改變。當在您的系統上創建和使用shell窗口,輕松地導航到您的項目目錄,并啟動其虛擬環境時,您可能想在您的~/.bash_profile文件中添加以下條目:
$aliasp1="cd~/p1;sourcep1_env/bin/activate"
代碼庫結構
在完成簡單的單文件“HelloWorld”程序的編寫之后,Python開發人員需要理解如何正確地組織其代碼庫的目錄和文件名。Java和Ruby語言在這方面都有各自的要求,Python也沒有什么不同。簡單來說,Python使用包的概念對相關的代碼進行分組,并提供了明確的名稱空間。出于演示目的,在本文中,代碼存在于某個給定項目的根目錄中,例如~/p1。在這個目錄中,存在一個用于相同名稱的Python包的locomotive目錄。清單3顯示了這個目錄結構。
清單3.示例目錄結構
locomotive_main.py
locomotive_tests.py
locomotive/
__init__.py
app.py
capture.py
category_associations.py
classify.py
news.py
recommend.py
rss.py
locomotive_tests/
__init__.py
app_test.py
category_associations_test.py
feed_item_test.pyc
rss_item_test.py
請注意名稱古怪的__init__.py文件。這些文件指示Python為您的包加載必要的庫和特定的應用程序代碼文件,它們都位于相同的目錄中。清單4顯示了文件locomotive/__init__.py的內容。
清單4.locomotive/__init__.py
#systemimports;loadsinstalledpackages
importcodecs
importlocale
importsys
#applicationimports;theseloadyourspecific*.pyfiles
importapp
importcapture
importcategory_associations
importclassify
importrss
importnews
importrecommend
有了結構如清單4所示的locomotive包之后,在項目的根目錄中的主程序就可以導入并使用它。例如,文件locomotive_main.py包含以下導入:
importsys#>--systemlibrary
importtime#>--systemlibrary
importlocomotive#>--customapplicationcodelibraryinthe"locomotive"directory
測試
Pythonunittest標準庫提供一個非常好的測試解決方案。熟悉JUnit的Java開發人員和熟悉Test::Unit框架的Ruby開發人員應該會覺得清單5中的Pythonunittest代碼很容易理解。
清單5.Pythonunittest
classAppTest(unittest.TestCase):
defsetUp(self):
self.app=locomotive.app.Application()
deftearDown(self):
pass
deftest_development_feeds_list(self):
feeds_list=self.app.development_feeds_list()
self.assertTrue(len(feeds_list)==15)
self.assertTrue('feed://news.yahoo.com/rss/stock-markets'infeeds_list)
清單5中的代碼還演示了Python的一個顯著的特點:所有的代碼必須一致縮進,否則無法成功編譯。tearDown(self)方法可能在開始時看起來有點古怪。您可能會問,為什么測試總是被硬編碼為通過?事實上并非如此。這只是在Python中編寫空方法的一種方式。
工具
我真正需要的是一個具備語法突出顯示、代碼完成和斷點調試功能的集成開發環境(IDE),用該環境幫助我掌握我的Python學習曲線。作為使用EclipseIDE進行Java開發的一名用戶,pyeclipse插件是我考慮的下一個工具。雖然該插件有時比較慢,但它工作得相當不錯。我最終投資了PyCharmIDE,它滿足了我的所有IDE要求。
在掌握了Python及其生態系統的基本知識之后,終于來到開始實現機器學習解決方案的時候。
使用Python和NLTK實現分類
實現解決方案涉及捕獲模擬的RSS提要、整理其文本、使用一個NaiveBayesClassifier和kNN算法對類別進行分類。下面將會介紹這些操作中的每一個。
捕獲和解析提要
該項目特別具有挑戰性,因為客戶還沒有定義目標RSS提要列表。因此,也不存在“訓練數據”。所以,在初始開發期間必須模擬提要和訓練數據。
我用來獲得示例提要數據的第一個方法是只提取在某個文本文件中指定的列表中的RSS提要。Python提供了一個很好的RSS提要解析庫,其名稱為feedparser,它抽象不同的RSS和Atom格式之間的差異。簡單的基于文本的對象序列化的另一個有用的庫被幽默地稱為pickle(泡菜)。這兩個庫在清單6的代碼中均有使用,清單6中的代碼將每一個RSS提要捕獲為“腌制過的”對象文件,以備后用。如您所見,Python代碼非常簡潔,且功能強大。
清單6.CaptureFeeds類
importfeedparser
importpickle
classCaptureFeeds:
def__init__(self):
for(i,url)inenumerate(self.rss_feeds_list()):
self.capture_as_pickled_feed(url.strip(),i)
defrss_feeds_list(self):
f=open('feeds_list.txt','r')
list=f.readlines()
f.close
returnlist
defcapture_as_pickled_feed(self,url,feed_index):
feed=feedparser.parse(url)
f=open('data/feed_'+str(feed_index)+'.pkl','w')
pickle.dump(feed,f)
f.close()
if__name__=="__main__":
cf=CaptureFeeds()
下一步的挑戰性之大是出乎意料的。現在,我有了樣例提要數據,必須對它進行分類,以便將它用作訓練數據。訓練數據是向您的分類算法提供的數據集,以便您能從中進行學習。
例如,我使用的樣例提要包括了體育電視網絡公司ESPN。提要的項目之一是關于DenverBroncos橄欖球隊的TimTebow被轉會到NewYorkJets橄欖球隊,在同一時間,Broncos簽了他們新的四分衛PeytonManning。提要結果中的另一個項目是BoeingCompany和它的新噴氣式飛機(jet)。所以,這里的問題是,應該將哪些具體的類別值分配給第一個故事?tebow、broncos、manning、jets、quarterback、trade和nfl這些值都是合適的。但只有一個值可以在訓練數據中被指定為訓練數據類別。同樣,在第二個故事中,類別應該是boeing還是jet?困難的部分在于這些細節。如果您的算法要產生精確的結果,那么大型訓練數據集的準確手工分類非常關鍵。要做到這一點,不應該低估所需的時間。
我需要使用更多的數據,而且這些數據必須已進行了準確的分類,這種情況很快就變得明顯。我可以在哪里找到這樣的數據呢?進入PythonNLTK。除了是一個出色的語言文本處理庫之外,它甚至還帶有可下載的示例數據集,或是其術語中的文集,以及可以輕松訪問此下載數據的應用程序編程接口。要安裝Reuters文集,可以運行如下所示的命令。會有超過10,000篇新聞文章將下載到您的~/nltk_data/corpora/reuters/目錄中。與RSS提要項目一樣,每篇Reuters新聞文章中都包含一個標題和一個正文,所以這個NLTK預分類的數據非常適合于模擬RSS提要。
$python#enteraninteractivePythonshell
>>>importnltk#importthenltklibrary
>>>nltk.download()#runtheNLTKDownloader,thenenter'd'Download
Identifier>reuters#specifythe'reuters'corpus
特別令人感興趣的是文件~/nltk_data/corpora/reuters/cats.txt。它包含了一個列表,其中包含文章文件名稱,以及為每個文章文件分配的類別。文件看起來如下所示,所以,子目錄test中的文件14828中的文章與主題grain有關。
test/14826trade
test/14828grain
自然語言是混亂的
RSS提要分類算法的原始輸入,當然是以英語書寫的文本。原始,確實如此。
從計算機處理的角度來看,英語或任何自然語言(口語或普通的語言)都是極不規范和不準確的。首先,存在大小寫的問題。單詞Bronco是否等于bronco?答案是,也許是。接下來,您要應付標點和空格。bronco.是否等于bronco或bronco,?算是吧。然后,有復數形式和相似的單詞。run、running和ran是否相等?這取決于不同的情況。這三個詞有一個共同的詞根。如果將自然語言詞匯嵌入在標記語言(如HTML)中,情況會怎么樣呢?在這種情況下,您必須處理像bronco這樣的文本。最后,還有一個問題,就是那些經常使用但基本上毫無意義的單詞,像a、and和the。這些所謂的停用詞非常礙事。自然語言非常凌亂;在處理之前,需要對它們進行整理。
幸運的是,Python和NLTK讓您可以收拾這個爛攤子。在清單7中,RssItem類的normalized_words方法可以處理所有這些問題。請特別注意NLTK如何只使用一行代碼就能夠清潔嵌入式HTML標記的原始文章文本!使用一個正則表達式刪除標點,然后每個單詞被拆分,并規范化為小寫。
清單7.RssItem類
classRssItem:
...
regex=re.compile('[%s]'%re.escape(string.punctuation))
...
defnormalized_words(self,article_text):
words=[]
oneline=article_text.replace('','')
cleaned=nltk.clean_html(oneline.strip())
toks1=cleaned.split()
fort1intoks1:
translated=self.regex.sub('',t1)
toks2=translated.split()
fort2intoks2:
t2s=t2.strip().lower()
ifself.stop_words.has_key(t2s):
pass
else:
words.append(t2s)
returnwords
只需這一行代碼就可以從NLTK獲得停用詞列表;并且還支持其他自然語言:
nltk.corpus.stopwords.words('english')
NLTK還提供了一些“詞干分析器”類,以便進一步規范化單詞。請查看有關詞干、詞形歸并、句子結構和語法的NLTK文檔,了解有關的更多信息。
使用NaiveBayes算法進行分類
算法在NLTK中被廣泛使用并利用nltk.NaiveBayesClassifier類實現。Bayes算法根據特性在其數據集中的每個存在或不存在對項目進行分類。在RSS提要項目的情況下,每一個特性都是自然語言的一個給定的(清潔過的)單詞。該算法是“樸實”的,因為它假設特性(在本例中,單詞)之間沒有任何關系。
然而,英語這種語言包含超過250,000個單詞。當然,我不希望為了將RSS提要項目傳遞給算法就要為每個RSS提要項目創建一個包含250,000個布爾值的對象。那么,我會使用哪些單詞?簡單來說,答案是在培訓數據組中除了停用詞之外最常見的單詞。NLTK提供了一個優秀的類,即nltk.probability.FreqDist,我可以用它來識別這些最常用的單詞。在清單8中,collect_all_words方法返回來自所有培訓文章的所有單詞的一個數組。
然后,此數組被傳遞給identify_top_words方法,以確定最頻繁的單詞。nltk.FreqDist類的一個有用的特性是,它實質上是一個散列,但是它的鍵按其對應的值或計數排序。因此,使用[:1000]Python語法可以輕松獲得最頻繁的1000個單詞。
清單8.使用nltk.FreqDist類
defcollect_all_words(self,items):
all_words=[]
foriteminitems:
forwinitem.all_words:
words.append(w)
returnall_words
defidentify_top_words(self,all_words):
freq_dist=nltk.FreqDist(w.lower()forwinall_words)
returnfreq_dist.keys()[:1000]
對于利用NLTKReuters文章數據模擬的RSS提要項目,我需要確定每個項目的類別。為此,我讀取前面提到的~/nltk_data/corpora/reuters/cats.txt文件。用Python讀取一個文件非常簡單,如下所示:
defread_reuters_metadata(self,cats_file):
f=open(cats_file,'r')
lines=f.readlines()
f.close()
returnlines
接下來的步驟是獲得每個RSS提要項目的特性。RssItem類的features方法(如下所示)可以做到這一點。在該方法中,在文章中的all_words數組首先被減少到一個較小的set對象,以消除重復的單詞。然后會遍歷top_words,并在該set中進行比較,確定是否存在重復的單詞。隨后返回1000個布爾值組成的一個散列,以w_為鍵,后面是單詞本身。這個Python非常簡潔。
deffeatures(self,top_words):
word_set=set(self.all_words)
features={}
forwintop_words:
features["w_%s"%w]=(winword_set)
returnfeatures
接下來,我收集了訓練集的RSS提要項目和它們各自的特性,并將它們傳遞給算法。清單9中的代碼演示了這個任務。請注意,分類器被訓練成為只有一行代碼。
清單9.訓練nltk.NaiveBayesClassifier
defclassify_reuters(self):
...
training_set=[]
foriteminrss_items:
features=item.features(top_words)
tup=(features,item.category)#tupisa2-elementtuple
featuresets.append(tup)
classifier=nltk.NaiveBayesClassifier.train(training_set)
NaiveBayesClassifier在運行中的Python程序的內存中,它現在是經過訓練的。現在,我只需遍歷需要進行分類的RSS提要項目集,并要求分類器猜測每個項目的類別。這很簡單。
foriteminrss_items_to_classify:
features=item.features(top_words)
category=classifier.classify(feat)
變得不那么樸實
如前所述,算法假設每個特性之間是沒有關系的。因此,像"machinelearning"和"learningmachine",或者"NewYorkJet"和"jettoNewYork"這樣的短語是等效的(to是一個停用詞)。在自然的語言上下文中,這些單詞之間有明顯的關系。所以,我怎么會讓算法變得“不那么天真”,并識別這些單詞的關系?
其中一個技巧是在特性集內包括常見的雙字詞(兩個單詞為一組)和三字詞(三個單詞為一組)。NLTK以nltk.bigrams(...)和nltk.trigrams(...)的形式對此提供了支持,現在我們對此應該不再感到驚訝了。正如可以從訓練數據組收集最常用的n個單詞那樣,也可以識別最常用的雙字詞和三字詞,并將它們用作特性。
您的結果會有所不同
對數據和算法進行完善是一門藝術。您是否應該進一步規范化單詞集,也許應該包括詞根?或者包括超過1000個最常用單詞?少一點是否合適?或者是否應該使用更大的訓練數據集?是否應該添加更多信用詞或“停用詞根”?這些都是您要問自己的正確問題。使用它們進行實驗,通過試錯法,您可以會為您的數據實現最佳算法。我發現,85%是一個很好的分類成功率。
利用k-NearestNeighbors算法提出建議
客戶希望顯示在選定類別或相似類別中的RSS提要項目。現在,這些項目已經用NaiveBayes算法進行分類,這一要求的第一部分已得到了滿足。較難的部分是實現“或相似類別”的要求。這是機器學習建議器系統開始發揮作用的地方。建議器系統根據其他項目的相似性來建議一個項目。Amazon.com的產品建議和Facebook的朋友建議就是此功能的很好的示例。
k-NearestNeighbors(kNN)是最常用的建議算法。思路是向它提供一組標簽(即類別),并且每個標簽都對應一個數據集。然后,該算法對各數據集進行了比較,以識別相似的項目。數據集由多個數值數組構成,數值的范圍往往被規范化為從0到1。然后,它可以從數據集識別相似的標簽。與只產生一個結果的NaiveBayes不同,kNN可以產生一個有排名的列表,其中包含若干(即,k的值)個建議。
我發現,建議器算法比分類算法更容易理解和實現,但對于本文來說,其代碼過于冗長,并且有復雜的數學,無法在這里詳述。請參閱由Manning出版的一本很好的新書MachineLearninginAction,獲取kNN編碼示例(請參閱參考資料中的鏈接)。在RSS提要項目實現的過程中,標簽值是項目類別,而數據集是最常用的1000個單詞的值數組。同樣,在構建這個數組時,一部分屬于科學范疇,一部分屬于數學范疇,還有一部分屬于藝術范疇。在數組中,每個單詞的值都可以是簡單的0或1的布爾值、文章中單詞出現次數的百分比、該百分比的指數值,或一些其他值。
結束語
探索Python、NLTK和機器學習一直是一個有趣的、令人愉快的經驗。Python語言強大而又簡潔,現在已成為我的開發工具包的核心部分。它非常適合于機器學習、自然語言和數學/科學應用程序。雖然本文中并沒有提到,但我還發現Python對于圖表和繪圖非常有用。
以上內容為大家介紹了探索Python、機器學習和NLTK庫,希望對大家有所幫助,如果想要了解更多Python相關知識,請關注千鋒教育。http://www.mobiletrain.org/

相關推薦HOT
更多>>
如何使用Pandas處理Excel?
如何使用Pandas處理Excel?做過行政或者人事,或者對此有過了解的小伙伴,一定對下發各個部分的表有著非常深刻的印象,最常見的就是需要我們將一...詳情>>
2023-11-14 07:43:15
python中np.insert()函數的使用方法
python中np.insert()函數的使用方法在numpy數組操作中,np.append()方法可以在每行每列的最后添加數據,但其位置是規定的,那如果想要指定添加...詳情>>
2023-11-14 05:06:13
SVM在python中的原理如何理解?
SVM在python中的原理如何理解?在python中除了編程化的知識點外,對于數學方法的算法也有所涉及,SVM就是一種很好地體現。我們學習過數學中的坐...詳情>>
2023-11-14 04:30:04
python處理絕對路徑和相對路徑函數有哪些?
python處理絕對路徑和相對路徑函數有哪些?絕對路徑和相對路徑是什么?絕對路徑:從根文件夾開始,Windows系統以盤符(C:)作為根文件夾,OSX或Lin...詳情>>
2023-11-14 03:33:02熱門推薦
如何使用python any()判斷多元素?
沸如何使用Pandas處理Excel?
熱python函數中的參數有哪些?
熱python中pygal模塊如何使用?
新Python的excel處理操作
python中doctest庫是什么?
python中series是什么意思
python中np.insert()函數的使用方法
SVM在python中的原理如何理解?
Python描述符中有哪三種方法?
python處理絕對路徑和相對路徑函數有哪些?
python單繼承和多繼承如何定義?
python封裝中的私有如何理解?
python模塊引入的三種方式
技術干貨






