本文深入探讨了文本向量化这一自然语言处理(NLP)的关键技术,并全面阐述了各种向量化方法及其重要性。
首先,我们会简要回顾NLP预处理和文本清洗的核心概念,包括NLP基础知识、各种应用场景以及分词、规范化、标准化和文本清洗等关键技术。
在详细介绍向量化之前,我们先来了解一下分词的概念,以及它与向量化的区别。
什么是分词?
分词是将句子分解成更小单元(即“词元”)的过程。这些词元有助于计算机更好地理解和处理文本信息。
例如:“这篇文章很棒”
分词结果:[‘这’, ‘篇’, ‘文章’, ‘很’, ‘棒’]
什么是向量化?
众所周知,机器学习模型和算法需要数值数据才能有效运作。向量化,顾名思义,是将文本或分类数据转换成数值向量的过程。通过这种转换,我们能够更有效地训练模型,提高其准确性。
为什么需要向量化?
标记化和向量化在自然语言处理(NLP)中扮演着不同的角色。标记化是将句子分解成更小的词元,而向量化则是将这些词元转换为计算机可以理解的数字形式。
向量化不仅可以将文本转换为数字形式,还可以帮助我们捕捉到文本的语义信息。
向量化能够降低数据维度,提高处理效率,这在处理大型数据集时尤为重要。
许多机器学习算法,如神经网络,都需要数值输入,而向量化恰好可以满足这一需求。
存在多种向量化技术,本文将详细介绍这些技术。
词袋模型
如果你有一系列文档或句子,并希望对其进行分析,词袋模型可以将文档看作是装满单词的袋子,从而简化分析过程。
词袋模型常被用于文本分类、情感分析和文档检索等任务。
假设你需要处理大量的文本数据。词袋模型通过为文本数据中出现的每个独特单词创建一个词汇表来帮助你表示文本数据。创建词汇表后,模型会根据每个单词在文本中出现的频率,将每个单词编码成一个向量。
这些向量由非负数(0,1,2…)组成,代表单词在文档中出现的次数。
词袋模型包含三个步骤:
步骤1:分词
将文档分解为词元。
例如:(句子:“我喜欢披萨,我也喜欢汉堡包”)
步骤2:分离独特词/创建词汇表
创建一个包含句子中所有独特词的列表。
[“我”,“喜欢”,“披萨”,“也”,“汉堡包”]
步骤3:计算单词出现次数/创建向量
此步骤会计算词汇表中每个单词的重复次数,并将其存储在稀疏矩阵中。在稀疏矩阵中,句子向量的每一行(矩阵的列)的长度等于词汇表的大小。
导入 CountVectorizer
我们将导入 CountVectorizer 来训练我们的词袋模型。
from sklearn.feature_extraction.text import CountVectorizer
创建向量化器
在此步骤中,我们将使用 CountVectorizer 创建模型,并使用示例文本文档对其进行训练。
# 示例文本文档 documents = [ "这是第一个文档。", "这个文档是第二个文档。", "这是第三个文档。", "这是第一个文档吗?", ] # 创建 CountVectorizer cv = CountVectorizer()
# 拟合和转换 X = cv.fit_transform(documents)
转换为密集数组
在此步骤中,我们将表示形式转换为密集数组,并获取特征名称(即单词)。
# 获取特征名称/单词 feature_names = vectorizer.get_feature_names_out() # 转换为密集数组 X_dense = X.toarray()
打印文档-词矩阵和特征词
# 打印文档-词矩阵 (DTM) 和特征名称 print("文档-词矩阵 (DTM):") print(X_dense) print("\n特征名称:") print(feature_names)
文档-词矩阵(DTM):
矩阵
特征名称:
特征词
如你所见,向量由非负数(0,1,2…)组成,代表单词在文档中出现的频率。
我们有四个示例文本文档,并从这些文档中识别出九个独特的单词。我们通过为它们分配“特征名称”来将这些独特的单词存储在词汇表中。
然后,词袋模型检查第一个文档中是否存在第一个唯一单词。如果存在,则分配值 1,否则分配值 0。
如果该词出现多次(例如 2 次),则它会相应地分配值。
例如,在第二个文档中,“文档”一词重复出现两次,因此它在矩阵中的值为 2。
如果我们希望将一个单词作为词汇表中的一个特征,那么这就是一元语法表示。
n-克=一元词、二元词等。
许多库(例如 scikit-learn)可以实现词袋模型:Keras、Gensim 等。它简单易懂,并在各种场景中非常实用。
然而,词袋模型虽然速度快,但也存在一些局限性。
- 它为每个单词分配相同的权重,而忽略了单词的重要性。在许多情况下,有些单词比其他单词更重要。
- BoW 只是计算单词的频率或单词在文档中出现的次数。这可能会导致对“the”、“and”、“is”等常见词产生偏见,而这些词可能没有太多意义。
- 较长的文档可能有更多的字数,从而产生更大的向量。这使得比较变得具有挑战性,并可能导致创建稀疏矩阵,这对执行复杂的 NLP 项目不利。
为了解决这些问题,我们可以选择更好的方法,其中一种就是TF-IDF。接下来,我们详细了解一下。
TF-IDF
TF-IDF,即词频-逆文档频率,是一种用于确定文档中单词重要性的数值表示形式。
为什么我们需要 TF-IDF 而不是词袋模型?
词袋模型对所有单词一视同仁,只关注句子中独特单词的频率。而TF-IDF通过考虑单词的频率和唯一性来赋予单词重要性。
它可以避免频繁重复出现的单词影响不太常见和重要的单词。
TF:词频,衡量单词在单个句子中的重要性。
IDF:逆文档频率,衡量单词在整个文档集合中的重要性。
TF = 文档中单词的频率 / 该文档中的单词总数
DF = 包含单词 w 的文档数量 / 文档总数
IDF = log(文档总数 / 包含单词 w 的文档数)
IDF 是 DF 的倒数。其基本原理是,一个单词在所有文档中越常见,它在当前文档中的重要性就越低。
最终 TF-IDF 得分:TF-IDF = TF * IDF
这是一种找出哪些单词在单个文档中常见,但在所有文档中却相对独特的方法。 这些词有助于找到文档的主题。
例如:
文档1 = “我喜欢机器学习”
文档2 = “我爱科技博客”
我们需要找到文档的 TF-IDF 矩阵。
首先,创建一个包含所有独特单词的词汇表。
词汇表 = [“我”, “喜欢”, “机器学习”, “科技博客”]
因此,我们有 5 个单词。 让我们找出这些单词的 TF 和 IDF。
TF = 文档中单词的频率 / 该文档中的单词总数
TF:
- 对于 “我” = 文档 1 的 TF:1/4 = 0.25,文档 2 的 TF:1/3 ≈ 0.33
- 对于 “喜欢” = 文档 1 的 TF:1/4 = 0.25,文档 2 的 TF:1/3 ≈ 0.33
- 对于 “机器学习” = 文档 1 的 TF:1/4 = 0.25,文档 2 的 TF:0/3 ≈ 0
- 对于 “科技博客” = 文档 1 的 TF:0/4 = 0,文档 2 的 TF:1/3 ≈ 0.33
现在计算 IDF。
IDF = log(文档总数 / 包含单词 w 的文档数)
IDF:
- 对于 “我”:IDF 为 log(2/2) = 0
- 对于 “喜欢”:IDF 为 log(2/2) = 0
- 对于 “机器学习”:IDF 为 log(2/1) = log(2) ≈ 0.69
- 对于 “科技博客”:IDF 为 log(2/1) = log(2) ≈ 0.69
现在,我们计算 TF-IDF 的最终得分:
- 对于 “我”:文档 1 的 TF-IDF:0.25 * 0 = 0,文档 2 的 TF-IDF:0.33 * 0 = 0
- 对于 “喜欢”:文档 1 的 TF-IDF:0.25 * 0 = 0,文档 2 的 TF-IDF:0.33 * 0 = 0
- 对于 “机器学习”:文档 1 的 TF-IDF:0.25 * 0.69 ≈ 0.17,文档 2 的 TF-IDF:0 * 0.69 = 0
- 对于 “科技博客”:文档 1 的 TF-IDF:0 * 0.69 = 0,文档 2 的 TF-IDF:0.33 * 0.69 ≈ 0.23
TF-IDF 矩阵如下所示:
我 喜欢 机器学习 科技博客 文档 1 0.0 0.0 0.17 0.0 文档 2 0.0 0.0 0.0 0.23
TF-IDF 矩阵中的值表示每个单词在每个文档中的重要性。高值表示某个词在特定文档中很重要,而低值表示该词在该上下文中不太重要或不太常见。
TF-IDF 主要用于文本分类、构建聊天机器人、信息检索和文本摘要。
导入 TfidfVectorizer
从 sklearn 导入 TfidfVectorizer。
from sklearn.feature_extraction.text import TfidfVectorizer
创建向量化器
正如你所见,我们将使用 TfidfVectorizer 创建 TF-IDF 模型。
# 示例文本文档 text = [ "这是第一个文档。", "这个文档是第二个文档。", "这是第三个文档。", "这是第一个文档吗?", ] # 创建 TfidfVectorizer cv = TfidfVectorizer()
创建 TF-IDF 矩阵
通过提供文本来训练我们的模型。之后,将表示形式转换为密集数组。
# 拟合和转换以创建 TF-IDF 矩阵 X = cv.fit_transform(text)
# 获取特征名称/单词 feature_names = vectorizer.get_feature_names_out() # 将 TF-IDF 矩阵转换为更易于操作的密集数组(可选) X_dense = X.toarray()
打印 TF-IDF 矩阵和特征词
# 打印 TF-IDF 矩阵和特征词 print("TF-IDF 矩阵:") print(X_dense) print("\n特征名称:") print(feature_names)
TF-IDF 矩阵:
特征词
正如你所看到的,这些小数表示特定文档中单词的重要性。
此外,你还可以使用 n-gram 将单词组合为 2、3、4 等组。
我们还可以包含其他参数:min_df、max_feature、sublinear_tf 等。
到目前为止,我们已经探索了基于频率的基本技术。
然而,TF-IDF 无法提供文本的语义和上下文理解。接下来,让我们了解更先进的技术,这些技术改变了词嵌入的世界,并更有利于语义意义和上下文理解。
词向量
Word2vec 是一种流行的词嵌入技术(词向量的一种,有助于捕获语义和句法相似性)。它由 Tomas Mikolov 和他在谷歌的团队于 2013 年开发。Word2vec 将单词表示为多维空间中的连续向量。
Word2vec 旨在以捕获单词语义的方式来表示单词。word2vec 生成的词向量位于连续的向量空间中。
例如,“猫”和“狗”的向量比“猫”和“女孩”的向量更接近。
来源: usna.edu
word2vec 可以使用两种模型架构来创建词嵌入。
CBOW:连续词袋模型(CBOW)尝试通过平均附近单词的含义来预测单词。它会采用目标单词周围固定数量或窗口的单词,然后将它们转换为数字形式(嵌入),接着对所有单词进行平均,并使用该平均值通过神经网络预测目标单词。
预测目标:“狐狸”
句子中的单词:“The”、“quick”、“brown”、“jumps”、“over”、“the”
词向量
- CBOW 采用固定大小的单词窗口(数量),例如 2(左侧 2 个,右侧 2 个)。
- 转换为词嵌入。
- CBOW 对词嵌入进行平均。
- CBOW 对上下文单词的词嵌入进行平均。
- 平均向量尝试使用神经网络来预测目标单词。
现在,让我们了解一下 Skip-gram 与 CBOW 的不同之处。
Skip-gram:它是一种词嵌入模型,但工作原理有所不同。Skip-gram 不是预测目标单词,而是预测给定目标单词的上下文单词。
Skip-gram 在捕捉单词之间的语义关系方面更胜一筹。
例如:“国王 – 男人 + 女人 = 女王”
如果你想使用 Word2Vec,你有两种选择:训练你自己的模型或使用预先训练的模型。我们将使用预先训练的模型。
导入 gensim
你可以使用 pip install 安装 gensim:
pip install gensim
使用 word_tokenize 对句子进行标记:
首先,将句子转换为小写形式。然后,使用 word_tokenize 对句子进行标记。
# 导入必要的库 from gensim.models import Word2Vec from nltk.tokenize import word_tokenize # 示例文子 sentences = [ "我喜欢雷神", "绿巨人是复仇者联盟的重要成员", "钢铁侠帮助蜘蛛侠", "蜘蛛侠是复仇者联盟中最受欢迎的成员之一", ] # 对句子进行标记 tokenized_sentences = [word_tokenize(sentence.lower()) for sentence in sentences]
让我们训练模型:
通过提供标记化句子来训练模型。在此训练模型中使用 5 个窗口,你可以根据你的要求进行调整。
# 训练 Word2Vec 模型 model = Word2Vec(sentences=tokenized_sentences, vector_size=100, window=5, min_count=1, sg=0) # 查找相似的单词 similar_words = model.wv.most_similar("复仇者联盟")
# 打印相似的单词 print("与 '复仇者联盟' 相似的单词:") for word, score in similar_words: print(f"{word}: {score}")
与“复仇者联盟”相似的词:
Word2Vec 相似度
这些是基于 Word2Vec 模型的一些与“复仇者联盟”相似的单词及其相似度得分。
该模型计算“复仇者联盟”的词向量与词汇表中其他词之间的相似度得分(主要是余弦相似度)。相似度分数表示两个单词在向量空间中的关联程度。
例如:
此处,“帮助”一词与“复仇者联盟”一词的余弦相似度为 -0.005911458611011982。负值表明它们可能彼此不同。
余弦相似度值的范围从 -1 到 1,其中:
- 1 表示两个向量相同并且具有正相似性。
- 接近 1 的值表示高度正相似性。
- 接近 0 的值表示向量相关性不强。
- 接近 -1 的值表示高度差异。
- -1 表示两个向量完全相反,具有完全的负相似度。
访问这个链接,如果你想更好地理解 word2vec 模型并直观地了解它们的工作方式。这是一个非常酷的工具,可以用来查看 CBOW 和 Skip-gram 的实际应用。
与 Word2Vec 相似,我们有 GloVe。与 Word2Vec 相比,GloVe 可以生成需要更少内存的嵌入。接下来,让我们进一步了解 GloVe。
GloVe
全局向量表示(GloVe)是一种类似于 word2vec 的技术,用于将单词表示为连续空间中的向量。GloVe 背后的概念与 Word2Vec 相同:它生成上下文词嵌入,同时考虑到 Word2Vec 的卓越性能。
为什么我们需要 GloVe?
Word2vec 是一种基于窗口的方法,它使用附近的单词来理解单词。这意味着目标词的语义仅受句子中周围词的影响,这是一种统计低效的利用方式。
而 GloVe 则通过词嵌入来捕捉全局和局部统计数据。
何时使用 GloVe?
当你希望词嵌入能够捕捉更广泛的语义关系和全局词关联时,请使用 GloVe。
GloVe 在命名实体识别任务、单词类比和单词相似性方面优于其他模型。
首先,我们需要安装 Gensim:
pip install gensim
步骤 1:安装必要的库
# 导入必要的库 import numpy as np import matplotlib.pyplot as plt from sklearn.manifold import TSNE import gensim.downloader as api
步骤 2:导入 GloVe 模型
import gensim.downloader as api glove_model = api.load('glove-wiki-gigaword-300')
步骤 3:检索单词 “cute” 的向量表示
glove_model["cute"]
“可爱”一词的向量
这些值捕获单词的含义以及与其他单词的关系。正值表示与某些概念的正关联,而负值表示与其他概念的负关联。
在 GloVe 模型中,单词向量的每个维度代表单词的含义或上下文的某个方面。
这些维度中的负值和正值确定了“可爱”一词与模型词汇表中其他单词的语义相关程度。
不同模型的值可能有所不同。让我们找一些与“男孩”相似的单词。
模型认为与“男孩”一词最相似的前 10 个词。
# 查找相似的单词 glove_model.most_similar("boy")
与“男孩”相似的前 10 个词
正如你所看到的,与“男孩”最相似的词是“女孩”。
现在,我们尝试找出模型从提供的单词中获取语义的准确程度。
glove_model.most_similar(positive=['boy', 'queen'], negative=['girl'], topn=1)
与“女王”最相关的词
我们的模型能够找到单词之间的完美关系。
定义词汇表:
现在,尝试使用绘图来理解语义或单词之间的关系。定义你想要可视化的单词列表。
# 定义你想要可视化的单词列表 vocab = ["boy", "girl", "man", "woman", "king", "queen", "banana", "apple", "mango", "cow", "coconut", "orange", "cat", "dog"]
创建嵌入矩阵:
编写用于创建嵌入矩阵的代码。
# 用于创建嵌入矩阵的代码 EMBEDDING_DIM = glove_model.vectors.shape[1] word_index = {word: index for index, word in enumerate(vocab)} num_words = len(vocab) embedding_matrix = np.zeros((num_words, EMBEDDING_DIM)) for word, i in word_index.items(): embedding_vector = glove_model[word] if embedding_vector is not None: embedding_matrix[i] = embedding_vector
定义 t-SNE 可视化的函数:
在这段代码中,我们将为可视化图定义函数。
def tsne_plot(embedding_matrix, words): tsne_model = TSNE(perplexity=3, n_components=2, init="pca", random_state=42) coordinates = tsne_model.fit_transform(embedding_matrix) x, y = coordinates[:, 0], coordinates[:, 1] plt.figure(figsize=(14, 8)) for i, word in enumerate(words): plt.scatter(x[i], y[i]) plt.annotate(word, xy=(x[i], y[i]), xytext=(2, 2), textcoords="offset points", ha="right", va="bottom") plt.show()
让我们看看图是什么样的:
# 使用你的嵌入矩阵和单词列表调用 tsne_plot 函数 tsne_plot(embedding_matrix, vocab)
t-SNE 图
因此,正如我们所看到的,该图的左侧有“香蕉”、“芒果”、“橙子”、“椰子”和“苹果”等单词。而“牛”、“狗”和“猫”彼此相似,因为它们都是动物。
因此,我们的模型也可以找到语义和单词之间的关系!
你只需更改词汇表或从头开始创建模型,就可以尝试不同的单词。
你可以随意使用此嵌入矩阵。它可以单独应用于单词相似性任务,也可以馈送到神经网络的嵌入层中。
GloVe 在共现矩阵上训练以推导语义。它的前提是词与词的共现是一种重要的知识,并且它们的使用是利用统计数据生成词嵌入的有效方法。这就是 GloVe 如何在最终产品中添加“全局统计数据”的方式。
这就是 GloVe。另一种流行的向量化方法是 FastText。接下来让我们详细讨论一下。
FastText
FastText 是由 Facebook 人工智能研究团队推出的一个开源库,用于文本分类和情感分析。 FastText 提供了训练词嵌入的工具,词嵌入是表示词的密集向量。 这对于捕获文档的语义非常有用。 FastText 支持多标签和多类分类。
为什么选择 FastText?
FastText 比其他模型更好,因为它能够泛化到未知单词,而这是其他方法所缺少的。 FastText 为不同语言提供了预先训练的词向量,这在我们需要关于单词及其含义的先验知识的各种任务中非常有用。
FastText 与 Word2Vec
它是如何工作的?
正如我们所讨论的那样,其他模型(如 Word2Vec 和 GloVe)使用单词进行词嵌入。但是,FastText 的构建块是字母,而不是单词。这意味着他们使用字母来嵌入单词。
使用字符代替单词还有另一个好处。训练所需的数据更少。当单词成为其上下文时,可以从文本中提取更多信息。
通过 FastText 获得的词嵌入是较低级别嵌入的组合。
现在,让我们看看 FastText 如何利用子词信息。
假设我们有单词“阅读”。对于这个单词,长度为 3-6 的字符 n-gram 将会生成如下:
- 开始和结束由尖括号指示。
- 使用哈希是因为可能存在大量的 n-gram;我们不是学习每个不同 n-gram 的嵌入,而是学习总共 B 个嵌入,其中 B 代表桶大小。原论文中使用的是 200 万个桶大小。
- 使用此哈希函数将每个字符 n-gram(例如“eadi”)映射到 1 到 B 之间的整数,并且该索引具有相应的嵌入。
- 通过对这些构成的 n 元词嵌入求平均值,可以获得完整的词嵌入。
- 即使这种哈希方法导致冲突,它也可以在很大程度上帮助处理词汇量。
- FastText 中使用的网络与 Word2Vec 类似。就像那样,我们可以用两种模式训练 FastText:CBOW 和 Skip-gram。因此,我们不需要在此处重复该部分。
你可以训练你自己的模型,也可以使用预先训练的模型。我们将使用预先训练的模型。
首先,你需要安装 FastText。
pip install fasttext
我们将使用一个由关于几种药物的对话文本组成的数据集,我们必须将这些文本分为 3 种类型,就像与