大家好,我是Sonhhxg_柒,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流
个人主页-Sonhhxg_柒的博客_CSDN博客
欢迎各位→点赞 + 收藏⭐️ + 留言
系列专栏 - 机器学习【ML】 自然语言处理【NLP】 深度学习【DL】
foreword
✔说明⇢本人讲解主要包括Python、机器学习(ML)、深度学习(DL)、自然语言处理(NLP)等内容。
如果你对这个系列感兴趣的话,可以关注订阅哟
文章目录
技术要求
句子嵌入简介
交叉编码器与双编码器
基准句子相似性模型
使用 BART 进行零样本学习
使用 FLAIR 进行语义相似性实验
平均词嵌入
基于 RNN 的文档嵌入
基于 Transformer 的 BERT 嵌入
句子-BERT 嵌入
使用 Sentence-BERT 进行文本聚类
使用 BERTopic 进行主题建模
使用 Sentence-BERT 进行语义搜索
概括
到目前为止,我们已经解决了转换器库的分类和生成问题。文本表示是现代自然语言处理( NLP ) 中的另一项关键任务,尤其是对于聚类、语义搜索和主题建模等无监督任务。使用各种模型表示句子,例如Universal Sentence Encoder ( USE) 和 Siamese BERT (Sentence-BERT) 以及句子转换器等附加库将在此处进行说明。还将解释使用 BART 的零样本学习,您将学习如何使用它。还将描述少量学习方法和无监督用例,例如语义文本聚类和主题建模。最后,将介绍语义搜索等一次性学习用例。
本章将涵盖以下主题:
我们将使用 Jupyter 笔记本来运行我们的编码练习。为此,您将需要 Python 3.6+ 和以下软件包:
预训练的 BERT 模型不会产生高效且独立的句子嵌入,因为它们始终需要在端到端的监督设置中进行微调。这是因为我们可以将预训练的 BERT 模型视为一个不可分割的整体,并且语义分布在所有层中,而不仅仅是最后一层。如果没有微调,独立使用其内部表示可能是无效的。处理聚类、主题建模、信息检索或语义搜索等无监督任务也很困难。例如,因为我们必须在聚类任务期间评估许多句子对,这会导致大量计算开销。
幸运的是,对原始 BERT 模型进行了许多修改,例如Sentence-BERT ( SBERT ),以导出语义上有意义和独立的句子嵌入。我们稍后将讨论这些方法。在 NLP 文献中,已经提出了许多用于将单个句子映射到公共特征空间(向量空间模型)的神经句子嵌入方法,其中通常使用余弦函数(或点积)来衡量相似度,而欧几里德距离则用来衡量不相似度.
以下是一些可以通过句子嵌入有效解决的应用:
最简单但最一种有效的神经句子嵌入是平均池化操作,它是对句子中单词的嵌入执行的。为了更好地表示这一点,一些早期的神经方法以无监督的方式学习句子嵌入,例如 Doc2Vec、Skip-Thought、FastSent 和 Sent2Vec。Doc2Vec 利用令牌级分布理论和目标函数来预测相邻单词,类似于 Word2Vec。该方法将额外的内存令牌(称为Paragraph-ID)注入每个句子都让人联想到转换器库中的 CLS 或 SEP 标记。这个额外的令牌充当表示上下文或文档嵌入的一块内存。SkipThought 和 FastSent 被认为是句子级别的方法,其中目标函数用于预测相邻的句子。这些模型提取句子的含义,以从相邻的句子及其上下文中获取必要的信息。
其他一些方法,例如 InferSent,利用监督学习和多任务迁移学习来学习通用句子嵌入。InferSent 训练了各种监督任务以获得更有效的嵌入。基于 RNN 的监督模型(例如 GRU 或 LSTM)利用最后一个隐藏状态(或堆叠的整个隐藏状态)在监督设置中获得句子嵌入。我们在第 1 章“从词袋到变形金刚”中谈到了 RNN 方法。
到目前为止,我们有讨论了如何训练基于 Transformer 的语言模型,并分别在半监督和监督设置中对其进行微调。正如我们在前几章中所了解的,由于变压器架构,我们获得了成功的结果。一旦将特定于任务的薄线性层置于预训练模型之上,网络的所有权重(不仅是最后一个特定于任务的薄层)都会使用特定于任务的标记数据进行微调。我们还体验了 BERT 架构如何针对两组不同的任务(单句或句子对)进行微调,而无需进行任何架构修改。唯一的区别是,对于句子对任务,句子被连接起来并用 SEP 标记进行标记。因此,自我注意应用于连接句子的所有标记。这是BERT模型的一大优势,其中两个输入句子可以在每一层从对方获取必要的信息。最后,它们同时被编码。这就是所谓的交叉编码。
但是,有两个缺点关于 SBERT 作者和Humeau 等人在 2019 年解决的交叉编码器,如下所示:
或者,双编码器(如 SBERT)独立将句子对映射到语义向量空间,如下图所示。由于表示是分开的,双编码器可以缓存每个输入的编码输入表示,从而缩短推理时间。BERT 的成功双编码器修改之一是 SBERT。基于 Siamese 和 Triplet 网络结构,SBERT 微调 BERT 模型以产生语义上有意义且独立的句子嵌入。
下图显示了双编码器架构:
图 7.1 – 双编码器架构
您可以找到数百个预训练的 SBERT 模型在https://public.ukp.informatik.tu-darmstadt.de/reimers/sentence-transformers/v0.2/接受过不同目标的培训。
我们将在下一节中使用其中的一些。
那里有许多可用的语义文本相似性模型,但强烈建议您使用指标进行基准测试并了解它们的能力和差异。Papers With Code在Semantic Textual Similarity | Papers With Code提供了这些数据集的列表。
此外,每个数据集中有许多模型输出,它们按结果排序。这些结果取自上述文章。
GLUE 提供了大多数这些数据集和测试,但它不仅用于语义文本相似性。GLUE,即General Language Understanding Evaluation的缩写,是一个通用的用于评估具有不同 NLP 特征的模型的基准。有关 GLUE 数据集及其用法的更多详细信息,请参见第 2 章,主题的动手介绍。在我们继续之前,让我们看一下它:
从数据集导入 load_metric, load_dataset
from datasets import load_metric, load_dataset
metric = load_metric('glue', 'mrpc')
mrpc = load_dataset('glue', 'mrpc')
该数据集中的样本被标记为1和0,分别表示它们是相似的还是不同的。无论架构如何,您都可以使用任何模型为两个给定句子生成值。换句话说,模型应该将两个句子分类为 0 和 1。
labels = [i['label'] for i in dataset['test']]
metric.compute(predictions=predictions, references=labels)
metric = load_metric('glue', 'stsb')
metric.compute(predictions=[1,2,3],references=[5,2,2])
预测和参考是与Microsoft Research Paraphrase Corpus ( MRPC ) 中的相同;预测是模型输出,而参考是数据集标签。
pip install tensorflow-hub
pip install sentence-transformers
from datasets import load_metric, load_dataset
stsb_metric = load_metric('glue', 'stsb')
stsb = load_dataset('glue', 'stsb')
import tensorflow_hub as hub
use_model = hub.load(
"https://tfhub.dev/google/universal-sentence-encoder/4")
from sentence_transformers import SentenceTransformer
distilroberta = SentenceTransformer(
'stsb-distilroberta-base-v2')
import tensorflow as tf
import math
def use_sts_benchmark(batch):
sts_encode1 = \
tf.nn.l2_normalize(use_model(tf.constant(batch['sentence1'])), axis=1)
sts_encode2 = \
tf.nn.l2_normalize(use_model(tf.constant(batch['sentence2'])), axis=1)
cosine_similarities = \
tf.reduce_sum(tf.multiply(sts_encode1,sts_encode2),axis=1)
clip_cosine_similarities = \
tf.clip_by_value(cosine_similarities,-1.0, 1.0)
scores = 1.0 - \
tf.acos(clip_cosine_similarities) / math.pi
return scores
def roberta_sts_benchmark(batch):
sts_encode1 = tf.nn.l2_normalize(distilroberta.encode(batch['sentence1']), axis=1)
sts_encode2 = tf.nn.l2_normalize(distilroberta.encode(batch['sentence2']), axis=1)
cosine_similarities = tf.reduce_sum(tf.multiply(sts_encode1, sts_encode2), axis=1)
clip_cosine_similarities = tf.clip_by_value(cosine_similarities, -1.0, 1.0)
scores = 1.0 - tf.acos(clip_cosine_similarities) / math.pi
return scores
use_results = use_sts_benchmark(stsb['validation'])
distilroberta_results = roberta_sts_benchmark(
stsb['validation'])
results = {
"USE":stsb_metric.compute(
predictions=use_results,
references=references),
"DistillRoberta":stsb_metric.compute(
predictions=distilroberta_results,
references=references)
}
import pandas as pd
pd.DataFrame(results)
输出如下:
图 7.2 – DistilRoberta 和 USE 上的 STSB 验证结果
在本节中,您了解了语义文本相似度的重要基准。无论模型如何,您都学习了如何使用这些指标中的任何一个来量化模型性能。在下一节中,您将了解小样本学习模型。
在机器学习领域,零样本学习是被称为无需明确接受培训即可执行任务的模型。在 NLP 的情况下,假设有一个模型可以预测某些文本被分配给模型的类的概率。然而,关于这种学习类型的有趣部分是模型没有在这些类上进行训练。
随着许多可以执行迁移学习的高级语言模型的兴起,零样本学习应运而生。在 NLP 的情况下,这种学习是由 NLP 模型在测试时执行的,其中模型看到属于新类别的样本,而这些样本以前没有看到过。
这种学习通常用于分类任务,其中表示类和文本,并比较两者的语义相似性。这两者的表示形式是嵌入向量,而相似度度量(如余弦相似度或密集层等预训练分类器)输出句子/文本被分类为类的概率。
我们可以使用许多方法和方案来训练此类模型,但最早的方法之一是使用从互联网上抓取的页面,其中包含元部分中的关键字标签。有关更多信息,请阅读https://amitness.com/2020/05/zero-shot-text-classification/上的以下文章和博客文章。
BART 等语言模型没有使用如此庞大的数据,而是使用多流派自然语言推理( MNLI ) 数据集微调和检测两个不同句子之间的关系。此外,HuggingFace 模型库包含许多已实现零样本学习的模型。它们还提供了一个零样本学习管道,以方便使用。
例如,捷运来自Facebook AI Research ( FAIR ) 的代码在以下代码中用于执行零样本文本分类:
from transformers import pipeline
import pandas as pd
classifier = pipeline("zero-shot-classification",
model="facebook/bart-large-mnli")
sequence_to_classify = "one day I will see the world"
candidate_labels = ['travel',
'cooking',
'dancing',
'exploration']
result = classifier(sequence_to_classify, candidate_labels)
pd.DataFrame(result)
这结果是如下:
图 7.3 – 使用 BART 的零样本学习结果
如您所见,旅行和探索标签的概率最高,但最可能的标签是旅行。
但是,有时,一个样本可以属于多个类别(多标签)。HuggingFace 为此提供了一个名为multi_label的参数。以下示例使用此参数:
result = classifier(sequence_to_classify,
candidate_labels,
multi_label=True)
Pd.DataFrame(result)
因此,它更改为以下内容:
图 7.4 – 使用 BART 的零样本学习结果(multi_label = True)
你可以进一步测试结果,看看模型如何执行,如果使用与旅行非常相似的标签。例如,如果将移动和移动添加到标签列表中,您可以查看它的执行情况。
还有其他模型也利用标签和上下文之间的语义相似性来执行零样本分类。在few-shot learning的情况下,给模型一些样本,但是这些样本不足以单独训练一个模型。模型可以使用这些样本来执行语义文本聚类等任务,稍后将对此进行解释。
既然您已经了解了如何使用 BART 进行零样本学习,那么您应该了解它的工作原理。BART 经过微调自然语言推理( NLI ) 数据集,例如 MNLI。这些数据集包含句子对和每对的三个类;即Neutral、Entailment和Contradiction。在这些数据集上训练过的模型可以捕获两个句子的语义,并通过以 one-hot 格式分配标签来对它们进行分类。如果去掉 Neutral 标签,只使用 Entailment 和 Contradiction 作为输出标签,如果两个句子可以紧随其后,则说明这两个句子是密切相关的。换句话说,您可以将第一句更改为标签(例如travel),将第二句更改为内容(例如,有一天我会看到这个世界)。据此,如果这两者能够先后出现,这意味着标签和内容在语义上是相关的。以下代码示例展示了如何直接使用 BART 模型没有根据前面的描述进行零样本分类流水线:
from transformers import AutoModelForSequenceClassification,AutoTokenizer
nli_model = AutoModelForSequenceClassification\
.from_pretrained(
"facebook/bart-large-mnli")
tokenizer = AutoTokenizer.from_pretrained(
"facebook/bart-large-mnli")
premise = "one day I will see the world"
label = "travel"
hypothesis = f'This example is {label}.'
x = tokenizer.encode(
premise,
hypothesis,
return_tensors='pt',
truncation_strategy='only_first')
logits = nli_model(x)[0]
entail_contradiction_logits = logits[:,[0,2]]
probs = entail_contradiction_logits.softmax(dim=1)
prob_label_is_true = probs[:,1]
print(prob_label_is_true)
结果如下:
tensor([0.9945], grad_fn=
你可以也称第一句为假设和句子包含标签的前提。根据结果,前提可以包含假设。这意味着假设被标记为前提。
到目前为止,您已经学会了如何通过利用 NLI 微调模型来使用零样本学习。接下来,您将学习如何使用语义文本聚类和语义搜索执行少量/一次性学习。
在这个实验中,我们将对句子进行定性评估表示模型,这要归功于flair库,它确实为我们简化了获取文档嵌入的过程。
我们将在采用以下方法的同时进行实验:
在开始实验之前,我们需要安装这些库:
!pip install sentence-transformers
!pip install dataset
!pip install flair
对于定性评估,我们定义了一个相似句子对列表和一个不相似句子对列表(每对五对)。我们对嵌入模型的期望是它们应该分别测量高分和低分。
句子对是从 SBS Benchmark 数据集中提取的,我们已经从第 6 章的句对回归部分熟悉了,用于标记分类的微调语言模型。对于相似的配对,两个句子是完全等价的,并且它们具有相同的含义。
带有 a 的对STSB 数据集中的相似度得分约为 5随机抽取,如下:
import pandas as pd
similar=[("A black dog walking beside a pool.",
"A black dog is walking along the side of a pool."),
("A blonde woman looks for medical supplies for work in a suitcase. ",
" The blond woman is searching for medical supplies in a suitcase."),
("A doubly decker red bus driving down the road.",
"A red double decker bus driving down a street."),
("There is a black dog jumping into a swimming pool.",
"A black dog is leaping into a swimming pool."),
("The man used a sword to slice a plastic bottle.",
"A man sliced a plastic bottle with a sword.")]
pd.DataFrame(similar, columns=["sen1", "sen2"])
图 7.5 – 相似对列表
这是列表相似度得分的不同句子大约为 0,取自 STS-B 数据集:
import pandas as pd
dissimilar= [
("A little girl and boy are reading books. ",
"An older child is playing with a doll while gazing out the window."),
("Two horses standing in a field with trees in the background.",
"A black and white bird on a body of water with grass in the background."),
("Two people are walking by the ocean.",
"Two men in fleeces and hats looking at the camera."),
("A cat is pouncing on a trampoline.",
"A man is slicing a tomato."),
("A woman is riding on a horse.",
"A man is turning over tables in anger.")]
pd.DataFrame(dissimilar, columns=["sen1", "sen2"])
输出如下:
图 7.6 – 不同对列表
现在,让我们准备评估嵌入的必要函数楷模。下面的sim()函数计算两个句子之间的余弦相似度;也就是说,s1,s2:
导入火炬,numpy 作为 np
import torch, numpy as np
def sim(s1,s2):
s1=s1.embedding.unsqueeze(0)
s2=s2.embedding.unsqueeze(0)
sim=torch.cosine_similarity(s1,s2).item()
return np.round(sim,2)
本实验中使用的文档嵌入模型都是预训练模型。我们将文档嵌入模型对象和句子对列表(相似或不同)传递给下面的评估()函数,一旦模型对句子嵌入进行编码,它将计算列表中每一对的相似度得分,以及列表平均值。函数定义如下:
from flair.data import Sentence
def evaluate(embeddings, myPairList):
scores=[]
for s1, s2 in myPairList:
s1,s2=Sentence(s1), Sentence(s2)
embeddings.embed(s1)
embeddings.embed(s2)
score=sim(s1,s2)
scores.append(score)
return scores, np.round(np.mean(scores),2)
现在,是时候评估句子嵌入模型。我们将从平均池化方法开始!
平均单词嵌入(或文档池)将平均池操作应用于句子中的所有单词,其中所有单词嵌入的平均值被认为是句子嵌入。以下执行实例化基于 GloVe 向量的文档池嵌入。请注意,虽然我们在这里只使用 GloVe 向量,但flair API 允许我们使用多个词嵌入。这是代码定义:
from flair.data import Sentence
from flair.embeddings import WordEmbeddings, DocumentPoolEmbeddings
glove_embedding = WordEmbeddings('glove')
glove_pool_embeddings = DocumentPoolEmbeddings(
[glove_embedding]
)
让我们在相似对上评估 GloVe 池模型,如下所示:
evaluate(glove_pool_embeddings, similar)
([0.97, 0.99, 0.97, 0.99, 0.98], 0.98)
结果似乎很好,因为这些结果值非常高,这是我们所期望的。但是,该模型也会为不同列表产生高分,例如平均 0.94。我们的期望值小于 0.4。我们将在本章后面讨论为什么会得到这个。这是执行:
evaluate(glove_pool_embeddings, dissimilar)
([0.94, 0.97, 0.94, 0.92, 0.93], 0.94)
接下来,让我们在同一问题上评估一些 RNN 嵌入。
让我们实例化一个 GRU 模型基于 GloVe 嵌入,其中DocumentRNNEmbeddings的默认模型是 GRU:
from flair.embeddings import WordEmbeddings, DocumentRNNEmbeddings
gru_embeddings = DocumentRNNEmbeddings([glove_embedding])
运行评估方法:
evaluate(gru_embeddings, similar)
([0.99, 1.0, 0.94, 1.0, 0.92], 0.97)
evaluate(gru_embeddings, dissimilar)
([0.86, 1.0, 0.91, 0.85, 0.9], 0.9)
同样,我们在不同列表中获得高分。这不是我们想要的句子嵌入。
以下执行实例化一个池化最后一层的基于bert-base-uncased 的模型:
from flair.embeddings import TransformerDocumentEmbeddings
from flair.data import Sentence
bert_embeddings = TransformerDocumentEmbeddings(
'bert-base-uncased')
跑过评价,如下:
evaluate(bert_embeddings, similar)
([0.85, 0.9, 0.96, 0.91, 0.89], 0.9)
evaluate(bert_embeddings, dissimilar)
([0.93, 0.94, 0.86, 0.93, 0.92], 0.92)
这更糟!不相似列表的得分高于相似列表的得分。
现在,让我们申请Sentence-BERT 用于区分相似对和不同对的问题,如下:
!pip install sentence-transformers
from flair.data import Sentence
from flair.embeddings import SentenceTransformerDocumentEmbeddings
sbert_embeddings = SentenceTransformerDocumentEmbeddings(
'bert-base-nli-mean-tokens')
evaluate(sbert_embeddings, similar)
([0.98, 0.95, 0.96, 0.99, 0.98], 0.97)
evaluate(sbert_embeddings, dissimilar)
([0.48, 0.41, 0.19, -0.05, 0.0], 0.21)
做得好!SBERT 模型产生了更好的结果。该模型为不同列表产生了较低的相似性分数,这是我们所期望的。
tricky_pairs=[
("An elephant is bigger than a lion",
"A lion is bigger than an elephant") ,
("the cat sat on the mat",
"the mat sat on the cat")]
evaluate(glove_pool_embeddings, tricky_pairs)
([1.0, 1.0], 1.0)
evaluate(gru_embeddings, tricky_pairs)
([0.87, 0.65], 0.76)
evaluate(bert_embeddings, tricky_pairs)
([1.0, 0.98], 0.99)
evaluate(sbert_embeddings, tricky_pairs)
([0.93, 0.97], 0.95)
有趣的!分数非常高,因为句子相似度模型的工作原理类似于主题检测并测量内容相似度。当我们查看这些句子时,它们共享相同的内容,即使它们相互矛盾。内容是关于狮子和大象或猫和垫子。因此,这些模型产生了很高的相似性分数。由于 GloVe 嵌入方法在不关心词序的情况下汇集了词的平均值,因此它将两个句子测量为相同。另一方面,GRU 模型产生的值较低,因为它关心词序。令人惊讶的是,即使是 SBERT 模型也不能产生有效的分数。这可能是由于 SBERT 模型中使用的基于内容相似性的监督。
从变压器\
from transformers import AutoModelForSequenceClassification, AutoTokenizer
nli_model = AutoModelForSequenceClassification.from_pretrained(
'joeddav/xlm-roberta-large-xnli')
tokenizer = AutoTokenizer.from_pretrained(
'joeddav/xlm-roberta-large-xnli')
import numpy as np
for permise, hypothesis in tricky_pairs:
x = tokenizer.encode(premise,
hypothesis,
return_tensors='pt',
truncation_strategy='only_first')
logits = nli_model(x)[0]
print(f"Permise: {permise}")
print(f"Hypothesis: {hypothesis}")
print("Top Class:")
print(nli_model.config.id2label[np.argmax(
logits[0].detach().numpy()). ])
print("Full softmax scores:")
for i in range(3):
print(nli_model.config.id2label[i],
logits.softmax(dim=1)[0][i].detach().numpy())
print("="*20)
Permise: An elephant is bigger than a lion
Hypothesis: A lion is bigger than an elephant
Top Class:
contradiction
Full softmax scores:
contradiction 0.7731286
neutral 0.2203285
entailment 0.0065428796
====================
Permise: the cat sat on the mat
Hypothesis: the mat sat on the cat
Top Class:
entailment
Full softmax scores:
contradiction 0.49365467
neutral 0.007260764
entailment 0.49908453
====================
在某些问题中,在某些问题中,NLI 比语义文本具有更高的优先级,因为它旨在找到矛盾或蕴涵,而不是原始相似度得分。对于下一个示例,同时使用两个句子表示蕴涵和矛盾。这有点主观,但对模型而言,第二句对似乎是蕴涵和矛盾之间的密切联系。
用于聚类算法,我们需要一个适合的模型文字相似度。让我们在这里使用paraphrase-distilroberta-base-v1模型进行更改。我们将首先为我们的集群实验加载 Amazon Polarity 数据集。该数据集包括截至 2013 年 3 月的 18 年期间的亚马逊网页评论。原始数据集包括超过 3500 万条评论。这些评论包括产品信息、用户信息、用户评分和用户评论。让我们开始吧:
import pandas as pd, numpy as np
import torch, os, scipy
from datasets import load_dataset
dataset = load_dataset("amazon_polarity",split="train")
corpus=dataset.shuffle(seed=42)[:10000]['content']
from sentence_transformers import SentenceTransformer
model_path="paraphrase-distilroberta-base-v1"
model = SentenceTransformer(model_path)
corpus_embeddings = model.encode(corpus)
corpus_embeddings.shape
(10000, 768)
from sklearn.cluster import KMeans
K=5
kmeans = KMeans(
n_clusters=5,
random_state=0).fit(corpus_embeddings)
cls_dist=pd.Series(kmeans.labels_).value_counts()
cls_dist
3 2772
4 2089
0 1911
2 1883
1 1345
在这里,我们获得了五组评论。正如我们从输出中看到的,我们有相当分布的集群。集群的另一个问题是我们需要了解这些集群的含义。作为建议,我们可以对每个集群应用主题分析或检查基于集群的 TF-IDF 以了解内容。现在,让我们看看另一种方法来做到这一点集群中心。Kmeans 算法计算聚类中心,称为质心,保存在kmeans.cluster_centers_属性中。质心只是每个集群中向量的平均值。因此,它们都是虚构的点,而不是现有的数据点。让我们假设最接近质心的句子将是相应集群的最具代表性的示例。
distances = scipy.spatial.distance.cdist(kmeans.cluster_centers_, corpus_embeddings)
centers={}
print("Cluster", "Size", "Center-idx",
"Center-Example", sep="\t\t")
for i,d in enumerate(distances):
ind = np.argsort(d, axis=0)[0]
centers[i]=ind
print(i,cls_dist[i], ind, corpus[ind] ,sep="\t\t")
输出如下:
图 7.7 – 集群的质心
从这些有代表性的句子,我们可以推理集群。似乎 Kmeans 将评论分为五个不同的类别:电子产品、音频 Cd/音乐、DVD 电影、书籍和家具与家居。现在,让我们在 2D 空间中可视化句子点和聚类质心。我们将使用统一流形逼近和投影( UMAP ) 库来减少维度。您可以使用的其他在 NLP 中广泛使用的降维技术包括 t-SNE 和 PCA(请参阅第 1 章,从词袋到变形金刚)。
!pip install umap-learn
import matplotlib.pyplot as plt
import umap
X = umap.UMAP(
n_components=2,
min_dist=0.0).fit_transform(corpus_embeddings)
labels= kmeans.labels_fig, ax = plt.subplots(figsize=(12,8))
plt.scatter(X[:,0], X[:,1], c=labels, s=1, cmap='Paired')
for c in centers:
plt.text(X[centers[c],0], X[centers[c], 1],"CLS-"+ str(c), fontsize=18)
plt.colorbar()
这输出为如下:
图 7.8 – 聚类点可视化
在里面之前的输出,点已根据到他们的集群成员和质心。看起来我们选择了正确数量的集群。
为了捕捉主题并解释集群,我们简单地将句子(每个集群一个句子)定位在靠近集群中心的位置。现在,让我们看一下通过主题建模更准确地捕捉主题的方法。
你可能熟悉许多无监督主题建模技术用于从文档中提取主题;潜在狄利克雷分配( LDA ) 主题建模和非负矩阵分解( NMF ) 是在文学。BERTopic 和 Top2Vec 是两个重要的基于 Transformer 的主题建模项目。在本节中,我们将 BERTopic 模型应用于我们的亚马逊语料库。它利用 BERT 嵌入和基于类的 TF-IDF 方法来获得易于解释的主题。
首先,BERTopic 模型首先使用句子转换器或任何句子嵌入对句子进行编码模型,然后是聚类步骤。聚类步骤有两个阶段:通过UMAP减少嵌入的维数,然后通过基于层次密度的带噪声应用空间聚类( HDBSCAN ) 对减少的向量进行聚类,这产生类似文档的组。在最后阶段,主题由集群方式的 TF-IDF 捕获,其中模型提取每个集群而不是每个文档的最重要的单词,并获得每个集群的主题描述。让我们开始吧:
!pip install bertopic
重要的提示
您可能需要重新启动运行时,因为此安装将更新一些已加载的包。因此,从 Jupyter notebook 转到Runtime | 重新启动运行时。
from bertopic import BERTopic
sentence_model = SentenceTransformer(
"paraphrase-distilroberta-base-v1")
topic_model = BERTopic(embedding_model=sentence_model)
topics, _ = topic_model.fit_transform(corpus)
topic_model.get_topic_info()[:6]
输出如下:
图 7.9 – BERTopic 结果
请注意,使用相同参数的不同 BERTopic 运行可能会产生不同的结果,因为 UMAP 模型是随机的。现在,我们来看看话题五的词分布,如下:
topic_model.get_topic(5)
输出如下:
图 7.10 – 主题模型的第五个主题词
这主题词是那些向量接近的词语义空间中的主题向量。在这个实验中,我们没有对语料库进行聚类;相反,我们将该技术应用于整个语料库。在我们之前的示例中,我们分析了具有最接近句子的聚类。现在,我们可以通过将主题模型分别应用于每个集群来找到主题。这非常简单,您可以自己运行它。
请参阅 Top2Vec 项目有关更多详细信息和有趣的主题建模应用程序,请访问GitHub - ddangelov/Top2Vec: Top2Vec learns jointly embedded topic, document and word vectors.。
我们可能已经熟悉基于关键字的搜索(布尔模型),对于给定的关键字或模式,我们可以检索与模式匹配的结果。或者,我们可以使用正则表达式,我们可以在其中定义高级模式,例如 lexico-syntactic 模式。这些传统方法无法处理同义词(例如,汽车与汽车相同)或词义问题(例如,将银行作为河流或银行的一侧)作为金融机构)。第一个同义词案例由于错过了不应错过的文档而导致召回率低,而第二个同义词案例由于捕获了不被捕获的文档而导致精度低。基于向量或语义的搜索方法可以通过构建查询和文档的密集数字表示来克服这些缺点。
让我们为网站上闲置的常见问题( FAQ )建立一个案例研究。我们将在语义搜索问题中利用常见问题解答资源。常见问题解答包含常见问题。我们将使用自然非政府组织世界自然基金会( WWF ) 的常见问题解答 ( The World Wildlife Fund: for people and nature to thrive | WWF )。
鉴于这些描述,很容易理解使用语义模型执行语义搜索与一次性学习问题非常相似,其中我们只有一个类(单个样本),并且我们想要重新排序根据它的其余数据(句子)。您可以将问题重新定义为搜索语义上接近给定样本的样本,或者根据样本进行二元分类。您的模型可以提供相似度指标,所有其他样本的结果将使用该指标重新排序。最终的有序列表是搜索结果,它根据语义表示和相似度度量重新排序。
WWF 在其网页上有 18 个问题和答案。在这个实验中,我们将它们定义为一个名为wf_faq的 Python 列表对象:
用户可以自由提出他们想要的任何问题。我们需要评估FAQ中哪个问题与用户的问题最相似,这是quora-distilbert-base模型的目标。SBERT 中心有两种选择——一种是英语,另一种是多语种,如下所示:
让我们构建一个语义按照以下步骤搜索模型:
from sentence_transformers import SentenceTransformer
model = SentenceTransformer('quora-distilbert-base')
faq_embeddings = model.encode(wwf_faq)
test_questions=["What should be done, if the adoption pack did not reach to me?",
" How fast is my adoption pack delivered to me?",
"What should I do to renew my adoption?",
"What should be done to change address and contact details ?",
"I live outside of the UK, Can I still adopt an animal?"]
test_q_emb= model.encode(test_questions)
from scipy.spatial.distance import cdist
for q, qe in zip(test_questions, test_q_emb):
distances = cdist([qe], faq_embeddings, "cosine")[0]
ind = np.argsort(distances, axis=0)[:3]
print("\n Test Question: \n "+q)
for i,(dis,text) in enumerate(
zip(distances[ind],[wwf_faq[i] for i in ind])):
print(dis,ind[i],text, sep="\t")
这输出如下:
图 7.11 – 问题-问题相似度
在这里,我们可以依次看到索引0、1、2、3和4,这意味着模型成功地找到了预期的类似问题。
def get_best(query, K=5):
query_emb = model.encode([query])
distances = cdist(query_emb,faq_embeddings,"cosine")[0]
ind = np.argsort(distances, axis=0)
print("\n"+query)
for c,i in list(zip(distances[ind], ind))[:K]:
print(c,wwf_faq[i], sep="\t")
get_best("How do I change my contact info?",3)
图 7.12 – 相似问题相似度结果
get_best("我如何获得我的机票\
get_best("How do I get my plane ticket if I bought it online?")
图 7.13 – 不同的问题相似度结果
最佳相异性得分为 0.35。因此,我们需要定义一个阈值,例如 0.3,以便模型忽略高于该阈值的问题,并且说没有找到类似的答案。
除了问题-问题对称搜索相似度之外,我们还可以利用 SBERT 的问答非对称搜索模型,例如msmarco-distilbert-base-v3,它是在大约 500K Bing 搜索查询的数据集上训练的。它被称为 MSMARCO Passage Ranking。这个模型帮助我们衡量问题和上下文的相关程度,并检查问题的答案是否在文章中。
在本章中,我们学习了文本表示方法。我们了解了如何使用不同且多样化的语义模型来执行诸如零/少数/一次性学习之类的任务。我们还了解了 NLI 及其在捕获文本语义方面的重要性。此外,我们还研究了一些有用的用例,例如语义搜索、语义聚类和使用基于 Transformer 的语义模型的主题建模。我们学习了如何可视化聚类结果并了解质心在此类问题中的重要性。
在下一章中,您将了解高效的 Transformer 模型。您将了解基于 Transformer 的模型的蒸馏、修剪和量化。您还将了解不同且高效的 Transformer 架构,这些架构可以提高计算和内存效率,以及如何在 NLP 问题中使用它们。