# 處理文本數據
校驗者:
[@NellyLuo](https://github.com/NellyLuo)
[@那伊抹微笑](https://github.com/apachecn/scikit-learn-doc-zh)
[@微光同塵](https://github.com/apachecn/scikit-learn-doc-zh)
翻譯者:
[@Lielei](https://github.com/apachecn/scikit-learn-doc-zh)
本指南旨在一個單獨實際任務中探索一些主要的 `scikit-learn` 工具: 分析關于 20 個不同主題的一個文件集合(新聞組帖子)。
在本節中,我們將會學習如何:
> - 讀取文件內容以及所屬的類別
> - 提取合適于機器學習的特征向量
> - 訓練一個線性模型來進行分類
> - 使用網格搜索策略找到特征提取組件和分類器的最佳配置
## 教程設置
開始這篇教程之前,你必須首先安裝 *scikit-learn* 以及所有其要求的庫。
更多信息和系統安裝指導請參考 [安裝說明](../../install.html#installation-instructions) 。
這篇入門教程的源代碼可以在你的 scikit-learn 文件夾下面找到:
```
scikit-learn/doc/tutorial/text_analytics/
```
這個入門教程包含以下的子文件夾:
> - `*.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 個新聞組的數據集
該數據集名為 “Twenty Newsgroups” 。 下面是這個數據集的官方介紹, 引自 [網站](http://people.csail.mit.edu/jrennie/20Newsgroups/):
> Twenty Newsgroups 數據集是一個包括近 20,000 個新聞組文檔的集合,(幾乎)平均分成了 20 個不同新聞組。 據我們所知,這最初是由 Ken Lang 收集的 ,很可能是為了他的論文 “Newsweeder: Learning to filter netnews,” 盡管他沒有明確提及這個集合。 這 20 個新聞組集合已成為一個流行的數據集,用于機器學習中的文本應用的試驗中,如文本分類和文本聚類。
接下來我們會使用 scikit-learn 中的這個內置數據集加載器來加載這 20 個新聞組。 或者,您也可以手動從網站上下載數據集,使用 [`sklearn.datasets.load_files`](../../modules/generated/sklearn.datasets.load_files.html#sklearn.datasets.load_files "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”: 一個簡單的包含多個 “field” 的存儲對象,可以方便的使用 python 中的 `dict` keys 或 `object` 屬性來讀取, 比如 `target_names` 包含了所請求的類別名稱:
```
>>> twenty_train.target_names
['alt.atheism', 'comp.graphics', 'sci.med', 'soc.religion.christian']
```
這些文件本身被讀進內存的 `data` 屬性中。 另外,這些文件名稱也可以容易獲取到:
```
>>> len(twenty_train.data)
2257
>>> len(twenty_train.filenames)
2257
```
讓我們打印出所加載的第一個文件的前幾行:
```
>>> print("\n".join(twenty_train.data[0].split("\n")[:3]))
From: sd345@city.ac.uk (Michael Collier)
Subject: Converting images to HP LaserJet III?
Nntp-Posting-Host: hampton
>>> print(twenty_train.target_names[twenty_train.target[0]])
comp.graphics
```
監督學習需要讓訓練集中的每個文檔對應一個類別標簽。 在這個例子中,類別是每個新聞組的名稱,也剛好是每個儲存文本文件的文件夾的名稱。
由于速度和空間上效率的原因 `scikit-learn` 加載目標屬性為一個整型數列, 它與 `target_names` 列表中類別名稱的 index(索引)相對應。 每個樣本的類別的整數型 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
```
你可以發現所有的樣本都被隨機打亂(使用了修正的 RNG 種子): 當你在重新訓練整個數據集之前,這樣可以幫助你只選取前幾個樣本來快速訓練一個模型以及獲得初步結果。
## 從文本文件中提取特征
為了在文本文件中執行機器學習算法, 我們首先要做的是將文本內容轉化成數值形式的特征向量。
### 詞袋
最直觀的方法就是用詞袋來表示:
> 1. 在訓練集中每一個出現在任意文中的單詞分配一個特定的整數 id(比如,通過建立一個從單詞到整數索引的字典)。
> 2. 對于每個文檔 `#i`,計算每個單詞 `w` 的出現次數并將其存儲在 `X[i, j]` 中作為特征 `#j` 的值,其中 `j` 是在字典中詞 `w` 的索引。
在這種方法中 `n_features` 是在整個文集(文章集合的縮寫,下同)中不同單詞的數量: 這個值一般來說超過 100,000 。
如果 `n_samples == 10000` , 存儲 `X` 為 “float32” 型的 numpy 數組將會需要 10000 x 100000 x 4 bytes = **4GB內存** ,在當前的計算機中非常不好管理的。
幸運的是, **X 數組中大多數的值為 0** ,是因為特定的文檔中使用的單詞數量遠遠少于總體的詞袋單詞個數。 因此我們可以稱詞袋模型是典型的 **high-dimensional sparse datasets(高維稀疏數據集)** 。 我們可以通過只在內存中保存特征向量中非 0 的部分以節省大量內存。
`scipy.sparse` 矩陣正是能完成上述操作的數據結構,同時 `scikit-learn` 有對這樣的數據結構的內置支持。
### 使用 `scikit-learn` 來對文本進行分詞
文本的預處理, 分詞以及過濾停用詞都包含在可以構建特征字典和將文檔轉換成特征向量的高級組件中
```
>>> 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`](../../modules/generated/sklearn.feature_extraction.text.CountVectorizer.html#sklearn.feature_extraction.text.CountVectorizer "sklearn.feature_extraction.text.CountVectorizer") 提供了 N-gram 模型以及連續字符模型。 一旦擬合, 向量化程序就會構建一個包含特征索引的字典:
```
>>> count_vect.vocabulary_.get(u'algorithm')
4690
```
在詞匯表中一個單詞的索引值對應的是該單詞在整個訓練的文集中出現的頻率。
### 從出現次數到出現頻率
出現次數的統計是非常好的開始,但是有個問題:長的文本相對于短的文本有更高的單詞平均出現次數,盡管他們可能在描述同一個主題。
為了避免這些潛在的差異,可以將文檔中每個單詞的出現次數除以文檔中單詞的總數:這些新的特征稱之為詞頻 `tf` (Term Frequencies)。
另一個在詞頻的基礎上改良是,降低在語料庫的很多文檔中均出現的單詞的權重,因此可以突出那些僅在語料庫中一小部分文檔中出現的單詞的信息量。
這種方法稱為 [tf–idf](https://en.wikipedia.org/wiki/Tf%E2%80%93idf) ,全稱為 “Term Frequency times Inverse Document Frequency” 。
**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(..)` 方法來擬合數據的 estimator(估算器),接下來使用 `transform(..)` 方法來把我們的次數矩陣轉換成 tf-idf 型。 通過跳過冗余處理,這兩步可以結合起來,來更快地得到同樣的結果。這種操作可以使用上節提到過的 `fit_transform(..)` 方法來完成,如下:
```
>>> tfidf_transformer = TfidfTransformer()
>>> X_train_tfidf = tfidf_transformer.fit_transform(X_train_counts)
>>> X_train_tfidf.shape
(2257, 35788)
```
## 訓練分類器
現在我們有了我們的特征,我們可以訓練一個分類器來預測一個帖子所屬的類別。 讓我們從 [樸素貝葉斯](../../modules/naive_bayes.html#naive-bayes) 分類器開始. 該分類器為該任務提供了一個好的基線(baseline). `scikit-learn` 包含了該分類器的若干變種;最適用在該問題上的變種是多項式分類器:
```
>>> from sklearn.naive_bayes import MultinomialNB
>>> clf = MultinomialNB().fit(X_train_tfidf, twenty_train.target)
```
為了嘗試預測新文檔所屬的類別,我們需要使用和之前同樣的步驟來抽取特征。 不同之處在于,我們在transformer調用 `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
```
## 構建 Pipeline(管道)
為了使得 向量化(vectorizer) => 轉換器(transformer) => 分類器(classifier) 過程更加簡單,``scikit-learn`` 提供了一個 `Pipeline` 類,操作起來像一個復合分類器:
```
>>> from sklearn.pipeline import Pipeline
>>> text_clf = Pipeline([('vect', CountVectorizer()),
... ('tfidf', TfidfTransformer()),
... ('clf', MultinomialNB()),
... ])
```
名稱 `vect`, `tfidf` 和 `clf` (分類器)都是任意的。 我們將會在下面的網格搜索(grid search)小節中看到它們的用法。 現在我們可以使用下面的一行命令來訓練模型:
```
>>> 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.834...
```
如上, 我們模型的準確度為 83.4%. 我們使用線性分類模型 [支持向量機(SVM)](../../modules/svm.html#svm) , 一種公認的最好的文本分類算法(盡管訓練速度比樸素貝葉斯慢一點)。 我們僅需要在 Pipeline(管道)中插入不同的分類器對象就可以改變我們的學習器:
```
>>> 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)
Pipeline(...)
>>> predicted = text_clf.predict(docs_test)
>>> np.mean(predicted == twenty_test.target)
0.912...
```
`scikit-learn` 同樣提供了更加細節化的模型評估工具:
```
>>> from sklearn import metrics
>>> print(metrics.classification_report(twenty_test.target, predicted,
... target_names=twenty_test.target_names))
...
precision recall f1-score support
alt.atheism 0.95 0.81 0.87 319
comp.graphics 0.88 0.97 0.92 389
sci.med 0.94 0.90 0.92 396
soc.religion.christian 0.90 0.95 0.93 398
avg / total 0.92 0.91 0.91 1502
>>> metrics.confusion_matrix(twenty_test.target, predicted)
array([[258, 11, 15, 35],
[ 4, 379, 3, 3],
[ 5, 33, 355, 3],
[ 5, 10, 4, 379]])
```
從 confusion matrix(混淆矩陣)中可以看出,在 atheism 和 christian 兩個類別的新聞比他們中某一類與 computer graphics 類別的新聞相比更容易混淆彼此。
## 使用網格搜索進行參數調優
我們已經接觸了類似于 `TfidfTransformer` 中 `use_idf` 這樣的參數 ,分類器有多種這樣的參數; 比如, `MultinomialNB` 包含了平滑參數 `alpha` 以及 `SGDClassifier` 有懲罰參數 `alpha` 和設置損失以及懲罰因子(更多信息請使用 python 的 `help` 文檔)。
通過構建巨大的網格搜索,而不是調整 chain(鏈)的各種組件的參數,來尋找最佳參數。 我們嘗試所有情況的分類器:使用詞袋或者二元模型,使用或者不使用 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 核心可以使用,通過設置 `n_jobs` 參數能夠進行并行處理。 如果我們將該參數設置為 `-1` , 該方法會使用機器的所有 CPU 核心:
```
>>> gs_clf = GridSearchCV(text_clf, parameters, n_jobs=-1)
```
網格搜索在 `scikit-learn` 中是非常常見的。 讓我們來選擇訓練集中的一小部分進行搜索以加速計算:
```
>>> gs_clf = gs_clf.fit(twenty_train.data[:400], twenty_train.target[:400])
```
上面的操作在 `GridSearchCV` 中調用了 `fit`,因此我們能夠調用 `predict`:
```
>>> twenty_train.target_names[gs_clf.predict(['God is love'])[0]]
'soc.religion.christian'
```
對象的 `best_score_` 和 `best_params_` 屬性存放了最佳的平均分數以及其所對應的參數:
```
>>> gs_clf.best_score_
0.900...
>>> for param_name in sorted(parameters.keys()):
... print("%s: %r" % (param_name, gs_clf.best_params_[param_name]))
...
clf__alpha: 0.001
tfidf__use_idf: True
vect__ngram_range: (1, 1)
```
更多的詳細信息可以在 `gs_clf.cv_results_` 中得到。
`cv_results_` 參數能夠容易的被導入到 pandas 的 [``](#id11)DataFrame``中,供后期使用。
### 練習
為了做這個練習,請拷貝 ‘skeletons’ 文件夾到新的文件夾,并將其命名為 ‘workspace’:
```
% cp -r skeletons workspace
```
這時候可以任意更改練習的代碼而不會破壞原始的代碼結構。
然后啟動 ipython 交互環境,并鍵入以下代碼:
```
[1] %run workspace/exercise_XX_script.py arg1 arg2 arg3
```
如果出現錯誤, 請使用 `%debug` 來啟動 ipdb 調試環境。
迭代更改答案直到練習完成。
**在每個練習中, skeleton 文件提供了所有必需的import語句,加載數據的模板代碼以及評估模型準確度的樣例代碼.**
## 練習 1:語言識別
- 請使用自定義的預處理器和 `CharNGramAnalyzer` ,并且使用維基百科中的文章作為訓練集,來編寫一個文本分類的 Pipeline(管道)。
- 評估某些測試集的性能.
ipython command line:
```
%run workspace/exercise_01_language_train_model.py data/languages/paragraphs/
```
## 練習 2:電影評論的情感分析
- 編寫一個文本分類 Pipeline(管道)來將電影評論分類為積極的(positive)還是消極的(negative)。
- 使用網格搜索來找到好的參數集。
- 使用測試集進行性能評估。
ipython 命令行:
```
%run workspace/exercise_02_sentiment.py data/movie_reviews/txt_sentoken/
```
## 練習 3:CLI 文本分類實用程序
使用前面的練習結果以及標準庫的 `cPickle``模塊,編寫一個命令行工具來檢測由 ``stdin` 輸入的文本,并評估該文本的極性(積極的還是消極的),如果輸入的文本是英文的話。
加分項:該工具能夠給出其預測的置信水平。
## 快速鏈接
當你完成這個章節時,下面是幾個建議幫助你進一步使用 scikit-learn :
- 嘗試使用 [`CountVectorizer`](../../modules/generated/sklearn.feature_extraction.text.CountVectorizer.html#sklearn.feature_extraction.text.CountVectorizer "sklearn.feature_extraction.text.CountVectorizer") 類下的 `analyzer` 以及 `token normalisation` 。
- 如果你沒有標簽,使用 [聚類](../../auto_examples/text/document_clustering.html#sphx-glr-auto-examples-text-document-clustering-py) 來解決你的問題。
- 如果每篇文章有多個標簽,請參考 [多類別和多標簽部分](../../modules/multiclass.html#multiclass) \_ 。
- 使用 [Truncated SVD](../../modules/decomposition.html#lsa) 解決 [隱語義分析](https://en.wikipedia.org/wiki/Latent_semantic_analysis).
- 使用 [Out-of-core Classification](../../auto_examples/applications/plot_out_of_core_classification.html#sphx-glr-auto-examples-applications-plot-out-of-core-classification-py) 來學習數據,不會存入計算機的主存儲器中。
- 使用 [Hashing Vectorizer](../../modules/feature_extraction.html#hashing-vectorizer) 來節省內存,以代替 [`CountVectorizer`](../../modules/generated/sklearn.feature_extraction.text.CountVectorizer.html#sklearn.feature_extraction.text.CountVectorizer "sklearn.feature_extraction.text.CountVectorizer") 。
- scikit-learn 0.19 中文文檔
- 用戶指南
- 1. 監督學習
- 1.1. 廣義線性模型
- 1.2. 線性和二次判別分析
- 1.3. 內核嶺回歸
- 1.4. 支持向量機
- 1.5. 隨機梯度下降
- 1.6. 最近鄰
- 1.7. 高斯過程
- 1.8. 交叉分解
- 1.9. 樸素貝葉斯
- 1.10. 決策樹
- 1.11. 集成方法
- 1.12. 多類和多標簽算法
- 1.13. 特征選擇
- 1.14. 半監督學習
- 1.15. 等式回歸
- 1.16. 概率校準
- 1.17. 神經網絡模型(有監督)
- 2. 無監督學習
- 2.1. 高斯混合模型
- 2.2. 流形學習
- 2.3. 聚類
- 2.4. 雙聚類
- 2.5. 分解成分中的信號(矩陣分解問題)
- 2.6. 協方差估計
- 2.7. 經驗協方差
- 2.8. 收斂協方差
- 2.9. 稀疏逆協方差
- 2.10. Robust 協方差估計
- 2.11. 新奇和異常值檢測
- 2.12. 密度估計
- 2.13. 神經網絡模型(無監督)
- 3. 模型選擇和評估
- 3.1. 交叉驗證:評估估算器的表現
- 3.2. 調整估計器的超參數
- 3.3. 模型評估: 量化預測的質量
- 3.4. 模型持久化
- 3.5. 驗證曲線: 繪制分數以評估模型
- 4. 數據集轉換
- 4.1. Pipeline(管道)和 FeatureUnion(特征聯合): 合并的評估器
- 4.2. 特征提取
- 4.3. 預處理數據
- 4.4. 無監督降維
- 4.5. 隨機投影
- 4.6. 內核近似
- 4.7. 成對的矩陣, 類別和核函數
- 4.8. 預測目標 (y) 的轉換
- 5. 數據集加載工具
- 6. 大規模計算的策略: 更大量的數據
- 7. 計算性能
- 教程
- 使用 scikit-learn 介紹機器學習
- 關于科學數據處理的統計學習教程
- 機器學習: scikit-learn 中的設置以及預估對象
- 監督學習:從高維觀察預測輸出變量
- 模型選擇:選擇估計量及其參數
- 無監督學習: 尋求數據表示
- 把它們放在一起
- 尋求幫助
- 處理文本數據
- 選擇正確的評估器(estimator)
- 外部資源,視頻和談話