Python 詞袋模型:完整指南

詞袋(Bag of Words,BoW)是自然語言處理(NLP)中的一種技術。它廣泛應用於將文本數據轉換為機器可讀的格式,特別是數值,而不考慮語法和單詞順序。對於任何從事文本數據工作的人來說,理解詞袋非常重要。Python 提供了多種工具和庫以有效地實現詞袋。

在本教程中,我們將深入了解詞袋,介紹其概念,涵蓋其用途,並逐步讲解在 Python 中的詳細實現。在這個教程結束時,你將能夠將詞袋模型應用到實際問題中。如果你是 NLP 的新手,請查看我們的 Python 自然語言處理技能軌道 以了解更多。 

詞袋是什麼?

詞袋(Bag of Words)是一種從文本數據中提取特徵的技術,用於機器學習任務,如文本分類和情感分析。這很重要,因為機器學習算法無法處理文本數據。將文本轉換為數字的過程被稱為特徵提取或特徵編碼。

詞袋基於文件中單詞的出現。這個過程從找到文本中的詞彙並衡量它們的出現次數開始。之所以稱之為詞袋,是因為該模型不考慮單詞的順序和結構,只考慮它們的出現。

詞袋模型與連續詞袋模型(Continuous Bag of Words Model,CBOW)不同,CBOW通過使用周圍單詞來預測目標單詞,捕捉單詞之間的語義關係,學習密集的單詞嵌入。CBOW需要在大語料庫上進行訓練,並產生低維向量,這些向量對於單詞上下文重要的複雜自然語言處理應用很有價值。

方面

BOW

CBOW

目的

計算每個單詞的出現次數

根據上下文預測目標單詞

輸出類型

高維度、稀疏向量

低維度、密集嵌入

考慮上下文

否(忽略單詞順序)

是(使用周圍單詞)

表示

稀疏頻率向量

捕捉語義的密集向量

複雜度

低(無需訓練)

高(需在大語料庫上進行訓練)

典型應用

文本分類、情感分析

詞向量、需要上下文的自然語言處理任務

為何使用詞袋模型?

詞袋模型在許多自然語言處理任務中很有用,使用它的原因包括:

  • 特徵提取:它將非結構化的文本數據轉換為結構化數據,可用作各種機器學習算法的輸入。
  • 簡潔與效率:詞袋模型在計算上簡單易於實現,並且對於小至中等規模的文本語料庫效果良好。
  • 文件相似度:它可以使用如餘弦相似度等技術來計算文本文件之間的相似度。
  • 文字分類:當與像Naive Bayes這樣的技術結合時,BoW對於垃圾郵件分類等文字分類任務非常有效,以及情緒分析

然而,也存在一些缺點,例如未考慮語義、單詞結構或單詞排序。

在Python中實現Bag of Words的步驟

為了建立詞袋模型,我們將語料庫中的所有單詞取出,為每個單詞建立一個列,行代表句子。如果某個單詞存在於句子中,就用1表示;如果單詞不存在,就用0表示。列中的每個單詞代表一個特徵。

最終,我們得到一個稀疏矩陣。稀疏矩陣是一個含有許多零的矩陣。

數據預處理

在Python中建立詞袋模型,我們需要進行一些預處理步驟。這些步驟包括分詞和移除停用詞。

斷詞(Tokenization)是將一段文本分解成更小的單位,通常是單詞的過程。您可以使用NLTK來進行斷詞。

停用詞是英語中常見的單詞,例如 “the”、”that” 和 “a”,它們不會對句子的極性產生貢獻。

import nltk from nltk.corpus import stopwords from nltk.tokenize import word_tokenize # 若您還未下載停用詞和分詞器,請先進行下載 nltk.download("punkt") nltk.download("stopwords") # 示例句子 sentence = "This is an example showing how to remove stop words from a sentence." # 將句子分詞為單詞 words = word_tokenize(sentence) # 獲取英文中的停用詞列表 stop_words = set(stopwords.words("english")) # 從句子中移除停用詞 filtered_sentence = [word for word in words if word.lower() not in stop_words] # 將單詞重新組合成句子 filtered_sentence = " ".join(filtered_sentence) print(filtered_sentence)

輸出:

example showing remove stop words sentence.

建立詞彙表

詞彙表是文本語料庫中發現的唯一單詞的集合。建立詞彙表包括從語料庫中收集所有唯一單詞並計算它們的出現次數。這個詞彙表對於各種自然語言處理任務,如語言建模、詞向量嵌入和文本分類非常有用。

以下代碼創建了語料庫中單詞的簡單頻率分佈,這對於基本自然語言處理任務如建立詞彙表或理解文本內容非常有用:

  • 語料庫變量包含一些範例句子。在實際應用中,這將包含更大型、更多樣化的文本數據。
  • 詞彙表 = defaultdict(int)簡化了單詞頻率計數,自動將任何新單詞初始化為計數0,可以直接增加計數而不需要檢查。
  • 每個句子會透過轉為小寫並使用正則表達式提取單詞進行標記化。\b\w+\b 此模式只用來識別僅包含字母數字的單詞,忽略標點符號和其他符號。
  • 每個單詞的計數都會在 vocab 字典中更新。
  • 詞彙表會按照頻率降序排序,這樣可以很容易地看到最常見的單詞排在前面,並供參考。
import re # 引入正則表達式模塊以幫助進行文字處理 from collections import ( defaultdict, ) # 引入defaultdict以方便處理詞頻統計 # 文本樣本集合 - 一個用於分析的句子小數據集 corpus = [ "Tokenization is the process of breaking text into words.", "Vocabulary is the collection of unique words.", "The process of tokenizing is essential in NLP.", ] # 初始化一個整數類型的defaultdict以存儲詞頻 # defaultdict(int)將每個新的鍵初始化為默認的整數值0 vocab = defaultdict(int) # 遍歷語料庫中的每個句子以進行分詞和規範化 for sentence in corpus: # 將句子轉換為小寫以確保計數的一致性(例如,'Tokenization' 和 'tokenization' 被視為相同的詞) # 使用正則表達式查找僅由字母數字字符組成的單詞 words = re.findall(r"\b\w+\b", sentence.lower()) # 對於找到的每個單詞,增加其在詞彙字典中的計數 for word in words: vocab[word] += 1 # 將defaultdict類型的詞彙轉換為普通字典以方便操作和排序 # 按詞頻降序對字典進行排序並轉換為新的字典 sorted_vocab = dict(sorted(vocab.items(), key=lambda x: x[1], reverse=True)) # 顯示排序後的詞彙表,包括每個單詞及其頻率計數 print("Vocabulary with Frequencies:", sorted_vocab)

輸出:

Vocabulary with Frequencies: {'is': 3, 'the': 3, 'of': 3, 'process': 2, 'words': 2, 'tokenization': 1, 'breaking': 1, 'text': 1, 'into': 1, 'vocabulary': 1, 'collection': 1, 'unique': 1, 'tokenizing': 1, 'essential': 1, 'in': 1, 'nlp': 1}

手動建立詞彙庫可能會耗時,特別是對於大型語料庫。Scikit-learn 的 CountVectorizer 會自動化這個過程,並且如我們稍後將看到的,允許更靈活的文本處理。

使用 Python 從零開始實現詞袋模型

讓我們從一個基於 Python 的詞袋模型簡單實現開始。Python。這將幫助你理解其底層工作的構建塊和機制。

手動實作

第一步:預處理文本數據

我們將從定義一個簡單的函數來處理文本開始,包括詞典化、小寫化和去除標點符號。

from collections import defaultdict import string # 示例文本數據:句子 corpus = [ "Python is amazing and fun.", "Python is not just fun but also powerful.", "Learning Python is fun!", ] # 處理文本的函數 def preprocess(text): # 轉換為小寫 text = text.lower() # 移除標點符號 text = text.translate(str.maketrans("", "", string.punctuation)) # 詞典化:將文本拆分為單詞 tokens = text.split() return tokens # 對樣本語料庫應用預處理 processed_corpus = [preprocess(sentence) for sentence in corpus] print(processed_corpus)

輸出:

[['python', 'is', 'amazing', 'and', 'fun'], ['python', 'is', 'not', 'just', 'fun', 'but', 'also', 'powerful'], ['learning', 'python', 'is', 'fun']]

第二步:建立詞彙表

現在,我們需要掃描所有文件並建立一個完整的獨特單詞列表,這就是我們的詞彙表。

初始化一個空的集合作為詞彙表 vocabulary = set() 建立詞彙表 for sentence in processed_corpus: vocabulary.update(sentence) 將其轉換為排序後的列表 vocabulary = sorted(list(vocabulary)) print("Vocabulary:", vocabulary)

步驟 3:計算單詞頻率並向量化

我們現在將計算處理過的語料庫中每個文件的詞彙表中每個單詞的頻率。

def create_bow_vector(sentence, vocab): vector = [0] * len(vocab) 初始化一個零向量 for word in sentence: if word in vocab: idx = vocab.index(word) 在詞彙表中找到單詞的索引 vector[idx] += 1 在該索引位置增加計數 return vector

在這個時候,你將為語料庫中的每個文件創建了一個詞袋表示。

為處理過的語料庫中的每個句子創建詞袋向量 bow_vectors = [create_bow_vector(sentence, vocabulary) for sentence in processed_corpus] print("Bag of Words Vectors:") for vector in bow_vectors: print(vector)

輸出:

Bag of Words Vectors: [0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1] [1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1] [0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1]

使用 Scikit-learn 的 CountVectorizer

手動建立詞袋模型對於學習來說是好的,但對於生產應用程序,您將希望使用高效、優化的庫,例如Scikit-learn

我們用於分詞的Python函數是CountVectorizer,從sklearn.feature_extraction.text導入。 CountVectorizer的一個特點是max_features,這代表了您希望在詞袋模型中出現的最多單詞數量。 在這個例子中,我們使用None,意味著將使用所有特徵。

創建CountVectorizer的實例後,使用.fit_transform()方法來創建詞袋模型。 接下來,使用.toarray()將詞袋模型轉換為可以供機器學習模型使用的numpy數組。

一旦配適後,CountVectorizer 建立了一個特徵指數的字典。詞彙中單詞的索引值與其在整個訓練語料庫中的頻率相關聯。

from sklearn.feature_extraction.text import CountVectorizer # 原始語料庫 corpus = [ "Python is amazing and fun.", "Python is not just fun but also powerful.", "Learning Python is fun!", ] # 創建一個 CountVectorizer 對象 vectorizer = CountVectorizer() # 對語料庫進行配適和轉換 X = vectorizer.fit_transform(corpus) # 打印生成的詞彙表 print("Vocabulary:", vectorizer.get_feature_names_out()) # 打印詞袋矩陣 print("BoW Representation:") print(X.toarray())

輸出:

markdownVocabulary: ['also' 'amazing' 'and' 'but' 'fun' 'is' 'just' 'learning' 'not' 'powerful' 'python'] BoW Representation: [[0 1 1 0 1 1 0 0 0 0 1] [1 0 0 1 1 1 1 0 1 1 1] [0 0 0 0 1 1 0 1 0 0 1]]

範例:應用詞袋模型

現在讓我們將 BoW 模型應用於一個由三個電影評論組成的小型文本語料庫,以說明整個過程。 

我們將使用 Scikit-learn 的 CountVectorizer 對這個小型文本語料庫應用 BoW 模型。

這裡是我們將要採取的步驟:

  • CountVectorizer 自動對文本進行分詞,移除標點符號,並將單詞轉為小寫。
  • .fit_transform(語料庫) 將語料庫轉換為文件-詞彙矩陣,其中每一行代表一個文件,每一列代表詞彙中的一個詞。
  • X_dense 是代表每個文件中每個詞頻率的稠密矩陣。
from sklearn.feature_extraction.text import CountVectorizer # 電影評論的樣本語料庫 corpus = [ "I loved the movie, it was fantastic!", "The movie was okay, but not great.", "I hated the movie, it was terrible.", ] # 初始化 CountVectorizer vectorizer = CountVectorizer() # 對語料庫進行擬合和轉換以得到文件-詞彙矩陣 X = vectorizer.fit_transform(corpus) # 將文件-詞彙矩陣轉換為稠密格式(視覺化的選項) X_dense = X.toarray() # 獲取詞彙表(詞語到索引位置的映射) vocab = vectorizer.get_feature_names_out() # 打印詞彙表和文件-詞彙矩陣 print("Vocabulary:", vocab) print("Document-Term Matrix:\n", X_dense)

輸出:

Vocabulary: ['but' 'fantastic' 'great' 'hated' 'it' 'loved' 'movie' 'not' 'okay' 'terrible' 'the' 'was'] Document-Term Matrix: [[0 1 0 0 1 1 1 0 0 0 1 1] # 第一評論: "我喜歡這部电影,太棒了!" [1 0 1 0 1 0 1 1 1 0 1 1] # 第二評論: "這部电影還可以,但不夠出色。" [0 0 0 1 1 0 1 0 0 1 1 1]] # 第三評論: "我討厭這部电影,太糟糕了。"

以下是對以上輸出的解釋:

  • 語料庫中的每一個獨特單詞都被分配了一個索引,並按字母順序排列。例如,”but” 在索引 0,”fantastic” 在索引 1,”movie” 在索引 6,依此类推。
  • 文檔矩陣中的每一行代表一篇電影評論,每一列對應詞彙中的一個單詞。矩陣中的值表示該單詞在特定文檔中的出現頻率。
    • 第一評論:[0 1 0 0 1 1 1 0 0 0 1 1] 表示:
      • 單詞 “fantastic” 出現一次(索引 1 的 1),
      • 單詞 “loved” 出現一次(索引 5 的 1),
      • 單詞 “movie” 出現一次(索引 6 的 1),
      • 單詞 “it” 出現一次(索引 4 的 1),
      • 依此类推。

詞袋向量可以解釋如下:

  • 每個文檔是一個表示單詞計數的數字向量。向量的維度等於詞彙的大小。在這個例子中,詞彙有 12 個單詞,所以每篇評論被轉換成為一個 12 維的向量。
  • 由於並不是每個文檔都包含詞彙中的每個單詞,因此每行中的大多數單詞都是零。因此,詞袋模型通常是稀疏的,也就是說,它們有很多零。

優點和限制 Bag of Words 模型

現在讓我們來探討一此 Bag of Words 模型的優點和限制。

優點

  1. 實作和解析簡單:Bag of Words 模型是最直接的文本表示技術之一,對初學者來說非常適合。它的簡單性讓人能夠快速實作,而無需复杂的預處理或專門的模型。
  2. 適合用於文本分類任務:Bag of Words 非常適合基本的任務,如文本分類、情感分析以及垃圾邮件偵測。這些任務通常不需要複雜的語言模型,所以 BOW 表征就足夠且有效率。

限制

  1. 詞彙表大小影響表征的稀疏性:詞彙表越大,表征就越稀疏和高維。這種稀疏性可能使得模型難以有效學習,並需要小心調整詞彙表大小以避免過度的計算成本。
  2. 產生計算成本昂貴的稀疏矩陣:因為每個文件都是通過可能在大型詞彙表中的每個單詞的頻率來表示,所以生成的矩陣往往大部分是零,這可能在機器學習管道中存儲和處理時效率低下。稀疏矩陣消耗大量的記憶體,並且在大型數據集上尤其需要專門的工具和庫來進行有效的存儲和計算。
  3. 失去意義與脈絡:BOW 忽略了詞序和句子結構,導致語法關係和含義的丧失。這種局限性使得它在脈絡、細節和詞序重要的任務中,如翻譯或複雜句子中的情感偵測,較不適用。

以下策略可用於減少詞袋中的詞彙量:

  • 忽視大小寫。
  • 移除標點符號。
  • 移除停用詞,例如「the」和「a」等常見詞。
  • 確保所有單詞拼寫正確。
  • 使用詞幹提取技術將詞語減少至其根形式。

下一步:超越詞袋模型

詞袋模型的局限性之一在於它平等對待所有詞語。不幸的是,這可能導致某些詞語仅因為出現頻繁而被賦予更多的重要性。

TF-IDF(詞頻-逆文件頻率)是這個問題的解決方案,它根據詞語在所有文件中的出現頻率調整詞語的權重。

TF-IDF:詞袋模型的擴展

詞頻(TF)代表一個術語在文件中的頻率。逆文件頻率(IDF)減少了跨多個文件常見詞語的影響。TF-IDF 分數是通過將這兩個度量相乘來計算的。

考慮一份含有200個單詞的文件,其中單詞”love”出現5次。則”love”的TF(詞頻)為(5 / 200)= 0.025。假設我們有100萬份文件,而”love”在其中的1000份文件中出現,那麼逆向文檔頻率(即IDF)計算為log(1000000 / 1000) = 3。TF-IDF權重是這兩個量的乘積:0.025 * 3 = 0.075。

在Scikit-learn中,使用TfidfVectorizer類別來計算這個值相對容易。

from sklearn.feature_extraction.text import TfidfVectorizer #範例語料庫 corpus = [ "Python is amazing and fun.", "Python is not just fun but also powerful.", "Learning Python is fun!", ] #創建Tf-idf向量化器 tfidf_vectorizer = TfidfVectorizer() #對語料庫進行擬合和轉換 X_tfidf = tfidf_vectorizer.fit_transform(corpus) #顯示詞彙表 print("Vocabulary:", tfidf_vectorizer.get_feature_names_out()) #顯示TF-IDF矩陣 print("TF-IDF Representation:") print(X_tfidf.toarray())

輸出:

Vocabulary: ['also' 'amazing' 'and' 'but' 'fun' 'is' 'just' 'learning' 'not' 'powerful' 'python'] TF-IDF Representation: [[0. 0.57292883 0.57292883 0. 0.338381 0.338381 0. 0. 0. 0. 0.338381 ] [0.40667606 0. 0. 0.40667606 0.24018943 0.24018943 0.40667606 0. 0.40667606 0.40667606 0.24018943] [0. 0. 0. 0. 0.41285857 0.41285857 0. 0.69903033 0. 0. 0.41285857]]

上面實現的TF-IDF矩陣給你一個加權度量,而不是原始頻率。

雖然詞袋模型有其局限性,特別是對於更大和更複雜的數據集,但它仍然是許多自然語言處理應用中的基本組成部分。理解它將會在您探索更先進的模型如詞向量變換器時對您有所幫助。

從這裡,您可以嘗試在您的專案中使用詞袋,包括垃圾郵件偵測情感分析、文件聚類等等。

如果您想要超越詞袋的進一步改進,您可以探索如Word2VecGloVe的方法,或者像BERT這樣的深度學習模型。

最後的想法

詞袋技術是自然語言處理中使用的基本技術。它提供了一種簡單而有效的方法,將非結構化文本轉換為機器學習算法可用的數值特徵。在這個教程中,我們涵蓋了:

  • 詞袋(BoW)模型是什麼?
  • 詞袋模型在構建機器學習模型中的好處。
  • 如何在Python中實現詞袋模型。
  • 詞袋的優點和限制。
  • 詞袋模型背後的理論和動機。
  • 介紹TF-IDF作為傳統詞袋方法的改進。

查看我們的Python自然語言處理技能軌道,深入了解自然語言處理。

Source:
https://www.datacamp.com/tutorial/python-bag-of-words-model