處理文本數據?
本指南的目的是在一項實際任務上探索一些主要的scikit學習工具:分析有關二十個不同主題的文本文檔(新聞組帖子)的集合。
在本節中,我們將看到如何:
加載文件內容和類別
提取適合機器學習的特征向量
訓練線性模型以執行分類
使用網格搜索策略找到特征提取組件和分類器的良好配置
教程設置
要開始本教程,您必須首先安裝scikit-learn及其所有必需的依賴項。
請參閱安裝說明頁面以獲取更多信息和特定于系統的說明。
您可以在scikit-learn文件夾中找到本教程的源代碼:
scikit-learn/doc/tutorial/text_analytics/
這個資源也可以在Github上找到。
教程文件夾應包含以下子文件夾:
*.rst files - 用sphinx編寫的教程文檔的源文件
data - 數據,放置教程中使用的數據集的文件夾
skeletons - 框架,練習的例子,是不完整的腳本
solutions - 答案,練習的解決方案
您已經可以將框架復制到硬盤驅動器上名為sklearn_tut_workspace的某個位置的新文件夾中,您將在練習中編輯自己的文件,同時保持原始框架完整:
% cp -r skeletons work_directory/sklearn_tut_workspace
機器學習算法需要數據。轉到每個$ TUTORIAL_HOME / data子文件夾,然后從那里運行fetch_data.py腳本(首先閱讀它們之后)。
例如:
% cd $TUTORIAL_HOME/data/languages
% less fetch_data.py
% python fetch_data.py
加載20個新聞組數據集
該數據集稱為“二十個新聞組”。這是官方引用,引用自網站:http://people.csail.mit.edu/jrennie/20Newsgroups/
20個新聞組數據集是大約20,000個新聞組文檔的集合,在20個不同的新聞組中平均(幾乎)劃分。據我們所知,它最初是由Ken Lang收集的,可能是因為他的論文“ Newsweeder:學習過濾網絡新聞”,盡管他沒有明確提及該收集。 20個新聞組集合已成為用于機器學習技術的文本應用程序(例如文本分類和文本聚類)中的實驗的流行數據集。
在下面的內容中,我們將使用內置的數據集加載器加載來自scikit-learn的20個新聞組。另外,也可以從網站手動下載數據集,并通過將sklearn.datasets.load_files功能指向未壓縮存檔文件夾的20news-bydate-train子文件夾來使用它。
為了在第一個示例中獲得更快的執行速度,我們將處理部分數據集,該數據集中只有20個類別中的4個類別:
categories = ['alt.atheism', 'soc.religion.christian',
'comp.graphics', 'sci.med']
現在,我們可以按以下方式加載與這些類別匹配的文件列表:
from sklearn.datasets import fetch_20newsgroups
twenty_train = fetch_20newsgroups(subset='train',
categories=categories, shuffle=True, random_state=42)
返回的數據集是一個scikit-learn 'bunch'(可譯為sklearn枝):一個簡單的holder對象,其字段可以方便地用作python的字典的鍵或對象屬性來訪問,例如target_names包含所請求類別名稱的列表:
輸入:
twenty_train.target_names
輸出:
['alt.atheism', 'comp.graphics', 'sci.med', 'soc.religion.christian']
文件本身被加載到data屬性的內存中。作為參考,文件名也可用:
輸入:
len(twenty_train.data)
len(twenty_train.filenames)
輸出:
2257
2257
讓我們打印第一個已加載文件的第一行:
print("\n".join(twenty_train.data[0].split("\n")[:3]))
print(twenty_train.target_names[twenty_train.target[0]])
輸出:
From: sd345@city.ac.uk (Michael Collier)
Subject: Converting images to HP LaserJet III?
Nntp-Posting-Host: hampton
comp.graphics
監督學習算法將為訓練集中的每個文檔要求一個類別標簽。在這種情況下,類別是新聞組的名稱,新聞組的名稱也恰好是存放各個文檔的文件夾的名稱。
出于速度和空間效率的原因,scikit-learn將target屬性加載為整數數組,該整數數組對應于target_names列表中類別名稱的索引。每個樣本的類別整數id存儲在target屬性中:
twenty_train.target[:10]
輸出:
array([1, 1, 3, 3, 3, 3, 3, 2, 2, 2])
可以按以下方式獲取類別名稱:
for t in twenty_train.target[:10]:
print(twenty_train.target_names[t])
輸出:
comp.graphics
comp.graphics
soc.religion.christian
soc.religion.christian
soc.religion.christian
soc.religion.christian
soc.religion.christian
sci.med
sci.med
sci.med
您可能已經注意到,當我們調用 fetch_20newsgroups(..., shuffle=True, random_state=42)時,樣本是隨機洗牌的:如果您希望僅選擇樣本子集以快速訓練模型并獲得第一個樣本,并且在重新訓練完整的數據集之前,先對結果有個印象,那樣本隨機洗牌會很有用。
從文本文件中提取特征
為了對文本文檔執行機器學習,我們首先需要將文本內容轉換為數字特征向量。
單詞包
做到這一點最直觀的方法是使用單詞表示法:
給出現在訓練集的任何文檔中的每個單詞分配一個固定的整數id(例如,通過建立從單詞到整數索引的字典)。
對于每個文檔#i,計算每個單詞w的出現次數,并將其存儲在X [i,j]中作為特征#j的值,其中j是詞典中單詞w的索引。
單詞包形式意味著n_features是語料庫中不同單詞的數量:該數量通常大于100,000。
如果n_samples == 10000,則將X存儲為float32類型的NumPy數組將需要10000 x 100000 x 4字節= 4GB RAM,這在當今的計算機上幾乎無法管理。
幸運的是,X中的大多數值將為零,因為對于給定的文檔,將使用少于數千個不同的單詞。因此,我們說單詞袋通常是高維稀疏數據集。通過僅將特征向量的非零部分存儲在內存中,我們可以節省大量內存。
scipy.sparse矩陣是可以完成此操作的數據結構,而scikit-learn內置了對這些結構的支持。
使用scikit-learn對文本進行標記
CountVectorizer中包括文本預處理,標記化和停用詞過濾功能,該功能可構建功能字典并將文檔轉換為功能向量:
from sklearn.feature_extraction.text import CountVectorizer
count_vect = CountVectorizer()
X_train_counts = count_vect.fit_transform(twenty_train.data)
X_train_counts.shape
輸出:
(2257, 35788)
CountVectorizer支持N克單詞或連續字符的計數。擬合后,矢量化器將建立特征索引字典:
count_vect.vocabulary_.get(u'algorithm')
輸出:
4690
詞匯在詞匯表中的索引值與整個訓練語料庫中的詞頻相關。
從發生到高頻
出現次數是一個好的開始,但是存在一個問題:較長的文檔將比較短的文檔具有更高的平均計數值,即使它們可能談論相同的主題。
為了避免這些潛在的差異,只需將文檔中每個單詞的出現次數除以文檔中單詞的總數即可:這些新功能稱為術語頻率tf。
在tf之上的另一種改進是降低了語料庫中許多文檔中出現的單詞的權重,因此比僅在語料庫中較小部分出現的單詞信息少。
這種縮減稱為“術語頻率乘以文檔的倒數頻率”的tf–idf。
使用TfidfTransformer可以如下計算tf和tf–idf:
from sklearn.feature_extraction.text import TfidfTransformer
tf_transformer = TfidfTransformer(use_idf=False).fit(X_train_counts)
X_train_tf = tf_transformer.transform(X_train_counts)
X_train_tf.shape
輸出:
(2257, 35788)
在上面的示例代碼中,我們首先使用fit(..)方法使估算器擬合數據,其次使用transform(..)方法將計數矩陣轉換為tf-idf表示形式。通過跳過冗余處理,可以將這兩個步驟結合起來以更快地達到相同的最終結果。這是通過使用fit_transform(..)方法(如下所示)以及上一節中的注釋中提到的來完成的:
tfidf_transformer = TfidfTransformer()
X_train_tfidf = tfidf_transformer.fit_transform(X_train_counts)
X_train_tfidf.shape
輸出:
(2257, 35788)
訓練一個分類器
現在我們有了功能,我們可以訓練分類器以嘗試預測帖子的類別。 讓我們從樸素的貝葉斯分類器開始,它為該任務提供了一個很好的基準。 scikit-learn包含此分類器的多種變體; 多項式最適合單詞計數:
from sklearn.naive_bayes import MultinomialNB
clf = MultinomialNB().fit(X_train_tfidf, twenty_train.target)
為了嘗試預測新文檔的結果,我們需要使用與以前幾乎相同的特征提取鏈來提取特征。 區別在于我們在轉換器上調用transform而不是fit_transform,因為它們已經適合訓練集:
docs_new = ['God is love', 'OpenGL on the GPU is fast']
X_new_counts = count_vect.transform(docs_new)
X_new_tfidf = tfidf_transformer.transform(X_new_counts)
predicted = clf.predict(X_new_tfidf)
for doc, category in zip(docs_new, predicted):
print('%r => %s' % (doc, twenty_train.target_names[category]))
輸出:
'God is love' => soc.religion.christian
'OpenGL on the GPU is fast' => comp.graphics
建立管道
為了使vectorizer => Transformer =>分類器更易于使用,scikit-learn提供了類似于復合分類器的Pipeline類:
from sklearn.pipeline import Pipeline
text_clf = Pipeline([
('vect', CountVectorizer()),
('tfidf', TfidfTransformer()),
('clf', MultinomialNB()),
])
名稱vect,tfidf和clf(分類器)是任意的。 我們將在下面使用它們對合適的超參數執行網格搜索。 現在,我們可以使用一個命令來訓練模型:
text_clf.fit(twenty_train.data, twenty_train.target)
輸出:
Pipeline(...)
模型在測試集上的表現的評估
評估模型的預測準確性同樣容易:
import numpy as np
twenty_test = fetch_20newsgroups(subset='test',
categories=categories, shuffle=True, random_state=42)
docs_test = twenty_test.data
predicted = text_clf.predict(docs_test)
np.mean(predicted == twenty_test.target)
輸出:
0.8348...
我們達到了83.5%的準確性。 讓我們看看我們是否可以使用線性支持向量機(SVM)來做得更好,線性支持向量機被廣泛認為是最好的文本分類算法之一(盡管它比樸素的貝葉斯算法還慢一些)。 我們可以通過簡單地將另一個分類器對象插入我們的管道來改變學習者:
from sklearn.linear_model import SGDClassifier
text_clf = Pipeline([
('vect', CountVectorizer()),
('tfidf', TfidfTransformer()),
('clf', SGDClassifier(loss='hinge', penalty='l2',
alpha=1e-3, random_state=42,
max_iter=5, tol=None)),
])
text_clf.fit(twenty_train.data, twenty_train.target)
predicted = text_clf.predict(docs_test)
np.mean(predicted == twenty_test.target)
輸出:
Pipeline(...)
0.9101...
使用SVM,我們達到了91.3%的精度。 scikit-learn提供了更多實用程序來對結果進行更詳細的性能分析:
from sklearn import metrics
print(metrics.classification_report(twenty_test.target, predicted,
target_names=twenty_test.target_names))
metrics.confusion_matrix(twenty_test.target, predicted)
輸出:
precision recall f1-score support
alt.atheism 0.95 0.80 0.87 319
comp.graphics 0.87 0.98 0.92 389
sci.med 0.94 0.89 0.91 396
soc.religion.christian 0.90 0.95 0.93 398
accuracy 0.91 1502
macro avg 0.91 0.91 0.91 1502
weighted avg 0.91 0.91 0.91 1502
array([[256, 11, 16, 36],
[ 4, 380, 3, 2],
[ 5, 35, 353, 3],
[ 5, 11, 4, 378]])
不出所料,混亂矩陣顯示來自無神論和基督教的新聞組中的帖子彼此之間的混淆比計算機圖形更多。
使用網格搜索調參
我們已經在TfidfTransformer中遇到了一些參數,例如use_idf。分類器也傾向于具有許多參數。例如,MultinomialNB包含一個平滑參數alpha,而SGDClassifier在目標函數中具有懲罰參數alpha和可配置的損失和懲罰項(請參閱模塊文檔,或使用Python幫助函數獲取這些描述)。
無需調整鏈中各個組成部分的參數,而是可以在可能值的網格上詳盡搜索最佳參數。我們對帶有或不帶有idf的單詞或雙字母組的所有分類器進行了試驗,線性SVM的懲罰參數為0.01或0.001:
from sklearn.model_selection import GridSearchCV
parameters = {
'vect__ngram_range': [(1, 1), (1, 2)],
'tfidf__use_idf': (True, False),
'clf__alpha': (1e-2, 1e-3),
}
顯然,這種詳盡的搜索可能很昂貴。如果我們有多個CPU內核可供使用,我們可以告訴網格搜索器嘗試將這8個參數組合與n_jobs參數并行進行。如果我們將此參數的值設置為-1,則網格搜索將檢測到安裝了多少個內核并全部使用它們:
gs_clf = GridSearchCV(text_clf, parameters, cv=5, n_jobs=-1)
網格搜索實例的行為類似于普通的scikit-learn模型。讓我們對訓練數據的較小子集執行搜索,以加快計算速度:
gs_clf = gs_clf.fit(twenty_train.data[:400], twenty_train.target[:400])
在GridSearchCV對象上調用fit的結果是一個分類器,我們可以用來預測:
twenty_train.target_names[gs_clf.predict(['God is love'])[0]]
輸出:
'soc.religion.christian'
對象的best_score_和best_params_屬性存儲最佳平均得分和與該得分相對應的參數設置:
gs_clf.best_score_
for param_name in sorted(parameters.keys()):
print("%s: %r" % (param_name, gs_clf.best_params_[param_name]))
輸出:
0.9...
clf__alpha: 0.001
tfidf__use_idf: True
vect__ngram_range: (1, 1)
有關更詳細的搜索摘要,請訪問gs_clf.cv_results_。
可以將cv_results_參數作為DataFrame輕松導入到熊貓中以進行進一步檢查。
練習
要進行練習,請將“skeletons”文件夾的內容復制為名為“workspace”的新文件夾:
% cp -r skeletons workspace
然后,您可以編輯工作區的內容,而不必擔心丟失原始的練習說明。
然后觸發一個ipython shell,并運行帶有以下內容的進行中腳本:
[1] %run workspace/exercise_XX_script.py arg1 arg2 arg3
如果觸發了異常,請使用%debug啟動事后ipdb會話。
完善實現并進行迭代,直到練習完成為止。
對于每次練習,框架文件(skeletons)都會提供所有必要的導入語句、用于加載數據的樣板代碼以及用于評估模型的預測準確性的示例代碼。
練習1:語言識別
使用自定義預處理器和CharNGramAnalyzer(使用Wikipedia文章中的數據作為訓練集)編寫文本分類管道。
評估某些測試集的性能。
ipython命令行:
%run workspace/exercise_01_language_train_model.py data/languages/paragraphs/
練習2:電影評論的情感分析
編寫文本分類管道以將電影評論分類為正面或負面。
使用網格搜索找到一組好的參數。
在保留的測試集上評估性能。
ipython命令行:
%run workspace/exercise_02_sentiment.py data/movie_reviews/txt_sentoken/
練習3:CLI文本分類實用程序
使用先前練習的結果和標準庫的cPickle模塊,編寫一個命令行實用程序,該程序可檢測stdin上提供的某些文本的語言,并估計如果使用英語編寫的文本的極性(正或負)。
如果公用事業能夠為其預測給出置信度,則可得到加分。
之后該做什么?
以下是一些建議,可幫助您在完成本教程后進一步提高關于scikit-learn的感覺:
嘗試在CountVectorizer下使用分析器和標記規范化(token normalisation)。
如果沒有標簽,請嘗試對問題使用群集。
如果每個文檔有多個標簽(例如類別),請查看“多類和多標簽”部分。
嘗試使用截斷的SVD進行潛在的語義分析。
看一下使用核外分類(out-of-core Classification)從不適合計算機主內存的數據中學習。
看看Hashing Vectorizer,這是CountVectorizer的一種內存有效替代方案。