transformers是huggingface提供的预训练模型库,可以轻松调用API来得到你的词向量。transformers的前身有pytorch-pretrained-bert,pytorch-transformers,原理基本都一致。本文以bert为例,主要介绍如何调用transformers库以及下游任务的使用方法。
在正式使用之前,首先要安装transformers包,此以python3.7为例:
python == 3.7.3
tensorflow == 2.0.0
pytorch == 1.5.1
transformers == 3.0.2
若准备采用GPU加速,需自于Pytorch、Tensorflow官网上进行CUDA、CuDNN版本遴选配置
transformers新版本中使用每个模型只需要三个标准类
configuration:configuration是模型具体的结构配置,例如可以配置多头的数量等,这里配置需要注意的地方就是,如果自定义配置不改变核心网络结构的则仍旧可以使用预训练模型权重,如果配置涉及到核心结构的修改,例如前馈网络的隐层神经元的个数,则无法使用预训练模型权重,这个时候transformers会默认你要重新自己预训练一个模型从而随机初始化整个模型的权重,这是是一种半灵活性的设计
# configuration.json
{
"architectures": [
"BertForMaskedLM"
],
"attention_probs_dropout_prob": 0.1,
"hidden_act": "gelu",
"hidden_dropout_prob": 0.1,
"hidden_size": 768,
"initializer_range": 0.02,
"intermediate_size": 3072,
"max_position_embeddings": 512,
"num_attention_heads": 12,
"num_hidden_layers": 12,
"type_vocab_size": 2,
"vocab_size": 30522
}
将配置好的configuration.json配置加载如下:
config = BertConfig.from_json_file(os.path.join(PATH, "config.json"))
model = BertModel.from_pretrained(PATH, config=config)
#Tensorflow2版本
from transformers import TFBertModel
import tensorflow as tf
config = BertConfig.from_json_file(os.path.join(PATH, "config.json"))
model = TFBertModel.from_pretrained(PATH, config=config)
models:models用于指定使用哪一种模型,例如model为bert,则相应的网络结构为bert的网络结构
# Pytorch版本
import torch
from transformers import BertModel, BertConfig, BertTokenizer
model = BertModel.from_pretrained("bert-base-uncased")
# Tensorflow2版本
import tensorflow as tf
from transformers import TFBertModel, BertConfig, BertTokenizer
model = TFBertModel.from_pretrained("bert-base-uncased")
tokenizer
# Pytorch版本
import torch
from transformers import BertModel, BertConfig, BertTokenizer
tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
# Tensorflow2版本
import tensorflow as tf
from transformers import TFBertModel, BertConfig, BertTokenizer
tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
所有这些类都可以使用通用的from_pretrained()实例化方法,以简单统一的方式从受过训练的实例中初始化
transformers.PretrainedConfig
transformers.BertConfig 可以自定义 Bert 模型的结构,以下参数都是可选的:
transformers.BertConfig.from_json_file可以从json文件中进行配置参数的修改:
transformers.BertConfig.from_json_file(json_file)
transformers.BertConfig.from_dict可以从字典中进行配置参数的修改
transformers.BertConfig.from_pretrained可以从预训练模型中进行配置参数的配置
transformers.BertConfig.from_pretrained(pretrained_model_name_or_path)
Examples:
config = BertConfig.from_pretrained('bert-base-uncased')
config = BertConfig.from_pretrained('./test/saved_model/')
config = BertConfig.from_pretrained('./test/saved_model/my_configuration.json')
config = BertConfig.from_pretrained('bert-base-uncased', output_attentions=True, foo=False)
transformers.PreTrainedModel
transformers.BertModel.from_pretrained 从预训练的模型配置实例化预训练的 pytorch 模型
BertModel 主要为transformer encoder结构,包含三个部分:
embeddings,即BertEmbeddings类的实体,根据单词符号获取对应的向量表示;
encoder,即BertEncoder类的实体;
pooler,即BertPooler类的实体,这一部分是可选的。
transformers.BertModel.from_pretrained(pretrained_model_name_or_path)
#Tensorflow2版本
transformers.TFBertModel.from_pretrained(pretrained_model_name_or_path)
BertModel前向传播过程中各个参数的含义以及返回值:
Examples:
from transformers import BertConfig, BertModel
# 从huggingface.co 下载模型和配置并缓存。
model = BertModel.from_pretrained('bert-base-uncased')
model = BertModel.from_pretrained('./test/saved_model/')
# 在加载期间更新配置
model = BertModel.from_pretrained('bert-base-uncased', output_attentions=True)
assert model.config.output_attentions == True
config = BertConfig.from_json_file('./tf_model/my_tf_model_config.json')
model = BertModel.from_pretrained('./tf_model/my_tf_checkpoint.ckpt.index', from_tf=True, config=config)
# Loading from a Flax checkpoint file instead of a PyTorch model (slower)
model = BertModel.from_pretrained('bert-base-uncased', from_flax=True)
transformers.AdamW
Iterable[nn.parameter.Parameter]) :可迭代的参数以优化或定义参数组的字典float, optional , defaults to 1e-3) :要使用的学习率Tuple[float,float], optional , defaults to (0.9, 0.999)) :Adam 的 betas 参数 (b1, b2)float, optional , defaults to 1e-6) :Adam's epsilon 数值稳定性float, optional , defaults to 0) :要应用的解耦权重衰减bool, optional , defaults to True ) :是否纠正 Adam 中的偏差transformers.Adafactor
Iterable[nn.parameter.Parameter]):用于优化的参数的Iterable或定义参数组的字典。float, optional) :要使用的学习率Tuple[float, float], optional, defaults to (1e-30, 1e-3)) :平方梯度和参数比例的正则化常数float, optional, defaults 1.0):最终梯度更新的均方根阈值float, optional, defaults to -0.8):用于计算平方的运行平均值的系数float, optional) :用于计算梯度运行平均值的系数float, optional, defaults to 0):权重衰减(L2惩罚)bool, optional, defaults to True):如果为True,则学习率按均方根进行缩放bool, optional, defaults to True):如果为True,则计算与时间相关的学习率,而不是外部学习率bool, optional, defaults to False) :时间相关的学习速率计算取决于是否使用预热初始化transformers.AdamWeightDecay(Tensorflow2)
float, optional, default to 1e-3 ) :基础学习率float, optional , defaults to 0.9, 0.999, 1e-7 ) :Adam设置参数bool, optional, default to False ) :决定是否应用Amsgradfloat, optional , defaults to 0 ) :权重衰减率List[str], optional) :应用权重衰减的参数名字List[str],optional ) :不参与权重衰减的参数名字str, optional, defaults to ‘AdamWeightDecay’ ) :名称from transformers.optimization import Adafactor, AdafactorSchedule
optimizer = Adafactor(model.parameters(), scale_parameter=True, relative_step=True, warmup_init=True, lr=None)
lr_scheduler = AdafactorSchedule(optimizer)
***training step***
optimizer.step()
scheduler.step()
transformers.PreTrainedTokenizer
所有分词器的基类:处理tokenization和special tokens的所有共享方法,以及下载/缓存/加载预训练标记器的方法以及向词汇表中添加标记的方法。
BertTokenizer有以下常用方法:
str):包含词汇表的文件bool, optional, defaults to True):标记化时是否将输入小写bool, optional, defaults to True):是否在WordPiece之前进行基本分词化Iterable, optional):在标记化过程中永远不会分割的标记集合。仅当do_basic_tokenize=True时才有效str, optional, defaults to "[UNK]"):未知标记。不在词汇表中的令牌无法转换为ID,而是设置为此标记str, optional, defaults to "[SEP]"):分隔符标记,用于从多个序列构建序列,例如用于序列分类的两个序列或用于文本和用于问答的问题。它还用作使用特殊令牌构建的序列的最后一个标记str, optional, defaults to "[PAD]"):用于填充的标记,例如在批处理不同长度的序列时补充str, optional, defaults to "[CLS]"):进行序列分类时使用的分类器标记(对整个序列进行分类,而不是按令牌分类)。当使用特殊标记构建时,它是序列的第一个标记。str, optional, defaults to "[MASK]"):用于MASK值的标记。这是使用掩码语言建模训练此模型时使用的标记,作为模型将尝试预测的标记Example:
# 获取最后一层隐层的embedding
from transformers import BertTokenizer, BertModel
import torch
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
model = BertModel.from_pretrained('bert-base-uncased')
inputs = tokenizer("Hello, my dog is cute", return_tensors="pt")
inputs = torch.tensor([inputs])
outputs = model(**inputs)
last_hidden_states = outputs.last_hidden_state
将数据集转换为可以训练BERT的格式
BERT令牌生成器
要将文本提供给BERT,必须将其拆分为令牌,然后将这些令牌映射到令牌生成器词汇表中的索引
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased', do_lower_case=True)
sentence = "Hello, my son is cuting."
input_ids_method1 = torch.tensor(
tokenizer.encode(sentence, add_special_tokens=True)) # Batch size 1
# tensor([ 101, 7592, 1010, 2026, 2365, 2003, 3013, 2075, 1012, 102])
input_token2 = tokenizer.tokenize(sentence)
# ['hello', ',', 'my', 'son', 'is', 'cut', '##ing', '.']
input_ids_method2 = tokenizer.convert_tokens_to_ids(input_token2)
# tensor([7592, 1010, 2026, 2365, 2003, 3013, 2075, 1012])
# 并没有开头和结尾的标记:[cls]、[sep]
特殊令牌添加
[SEP]标记,该令牌是两句任务的产物,其中给BERT两个单独的句子并要求确定某些内容[CLS]每个句子的开头添加特殊标记。此令牌具有特殊意义。BERT由12个Transformer层组成。每个转换器接收一个令牌嵌入列表,并在输出上产生相同数量的嵌入[PAD]令牌完成填充,该令牌在BERT词汇表中的索引为0处examples:
# Tokenize all of the sentences and map the tokens to thier word IDs.
input_ids = []
attention_masks = []
# For every sentence...
for sent in sentences:
# `encode_plus` will:
# (1) Tokenize the sentence.
# (2) Prepend the `[CLS]` token to the start.
# (3) Append the `[SEP]` token to the end.
# (4) Map tokens to their IDs.
# (5) Pad or truncate the sentence to `max_length`
# (6) Create attention masks for [PAD] tokens.
encoded_dict = tokenizer.encode_plus(
sent, # Sentence to encode.
add_special_tokens = True, # Add '[CLS]' and '[SEP]'
max_length = 64, # Pad & truncate all sentences.
pad_to_max_length = True,
return_attention_mask = True, # Construct attn. masks.
return_tensors = 'pt', # Return pytorch tensors.
)
# Add the encoded sentence to the list.
input_ids.append(encoded_dict['input_ids'])
# And its attention mask (simply differentiates padding from non-padding).
attention_masks.append(encoded_dict['attention_mask'])
# Convert the lists into tensors.
input_ids = torch.cat(input_ids, dim=0)
attention_masks = torch.cat(attention_masks, dim=0)
labels = torch.tensor(labels)
# Print sentence 0, now as a list of IDs.
print('Original: ', sentences[0])
print('Token IDs:', input_ids[0])
'''
Original : Our friends won't buy this analysis, let alone the next one we propose.
Token IDs:
tensor([ 101, 2256, 2814, 2180, 1005, 1056, 4965, 2023, 4106, 1010,
2292, 2894, 1996, 2279, 2028, 2057, 16599, 1012, 102, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0])
'''
后续直接将BERT进行模型训练、模型存储即可
本文将在这一节重点介绍Bert的全系使用方法,包括工具引入、数据处理、计算资源选择、模型训练与测试使用。由于Bert不同任务存在重叠性,后续讲不再赘述重合部分,而是专注于网络结构。
所需引入工具包
import random
import torch
from torch.utils.data import TensorDataset, DataLoader, random_split
from transformers import BertTokenizer
from transformers import BertForSequenceClassification, AdamW
from transformers import get_linear_schedule_with_warmup
使用计算资源为GPU还是CPU(Bert建议使用GPU启动加载,否则训练过程过慢),以及所用seed确定
# device可选择cuda | cpu
device = torch.device('cuda')
# 随机种子数值确定
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
torch.cuda.manual_seed_all(seed)
torch.backends.cudnn.deterministic = True
加载数据
# pandas读取数据[sentence, type]
data = pd.read_pickle("XXX.csv")
# sentences
text_values = list(df(['sentence']))
# label
label_sample = list(df['type'])
加载bert模型
# 加载预训练分词模型
tokenizer = BertTokenizer.from_pretrained('bert-base-chinese', do_lower_case=False)
# 加载预训练Bert模型
model = BertForSequenceClassification.from_pretrained(
'bert-base-uncased',
num_labels=num_labels,
output_attentions=False,
output_hidden_states=False
)
# 决定是否将模型推送到GPU
model.cuda()
训练数据准备
# 函数获取文本列表的令牌ID
def encode_fn(text_list):
all_input_ids = []
for text in text_list:
input_ids = tokenizer.encode(
text,
add_special_tokens = True, # 添加special tokens, 也就是CLS和SEP
max_length = 160, # 设定最大文本长度
pad_to_max_length = True, # pad到最大的长度
return_tensors = 'pt' # 返回的类型为pytorch tensor
)
all_input_ids.append(input_ids)
all_input_ids = torch.cat(all_input_ids, dim=0)
return all_input_ids
all_input_ids = encode_fn(text_values)
讲数据分为训练集与验证集,并构建dataloader
epochs = 4
batch_size = 32
# 将数据拆分为训练集和验证集
dataset = TensorDataset(all_input_ids, labels)
# 此处采用9:1的数据集构建
train_size = int(0.90 * len(dataset))
val_size = len(dataset) - train_size
train_dataset, val_dataset = random_split(dataset, [train_size, val_size])
# 创建训练和验证数据集的DataLoader
train_dataloader = DataLoader(train_dataset, batch_size = batch_size, shuffle = True)
val_dataloader = DataLoader(val_dataset, batch_size = batch_size, shuffle = False)
定义Bert训练所需optimizer与learning rate scheduler
# 创建优化器和学习率计划
optimizer = AdamW(model.parameters(), lr=2e-5)
total_steps = len(train_dataloader) * epochs
scheduler = get_linear_schedule_with_warmup(optimizer, num_warmup_steps=0, num_training_steps=total_steps)
定义一个可视化指标计算的方法,此处以sklearn的accuracy为例
from sklearn.metrics import f1_score, accuracy_score
def flat_accuracy(preds, labels):
pred_flat = np.argmax(preds, axis=1).flatten()
labels_flat = labels.flatten()
return accuracy_score(labels_flat, pred_flat)
Bert的训练与验证
for epoch in range(epochs):
# 训练集过程
model.train()
total_loss, total_val_loss = 0, 0
total_eval_accuracy = 0
for step, batch in enumerate(train_dataloader):
model.zero_grad()
loss, logits = model(batch[0].to(device), token_type_ids=None, attention_mask=(batch[0]>0).to(device), labels=batch[1].to(device))
total_loss += loss.item()
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
optimizer.step()
scheduler.step()
# 测试集过程
model.eval()
for i, batch in enumerate(val_dataloader):
with torch.no_grad():
loss, logits = model(batch[0].to(device), token_type_ids=None, attention_mask=(batch[0]>0).to(device), labels=batch[1].to(device))
total_val_loss += loss.item()
logits = logits.detach().cpu().numpy()
label_ids = batch[1].to('cpu').numpy()
total_eval_accuracy += flat_accuracy(logits, label_ids)
avg_train_loss = total_loss / len(train_dataloader)
avg_val_loss = total_val_loss / len(val_dataloader)
avg_val_accuracy = total_eval_accuracy / len(val_dataloader)
print(f'Train loss : {avg_train_loss}')
print(f'Validation loss: {avg_val_loss}')
print(f'Accuracy: {avg_val_accuracy:.2f}')
print('\n')
'''
输出格式
Train loss : 0.3275374324204257
Validation loss: 0.3286557973672946
Accuracy: 0.88
'''
模型预测过程,数据集构建参考训练集dataloader
model.eval()
preds = []
for i, (batch,) in enumerate(pred_dataloader):
with torch.no_grad():
outputs = model(batch.to(device), token_type_ids=None, attention_mask=(batch>0).to(device))
logits = outputs[0]
logits = logits.detach().cpu().numpy()
preds.append(logits)
final_preds = np.concatenate(preds, axis=0)
final_preds = np.argmax(final_preds, axis=1)
'''
输出格式如下,后续按照标签编号回推即可
1 0 0 1 0
'''
Tensorflow2上应用预训练模型进行文本分类的一个简单例子。
准备过程,加载模型和数据
import tensorflow as tf
import tensorflow_datasets
from transformers import *
tokenizer = BertTokenizer.from_pretrained('bert-base-cased')
model = TFBertForSequenceClassification.from_pretrained('bert-base-cased')
data = tensorflow_datasets.load('glue/mrpc')
训练数据准备
train_dataset = glue_convert_examples_to_features(data['train'], tokenizer, 128, 'mrpc')
valid_dataset = glue_convert_examples_to_features(data['validation'], tokenizer, 128, 'mrpc')
train_dataset = train_dataset.shuffle(100).batch(32).repeat(2)
valid_dataset = valid_dataset.batch(64)
优化器设置以及训练
optimizer = tf.keras.optimizer.Adam(learning_rate=3e-5, epsilon=1e-8, clipnorm=1.0)
metric = tf.keras.metrics.SparseCategoricalAccuracy('accuracy')
loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
model.compile(optimizer=optimizer, loss=loss, metrics=[metric])
model.fit(train_dataset, epochs=2, steps_per_epoch=115, validation_data=valid_dataset, validation_step=7)
模型预测
sentence_0 = "This research was consistent with his findings"
sentence_1 = "his findings were compatible with this research"
sentence_2 = "his findings were not compatible with this research"
inputs1 = tokenizer.encoder_plus(sentence_0, sentence_1, add_special_tokens=True, return_tensors='pt')
inputs2 = tokenizer.encoder_plus(sentence_0, sentence_2, add_special_tokens=True, return_tensors='pt')
pred1 = tf.argmax(model.predict(**inputs1)[0])
pred2 = tf.argmax(model.predict(**inputs2)[0])
以NER任务为例,命名实体识别任务是NLP中的一个基础任务。主要是从一句话中识别出命名实体,下为Bert+CRF的模型结构,训练过程与数据预处理部分参考上章内容,后续不再赘述,此处展示两种BERT使用方法
常规方案:直接使用Bert作为底层embedding,后续拼接其他网络,其网络结构如下:
import torch
from transformers import BertModel, BertPreTrainedModel
import torch.nn as nn
from torchcrf import CRF
class BertNER(BertPreTrainedModel):
def __init__(self, config):
super(BertNER, self).__init__(config)
self.num_labels = config.num_labels
self.bert = BertModel(config)
self.dropout = nn.Dropout(config.hidden_dropout_prob)
self.classifier = nn.Linear(config.hidden_size, config.num_labels)
self.crf = CRF(config.num_labels, batch_first=True)
self.init_weights()
def forward(self, input_ids, token_type_ids=None, attention_mask=None, labels=None,
position_ids=None, inputs_embeds=None, head_mask=None):
outputs = self.bert(input_ids=input_ids,
attention_mask=attention_mask,
token_type_ids=token_type_ids,
position_ids=position_ids,
head_mask=head_mask,
inputs_embeds=inputs_embeds)
sequence_output = outputs[0]
sequence_output = self.dropout(sequence_output)
# 得到判别值
logits = self.classifier(sequence_output)
outputs = (logits,)
if labels is not None:
if attention_mask is not None:
active_loss = attention_mask.view(-1) == 1
active_logits = logits.view(-1, self.num_labels)
active_labels = torch.where(
active_loss, labels.view(-1), torch.tensor(-100).type_as(labels)
) # [-100, -100, 1, 0...]
else:
active_logits = logits.view(-1, self.num_labels)
active_labels = labels.view(-1)
select_index = []
final_labels = []
for index, label in enumerate(active_labels):
if label != -100:
final_labels.append(label)
select_index.append(index)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
active_logits.to(device)
select_index = torch.tensor(select_index, device=active_logits.device)
final_labels = torch.tensor(final_labels, device=active_logits.device).unsqueeze(0)
final_logits = active_logits.index_select(0, select_index).unsqueeze(0)
loss = self.crf(final_logits, final_labels) * (-1)
outputs = (loss,) + outputs
# contain: (loss), scores
return outputs
直接使用BertForTokenClassification类进行序列标注,可对其模型进行修改拼接,其模型结构如下
import torch
from transformers import BertForTokenClassification
class TokenClassifier(BertForTokenClassification):
def forward(self, input_ids, attention_mask=None, token_type_ids=None,
labels=None, valid_ids=None, attention_mask_label=None):
sequence_output = self.bert(input_ids, attention_mask, token_type_ids,
head_mask=None)[0]
batch_size, max_len, feat_dim = sequence_output.shape
valid_output = torch.zeros(
batch_size, max_len, feat_dim, dtype=torch.float32,
device=next(self.parameters()).device
)
for i in range(batch_size):
jj = -1
for j in range(max_len):
if valid_ids[i][j].item() == 1:
jj += 1
valid_output[i][jj] = sequence_output[i][j]
sequence_output = self.dropout(valid_output)
logits = self.classifier(sequence_output)
if labels is not None:
loss_fct = torch.nn.CrossEntropyLoss(ignore_index=0)
attention_mask_label = None
if attention_mask_label is not None:
active_loss = attention_mask_label.view(-1) == 1
active_logits = logits.view(-1, self.num_labels)[active_loss]
active_labels = labels.view(-1)[active_loss]
loss = loss_fct(active_logits, active_labels)
else:
loss = loss_fct(logits.view(-1, self.num_labels),
labels.view(-1))
return (loss, logits)
else:
return (logits,)
此类适用于问答系统,输入中将上面几个模型中的label改成了start_position和end_position,即答案在原文中起始和结束位置,输出是将预测分数改成了对答案起始位置和结束位置的预测分数。下面为直接使用预训练模型的代码参考
准备过程,加载模型
from transformers import BertTokenizer, BertForQuestionAnswering
model = BertForQuestionAnswering.from_pretrained(MODEL_PATH)
tokenizer = BertTokenizer.from_pretrained(VOCAB_PATH)
保留概率最大的答案
with torch.no_grad():
model.eval()
pred_results = {}
for batch in tqdm(test_dataloader):
q_ids, raw_sentence, input_ids, segment_ids = batch
input_ids, segment_ids = \
input_ids.to(device), segment_ids.to(device)
input_mask = (input_ids > 0).to(device)
start_prob, end_prob = model(input_ids.to(device),
token_type_ids=segment_ids.to(device),
attention_mask=input_mask.to(device)
)
# start_prob = start_prob.squeeze(0)
# end_prob = end_prob.squeeze(0)
for i in range(len(batch[0])):
try:
(best_start, best_end), max_prob = find_best_answer_for_passage(start_prob[i], end_prob[i])
if type(max_prob) == int:
max_prob = 0
else:
max_prob = max_prob.cpu().numpy()[0]
except:
pass
if q_ids[i] in pred_results:
pred_results[q_ids[i]].append(
(raw_sentence[i][best_start.cpu().numpy()[0]:best_end.cpu().numpy()[0]], max_prob))
else:
pred_results[q_ids[i]] = [(raw_sentence[i][best_start.cpu().numpy()[0]:best_end.cpu().numpy()[0]], max_prob)]
# 保留最大概率的答案
for id in pred_results:
pred_results[id] = sorted(pred_results[id], key=lambda x: x[1], reverse=True)[0]
submit = {}
for item in test_data:
q_id = item[0]
question = item[2]
if q_id not in pred_results:continue
submit[q_id] = pred_results[q_id][0].strip()
print(question, pred_results[q_id][0].strip())
BERT的全称为Bidirectional Encoder Representation from Transformers,是一个预训练的语言表征模型。它强调了不再像以往一样采用传统的单向语言模型或者把两个单向语言模型进行浅层拼接的方法进行预训练,而是采用新的masked language model(MLM),以致能生成深度的双向语言表征。BERT论文发表时提及在11个NLP(Natural Language Processing,自然语言处理)任务中获得了新的state-of-the-art的结果,令人目瞪口呆。
该模型有以下主要优点:
以往的预训练模型的结构会受到单向语言模型(从左到右或者从右到左)的限制,因而也限制了模型的表征能力,使其只能获取单方向的上下文信息。而BERT利用MLM进行预训练并且采用深层的双向Transformer组件(单向的Transformer一般被称为Transformer decoder,其每一个token(符号)只会attend到目前往左的token。而双向的Transformer则被称为Transformer encoder,其每一个token会attend到所有的token)来构建整个模型,因此最终生成能融合左右上下文信息的深层双向语言表征。关于Transformer的详细解释可以参见Attention Is All You Need 或者 The Illustrated Transformer,经过多层Transformer结构的堆叠后,形成BERT的主体结构

BERT的输入为每一个token对应的表征(图中的粉红色块就是token,黄色块就是token对应的表征),并且单词字典是采用WordPiece算法来进行构建的。为了完成具体的分类任务,除了单词的token之外,作者还在输入的每一个序列开头都插入特定的分类token([CLS]),该分类token对应的最后一个Transformer层输出被用来起到聚集整个序列表征信息的作用。
由于BERT是一个预训练模型,其必须要适应各种各样的自然语言任务,因此模型所输入的序列必须有能力包含一句话(文本情感分类,序列标注任务)或者两句话以上(文本摘要,自然语言推断,问答任务)。那么如何令模型有能力去分辨哪个范围是属于句子A,哪个范围是属于句子B呢?BERT采用了两种方法去解决:
上面提到了BERT的输入为每一个token对应的表征,实际上该表征是由三部分组成的,分别是对应的token,分割和位置 embeddings(位置embeddings的详细解释可参见Attention Is All You Need 或 The Illustrated Transformer),如下图:

C为分类token([CLS])对应最后一个Transformer的输出,
则代表其他token对应最后一个Transformer的输出。对于一些token级别的任务(如,序列标注和问答任务),就把
输入到额外的输出层中进行预测。对于一些句子级别的任务(如,自然语言推断和情感分类任务),就把C输入到额外的输出层中,这里也就解释了为什么要在每一个token序列前都要插入特定的分类token。
Masked Language Model(MLM):是BERT能够不受单向语言模型所限制的原因。简单来说就是以15%的概率用[MASK]随机地对每一个训练序列中的token进行替换,然后预测出[MASK]位置原有的单词。然而,由于[MASK]并不会出现在下游任务的微调(fine-tuning)阶段,因此预训练阶段和微调阶段之间产生了不匹配。因此BERT采用了以下策略来解决这个问题:
首先在每一个训练序列中以15%的概率随机地选中某个token位置用于预测,假如是第i个token被选中,则会被替换成以下三个token之一
Next Sentence Prediction(NSP):MLM任务倾向于抽取token层次的表征,因此不能直接获取句子层次的表征。为了使模型能够有能力理解句子间的关系,BERT使用了NSP任务来预训练,简单来说就是预测两个句子是否连在一起。具体的做法是:对于每一个训练样例,我们在语料库中挑选出句子A和句子B来组成,50%的时候句子B就是句子A的下一句,剩下50%的时候句子B是语料库中的随机句子。接下来把训练样例输入到BERT模型中,用[CLS]对应的C信息去进行二分类的预测。
优点:
缺点:
ALBERT:提出了两种参数缩减技术,以降低内存消耗并提高BERT的训练速度
XLNet:XLnet是Transformer XL模型的一个扩展,该模型使用自回归方法预先训练,通过最大化输入序列分解顺序的所有排列的期望似然来学习双向上下文
Roberta:建立在BERT的基础上,修改关键的超参数,删除next-sentence pretraining的预训练目标,并以更大的mini-batches和learning rates进行训练
GPT2:超大规模语料上通过transformer结构进行的无监督训练的语言模型,适用于各项生成任务

我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看rubyzip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于
我正在尝试使用ruby和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t
我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h
我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po