如何让机器理解人类语言?Embedding技术详解
文章目录
- 如何让机器理解人类语言?Embedding技术详解
- 介绍
- 什么是词嵌入?
- 什么是句子嵌入?
- 句子嵌入模型
- 实现句子嵌入的方法
- 值得尝试的句子嵌入模型
- 句子嵌入库
- 实践
- Step 1
- Step 2
- Step 3
- Doc2Vec
- SentenceBERT(句子BERT)
- InferSent
- 句子编码器架构
- GLovE (Global Vectors for Word Representation)
- FastText
- 示例
- Universal Sentence Encoder
- 语义相似
- 文本分类
- 示例
- Skip-Thought Vectors
- 示例
- Quick-Thought Vectors
- 示例
- 参考资料
介绍
在语言理解方面,人类的能力是无与伦比的。因为人脑能够轻松理解语言中的各种情感信息,例如幽默、讽刺、负面情绪等,只是我们必须了解句子所用的语言。
例如,如果有人用英语评论我的文章,如果不借助翻译我可能很难理解对方想要表达的内容,所以为了有效的沟通,我们需要用对方最熟悉的语言进行交流。
同理,为了让机器能够处理并理解任何类型的文本,需要我们将文本表示为机器能够理解的“语言”。那么,机器最擅长理解哪种语言呢?没错,就是数字。无论我们提供什么样的数据给机器,例如视频、音频、图片或是文本,机器最终都只能处理数字。因此,将文本转换为数字,或者说进行文本嵌入,就是一个非常重要的手段。
在本文中,我将介绍一些主要的句子嵌入技术,并提供Python代码实现。文章将限于介绍这些技术的架构概览以及如何在Python中实现它们。我们将以查找给定句子的相似句子为例,演示如何使用这些embedding技术。
什么是词嵌入?
最初的嵌入技术只处理单词。给定一组单词,模型会为集合中的每个单词生成一个嵌入。最简单的方法是对提供的单词序列进行one-hot编码,使对应的单词用1表示,其他单词用0表示。虽然这在表示单词和其他简单的文本处理任务中比较有效,但它并不适用于更复杂的任务,例如查找相似单词。
例如,如果我们搜索查询:“广州受欢迎的景点”,我们希望获得与景点、广州的景点以及最受欢迎的一系列搜索结果。然而,如果我们得到一个搜索结果是:“广州著名旅游胜地”,我们简单的方法将无法检测到“受欢迎”与“著名”或“景点”与“旅游胜地”之间的相似性。
这类问题催生了词嵌入(Word embedding)技术。词嵌入不仅转换单词,还识别单词的语义和句法,构建这些信息的向量表示。一些流行的词嵌入技术包括Word2Vec、GloVe、ELMo、FastText等。
这些技术的基本概念是利用单词周围的信息来进行建模。研究人员在词嵌入技术方面取得了突破性的创新,他们找到了更好的方式来表达越来越多关于单词的信息,并且不仅仅是表示单词,而是整个句子甚至段落。
什么是句子嵌入?
在自然语言处理(NLP)中,句子嵌入指的是将句子表示为实数向量的过程,这种向量编码了句子的语义信息。这使得我们能够利用这些向量之间的距离或相似性来比较句子相似性。例如,使用深度学习模型训练的通用句子编码器(USE)生成这些嵌入,这些嵌入可用于文本分类、聚类和相似性匹配等任务。
在处理大量文本时,仅使用单词会非常繁琐,我们将受到从词嵌入中提取信息的限制,所以如果我们可以直接处理整个句子,而不仅仅是单词,那么性能将会大大提升。
例如对于这句话:“我不喜欢拥挤的地方”,然后几句话后,我们读到“然而,我喜欢世界上最繁忙的城市之一,北京”。如何使机器能够理解“拥挤的地方”和“繁忙的城市”之间的联系呢?
显然,词嵌入在这里是远远不够的,因此,我们可以使用句子嵌入来解决这一问题。句子嵌入技术表示整个句子及其语义信息为向量。这有助于机器理解文本中的上下文、意图和其他细微差别。
句子嵌入模型
句子嵌入模型旨在将句子的语义封装在固定长度的向量中。与传统的词袋模型(BoW)表示或one-hot编码不同,句子嵌入能够捕捉上下文、含义和词汇间的关系。这种转换使得机器能够把握人类语言的微妙之处。
实现句子嵌入的方法
为了生成句子嵌入,采用了几种方法:
- 平均词嵌入法:这种方法对句子中的词嵌入取平均值。虽然简单,但可能无法捕捉复杂的上下文细节。
- 预训练模型如BERT:BERT(双向编码器表示从Transformer)等模型彻底改变了句子嵌入的方式。基于BERT的模型考虑句子中每个词的上下文,产生丰富且具有上下文意识的嵌入。
- 基于神经网络的方法:Skip-Thought向量和InferSent等神经网络基句子嵌入模型。它们通过预测周围句子来训练,从而促使它们理解句子语义。
值得尝试的句子嵌入模型
- BERT(双向编码器表示从Transformers):BERT设定了句子嵌入的基准,提供了针对各种NLP任务的预训练模型。其双向注意力和上下文理解使其在语义理解上尤为突出。
- RoBERTa:作为BERT的进化版,RoBERTa改进了其训练方法,达到了多项NLP任务的SOTA。
- USE(通用句子编码器):由谷歌开发的USE,可以为文本生成嵌入,这些嵌入可用于包括跨语言任务在内的多种应用。
句子嵌入库
像词嵌入一样,句子嵌入也是一个非常火的研究领域,其中包含了许多有趣的技术,这些技术突破了帮助机器理解我们语言的障碍。
我们假设您已经具有词嵌入和其他基础NLP概念的知识。在继续学习下文之前,我推荐您阅读以下文章:
- 自然语言处理入门指南(附Python代码)
- NLP从业者必读:预训练词嵌入指南
实践
接下来,我们将设置一些基本的库并定义我们的句子列表。以下步骤将帮助您实现这一点:
Step 1
首先,导入库并下载 punk’:
import nltk
nltk.download('punkt')
from nltk.tokenize import word_tokenize
import numpy as np
Step 2
然后,我们定义我们的句子列表。您可以使用更大的列表(最好使用句子列表以便于每个句子的处理)。
sentences = ["I ate dinner.",
"We had a three-course meal.",
"Brad came to dinner with us.",
"He loves fish tacos.",
"In the end, we all felt like we ate too much.",
"We all agreed; it was a magnificent evening."]
Step 3
然后我们对这些单词进行分词:
tokenized_sent = []
for s in sentences:
tokenized_sent.append(word_tokenize(s.lower()))
print(tokenized_sent)
output:
[['i', 'ate', 'dinner', '.'], ['we', 'had', 'a', 'three-course', 'meal', '.'], ['brad', 'came', 'to', 'dinner', 'with', 'us', '.'], ['he', 'loves', 'fish', 'tacos', '.'], ['in', 'the', 'end', ',', 'we', 'all', 'felt', 'like', 'we', 'ate', 'too', 'much', '.'], ['we', 'all', 'agreed', ';', 'it', 'was', 'a', 'magnificent', 'evening', '.']]
最后,我们定义一个函数,返回两个向量之间的余弦相似性。
def cosine(u, v):
return np.dot(u, v) / (np.linalg.norm(u) * np.linalg.norm(v))
Doc2Vec
Doc2Vec是Word2Vec的扩展,它是目前最流行的句子嵌入技术之一。该技术通过引入“段落向量”来扩展Word2Vec模型。有以下两种方法可以将段落向量添加到模型中:
- PV-DM(分布式内存的段落向量):PV-DM通过将段落(文档、句子等)表示为唯一的向量,同时将文中的单词表示为向量,来捕捉段落的语义信息。PV-DM的基本思想借鉴了Word2Vec中的连续词袋(CBOW)模型。在CBOW中,模型根据上下文单词预测当前单词,而在PV-DM中,模型使用段落向量和几个上下文单词来预测下一个单词。
- PV-DBOW(分布式词袋的段落向量):PV-DBOW只使用段落向量来预测句子中随机选择的单词。这种方法与Word2Vec中的Skip-gram模型相似,其中Skip-gram是通过当前单词预测上下文单词。但在PV-DBOW中,没有使用上下文单词,而是直接从段落向量预测句子中的单词。
SentenceBERT(句子BERT)
SentenceBERT 是一个修改版的 BERT 模型,能够直接为句子对生成嵌入。这是通过使用Siamese和Triplet网络结构来实现的,这意味着它能够理解和编码两个句子之间的关系。
假设我们有两个句子:“我爱巴黎”和“我不喜欢巴黎”。SentenceBERT 可以有效地捕捉这两个句子的语义差异,即使它们包含相似的词汇。
!pip install -q sentence-transformers
from sentence_transformers import SentenceTransformer, util
model = SentenceTransformer('all-MiniLM-L6-v2')
sentences = ["我爱巴黎", "我不喜欢巴黎"]
# 计算嵌入
embeddings = model.encode(sentences)
# 计算两个句子间的相似度
similarity = util.pytorch_cos_sim(embeddings[0], embeddings[1])
InferSent
InferSent 是由 Facebook AI Research 提出的模型,用于生成句子的通用表示。它使用监督学习方法,标签为蕴涵、矛盾或中性,训练数据为自然语言推断(NLI)任务的标签数据集。
句子编码器架构
总共评估了7种不同的架构,看看哪一种最能捕获句子的通用编码:
-
长短期记忆(LSTM)标准循环编码器
-
带门控循环单元(GRU)的标准循环编码器
-
前向和后向GRU的最后隐藏状态的连接
-
具有平均池化的双向lstm (BiLSTM)
-
大池化的双向lstm (BiLSTM)
-
Self-attentive网络
-
分层卷积网络
这七个体系结构是根据它们对广泛的NLP问题捕捉有用的信息的能力来评估的。因此,这些句子编码器体系结构生成的编码被用于以下任务:
-
二元和多类分类:这些任务包括情感分析,主客观分类,以及寻找意见极性等任务。
-
蕴涵和语义关联:使用的两个数据集(SICK-R和SICK-E)分别基于语义相似性和蕴涵对句子对之间的关系进行分类。
-
STS14 -语义文本相似度:该数据集包含基于相似度的人工标记的句子对。
-
释义检测:该数据集包含根据是否捕获释义/语义等价关系而进行人工标记的句子对。
-
标题-图像检索:图像检索的目标是根据与给定标题的相关性对图像集合进行排序。标题检索的目标是根据与给定图像的相关性对一组标题进行排序。
实证结果表明,具有max pooling的BiLSTM在这些任务上取得了最高的性能。
InferSent有两个版本。版本1使用GLovE,而版本2使用fastText向量。我们选择任意版本即可。
GLovE (Global Vectors for Word Representation)
斯坦福大学的研究人员开发了GloVe,它构建了一个大表来监控单词在文本数据集中同时出现的频率。然后,该模型使用数学方法简化该表,为单个单词生成数值向量。
这些向量封装了单词之间的含义和关系,为与语言相关的各种机器学习任务奠定了基础。
FastText
Facebook的人工智能研究实验室创建了FastText,它在Word2Vec模型的基础上进行了改进,将单词视为更小的字符串或字符n图的集合。
这种方法使模型能够更有效地捕获具有复杂单词结构的语言的复杂性,并合并原始训练数据中不存在的单词。
因此,FastText产生了一种适应性更强、更全面的语言模型,适用于各种各样的机器学习任务。
示例
给定两个句子:“天气很好。”和“今天阳光明媚。”,InferSent 能够学习到这两个句子在语义上的相似性。
from models import InferSent
import torch
model_version = 1
MODEL_PATH = "infersent%d.pkl" % model_version
params_model = {'bsize': 64, 'word_emb_dim': 300, 'enc_lstm_dim': 2048,
'pool_type': 'max', 'dpout_model': 0.0, 'version': model_version}
model = InferSent(params_model)
model.load_state_dict(torch.load(MODEL_PATH))
model.set_w2v_path(W2V_PATH)
sentences = ['天气很好。', '今天阳光明媚。']
model.build_vocab(sentences, tokenize=True)
embeddings = model.encode(sentences, bsize=128, tokenize=False, verbose=True)
print(embeddings)
Universal Sentence Encoder
Universal Sentence Encoder(USE)通用句子编码器将文本编码为高维向量,可用于文本分类、语义相似、聚类和其他自然语言任务。
该模型针对句子、短语或短段落文本进行了训练和优化。它在各种数据源和各种任务上进行训练,目的是动态适应各种自然语言理解任务。输入是可变长度的英文文本,输出是512维向量。
语义相似
简单的应用包括提高系统的覆盖范围,这些系统可以根据某些关键字、短语或话语触发行为。
文本分类
示例
embed = hub.Module("https://kaggle.com/models/google/universal-sentence-encoder/frameworks/TensorFlow1/variations/universal-sentence-encoder/versions/1")
embeddings = embed([
"The quick brown fox jumps over the lazy dog.",
"I am a sentence for which I would like to get its embedding"])
print(session.run(embeddings))
# The following are example embedding output of 512 dimensions per sentence
# Embedding for: The quick brown fox jumps over the lazy dog.
# [-0.016987282782793045, -0.008949815295636654, -0.0070627182722091675, ...]
# Embedding for: I am a sentence for which I would like to get its embedding.
# [0.03531332314014435, -0.025384284555912018, -0.007880025543272495, ...]
Skip-Thought Vectors
Skip-Thought Vectors是受Word2Vec中Skip-Gram模型启发的句子级嵌入技术。与SentenceBERT和InferSent类似,Skip-Thought Vectors也使用编码器-解码器架构,但它的目标是根据前后句子来生成中间句子的表示,然后利用编码网络学习到的中心句子的表征(embedding)来重建其前一个句子和后一个句子。
Skip-Thoughts 模型由三部分组成:
- 编码网络(Encoder Network):接收位于索引 i i i 的句子 x ( i ) x(i) x(i),并生成一个固定长度的表示 z ( i ) z(i) z(i)。这是一个循环网络(通常是 GRU 或 LSTM),用于依次处理句中的单词。
- 前一个解码网络(Previous Decoder Network):取 z ( i ) z(i) z(i) 的嵌入,并尝试生成句子 x ( i − 1 ) x(i-1) x(i−1)。这也是一个循环网络(通常是 GRU 或 LSTM),用于依次生成句子。
- 下一个解码网络(Next Decoder Network):取 z ( i ) z(i) z(i) 的嵌入,并尝试生成句子 x ( i + 1 ) x(i+1) x(i+1)。同样是一个与前一个解码网络类似的循环网络。
示例
如果我们有一系列句子:“我昨天去了电影院。”,“电影很精彩。”,和“我们决定再去一次。”,Skip-Thought Vectors会试图使用第一句和第三句来预测第二句。
import skipthoughts
# 载入模型
model = skipthoughts.load_model()
# 编码句子
encoded_sentences = skipthoughts.encode(model, ["我昨天去了电影院。", "电影很精彩。", "我们决定再去一次。"])
# 查看编码向量
print(encoded_sentences)
Quick-Thought Vectors
Quick-Thought Vectors 与 Skip-Thought Vectors 不同的是Quick-Thoughts目标是分类候选句子是否属于相邻句子。
Quick-Thought Vectors 通过选择一个句子及其上下文(前后句子),并训练模型通过分类任务来识别正确的上下文,而不是生成上下文。这使得训练过程更加高效。
示例
如果给定句子是:“我昨天去了电影院。”,模型将尝试识别哪些候选句子最可能是前后文。这可能包括“电影很精彩。”作为后文,而将“我喜欢在海滩上散步。”识别为不相关的句子。
# !pip install -q sentence_transformers
from sentence_transformers import SentenceTransformer, util
model = SentenceTransformer('all-MiniLM-L6-v2')
sentences = [
"我昨天去了电影院。",
"电影很精彩。",
"我们决定再去一次。",
"我喜欢在海滩上散步。"
]
# 计算所有句子的嵌入
embeddings = model.encode(sentences)
# 假设第二个句子是中心句子,找出与其最相关的句子
query_embedding = embeddings[1]
scores = util.pytorch_cos_sim(query_embedding, embeddings).squeeze() # 计算相似度并转换为一维数组
# 排除当前句子自身
scores[1] = -1 # 将当前句子的得分设为最低
# 找到最高得分的句子的索引
best_match = scores.argmax()
# 打印与“电影很精彩。”最相关的句子
print("最相关的句子:", sentences[best_match])
###最相关的句子: 我昨天去了电影院。
参考资料
-
InferSent Explained
-
My thoughts on Skip-Thoughts
-
Building sentence embeddings via quick thoughts
-
Top 4 Sentence Embedding Techniques using Python