Python词袋模型:完整指南

词袋模型(BoW)是自然语言处理(NLP)中的一种技术。它被广泛用于将文本数据转换为机器可读的格式,尤其是数值数据,同时不考虑语法和单词顺序。理解词袋模型对于处理文本数据的任何人来说都是重要的。Python提供了多种工具和库来有效地实现词袋模型。

在本教程中,我们将深入了解词袋模型,介绍其概念,探讨其用途,并详细讲解在Python中的实现过程。在本教程结束时,您将能够将词袋模型应用于现实世界的问题。如果您是NLP的新手,请查看我们的Python中的自然语言处理技能轨道以了解更多。 

什么是词袋模型?

词袋模型是一种从文本数据中提取特征用于机器学习任务的技术,例如文本分类和情感分析。这很重要,因为机器学习算法无法处理文本数据。将文本转换为数字的过程被称为特征提取或特征编码。

词袋模型基于文档中单词的出现。该过程从找到文本中的词汇并衡量它们的出现开始。之所以称为“袋”,是因为不考虑单词的顺序和结构,只考虑它们的出现。

词袋模型与连续词袋模型(CBOW)不同,CBOW通过使用周围的单词来预测目标单词,捕捉单词之间的语义关系,从而学习密集的词嵌入。CBOW需要在大型语料库上训练,并生成低维向量,这些向量对于单词上下文重要的复杂自然语言处理应用非常有价值。

方面

词袋

CBOW

用途

统计每个词的出现次数

根据上下文预测目标词

输出类型

高维、稀疏向量

低维、稠密嵌入

考虑上下文

否(忽略词序)

是(使用周围的词)

表示

稀疏频率向量

捕捉语义的稠密向量

复杂度

低(无需培训)

高(需要在大语料库上培训)

典型应用

文本分类、情感分析

词向量、需要上下文的NLP任务

为什么使用词袋模型?

词袋模型在许多NLP任务中很有用,使用它的原因包括:

  • 特征提取:它将非结构化文本数据转换为结构化数据,可以作为各种机器学习算法的输入。
  • 简洁高效:词袋模型在计算上易于实现,对于小型到中等规模的文本语料库效果良好。
  • 文档相似度:它可以使用余弦相似度等技术来计算文本文档之间的相似度。
  • 文本分类:当与朴素贝叶斯等技术结合使用时,词袋模型(BoW)对于垃圾邮件分类等文本分类任务非常有效,以及情感分析

然而,它也有缺点,比如不考虑语义、词的结构或词的顺序。

在Python中实现词袋模型的步骤

为了创建词袋模型,我们取语料库中的所有单词,并为每个单词创建一列。行代表句子。如果某个单词存在于句子中,它由1表示,如果单词不存在,则由0表示。列中的每个单词代表一个单一特征。

最终,我们得到一个稀疏矩阵。稀疏矩阵是一个包含许多零的矩阵。

数据预处理

在Python中创建词袋模型,我们需要进行一些预处理步骤。这些步骤包括分词和去除停用词。

分词是将一段文本分解为更小单元的过程,通常是单词。您可以使用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 该模式识别仅包含字母数字的单词,忽略标点和其他符号。
  • 每个单词的计数在 词汇表 字典中更新。
  • 词汇表按频率降序排序,便于查看最常见的单词位于顶部,并展示供参考。
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这将帮助你理解其底层工作原理的构建模块和机制。

手动实现

步骤1:预处理文本数据

我们将从定义一个简单的函数开始,用于处理文本,包括分词、小写转换和去除标点符号。

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']]

步骤2:构建词汇表

现在,我们需要扫描所有文档并构建一个完整的唯一单词列表,这就是我们的词汇表。

初始化一个空集合用于词汇表 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(corpus) 将语料库转换为文档-词频矩阵,每一行代表一个文档,每一列代表词汇中的一个词。
  • 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),
      • 依此类推。

BoW(词袋)向量可以这样解释:

  • 每个文档都是一个代表词频的数字向量。向量的维度等于词汇的大小。在这个例子中,词汇有12个单词,所以每条评论被转换成一个12维的向量。
  • 每一行中的大多数词都是零,因为不是每个文档都包含词汇中的每个单词。因此,BoW模型通常是稀疏的,也就是说,它们有很多零。

优点和局限性

现在我们来讨论一下词袋模型的优点和局限性。

优点

  1. 易于实现和解释:词袋模型是最直接明了的文本表示技术之一,非常适合初学者。它的简单性使得可以快速实现,无需复杂的预处理或专门的模型。
  2. 易于用于文本分类任务:词袋模型非常适合文本分类、情感分析和垃圾邮件检测等基本任务。这些任务通常不需要复杂语言模型,因此使用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