BERT系列-BERT模型的初步认识
1.BERT模型的初步认识BERT(Pre-training of Deep Bidirectional Transformers,原文链接:BERT)是近年来NLP圈中最火爆的模型,让我们来看一些数据。自从2018年BERT模型发布以来,BERT模型仅用 2019 年一年的时间,便以势如破竹的姿态成为了 NLP 领域首屈一指的红人,BERT 相关的论文也如涌潮般发表出来。2019 年,可谓是 N
1.BERT模型的初步认识
BERT(Pre-training of Deep Bidirectional Transformers,原文链接:BERT)是近年来NLP圈中最火爆的模型,让我们来看一些数据。
自从2018年BERT模型发布以来,BERT模型仅用 2019 年一年的时间,便以势如破竹的姿态成为了 NLP 领域首屈一指的红人,BERT 相关的论文也如涌潮般发表出来。2019 年,可谓是 NLP 发展历程中具有里程碑意义的一年,而其背后的最大功臣当属 BERT !在NLP领域,如果把2019年称为“BERT年”也不为过。
据统计,2019以BERT为主要内容的论文发表数量近200篇,具体数据可以看看下面图片的github链接呦。
图片出处:BERT_papers。
2.BERT之前NLP的状态
在BERT模型发布之前,NLP任务的解决方案是基于word2vec这样的词特征+RNNs等网络结构的解决方案。由于RNNs模型的特征提取能力不足,为了满足业务指标,往往需要大量的标注数据才能满足上线需求。这样,小一些的NLP公司,由于相对数据的匮乏,难以推动NLP业务,致使NLP技术的发展并没有像计算机视觉领域那么顺利。不过,自然语言处理和计算机视觉的研究本身就是互相交叉互相影响的,所以就有学者基于图像领域的思想,将NLP问题的思路转化为预训练+微调的模式,取得了优异的成绩,在BERT模型发表之前,ELMo、GPT就是这一模式的典型开创者。
ELMo提出的预训练+微调的模式,当时只是为了解决word2vec词向量不能表达”一词多义“的问题提出来的。它是一种动态词向量的思想,不过这种预训练+微调的模式借助迁移学习的思想为后来BERT的出现打下了一定的基础,本章不会具体阐述ELMo的原理,如果你还不了解ELMo,我强烈建议你阅读下面的材料来补充相关的知识。
ELMo相关阅读资料:(ELMo原理解析及简单上手使用)
这里留一个思考题,大家可以思考下:ELMo的缺点是什么呢?
为了解决上述问题,2017年Google发布了特征提取能力更为强大的Transformer网络,论文链接:Attention is all you need。Transformer中的具体结构以及代码都会在后面章节结合BERT详细剖析,在此不过多介绍。
有了Transformer网络结构后,改造ELMo解决更多更难的问题,提供了一个方向。
对于近年来NLP领域模型发展的历史可以观看下图,该图出自ACL2019大会报告(The Bright Future of ACL/NLP)。
不过话说回来,第一个使用Transformer的预训练模型不是bert,而是GPT。想要进一步了解GPT模型的同学,可以阅读补充资料(OpenAI GPT: Generative Pre-Training for Language Understanding),如果你不了解Transformer结构,我建议你先不要阅读,等阅读完系列文章后,再来品味一下GPT与BERT的不同。
提前透露一下GPT和BERT的最大的不同,GPT里的基本结构是由单向的Transformer-Decoder结构组成,而BERT是由双向的Transformer-Encoder结构组成。
不管是ELMo、GPT还是BERT,他们改变解决NLP的重要思路是预训练+微调的模式。如图所示,预训练+微调的模式是一种迁移学习的思想,预训练阶段可以使用大规模的数据(比如wiki),使得一个强大的模型学习到大量的知识,而且这些知识的学习方式是无监督的。通过预训练的学习之后,模型就已经具备大量的先验知识,在微调阶段继续使用预训练好的模型参数,采用业务自身标注数据在此基础上完成最后一步的监督学习。
了解到这,大家对于BERT应该有了一个初步的认识,那顺便思考一下。
BERT采用了迁移学习的思想,如果在相同的NLP任务上达到传统模型(如RNN等)一样的性能指标,比如准确度都是90%,在准备数据成本上有优势么,什么样的优势?
3.带你读BERT原论文
该部分会精读一下BERT的原文,我也已经将重要的信息做了标注和解释。
把标注的地方全部弄清楚,就可以进行下面的学习了,一个新的算法,理论啃完,在把源码吃透,才算真的掌握,希望大家多注意细节。
完整解读版,可以自行下载,链接: https://pan.baidu.com/s/1Q0DcoIR1boHd4qi7vkwoTg 密码: ee20
4.跑通BERT代码
BERT当年发表时就在SQuAD v1.1上,获得了93.2%的 F1 分数,超过了之前最高水准的分数91.6%,同时超过了人类的分数91.2%。
BERT 还在极具挑战性的 GLUE 基准测试中将准确性的标准提高了7.6%。这个基准测试包含 9 种不同的自然语言理解(NLU)任务。在这些任务中,具有人类标签的训练数据跨度从 2,500 个样本到 400,000 个样本不等。BERT 在所有任务中都大大提高了准确性。
上述的微调任务介绍如下表
- MNLI:给定一个前提 (Premise) ,根据这个前提去推断假设 (Hypothesis) 与前提的关系。该任务的关系分为三种,蕴含关系 (Entailment)、矛盾关系 (Contradiction) 以及中立关系 (Neutral)。所以这个问题本质上是一个分类问题,我们需要做的是去发掘前提和假设这两个句子对之间的交互信息。
- QQP:基于Quora,判断 Quora 上的两个问题句是否表示的是一样的意思。
- QNLI:用于判断文本是否包含问题的答案,类似于我们做阅读理解定位问题所在的段落。
- STS-B:预测两个句子的相似性,包括5个级别。
- MRPC:也是判断两个句子是否是等价的。
- RTE:类似于MNLI,但是只是对蕴含关系的二分类判断,而且数据集更小。
- SWAG:从四个句子中选择为可能为前句下文的那个。
- SST-2:电影评价的情感分析。
- CoLA:句子语义判断,是否是可接受的(Acceptable)。
初步了解了BERT以后,我们就简单跑一个BERT的例子吧,让大家有一个整体的认识。
# 正式开始实验之前首先通过如下命令安装最新版本的 paddlenlp
!python -m pip install --upgrade paddlenlp -i https://pypi.org/simple
# 导入所需要的包
from functools import partial
import argparse
import os
import random
import time
import numpy as np
import paddle
import paddle.nn.functional as F
import paddlenlp as ppnlp
from paddlenlp.data import Stack, Tuple, Pad
from paddlenlp.datasets import load_dataset
from paddlenlp.transformers import LinearDecayWithWarmup
# 下载paddlenlp预置数据
train_ds, dev_ds, test_ds = load_dataset("chnsenticorp", splits=["train", "dev", "test"])
# 输出训练集的前 10 条样本
for idx, example in enumerate(train_ds):
if idx <= 10:
print(example)
# 超参数
EPOCHS = 10 # 训练的轮数
BATCH_SIZE = 8 # 批大小
MAX_LEN = 300 # 文本最大长度
LR = 1e-5 # 学习率
WARMUP_STEPS = 100 # 热身步骤
T_TOTAL = 1000 # 总步骤
# 调用bert模型用的tokenizer
tokenizer = ppnlp.transformers.BertTokenizer.from_pretrained('bert-base-chinese')
# 将文本内容转化成模型所需要的token id
def convert_example(example, tokenizer, max_seq_length=512, is_test=False):
"""
Builds model inputs from a sequence or a pair of sequence for sequence classification tasks
by concatenating and adding special tokens. And creates a mask from the two sequences passed
to be used in a sequence-pair classification task.
"""
encoded_inputs = tokenizer(text=example["text"], max_seq_len=max_seq_length)
input_ids = encoded_inputs["input_ids"]
token_type_ids = encoded_inputs["token_type_ids"]
if not is_test:
label = np.array([example["label"]], dtype="int64")
return input_ids, token_type_ids, label
else:
return input_ids, token_type_ids
# 创建dataloader
def create_dataloader(dataset,
mode='train',
batch_size=1,
batchify_fn=None,
trans_fn=None):
if trans_fn:
dataset = dataset.map(trans_fn)
shuffle = True if mode == 'train' else False
if mode == 'train':
batch_sampler = paddle.io.DistributedBatchSampler(
dataset, batch_size=batch_size, shuffle=shuffle)
else:
batch_sampler = paddle.io.BatchSampler(
dataset, batch_size=batch_size, shuffle=shuffle)
return paddle.io.DataLoader(
dataset=dataset,
batch_sampler=batch_sampler,
collate_fn=batchify_fn,
return_list=True)
# 样本转换函数
trans_func = partial(
convert_example,
tokenizer=tokenizer,
max_seq_length=MAX_LEN)
batchify_fn = lambda samples, fn=Tuple(
Pad(axis=0, pad_val=tokenizer.pad_token_id), # input
Pad(axis=0, pad_val=tokenizer.pad_token_type_id), # segment
Stack(dtype="int64") # label
): [data for data in fn(samples)]
train_data_loader = create_dataloader(
train_ds,
mode='train',
batch_size=BATCH_SIZE,
batchify_fn=batchify_fn,
trans_fn=trans_func)
dev_data_loader = create_dataloader(
dev_ds,
mode='dev',
batch_size=BATCH_SIZE,
batchify_fn=batchify_fn,
trans_fn=trans_func)
test_data_loader = create_dataloader(
test_ds,
mode='test',
batch_size=BATCH_SIZE,
batchify_fn=batchify_fn,
trans_fn=trans_func)
# 调用bert的预训练模型
model = ppnlp.transformers.BertForSequenceClassification.from_pretrained(
'bert-base-chinese', num_classes=len(train_ds.label_list))
# 完成训练设置
num_training_steps = len(train_data_loader) * EPOCHS
lr_scheduler = LinearDecayWithWarmup(LR, num_training_steps, WARMUP_STEPS)
# Generate parameter names needed to perform weight decay.
# All bias and LayerNorm parameters are excluded.
decay_params = [
p.name for n, p in model.named_parameters()
if not any(nd in n for nd in ["bias", "norm"])
]
optimizer = paddle.optimizer.AdamW(
learning_rate=lr_scheduler,
parameters=model.parameters(),
weight_decay=0.0,
apply_decay_param_fun=lambda x: x in decay_params)
criterion = paddle.nn.loss.CrossEntropyLoss()
metric = paddle.metric.Accuracy()
# 开始训练
global_step = 0
tic_train = time.time()
for epoch in range(1, EPOCHS + 1):
for step, batch in enumerate(train_data_loader, start=1):
input_ids, token_type_ids, labels = batch
logits = model(input_ids, token_type_ids)
loss = criterion(logits, labels)
probs = F.softmax(logits, axis=1)
correct = metric.compute(probs, labels)
metric.update(correct)
acc = metric.accumulate()
global_step += 1
if global_step % 10 == 0:
print(
"global step %d, epoch: %d, batch: %d, loss: %.5f, accu: %.5f, speed: %.2f step/s"
% (global_step, epoch, step, loss, acc,
10 / (time.time() - tic_train)))
tic_train = time.time()
loss.backward()
optimizer.step()
lr_scheduler.step()
optimizer.clear_grad()
if global_step % 100 == 0:
break
break
print('bert训练Demo完成了...')
ok,终于可以完成一个BERT的训练demo了。
5.后续计划
本节课内容就这些,喜欢的同学可以关注后续内容。
计划:
《BERT模型的核心架构》
《BERT如何完成预训练》
《BERT微调细节详解》
《使用BERT完成任务》
《。。。》
更多推荐
所有评论(0)