Press "Enter" to skip to content

使用嵌入模型在语料库中探索语义关系

最近,我与一些同学和学者进行了交流,他们的研究兴趣涉及自由形式文本的分析。不幸的是,以任何方式来获得对书面自然语言的有意义的洞察都不是一项微不足道的任务。当然,仔细阅读是一个选择,但你理想的情况是希望通过更宏观的/定量的角度来观察文本数据。更不用说在大数据时代,仔细阅读很少是一个可行的选择。

到目前为止,我最喜欢用主题模型来对语料库进行探索性数据分析,并且我已经写了多篇文章介绍如何以最轻松的方式进行探索。虽然主题模型很棒,但它们并不是所有文本问题的最佳解决方法。

词嵌入是文本数据的数值表示,并且已经成为文本的语义查询的规范方法。在本文中,我们将探讨一些使用嵌入来探索文本数据的方法。

使用词嵌入捕捉概念之间的关系

词嵌入模型是一组以无监督方式学习术语的潜在向量表示的方法。当从自然语言学习词嵌入时,实际上可以获得一个嵌入空间中的语义关系图。

词嵌入通常在大型语料库上进行训练,以便能够捕捉人类语言中的一般词与词之间的关系。这非常有用,因为可以将关于语言的一般知识融入到特定应用的模型中。这也被称为迁移学习,在机器学习领域已经成为一个热门话题。

如果我们不想将通用知识转移到特定模型中,而只是想获得较小语料库的语义特定方面的映射,该怎么办?假设我们有一个来自论坛的评论语料库,我们想探索其中可以找到的各种联想关系。

我们可以通过从头开始在该语料库上训练词嵌入模型来实现这一目标,而不是使用已经为我们预训练好的模型。在这个例子中,我将使用20Newsgroups数据集作为语料库,我们将探索语义关系。

训练模型

现在让我们从一个词嵌入模型开始。你可能熟悉Word2Vec,这是在研究和实践中推广静态词嵌入使用的方法。另一方面,由斯坦福大学的人们开发的GloVe在大多数情况下似乎是一种更好的方法,并且我的经验表明它在较小语料库中提供更高质量的词嵌入。不幸的是,GloVe没有在Gensim中实现,但我幸运地为原始GloVe代码制作了一个完全兼容Gensim的接口,我们将使用它来训练模型。

让我们安装gensim、glovpy、scikit-learn,这样我们就可以获取20Newsgroups数据集以及embedding-explorer:

pip install glovpy gensim scikit-learn

我们首先需要加载数据集,并对其进行分词,为此我们将使用gensim内置的分词器。我们还将过滤掉停用词,因为它们对任务没有任何有意义的信息。

from gensim.utils import tokenizefrom sklearn.datasets import fetch_20newsgroupsfrom sklearn.feature_extraction.text import ENGLISH_STOP_WORDSdef clean_tokenize(text: str) -> list[str]:    """这个函数对文本进行分词并去除其中的停用词"""    tokens = tokenize(text, lower=True, deacc=True)    tokens = [token for token in tokens if token not in ENGLISH_STOP_WORDS]    return tokens# 加载数据集dataset = fetch_20newsgroups(    remove=("headers", "footers", "quotes"), categories=["sci.med"])newsgroups = dataset.data# 对数据集进行分词tokenized_corpus = [clean_tokenize(text) for text in newsgroups]

完成后,我们就可以在经过分词的语料库上轻松训练GloVe模型了。

from glovpy import GloVe# 训练词嵌入模型model = GloVe(vector_size=25)model.train(tokenized_corpus)

我们已经可以查询这个词嵌入模型了,让我们看看与“child”最接近的十个词是什么。

model.wv.most_similar("child")==============================+------------+----------+| age        | 0.849304 || consistent | 0.844267 || adult      | 0.805101 || range      | 0.800615 || year       | 0.798799 || hand       | 0.792965 || children   | 0.792113 || use        | 0.789804 || restraint  | 0.773764 || belt       | 0.77003  |+------------+----------+

探索,可视化!

然而,逐个调查每个词与其他词的关系很快就变得乏味。理想情况下,我们还希望能够可视化这些关系,甚至获得一些网络。

幸运的是,我开发的embedding-explorer软件包可以帮助我们。在计算人文学科中,我们经常使用词嵌入模型和由这些模型中的关系构建的语义网络,并且embedding-explorer可以帮助我们以交互和可视化的方式探索这些内容。该软件包包含多个交互式Web应用程序,我们首先来看一下“网络探索器”。

这个应用程序的理念是嵌入模型中的概念自然形成某种网络结构。相关性较高的词之间存在强连接,而其他词可能没有连接。在该应用程序中,您可以基于您指定的一组种子词构建概念图,并进行两个级别的自由联想。

在每个联想级别上,我们将根据嵌入模型中与已有词最接近的五个词,将它们添加到我们的网络中,并与它们相关联的词建立连接。连接的强度由嵌入空间中概念的余弦距离确定。这类网络已经被我或我的同事在多个研究项目中证明是有用的。

让我们从我们的词嵌入模型启动应用程序。

from embedding_explorer import show_network_explorervocabulary = model.wv.index_to_keyembeddings = model.wv.vectorsshow_network_explorer(vocabulary, embeddings=embeddings)

这将在浏览器窗口中打开一个页面,您可以自由地探索语料库中的语义关系。下面是我在查看围绕“耶稣”、“科学”和“宗教”等词汇形成的网络时的屏幕截图。

浏览我们的GloVe模型中的语义关系

例如,我们可以看到人们在线上讨论这些话题时似乎暗示宗教和科学与政治、社会和哲学之间存在关联,这非常有道理。观察教育在科学和宗教之间处于中间位置,但明显与科学更相关也很有趣。这值得进一步探索。

使用句子变换器的N-gram网络

现在,如果我们不仅想查看单词级别的关系,还想查看短语或句子呢?

我的建议是使用N-gram。N-gram实际上只是在文本中连续出现的N个项。例如,在句子“I love my little cute dog”中,我们有四个连续词组:“I love my little”、“love my little cute”和“my little cute dog”。现在的问题是,我们如何学习良好的N-gram的语义表示?

您理论上仍然可以使用GloVe,将短语或句子视为一个标记,但有一个问题。由于N-gram的多样性随着N的增加而急剧增加,某个特定的N-gram可能只会出现一两次,我们可能无法学习到良好的表示。

那么,我们将词嵌入的平均值作为短语的表示方法如何?这可能会有很大帮助,但问题是我们完全丢失了有关不同单词重要性、它们在句子中的顺序以及所有上下文信息的所有信息。

解决这个问题的方法是使用句子变换器,它是产生上下文敏感的文本表示的深度神经语言模型。几年来,它们已经胜过所有其他方法,已成为文本嵌入的行业标准。训练这样的模型需要大量的数据,而我们手头没有这些数据,但幸运的是,我们可以使用一些好的预先训练模型。

N-gram提取

让我们首先从我们的语料库中提取N-gram。我选择使用四个连续词组,但您可以选择任何数字。我们将使用scikit-learn的CountVectorizer进行此操作。

从sklearn.feature_extraction.text中导入CountVectorizer
# 首先我们在语料库上训练一个模型,学习所有的四元组
# 我们现在只考虑最常见的4000个四元组
# 但你可以随意尝试不同的数量
feature_extractor = CountVectorizer(ngram_range=(4,4), max_features=4000)
feature_extractor.fit(newsgroups)
# 然后我们获得向量化器的词汇表
four_grams = feature_extractor.get_feature_names_out()

嵌入模型

我们需要一个用于文本表示的嵌入模型。如前所述,我们将使用一个预训练模型。我选择了all-MiniLM-L6-v2,因为它非常稳定,被广泛使用,并且相当小巧,所以即使在您的个人计算机上也可能运行顺畅。

我们将使用另一个包embetter,以便我们可以以scikit-learn兼容的方式使用句子转换器。

pip install embetter[text]

我们可以这样在Python中加载模型:

from embetter.text import SentenceEncoder
encoder = SentenceEncoder("all-MiniLM-L6-v2")

探索!

然后我们可以将模型和四元组加载到embedding-explorer中。

from embedding_explorer import show_network_explorer
show_network_explorer(four_grams, vectorizer=encoder)

请注意,这样我们可以指定任意种子,而不仅仅是我们四元组词汇表中的种子。下面是一个我输入两个句子并查看由它们周围四个四元组构建的网络的屏幕截图。

Exploring Phrases and Sentences in the Corpus

有趣的是观察哪些短语位于中间位置。看起来,法律和历史在这里起到了宗教和科学之间的某种连接作用。

使用文档嵌入来调查语料库级别的语义结构

我们现在已经在词和短语级别上查看了我们的语料库,并看到了其中自然产生的语义结构。如果我们想要获取关于文档层次上发生的情况的一些信息呢?哪些文档彼此靠近,会出现哪些组?

请注意,这个问题的一个自然解决方案是主题建模,如果您还没有尝试过,应该了解一下。在本文章中,我们将探索与此任务有关的其他相关概念。

文档表示

与之前一样,我们需要考虑如何表示单个文档,以便捕捉其中的语义内容。

更传统的机器学习方法通常会使用词袋表示或训练Doc2Vec模型。这些都是不错的选择(您可以进行实验),但它们仍然缺乏对文本的上下文理解。由于我们的语料库中的文本长度不太长,我们仍然可以使用句子转换器对其进行嵌入。让我们继续使用我们用于短语的相同嵌入模型。

投影和聚类

探索文档的语义表示的一种自然方法是将它们投影到较低维空间(通常是2D),并使用这些投影来可视化文档。我们还可以查看在给定一些聚类方法的情况下文档的聚类。

现在这一切都很棒,但投影、降维和聚类方法的空间如此广阔,以至于我经常在想:“如果我使用其他方法,会有很大区别吗?”为了解决这个问题,我在embedding-explorer中添加了另一个应用程序,您可以自由快速地探索各种不同方法给出的可视化结果。

我们的工作流程如下:

  1. 在进行下一步之前,我们可能想要减少嵌入的维度。您可以选择各种降维方法,也可以关闭它。
  2. 我们想要将嵌入的结果投影到2D空间,以便我们可以将其可视化。
  3. 我们可能想要对嵌入进行聚类,以查看被分组在一起的文档种类。
在嵌入资源浏览器中进行聚类和投影工作流程

现在我们在进行这个工作时还需要了解一些有关文档的外部信息(文本内容,标题等),否则我们很难进行解释。

让我们创建一个包含以下列的数据框:1. 每个文档的前400个字符,这样我们可以了解文本的大致内容。2. 文本的长度,这样我们可以在可视化中看到哪些文本较长,哪些较短。3. 数据集中文本所属的组。

import pandas as pdimport numpy as np# 计算每个文本的长度(字符数)lengths = [len(text) for text in corpus]# 提取每个文本的前400个字符text_starts = [text[:400] for text in corpus]# 提取每个文本所属的组# Sklearn将标签返回为整数,我们需要将其映射回# 实际的文本标签group_labels = np.array(dataset.target_names)[dataset.target]# 使用可用的元数据构建一个数据框metadata = pd.DataFrame(dict(length=lengths, text=text_starts, group=group_labels))

然后,我们可以使用传递的元数据启动应用程序,这样我们可以悬停并查看有关文档的信息。

from embedding_explorer import show_clusteringshow_clustering(  newsgroups,  vectorizer=encoder,  metadata=metadata,  hover_name="group", # 悬停盒标题将是组标题  hover_data=["text", "length"] # 我们还想在悬停时看到这些信息)

当应用程序首次启动时,您将看到以下屏幕:

聚类应用中的选项

运行聚类后,您将能够查看按簇着色的所有文档的地图。您可以悬停在点上以查看有关文档的元数据…

聚类应用截图

在底部,您甚至可以选择如何着色、标记和调整点的大小。

带有文档大小的聚类

总结

对文本数据进行探索性分析是困难的。我们已经介绍了一些使用先进机器学习技术进行交互式调查的方法。我希望本文中讨论的方法和embedding-explorer Python软件包对您未来的研究/工作有所帮助。

祝和平 ✌️

(文章中的所有图片均来自embedding-explorer的文档,由作者制作)

Leave a Reply

Your email address will not be published. Required fields are marked *