人类理解语言细微差别的能力是非常强大的——我们敏锐的大脑可以在一句话里轻易地感受到幽默、讽刺、负面情绪等,但发挥这个“超能力”的前提是,我们必须知道话语所使用的语言。
例如,如果有小伙伴用日文评论这篇文章,大部分人应该无法理解他想说什么吧,自然也不敢轻易回复。所以,为了实现有效沟通,我们需要用彼此最能理解的语言进行互动。
而对于机器来说,为了让机器可以处理和理解任意文本,我们必须用机器能够理解的语言来表示文本。那么它最懂哪种语言呢?
答案是数字(numbers)——无论我们提供给机器什么数据:视频、音频、图像或文本,机器都只会与数字打交道,这也就是为什么将文本表示为数字或嵌入文本(embedding text)是自然语言处理中最热门的话题之一。
这篇文章,我将介绍Python代码中最常用的4种句嵌入技术并且概述它们的体系结构以及如何用Python实现这些技术,包括如下内容:什么是词嵌入(Word Embedding)
什么是句嵌入(Sentence Embedding)
Doc2Vec是什么
SentenceBERT介绍
InferSent
Universal Sentence Encoder
什么是词嵌入?
最初的嵌入技术只处理单词,即给定一组单词,而该技术就是把集合中的每个单词生成一个嵌入。最原始的方法是对单词序列进行热编码,如此一来,集合中的单词就由1表示,其他单词由0表示。
虽然这种方法在表示单词和其他简单的文本处理任务上很有效,但面对更复杂的任务(查找相似单词)时却没什么用了。比如,我们需要查询:“北京最好的日料店”,我们希望得到与“日料”、“北京餐厅”和“最好”相对应的搜索结果,若我们得到的结果是“北京的顶级日本料理“,那么用原始方法就无法检测出“最好”和“顶级”,或“料理”和“店”之间的相似性了。词嵌入技术图示
这个词关联问题让词嵌入技术得以开发和发展,与传统的热编码相比,词嵌入技术转换的不仅是单词,还能识别出该词的语义和语法以构建该信息的向量。目前常用的词嵌入技术有Word2Vec、GloVe、ELMo、FastText等。
词嵌入的基本原理就是使用与该词相邻的词的信息。随着词嵌入技术的发展,技术人员已经找到了更好的方法来表达更多的信息,由此扩展到了句子和段落。
什么是句嵌入?
如果我们直接处理句子呢?要知道,在大文本的情况下,很大程度上我们的分析会被从词嵌入中提取的信息所限制。
假设,我们读到一个句子“我不喜欢拥挤的地方”,接着,我们又读到“然而,我喜欢世界上最繁忙的城市之一,纽约”。我们怎样才能让机器明白“拥挤的地方”和“繁忙的城市”之间的关联呢?
显然,词嵌入技术在这里是不够的,我们需要用到句嵌入技术了。句嵌入是将整个句子及其语义信息表示为向量,让机器能理解上下文、用意和其他潜藏在文本中的细微差别。
接下来我们将用Python代码实例,直观地给大家介绍目前最受欢迎的4中句嵌入技术:Doc2Vec、SentenceBERT、InferSent、Universal Sentence Encoder
那我们一起来建立基本语料库并定义句子列表吧!
Step 1:
首先,导入库并下载‘punkt’
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:
我们再保留一份这些句子的标记版
# Tokenization of each document
tokenized_sent = []
for s in sentences:
tokenized_sent.append(word_tokenize(s.lower()))
tokenized_sent
Step 4:
最后,我们定义一个函数,让它返回两个向量间的余弦相似度(cosine similarity)
def cosine(u, v):
return np.dot(u, v) / (np.linalg.norm(u) * np.linalg.norm(v))
好了!接下来,我们就用这个句子列表来学习这4个句嵌入技术吧!
Doc2Vec
作为Word2Vec的扩展,Doc2Vec是当下最流行的句嵌入技术之一。该算法于2014年引入,是一种无监督算法,主要原理是将一个“段落向量”添加到Word2Vec模型中,而其中,添加方法两个:
1) PVDM(分布式存储版本的段落向量):
在分配一个段落向量句的同时给所有句子共享词向量。然后通过平均或连接(段落向量和单词向量)来得到最后的句关联。其实,它是Word2Vec连续词袋法的扩展——在词袋中,我们根据一组单词预测下一个单词。在PVDM中,我们是根据一组句子预测下一个句子。PVDM图示
2) PVDOBW(分布式包词版本的段落向量):
和PVDM一样, PVDOBW也是Word2Vec的另一扩展,但与PVDM不同的是,我们只在句子中随机抽取单词,然后让模型去预测它来自哪个句子(可以理解成是一个分类任务)。
Step 1:
我们将使用Gensim来演示如何使用Doc2Vec。首先要导入模型、语料库和其他库,然后构建一个带标记的语料库。每个句子都被表示为一个TaggedDocument,其中包含单词列表和与之关联的标记。
# import
from gensim.models.doc2vec import Doc2Vec, TaggedDocument
tagged_data = [TaggedDocument(d, [i]) for i, d in enumerate(tokenized_sent)]
tagged_data
Step 2:
接着,我们将用以下参数对模型进行训练:
## Train doc2vec model
model = Doc2Vec(tagged_data, vector_size = 20, window = 2, min_count = 1, epochs = 100)
'''
vector_size = Dimensionality of the feature vectors.
window = The maximum distance between the current and predicted word within a sentence.
min_count = Ignores all words with total frequency lower than this.
alpha = The initial learning rate.
'''
## Print model vocabulary
model.wv.vocab
Step 3:
我们现在来挑一个句子来测试吧,让模型去从数据中找出与其最相似的5个句子,并按相似度降序显示。其中,infer_vector方法返回测试句子的向量化形式(包括段落向量);most_similar方法返回相似的句子。
test_doc = word_tokenize("I had pizza and pasta".lower())
test_doc_vector = model.infer_vector(test_doc)
model.docvecs.most_similar(positive = [test_doc_vector])
'''
positive = List of sentences that contribute positively.
'''
成功找到了!
SentenceBERT
SentenceBERT在2018年一经推出就立即在句嵌入测试中受到热捧。这个基于BERT模型的技术有4个关键概念:Attention
Transformer
BERT
孪生神经网络(Siamese Network)
SentenceBERT使用孪生神经网络结构,使用2个句子作为输入。这2个句子会被传递给BERT模型,然后使用pooling层来生成它们的嵌入。最后使用句子对的嵌入作为输入来计算余弦相似度。孪生神经网络图示
SentenceBERT技术图示
Step 1:
加载预训练的BERT模型。sentence-transformers里还有许多其他开源的预训练模型,可以在此处找到模型的完整列表。
!pip install sentence-transformers
from sentence_transformers import SentenceTransformer
sbert_model = SentenceTransformer('bert-base-nli-mean-tokens')
Step 2:
对句子进行编码并显示句子向量:
sentence_embeddings = model.encode(sentences)
#print('Sample BERT embedding vector - length', len(sentence_embeddings[0]))
#print('Sample BERT embedding vector - note includes negative values', sentence_embeddings[0])
Step 3:
定义一个测试查询并对其进行编码:
query = "I had pizza and pasta"
query_vec = model.encode([query])[0]
Step 4:
使用scipy计算余弦相似度,然后检索句子和测试查询间的相似度
for sent in sentences:
sim = cosine(query_vec, model.encode([sent])[0])
print("Sentence = ", sent, "; similarity = ", sim):
我们获得文本中的句子与测试句之间的相似性了!实例使用的是已经预训练的BERT模型,如果小伙伴想从头训练SentenceBERT,它的运行速度将会变得异常慢hhh。
InferSent
InferSent是Facebook人工智能研究所在2018年推出的一项有监督的句嵌入技术。该模型最大的特点是它是在自然语言推理(NLI)数据集里训练的,更具体地说,就是SNLI (Stanford自然语言推理)数据集。该数据集由57万对人类生成的英语句子组成,而这些句子已经被人工标记为隐含、矛盾或中性了。
和SentenceBERT一样,InferSent也是使用一对句子并对它们进行编码以生成实际的句子嵌入:
输出向量进入分类器,分类器会将向量分类到3个定义类别之一中(concatenation、element-wise product、absolute element-wise difference)
让我们看看句子相似度任务是如何使用推理的。我们将使用PyTorch进行此操作:
Step 1:
首先,下载推断模型和已经预训练的单词向量。为此,请从这里保存model .py文件并将其存储到工作目录中。
我们还需要保存训练好的模型和预训练好的GLoVe词向量。所以,我们的工作目录应该有一个“encoders”文件夹和一个名为“GLoVe”的文件夹。encoders文件夹将有我们的模型,而GloVe文件夹应该有词向量:
! mkdir encoder
! curl -Lo encoder/infersent2.pkl https://dl.fbaipublicfiles.com/infersent/infersent2.pkl
! mkdir GloVe
! curl -Lo GloVe/glove.840B.300d.zip http://nlp.stanford.edu/data/glove.840B.300d.zip
! unzip GloVe/glove.840B.300d.zip -d GloVe/
加载模型和词向量:
from models import InferSent
import torch
V = 2
MODEL_PATH = 'encoder/infersent%s.pkl' % V
params_model = {'bsize': 64, 'word_emb_dim': 300, 'enc_lstm_dim': 2048,
'pool_type': 'max', 'dpout_model': 0.0, 'version': V}
model = InferSent(params_model)
model.load_state_dict(torch.load(MODEL_PATH))
W2V_PATH = '/content/GloVe/glove.840B.300d.txt'
model.set_w2v_path(W2V_PATH)
Step 2:
从最开始准备好的句子列表中构建词汇表:
model.build_vocab(sentences, tokenize=True)
Step 3:
测试——使用InferSent对该测试查询进行编码并生成嵌入:
query = "I had pizza and pasta"
query_vec = model.encode(query)[0]
query_vec
Step 4:
计算此查询与文本中每个句子的余弦相似度:
similarity = []
for sent in sentences:
sim = cosine(query_vec, model.encode([sent])[0])
print("Sentence = ", sent, "; similarity = ", sim)
Universal Sentence Encoder
目前性能最好的句嵌入技术应该就是Universal Sentence Encoder了,是我们可以用它来进行多任务学习。
这意味着我们生成的句嵌入可以用于多种任务,如情绪分析、文本分类、句子相似度等,这些请求的结果会反馈到模型中,从而得到比之前更好的句向量。
Universal Sentence Encoder是基于两种编码器的模型:Transformer和Deep Averaging Network(DAN)这两种模型都能够将一个单词或句子作为输入,并为其生成嵌入。基本流程如下:将句子转换为小写字母后将其标记
根据编码器的类型,句子被转换为512维向量如果我们使用Transformer,它类似于transformer模块的架构,会使用自注意机制。
使用DAN则会首先计算unigram和bigram嵌入,然后对它们进行平均以得到单个嵌入。然后,它被传递到深度神经网络,以得到512维的句嵌入。
这些句嵌入然后用于各种非监督和监督任务,如Skipthoughts, NLI等,然后再次重用训练好的模型,生成新的512维句子嵌入。
首先下载TensorFlow and TensorFlow hub:
!pip3 install --upgrade tensorflow-gpu
# Install TF-Hub.
!pip3 install tensorflow-hub
Step 1:
导入需要的库:
import tensorflow as tf
import tensorflow_hub as hub
import numpy as np
Step 2:
通过TFHub使用该模型:
module_url = "https://tfhub.dev/google/universal-sentence-encoder/4"
model = hub.load(module_url)
print ("module %s loaded" % module_url)
Step 3:
为句子列表和查询生成嵌入:
sentence_embeddings = model(sentences)
query = "I had pizza and pasta"
query_vec = model([query])[0]
Step 4:
计算测试查询与句子列表之间的相似度:
for sent in sentences:
sim = cosine(query_vec, model([sent])[0])
print("Sentence = ", sent, "; similarity = ", sim)
小结
小编给大家介绍了自然语言处理中中最常用的4种句嵌入技术以及用于查找文本相似度的基本代码。这里小编建议大家使用更大的数据集来尝试这些模型。此外,这里只提供了计算句子相似度的基本代码。对于其他适合的模型,您需要首先对这些句子进行预处理,然后将它们转换为嵌入。
此外,小编这里并没有说没有其他流行的模式(包括FastSent, Skip-thought, Quick-thought, Word Movers Embedding等)
如果你已经尝试过这些或任何其他模式,请在评论中与我们分享!
欢迎点赞、收藏、留言!【自然语言处理】学习帐www.zhihu.com