BERT单词嵌入教程

BERT词嵌入教程

BERT Word Embeddings Tutorial

原文链接:https://mccormickml.com/2019/05/14/BERT-word-embeddings-tutorial/
作者:Chris McCormick and Nick Ryan

在本文中,我深入研究了Google的BERT产生的词嵌入,并向您展示了如何通过制作自己的词嵌入来使用BERT。
20年5月27日更新:以使用huggingface中的新transformers库代替旧的pytorch-pretrained-bert库。 如果需要,您仍然可以在这里找到旧帖子/笔记本。

介绍

历史

2018年是NLP突破的一年。 转移学习,特别是诸如Allen AI的ELMO,OpenAI的Open-GPT和Google的BERT之类的模型,使研究人员可以通过最小的任务特定的微调来粉碎多个基准,并为NLP社区的其他成员提供可以轻松地(减少数据量)的预训练模型 和更少的计算时间)进行微调并实施以产生最新的结果。 不幸的是,对于许多从NLP入手的人,甚至对于一些经验丰富的从业者,这些功能强大的模型的理论和实际应用仍未得到很好的理解。

What is BERT?

BERT(《Bidirectional Encoder Representations from Transformers》)于2018年末发布,是我们在本教程中将使用的模型,旨在为读者提供有关在NLP中使用传输学习模型的更好的理解和实用指南。 BERT是一种预训练语言表示的方法,用于创建模型,然后NLP从业人员可以免费下载和使用这些模型。 您可以使用这些模型从文本数据中提取高质量的语言功能,也可以使用自己的数据针对特定任务(分类,实体识别,问题回答等)对这些模型进行微调,以生成状态信息。 艺术预测。

为什么要使用BERT词嵌入?

在本教程中,我们将使用BERT从文本数据中提取特征,即单词和句子嵌入向量。这些单词和句子的嵌入向量可以做什么?首先,这些嵌入可用于关键字/搜索扩展,语义搜索和信息检索。例如,如果您想将客户问题或搜索结果与已回答的问题或有据可查的搜索结果进行匹配,即使没有关键字或词组重叠,这些表示形式也将帮助您准确地检索出符合客户意图和上下文含义的结果。

其次,也许更重要的是,这些向量被用作下游模型的高质量特征输入。 NLP模型(例如LSTM或CNN)需要以数字矢量形式输入,这通常意味着将诸如词汇和词性之类的特征转换为数字表示。过去,单词被表示为唯一索引值(一次编码),或者更有用地表示为神经词嵌入,其中词汇词与诸如Word2Vec或Fasttext之类的模型生成的固定长度特征嵌入相匹配。 BERT提供了优于Word2Vec之类的模型的优势,因为尽管每个单词在Word2Vec下都具有固定的表示形式,而不管该单词出现的上下文如何,但BERT都会根据周围的单词动态地产生单词表示形式。例如,给出两个句子:

“The man was accused of robbing a bank.” “The man went fishing by the bank of the river.” (“该人被指控抢劫银行。” “那人在河边钓鱼。”)

Word2Vec将在两个句子中为单词“ bank”嵌入相同的单词,而在BERT下,每个单词中“ bank”嵌入的单词将有所不同。除了捕获诸如多义性之类的明显差异外,上下文通知的单词嵌入还捕获其他形式的信息,这些信息可产生更准确的特征表示,从而可带来更好的模型性能。

从教育的角度来看,仔细检查BERT单词嵌入是使用BERT及其迁移学习模型系列的好方法,它为我们提供了一些实践知识和背景知识,可以更好地理解该模型的内部细节。在以后的教程中。

1.加载预训练的BERT

通过Hugging Face安装BERT的pytorch接口。 (该库包含其他预训练语言模型的接口,例如OpenAI的GPT和GPT-2。)

我们选择pytorch接口是因为它在高级API(易于使用但无法深入了解工作原理)和tensorflow代码(包含很多细节但经常使我们陷入困境)之间取得了很好的平衡 (这里的目的是BERT!)

如果您是在Google Colab上运行此代码,则每次重新连接时都必须安装此库。 接下来的代码将为您解决这个问题。

!pip install transformers

现在,我们导入pytorch,预训练的BERT模型和BERT标记程序。

我们将在以后的教程中详细解释BERT模型,但这是Google发布的经过预先训练的模型,该模型在Wikipedia和Book Corpus(包含+10,000种不同类型的书)的数据集上运行了很多小时。 该模型负责(稍做修改)在一系列任务中击败NLP基准测试。 Google发布了BERT模型的一些变体,但是我们将在这里使用的是两种可用尺寸(“基本”和“大”)中的较小者,并且忽略大小写,因此是“无大小写”。”

transformers提供了许多将BERT应用于不同任务的类(令牌分类,文本分类等)。 在这里,我们使用的是基本的BertModel,它没有特定的输出任务-对于仅使用BERT提取嵌入内容来说,这是一个不错的选择。

import torch
from transformers import BertTokenizer, BertModel

# OPTIONAL: if you want to have more information on what's happening, activate the logger as follows
#可选:如果您想了解正在发生的事情的更多信息,请点击以下方式激活记录器
import logging
#logging.basicConfig(level=logging.INFO)

import matplotlib.pyplot as plt
#% matplotlib inline   (jupyter notebook only)

# Load pre-trained model tokenizer (vocabulary)
#加载预训练的模型标记器(词汇表)
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')

2.输入格式

由于BERT是经过预训练的模型,需要使用特定格式的输入数据,因此我们需要:

  1. 特殊token**[SEP]**,用于标记句子的结尾或两个句子之间的分隔
  2. 在本文开头的特殊token**[CLS]**。 该令牌用于分类任务,但是无论您的应用程序是什么,BERT都希望它。
  3. 符合BERT中使用的固定词汇的令牌(tokens)
  4. 来自BERT的令牌生成器的tokens的tokens IDs
  5. Mask IDs,用于指示序列中的哪些元素是标记,哪些是填充元素
  6. Segment IDs用于区分不同的句子
  7. 用于显示序列中标记位置的位置嵌入Positional Embeddings

幸运的是,转transformers接口满足了上述所有要求(使用tokenizer.encode_plus函数)。

不过,由于这只是作为使用BERT的介绍,因此,我们将以(主要)手动方式执行这些步骤。

有关使用tokenizer.encode_plus的示例,请参见此处的下一篇有关句子分类的文章。

2.1. 特殊 Tokens

BERT可以将一两个句子作为输入,并使用特殊标记[SEP]来区分它们。 [CLS]标记始终出现在文本的开头,并且特定于分类任务。

但是,即使我们只有一个句子,即使我们没有使用BERT进行分类,也始终需要两个标记。 这样就对BERT进行了预训练,这就是BERT希望看到的。

两个句子 Input:

  • [CLS] The man went to the store. [SEP] He bought a gallon of milk.

一个句子Input:

  • [CLS] The man went to the store. [SEP]
2.2. 标记Tokenization

BERT提供了自己的令token生成器,我们在上面将其导入。 让我们看看它如何处理下面的句子。

text = "Here is the sentence I want embeddings for."
marked_text = "[CLS] " + text + " [SEP]"

# Tokenize our sentence with the BERT tokenizer.
tokenized_text = tokenizer.tokenize(marked_text)

# Print out the tokens.
print (tokenized_text)

输出:
['[CLS]', 'here', 'is', 'the', 'sentence', 'i', 'want', 'em', '##bed', '##ding', '##s', 'for', '.', '[SEP]']
注意“ embedddings”一词是如何表示的:
['em', '##bed', '##ding', '##s']
原始单词已拆分为较小的子单词和字符。 这些子词中某些词之前的两个哈希符号只是我们的分词器表示这种子词或字符是较大词的一部分并在另一个子词之前的方式。 因此,例如,“ ## bed”令牌与“ bed”令牌是分开的; 第一个用于子词“床”出现在较大词中时,第二个用于显式使用独立令牌“您睡在里面的东西”时。
为什么看起来是这样? 这是因为BERT令牌生成器是使用WordPiece模型创建的。 该模型贪婪地创建一个固定大小的词汇表,其中包含最适合我们的语言数据的单个字符,子词和单词。 由于我们的BERT标记器模型的词汇限制大小为30,000,因此WordPiece模型生成的词汇表包含所有英语字符以及在该模型所训练的英语语料库中找到的约30,000个最常见的单词和子单词。 该词汇表包含四件事:

  1. 全词Whole words
  2. 出现在单词开头或单独出现的子单词(为“嵌入”中的“ em”分配了与“ go get em”中的独立字符序列“ em”相同的向量)
  3. 不在单词开头的子词Subwords,其前面带有“ ##”以表示这种情况
  4. 独立字符Individual characters
    为了在此模型下标记单词,标记器首先检查整个单词是否在词汇表中。如果不是,它将尝试将单词分解为词汇表中包含的最大可能的子单词,并且作为最后的选择,会将单词分解为单个字符。请注意,因此,我们至少可以始终将一个单词表示为单个字符的集合。

因此,不在词汇表之外的单词分配给诸如“ OOV”或“ UNK”之类的包罗万象的令牌,不在词汇表中的单词被分解为子单词和字符令牌,然后我们可以为其生成嵌入。

因此,我们没有将词汇单词中的“嵌入”和其他单词分配给过载的未知词汇标记,而是将其拆分为子单词标记[‘em’,’## bed’,’## ding’,’## s’ ],将保留原始单词的某些上下文含义。我们甚至可以平均这些子词嵌入向量,以生成原始词的近似向量。

(有关WordPiece的更多信息,请参见原始论文,以及有关Google神经机器翻译系统的进一步讨论。)

这是我们词汇表中包含的标记的一些示例。以两个散列开头的标记是子词或单个字符。

要了解BERT词汇的内容,请在此处查看我创建的笔记本和随附的YouTube视频

list(tokenizer.vocab.keys())[5000:5020]

输出:

['knight',
 'lap',
 'survey',
 'ma',
 '##ow',
 'noise',
 'billy',
 '##ium',
 'shooting',
 'guide',
 'bedroom',
 'priest',
 'resistance',
 'motor',
 'homes',
 'sounded',
 'giant',
 '##mer',
 '150',
 'scenes']

将文本分解为标记后,我们必须将句子从字符串列表转换为词汇索引列表。

从这里开始,我们将使用下面的示例句子,其中包含单词“ bank”的两个实例,它们的含义不同。

# 定义一个新的例句,它具有单词“ bank”的多种含义
text = "After stealing money from the bank vault, the bank robber was seen " \
       "fishing on the Mississippi river bank."

# 加入 special tokens.
marked_text = "[CLS] " + text + " [SEP]"

# 将句子拆分为tokens。
tokenized_text = tokenizer.tokenize(marked_text)

# 将tokens字符串映射到其词汇索引vocabulary indices。
indexed_tokens = tokenizer.convert_tokens_to_ids(tokenized_text)

# 显示单词及其索引indices。
for tup in zip(tokenized_text, indexed_tokens):
    print('{:<12} {:>6,}'.format(tup[0], tup[1]))

输出:

[CLS]           101
after         2,044
stealing     11,065
money         2,769
from          2,013
the           1,996
bank          2,924
vault        11,632
,             1,010
the           1,996
bank          2,924
robber       27,307
was           2,001
seen          2,464
fishing       5,645
on            2,006
the           1,996
mississippi   5,900
river         2,314
bank          2,924
.             1,012
[SEP]           102
2.3. 分词 ID

BERT受过训练并期望使用一对1和0来区分两个句子的句子对。 也就是说,对于“ tokenized_text”中的每个标记,我们必须指定其所属的句子:句子0(一系列0)或句子1(一系列1)。 就我们的目的而言,单句输入仅需要一系列的1,因此我们将为输入句子中的每个标记创建一个1的向量。

如果您要处理两个句子,请将第一个句子中的每个单词加上“ [SEP]”标记指定为0,将第二个句子的所有标记指定为1。

# Mark each of the 22 tokens as belonging to sentence "1".
# 将22个token中的每一个标记为属于句子“ 1”。
segments_ids = [1] * len(tokenized_text)

print (segments_ids)

输出:[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

3.提取嵌入

3.1 在我们的文本上运行BERT

接下来,我们需要将数据转换为Torch张量,并调用BERT模型。 BERT PyTorch接口要求数据使用火炬张量而不是Python列表,因此我们在此处转换列表-这不会更改形状或数据。

# Convert inputs to PyTorch tensors
# 将输入转换为PyTorch张量
tokens_tensor = torch.tensor([indexed_tokens])
segments_tensors = torch.tensor([segments_ids])

调用from_pretrained将从互联网上获取模型。 当我们加载bert-base-uncased时,我们会在日志记录中看到打印的模型的定义。 该模型是一个具有12层的深度神经网络! 解释层及其功能超出了本文的范围,您现在可以跳过此输出。

model.eval()将我们的模型置于评估模式,而不是训练模式。 在这种情况下,评估模式将关闭训练中使用的辍学正则化。

# 加载预训练模型(权重)
model = BertModel.from_pretrained('bert-base-uncased',
                                  output_hidden_states = True, # 模型是否返回所有隐状态。
                                  )

# Put the model in "evaluation" mode, meaning feed-forward operation.
# 将模型置于“评估”模式,这意味着前馈操作。
model.eval()

注意:由于篇幅如此之长,我已从博客文章中删除了该文章的输出。 如果您有兴趣,可以在这里的Colab笔记本中找到它。

接下来,让我们在示例文本中评估BERT,并获取网络的隐藏状态!

旁注:torch.no_grad告诉PyTorch在此前向传递期间不要构造计算图(因为这里我们将不运行backprop)–这只是减少了内存消耗,并加快了速度。

# Run the text through BERT, and collect all of the hidden states produced
# from all 12 layers. 
# 通过BERT运行文本,并收集从所有12层产生的所有隐状态。
with torch.no_grad():

    outputs = model(tokens_tensor, segments_tensors)

    # 根据先前在from_pretrained调用中对模型的配置方式,评估模型将返回不同数量的objects。 在这种情况下,假设我们设置“ output_hidden_states = True”,则第三项将是所有层的隐状态。 请参阅文档以获取更多详细信息:
    # https://huggingface.co/transformers/model_doc/bert.html#bertmodel
    hidden_states = outputs[2]
3.2. 理解输出

存储在对象hidden_states中的该模型的全部隐藏状态有点令人头晕。 该对象具有四个维度,按以下顺序:

  1. The layer number (13 layers)
  2. The batch number (1 sentence)
  3. The word / token number (22 tokens in our sentence)
  4. The hidden unit / feature number (768 features)

等一下,十三层? BERT只有12个吗? 之所以为13,是因为第一个元素是输入嵌入,其余的是BERT的12层每一层的输出。

那是219,648个唯一值,仅代表我们的一句话!

第二个维度,即批处理batch size大小,是在一次向模型提交多个句子时使用的。 不过,在这里,我们只有一个例句。

print ("Number of layers:", len(hidden_states), "  (initial embeddings + 12 BERT layers)")
layer_i = 0

print ("Number of batches:", len(hidden_states[layer_i]))
batch_i = 0

print ("Number of tokens:", len(hidden_states[layer_i][batch_i]))
token_i = 0

print ("Number of hidden units:", len(hidden_states[layer_i][batch_i][token_i]))
Number of layers: 13   (initial embeddings + 12 BERT layers)
Number of batches: 1
Number of tokens: 22
Number of hidden units: 768

让我们快速查看给定图层和令牌的值范围。

您会发现,所有图层和令牌的范围都非常相似,大多数值都在[-2,2]之间,少量值则在-10附近。

# 对于我们句子中的第5个token,请从第5层中选择其特征值。
token_i = 5
layer_i = 5
vec = hidden_states[layer_i][batch_i][token_i]

# 将值绘制为直方图以显示其分布。
plt.figure(figsize=(10,10))
plt.hist(vec, bins=200)
plt.show()

在这里插入图片描述
将值按层分组对于模型是有意义的,但是出于我们的目的,我们希望按令牌将其分组。

当前尺寸:
[# layers, # batches, # tokens, # features]
所需尺寸:
[# tokens, # layers, # features]
幸运的是,PyTorch包含置换功能,可轻松重新布置张量的尺寸。

但是,第一个维度目前是一个Python列表!

# hidden_states是一个Python列表。
print('      Type of hidden_states: ', type(hidden_states))

# 列表中的每一层都是torch tensor。
print('Tensor shape for each layer: ', hidden_states[0].size())

 Type of hidden_states:  <class 'tuple'>
Tensor shape for each layer:  torch.Size([1, 22, 768]

让我们结合各层,使它成为一个完整的大张量。


# 连接所有层的tensor。 我们在这里使用`stack`在这个tensor中创建一个新维度。
token_embeddings = torch.stack(hidden_states, dim=0)

token_embeddings.size()
torch.Size([13, 1, 22, 768])

让我们摆脱“batch”维度,因为我们不需要它。

# Remove dimension 1, the "batches".
token_embeddings = torch.squeeze(token_embeddings, dim=1)
token_embeddings.size()
torch.Size([13, 22, 768])

最后,我们可以通过permute在“层”和“令牌”维度之间切换。

# Swap dimensions 0 and 1.
# 交换维度0和1。
token_embeddings = token_embeddings.permute(1,0,2)

token_embeddings.size()
torch.Size([22, 13, 768])
3.3 从隐状态创建词和句子向量

现在,我们如何处理这些隐状态? 我们希望为每个标记获得单个向量,或者为整个句子获取单个向量表示,但是对于输入的每个标记,我们有13个独立的向量,每个向量的长度为768。

为了获得单独的向量,我们将需要组合一些层向量……但是哪个层或层的组合提供了最好的表示?

不幸的是,没有简单的答案。不过,让我们尝试一些合理的方法。 然后,我将为您指出一些有用的资源,这些资源将进一步调查这个问题。
词向量
为了给您一些示例,让我们以两种方式创建单词向量。

首先,我们将最后四层连接起来,为每个令牌提供一个单词向量。 每个向量的长度为4 x 768 = 3,072

# 存储token向量,形状为[22 x 3,072]
token_vecs_cat = []

# `token_embeddings` is a [22 x 12 x 768] tensor.

# For each token in the sentence...
for token in token_embeddings:
    
    # `token` is a [12 x 768] tensor

    # 连接来自最后四层的向量(即,将它们附加在一起)。 每个层向量为768个值,因此“ cat_vec”的长度为3,072。
    cat_vec = torch.cat((token[-1], token[-2], token[-3], token[-4]), dim=0)
    
    # 使用`cat_vec`代表`token`。
    token_vecs_cat.append(cat_vec)

print ('Shape is: %d x %d' % (len(token_vecs_cat), len(token_vecs_cat[0])))
Shape is: 22 x 3072

作为一种替代方法,让我们尝试通过将最后四层相加来创建词向量。

# 存储token向量,形状为[22 x 768]
token_vecs_sum = []

# `token_embeddings` is a [22 x 12 x 768] tensor.

# For each token in the sentence...
for token in token_embeddings:

    # `token` is a [12 x 768] tensor

    # 对来自最后四层的向量求和。
    sum_vec = torch.sum(token[-4:], dim=0)
    
    # Use `sum_vec` to represent `token`.
    token_vecs_sum.append(sum_vec)

print ('Shape is: %d x %d' % (len(token_vecs_sum), len(token_vecs_sum[0])))
Shape is: 22 x 768

句子向量
为了获得整个句子的向量,我们有多种与应用程序有关的策略,但是一种简单的方法是对每个token的倒数第二个隐藏层取平均值,从而生成单个768长度的向量。

# `hidden_states` has shape [13 x 1 x 22 x 768]

# `token_vecs` is a tensor with shape [22 x 768]
token_vecs = hidden_states[-2][0]

# 计算所有22个token向量的平均值。
sentence_embedding = torch.mean(token_vecs, dim=0)
print ("Our final sentence embedding vector of shape:", sentence_embedding.size())
Our final sentence embedding vector of shape: torch.Size([768])
我们的最后句子嵌入向量的大小:torch.Size([768]
3.4确认上下文相关向量

为了确认这些向量的值实际上是上下文相关的,我们来看一下例句中“ bank”一词的不同实例:

“After stealing money from the bank vault, the bank robber was seen fishing on the Mississippi river bank.”
“从银行保险库偷钱后,看到银行抢劫犯在密西西比河岸钓鱼。”

让我们在示例句子中找到单词“ bank”的这三个实例的索引。

for i, token_str in enumerate(tokenized_text):
  print (i, token_str)
0 [CLS]
1 after
2 stealing
3 money
4 from
5 the
6 bank
7 vault
8 ,
9 the
10 bank
11 robber
12 was
13 seen
14 fishing
15 on
16 the
17 mississippi
18 river
19 bank
20 .
21 [SEP]

它们分别是6、10和19。

在此分析中,我们将使用通过将最后四层相加而创建的单词向量。

我们可以尝试打印出它们的向量以进行比较。

print('First 5 vector values for each instance of "bank".')
print('')
print("bank vault   ", str(token_vecs_sum[6][:5]))
print("bank robber  ", str(token_vecs_sum[10][:5]))
print("river bank   ", str(token_vecs_sum[19][:5]))
First 5 vector values for each instance of "bank".

bank vault    tensor([ 3.3596, -2.9805, -1.5421,  0.7065,  2.0031])
bank robber   tensor([ 2.7359, -2.5577, -1.3094,  0.6797,  1.6633])
river bank    tensor([ 1.5266, -0.8895, -0.5152, -0.9298,  2.8334])

我们可以看到值有所不同,但是让我们计算向量之间的余弦相似度,以进行更精确的比较。

from scipy.spatial.distance import cosine

# 计算“bank robbe”与“river bank”中的词库之间的余弦相似度(不同的含义)。
diff_bank = 1 - cosine(token_vecs_sum[10], token_vecs_sum[19])

# Calculate the cosine similarity between the word bank
# in "bank robber" vs "bank vault" (same meaning).
same_bank = 1 - cosine(token_vecs_sum[10], token_vecs_sum[6])

print('Vector similarity for  *similar*  meanings:  %.2f' % same_bank)
print('Vector similarity for *different* meanings:  %.2f' % diff_bank)
Vector similarity for  *similar*  meanings:  0.94
Vector similarity for *different* meanings:  0.69

是不是看起来超级棒?!

3.5 合并策略和层选择

以下是探索该主题的一些其他资源。
BERT作者
BERT作者通过将不同的向量组合作为输入特征提供给在命名实体识别任务上使用的BiLSTM并观察所得的F1分数,从而测试了词嵌入策略。
(图来自 Jay Allamar’s blog)
(图来自 Jay Allamar’s blog)
虽然最后四层的连接在此特定任务上产生了最佳结果,但许多其他方法紧随其后,通常建议针对特定应用测试不同版本:结果可能会有所不同。

通过注意到BERT的不同层对非常不同的信息进行编码,可以部分地证明这一点,因此,由于不同的层对不同种类的信息进行编码,因此适当的合并策略将根据应用程序而变化。

Han Xiao’s BERT-as-service
韩晓在GitHub上创建了一个名为bert-as-service的开源项目,该项目旨在使用BERT为您的文本创建单词嵌入。 Han尝试了多种方法来组合这些嵌入,并在该项目的FAQ页面上分享了一些结论和基本原理。

默认情况下,bert-as-service使用模型的倒数第二层的输出。
我将通过以下总结韩的观点:

  1. 嵌入开始于第一层,因为它没有上下文信息(即最初的“银行”嵌入的含义并不特定于河岸或金融银行)。
  2. 随着嵌入越来越深入网络,它们在每一层中获取越来越多的上下文信息。
  3. 但是,当您进入最后一层时,您将开始获取特定于BERT的预训练任务的信息(“屏蔽语言模型”(MLM)和“下一句预测”(NSP))。
    • 我们想要的是能很好地编码单词含义的嵌入…
    • BERT愿意这样做,但也可以对其他任何东西进行编码,这可以帮助它确定丢失的单词是什么(MLM)或第二句话是否在第一个句子之后(NSP)。
  4. 倒数第二层是汉安定下来的一个合理的靶点(sweet spot)。

4附录

4.1special tokens

应该注意的是,尽管[CLS]充当分类任务的“集合表示”,但这并不是高质量句子嵌入向量的最佳选择。根据BERT作者Jacob Jacob Devlin的说法:“我不确定这些向量是什么,因为BERT不会生成有意义的句子向量。似乎这是对单词标记进行平均池化以获得句子向量,但我们从未建议过这会产生有意义的句子表示。”

(但是,如果模型已经过微调,[CLS]令牌确实变得有意义,其中该令牌的最后一个隐藏层用作序列分类的“句子向量”。)

4.2词汇量不足

对于由多个句子和字符级嵌入组成的词汇之外的单词,还有一个问题是如何最好地恢复这种嵌入。平均嵌入是最直接的解决方案(在类似的嵌入模型中使用子词词汇(如快速文本)来依赖该解决方案),但是子词嵌入的总和和简单地采用最后的令牌嵌入(请记住向量对上下文敏感)是可接受的替代策略。

4.3相似度指标

值得注意的是,词级相似度比较不适用于BERT嵌入,因为这些嵌入是上下文相关的,这意味着词向量会根据其出现的句子而变化。您的表示编码的是河流“银行”,而不是金融机构的“银行”,但是直接进行词间相似度比较的价值不高。但是,对于句子嵌入,相似性比较仍然有效,例如,一个人可以针对其他句子的数据集查询单个句子,以找到最相似的句子。根据所使用的相似性度量,所得到的相似性值将比相似性输出的相对排名信息少,因为许多相似性度量都对向量空间(例如相等加权的维度)进行了假设,而这些假设并不适用于我们的768维向量空间。

4.4实现

您可以使用此笔记本中的代码作为您自己的应用程序的基础,以从文本中提取BERT功能。但是,已经存在官方的tensorflow和备受好评的pytorch实现,可以为您执行此操作。此外,作为服务的bert-as-a-service是一种出色的工具,专为高性能执行此任务而设计,是我为生产应用程序推荐的工具。作者非常注意该工具的实现,并提供了出色的文档(其中一些用于帮助创建本教程)可以帮助用户了解用户所面临的更加细微的细节,例如资源管理和池化策略。

  • 14
    点赞
  • 71
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值