練手紮實基本功必備:非結構文本特徵提取方法
- 2019 年 10 月 7 日
- 筆記

【導讀】本文介紹了一些傳統但是被驗證是非常有用的,現在都還在用的策略,用來對非結構化的文本數據提取特徵。
介紹
在本文中,我們將研究如何處理文本數據,這無疑是最豐富的非結構化數據來源之一。文本數據通常由文檔組成,文檔可以表示單詞、句子甚至是文本的段落。文本數據固有的非結構化(沒有格式整齊的數據列)和嘈雜的特性使得機器學習方法更難直接處理原始文本數據。因此,在本文中,我們將採用動手實踐的方法,探索從文本數據中提取有意義的特徵的一些最流行和有效的策略。這些特徵可以很容易地用於構建機器學習或深度學習模型。
動機
特徵工程通常被稱為創建性能更好的機器學習模型的秘密武器。只要有一個出色的特徵就可能是你贏得Kaggle挑戰的門票!特徵工程的重要性對於非結構化的文本數據更為重要,因為我們需要將自由流動的文本轉換成一些數字表示形式,然後機器學習演算法就可以理解這些數字表示形式。即使出現了自動化的特徵工程,在將不同的特徵工程策略應用為黑盒模型之前,你仍然需要理解它們背後的核心概念。永遠記住,「如果給你一盒工具來修理房子,你應該知道什麼時候使用電鑽,什麼時候使用鎚子!」
理解文本數據
我相信你們所有人都對這個場景中包含的文本數據有一個合理的概念。請記住,文本數據總是可以以結構化數據屬性的形式存在,但通常這屬於結構化分類數據的範疇。
在這個場景中,我們討論的是單詞、短語、句子和整個文檔形式的自由流動文本。本質上,我們有一些句法結構,比如單片語成短語,短語組成句子,句子又組成段落。然而,文本文檔沒有固有的結構,因為可以有各種各樣的單詞,這些單詞在不同的文檔中會有所不同,而且與結構化數據集中固定數量的數據維度相比,每個句子的長度也是可變的。
特徵工程策略
讓我們看看一些流行的和有效的策略來處理文本數據,並從中提取有意義的特徵,這些特徵可以用於下游的機器學習系統。請注意,你可以在https://github.com/dipanjanS/practical-machine-learning-with-python中訪問本文中使用的所有程式碼,以供將來參考。我們將從載入一些基本的依賴項和設置開始。
import pandas as pd import numpy as np import re import nltk import matplotlib.pyplot as plt pd.options.display.max_colwidth = 200 %matplotlib inline
現在,讓我們以一個示例文檔語料庫為例,我們將在該語料庫上運行本文中的大部分分析。corpus是具有一個或多個主題的文本文檔集合。
corpus = ['The sky is blue and beautiful.', 'Love this blue and beautiful sky!', 'The quick brown fox jumps over the lazy dog.', "A king's breakfast has sausages, ham, bacon, eggs, toast and beans", 'I love green eggs, ham, sausages and bacon!', 'The brown fox is quick and the blue dog is lazy!', 'The sky is very blue and the sky is very beautiful today', 'The dog is lazy but the brown fox is quick!' ] labels = ['weather', 'weather', 'animals', 'food', 'food', 'animals', 'weather', 'animals'] corpus = np.array(corpus) corpus_df = pd.DataFrame({'Document': corpus, 'Category': labels}) corpus_df = corpus_df[['Document', 'Category']]

可以看到,我們已經為我們的toy語料庫獲取了一些屬於不同類別的文本文檔示例。像往常一樣,在討論特徵工程之前,我們需要進行一些數據預處理或整理,以刪除不必要的字元、符號和tokens。
文本預處理
可以有多種方法來清理和預處理文本數據。在接下來的幾點中,我們將重點介紹在自然語言處理(NLP)中大量使用的一些最重要的方法。
- 刪除標籤:我們的文本經常包含不必要的內容,如HTML標籤,分析文本的時候這不會增加多少價值。BeautifulSoup庫可以幫我們做很多必須的工作。
- 刪除重音字元:在任何文本語料庫中,特別是在處理英語時,通常可能要處理重音字元/字母。因此,我們需要確保將這些字元轉換並標準化為ASCII字元。一個簡單的例子是將é轉換為e。
- 擴展縮略語:在英語中,縮略語基本上是單詞或音節的縮寫形式。這些現有單詞或短語的縮略形式是通過刪除特定的字母和聲音來創建的。例如,do not變為don 't以及I would 變為I 'd 。將每個縮略語轉換為其擴展的原始形式通常有助於文本標準化。
- 刪除特殊字元:非字母數字字元的特殊字元和符號通常會增加非結構化文本中的額外噪音。通常,可以使用簡單正則表達式(regexes)來實現這一點。
- 詞根提取和詞形還原:詞幹通常是可能的單詞的基本形式,可以通過在詞幹上附加詞綴,如前綴和後綴來創建新單詞。這就是所謂的拐點。獲取單詞基本形式的反向過程稱為「詞根提取」。一個簡單的例子是單詞WATCHES, WATCHING,和WATCHED。它們以詞根WATCH作為基本形式。詞形還原與詞根提取非常相似,在詞根提取中,我們去掉詞綴以得到單詞的基本形式。然而,在這種情況下,基本形式被稱為根詞,而不是詞根。不同之處在於,詞根總是一個詞典上正確的單詞(存在於字典中),但根詞的詞幹可能不是這樣。
- 刪除停止詞:在從文本中構造有意義的特徵時,意義不大或者沒有意義的詞被稱為停止詞或停止詞。如果你在語料庫中做一個簡單的詞或詞的頻率,這些詞的頻率通常是最高的。像a、an、the、and等詞被認為是停止詞。沒有一個通用的停止詞列表,但是我們使用了一個來自「nltk」的標準英語停止詞列表。你還可以根據需要添加自己的域特定的停止詞。
除此之外,你還可以執行其他標準操作,如標記化、刪除額外的空格、文本小寫轉換和更高級的操作,如拼寫糾正、語法錯誤糾正、刪除重複字元等等。
由於本文的重點是特徵工程,所以我們將構建一個簡單的文本預處理程式,該程式的重點是刪除特殊字元、額外的空格、數字、停止詞和把文本語料庫的大寫變成小寫。
wpt = nltk.WordPunctTokenizer() stop_words = nltk.corpus.stopwords.words('english') def normalize_document(doc): # lower case and remove special characterswhitespaces doc = re.sub(r'[^a-zA-Zs]', '', doc, re.I|re.A) doc = doc.lower() doc = doc.strip() # tokenize document tokens = wpt.tokenize(doc) # filter stopwords out of document filtered_tokens = [token for token in tokens if token not in stop_words] # re-create document from filtered tokens doc = ' '.join(filtered_tokens) return doc
一旦我們準備好了基本的預處理pipeline,我們可以將其應用於示例語料庫。
norm_corpus = normalize_corpus(corpus) norm_corpus Output ------ array(['sky blue beautiful', 'love blue beautiful sky', 'quick brown fox jumps lazy dog', 'kings breakfast sausages ham bacon eggs toast beans', 'love green eggs ham sausages bacon', 'brown fox quick blue dog lazy', 'sky blue sky beautiful today', 'dog lazy brown fox quick'], dtype='<U51')
上面的輸出應該可以讓你清楚地看到我們的每個示例文檔在預處理之後的樣子。現在讓我們來設計一些特徵!
詞袋模型
這可能是非結構化文本最簡單的向量空間表示模型。向量空間模型只是一個數學模型,它將非結構化文本(或任何其他數據)表示為數值向量,這樣向量的每個維度都是一個特定的特性屬性。單詞包模型將每個文本文檔表示為一個數字向量,其中每個維度都是來自語料庫的特定單詞,其值可以是其在文檔中的頻率、出現頻率(用1或0表示),甚至是加權值。模型的名稱是這樣的,因為每個文檔都按照字面意思表示為自己單詞的「包」,不考慮單詞順序、序列和語法。
from sklearn.feature_extraction.text import CountVectorizer cv = CountVectorizer(min_df=0., max_df=1.) cv_matrix = cv.fit_transform(norm_corpus) cv_matrix = cv_matrix.toarray() cv_matrix
Output ------ array([[0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0], [0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0], [1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0], [1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0], [0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0], [0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 1], [0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0] ], dtype=int64)
因此,可以看到我們的文檔已經被轉換成數字向量,這樣每個文檔都由上面的特徵矩陣中的一個向量(行)表示。下面的程式碼將幫助以更容易理解的格式表示這一點。
# get all unique words in the corpus vocab = cv.get_feature_names() # show document feature vectors pd.DataFrame(cv_matrix, columns=vocab)

可以清楚地看到,特徵向量中的每一列表示語料庫中的一個單詞,每一行表示我們的一個文檔。任何單元格中的值表示該單詞(用列表示)在特定文檔中出現的次數(用行表示)。因此,如果一個文檔語料庫由所有文檔中的N唯一單片語成,那麼每個文檔都有一個N維向量。
N-Grams袋模型
一個單詞只是一個符號,通常被稱為unigram或1-gram。我們已經知道詞袋模型不考慮單詞的順序。但是,如果我們也想考慮按順序出現的短語或單詞的集合呢?N-gram幫助我們達到這個目的。N-gram基本上是文本文檔中單詞tokens的集合,這些標記是連續的,並以序列的形式出現。Bi-gram表示2階n-grams(2個單詞),Tri-grams表示3階n-grams(3個單詞),依此類推。因此,N-Grams袋模型只是詞袋模型的一個擴展,因此我們也可以利用基於N-gram的特徵。下面的示例描述了每個文檔特徵向量中基於bi-gram的特徵。
# you can set the n-gram range to 1,2 to get unigrams as well as bigrams bv = CountVectorizer(ngram_range=(2,2)) bv_matrix = bv.fit_transform(norm_corpus) bv_matrix = bv_matrix.toarray() vocab = bv.get_feature_names() pd.DataFrame(bv_matrix, columns=vocab)

這為我們的文檔提供了特徵向量,其中每個特徵由表示兩個單詞序列的bi-gram組成,值表示該bi-gram出現在文檔中的次數。
TF-IDF模型
在大型語料庫中使用詞袋模型可能會產生一些潛在的問題。由於特徵向量是基於絕對頻率,可能有一些項在所有文檔中都經常出現,這可能傾向於掩蓋其他方面的特徵。TF-IDF模型試圖解決這一問題,在計算中使用了縮放或歸一化因子。TF-IDF是Term Frequency- reverse Document Frequency的縮寫。
其計算方法為:詞頻(tf)和逆文檔頻率(idf)。該技術是為搜索引擎中查詢結果的排序而發展起來的,目前已成為資訊檢索和自然語言處理領域中一個不可或缺的模型。
在數學上,我們可以將TF-IDF定義為tfidf = tf x idf,可以進一步展開為:

這裡,tfidf(w, D)是文檔D中單詞w的TF-IDF得分。tf(w, D)表示文檔D中w的詞頻,可以從詞袋模型中得到。idf (w, D)是w這個單詞的逆文檔頻率,可以通過計算語料庫中的文檔的總數C除以w這個詞的文檔頻率的對數變換得到, 這基本上是文檔的語料庫詞w的頻率。這個模型有多種變體,但最終都得到了非常相似的結果。現在讓我們把它應用到我們的語料庫上!

每個文本文檔的基於TF-IDF的特徵向量與原始的詞袋模型值相比具有了縮放和標準化的值。
文檔相似度
文檔相似度是使用基於距離或相似度的度量的過程,該度量可用於根據從文檔中提取的特徵(如詞袋或tf-idf)確定文本文檔與任何其他文檔的相似程度。
因此,可以看到,我們可以構建在上一節中設計的基於tf-idf的特徵的基礎上,並使用它們來生成新的特徵,通過利用基於這些特徵的相似性,可以在搜索引擎、文檔集群和資訊檢索等領域中發揮作用。
語料庫中的成對文檔相似性涉及到為語料庫中的每對文檔計算文檔相似性。因此,如果在一個語料庫中有C文檔,那麼最終將得到一個C x C矩陣,其中每一行和每一列表示一對文檔的相似度得分,這對文檔分別表示行和列的索引。有幾個相似度和距離度量用於計算文檔相似度。其中包括餘弦距離/相似度、歐幾里德距離、曼哈頓距離、BM25相似度、jaccard距離等。在我們的分析中,我們將使用可能是最流行和廣泛使用的相似性度量,餘弦相似度和基於TF-IDF特徵向量的成對文檔相似度比較。
from sklearn.metrics.pairwise import cosine_similarity similarity_matrix = cosine_similarity(tv_matrix) similarity_df = pd.DataFrame(similarity_matrix) similarity_df

餘弦相似度給出了一個度量,表示兩個文本文檔的特徵向量表示之間夾角的餘弦值。文檔之間的夾角越小,它們之間的距離就越近,也就越相似,如下圖所示。

仔細觀察相似矩陣可以清楚地看出,文檔(0,1和6)、(2,5和7)彼此非常相似,文檔3和文檔4彼此略有相似,但幅度不是很大,但仍然比其他文檔強。這必須表明這些類似的文檔具有一些類似的特性。這是一個完美的分組或聚類的例子,可以通過無監督學習來解決,尤其是在處理數百萬文本文檔的大型語料庫時。
使用相似特徵對文檔進行聚類
聚類利用無監督學習將數據點(本場景中的文檔)分組或聚集。在這裡,我們將利用一種無監督的分層聚類演算法,通過利用前面生成的文檔特徵相似性,嘗試將我們的玩具語料庫中的類似文檔分組在一起。層次聚類演算法有兩種,即聚合演算法和分裂演算法。我們將使用一個聚合聚類演算法,這是分層聚類使用自底向上的方法,即從自己的簇中開始,然後使用一個度量數據點之間距離的距離度量和一個鏈接合併準則將簇依次合併在一起。下圖顯示了一個示例描述。

鏈接準則的選擇控制了合併的策略。鏈接準則的例子有Ward、Complete、Average等。該準則對於選擇每一步合併的簇對(最低級的單個文檔和較高級的簇)非常有用,它基於目標函數的最優值。我們選擇Ward 's minimum variance method作為我們的鏈接準則來最小化總簇內方差。因此,在每個步驟中,我們都找到了合併後總簇內方差增加最小的一對簇。既然我們已經有了特徵相似性,讓我們在示例文檔上構建鏈接矩陣。
from scipy.cluster.hierarchy import dendrogram, linkage Z = linkage(similarity_matrix, 'ward') pd.DataFrame(Z, columns=['DocumentCluster 1', 'DocumentCluster 2', 'Distance', 'Cluster Size'], dtype='object')
如果仔細查看鏈接矩陣,可以看到鏈接矩陣的每一步(行)都告訴我們哪些數據點(或簇)合併在一起。如果有n數據點,鏈接矩陣Z的形狀將是(n – 1) x 4,其中Z[i]將告訴我們在步驟i合併了哪些集群。每一行有四個元素,前兩個元素要麼是數據點標識符,要麼是簇標籤(在矩陣的後半部分中有一次合併了多個數據點),第三個元素是前兩個元素(數據點或集群)之間的簇距離,最後一個元素是合併完成後簇中元素數據點的總數。
現在讓我們把這個矩陣形象化為一個樹形圖,以便更好地理解這些元素!
plt.figure(figsize=(8, 3)) plt.title('Hierarchical Clustering Dendrogram') plt.xlabel('Data point') plt.ylabel('Distance') dendrogram(Z) plt.axhline(y=1.0, c='k', ls='--', lw=0.5)

我們可以看到,每個數據點開始時是一個單獨的簇,然後慢慢地開始與其他數據點合併,形成聚類。從顏色和樹狀圖的高度來看,如果考慮距離度量在1.0或以上(用虛線表示),則可以看到模型正確地識別了三個主要聚類。利用這個距離,我們得到了聚類標籤。
from scipy.cluster.hierarchy import fcluster max_dist = 1.0 cluster_labels = fcluster(Z, max_dist, criterion='distance') cluster_labels = pd.DataFrame(cluster_labels, columns=['ClusterLabel']) pd.concat([corpus_df, cluster_labels], axis=1)

可以清楚地看到,我們的演算法根據分配給文檔的聚類標籤正確地標識了文檔中的三個不同類別。這將使你對如何利用TF-IDF特徵來構建相似特徵有一個很好的了解,而相似特徵反過來又有助於對文檔進行聚類。
總結
這些示例應該讓你對文本數據上的特徵工程的流行策略有一個很好的了解。請記住,這些都是基於數學、資訊檢索和自然語言處理概念的傳統策略。因此,隨著時間的推移,這些經過嘗試和測試的方法在各種數據集和問題中都證明是成功的。下一步將是利用文本數據上的特性工程的深度學習模型的詳細策略!