https://www.bilibili.com/video/BV1K5411t7MD?p=5
https://www.youtube.com/channel/UCoRX98PLOsaN8PtekB9kWrw/videos
深入BERT实战(PyTorch) by ChrisMcCormickAI
这是ChrisMcCormickAI在油管bert,8集系列第三篇fine-Tuning的pytorch的讲解的代码,在油管视频下有cloab地址,如果不能的可以留下邮箱我全部看完整理后发给你。但是在fine-tuning最好还是在cloab上运行
By Chris McCormick and Nick Ryan 翻译:名字填充中
并不是完全翻译,只是针对读英文比较累的同学,英文好的可以直接在Cloab看原文。
这个教程,展示如何使用BERT和huggingface PyTorch库来快速有效地fine-tune模型,以获得接近最先进的句子分类性能(sentence classification)。更广泛地说,我描述了迁移学习在自然语言处理中的实际应用,最小的努力创建高性能模型。
这篇文章有两种形式——原文bloghere,和cloab Notebookhere。 (我在这里是进行大致翻译,有增减)
两者的内容相同,但:
作者视频的油管地址为 Part 1 和 Part 2
在cloab左侧有显示,也可以在我上面的目录看
示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。
2018年是NLP的突破之年。迁移学习例如 AILEN AL的ELMO, OpenAI’s Open-GPT, 和Google’s BERT,都提供了预训练模型,这些预训练模型在多种特定任务上进行fine-tuning 就可以取得非常好的结果,使用更少的数据和计算时间。不幸的是,对于许多刚开始从事自然语言处理的人,甚至是一些经验丰富的从业人员来说,这些强大模型的理论和实际应用仍然没有得到很好的理解。(不重要,随便翻的 _)
BERT(Bidirectional Encoder Representations from Transformers, 基于Transformers的双向编码表示),发布于2018年,这个教程使用的模型,为读者提供更好的理解和在自然语言处理中使用迁移学习模型的实际指导。BERT是一种预训练模型,可以免费下载。可以用BERT从文本中提取更好的语言特征,也可以直接用到某种特殊任务(分类、实体识别、问题回答等),在自己的数据上微调后获得非常好的结果
这个大佬将会教你修改和微调BERT,创建一个强大的NLP模型,快速得到最先进的结果。(如果你愿意给他花钱可以学到更多,大佬的课程链接)
这教程训练了BERT的文本分类,具体来说,我们将采用预训练的BERT模型,在最后添加一层未训练的神经元,训练新的模型来完成分类任务。为什么这样做,而不是训练一个特定的深度学习模型(CNN, BiLSTM,等等),使这个模型非常适合你需要的特定的NLP任务?
这种转移学习的转变与几年前发生在计算机视觉领域的转变是相似的。为计算机视觉任务创建一个良好的深度学习网络需要数百万个参数,而且训练成本非常高。研究人员发现,深度网络可以学习层次特征表示(简单的特征,如最低层的边缘,逐渐复杂的特征在更高层)。而不是每次从头开始训练一个新的网络,一个训练过的具有广义图像特征的网络的下层可以被复制和转移到另一个具有不同任务的网络中使用。下载一个预训练过的深层网络,然后快速地为新的任务重新训练它,或者在上面添加额外的层,这很快成为一种普遍的做法——这比从头训练一个网络的昂贵过程要好得多。对于许多人来说,2018年引入的深度预训练语言模型(ELMO、BERT、ULMFIT、Open-GPT等)标志着NLP中迁移学习的转变,与计算机视觉看到的转变是一样的。
ok! 前面的都扯完了,从概念上已经学会BERT了,四舍五入已经完成了99%,现在开始最后的1% !胜利就在眼前!
BERT eBook Display Ad
他们的广告,在这个地方充钱,你将变得更强
Google Colab有免费的GPU和TPU!因为我们将训练一个大型的神经网络,所以最好利用这一点(在这种情况下,我们将附加一个GPU),否则训练将花费很长时间。
GPU可以添加到菜单中并选择:
Edit Notebook Settings Hardware accelerator (GPU)
然后运行以下单元,确认检测到GPU。
这里建议直接搭个梯子在Cloab上跑,这个教程重点是学怎么微调BERT,不要再去自己配环境了!
# tensorflow测试GPU
import tensorflow as tf
# Get the GPU device name.
device_name = tf.test.gpu_device_name()
# The device name should look like the following:
if device_name == '/device:GPU:0':
print('Found GPU at: {}'.format(device_name))
else:
raise SystemError('GPU device not found')
为了在torch上使用GPU,将device指定为gpu
import torch
# If there's a GPU available...
if torch.cuda.is_available():
# Tell PyTorch to use the GPU.
device = torch.device("cuda")
print('There are %d GPU(s) available.' % torch.cuda.device_count())
print('We will use the GPU:', torch.cuda.get_device_name(0))
# If not...
else:
print('No GPU available, using the CPU instead.')
device = torch.device("cpu")
这里我得到了一块Tesla T4
安装Hugging Face用Pytorcch的transformers包,提供一个BERT的接口。(也包括像OpenAI’s GPT and GPT-2.的其他预训练模型的接口),这里用pytorch的高级api接口(容易使用,可以忽略细节)和tensorflow的代码之间取得了很好的平衡。(包含很多细节,经常会偏离到关于tensorflow的课程,而这里的重点是BERT!)
目前,Hugging Face的库是最被强大与广泛接受的BERT的pytorch接口。除了支持各种不同预训练的transformer模型,针对特定任务进行的模型修改。这里用BertForSequenceClassification
.
该库还包括token classification
, question answering
, next sentence prediciton
等的特定任务类。使用这些预构建的类,可以简化自己修改BERT的过程。
!pip install transformers
这里用的教程代码是huggingface简化的脚本run_glue.py
run_glue.py
很有用!你可以选GLUE里你想要的基准任务来测试,以及你想要使用的预训练过的模型(具体就看这里),有CPU,多GPU和单GPU。进一步加速还支持16位精度(代码里面都是32位)
就是这些可配置性都是牺牲了可读性做代价,在这个教程就简化了代码,添加的特别多注释(简单的注释就不翻译了)
用The Corpus of Linguistic Acceptability (CoLA)做做单句子分类。它是一组标记为语法正确或错误的句子。它首次于2018年5月发布,是包括BERT等模型竞争的“GLUE Benchmark”测试之一。
用wget
下载数据集到Cloab
!pip install wget
数据集在github的地址:https://nyu-mll.github.io/CoLA/
import wget
import os
print('Downloading dataset...')
# The URL for the dataset zip file.
url = 'https://nyu-mll.github.io/CoLA/cola_public_1.1.zip'
# Download the file (if we haven't already)
if not os.path.exists('./cola_public_1.1.zip'):
wget.download(url, './cola_public_1.1.zip')
下载解压,可以在Cloab左侧文件系统浏览
# Unzip the dataset (if we haven't already)
if not os.path.exists('./cola_public/'):
!unzip cola_public_1.1.zip
我们可以从文件名中看到数据的tokenized
和raw
版本都是可用的。我们不能使用tokenized
版本,因为为了应用pre-trained的BERT,我们必须使用模型提供的tokenizer。这是因为
用pandas来解析“in-domain”训练集,并查看它的一些属性和数据点。
import pandas as pd
# Load the dataset into a pandas dataframe.
df = pd.read_csv("./cola_public/raw/in_domain_train.tsv", delimiter='\t', header=None, names=['sentence_source', 'label', 'label_notes', 'sentence'])
# Report the number of sentences.
print('Number of training sentences: {:,}\n'.format(df.shape[0]))
# Display 10 random rows from the data.
df.sample(10)
sentence
和对应的label
,"acceptibility judgment"称为可接受度(0=unacceptable, 1=acceptable)
下面有五个被认为在语法上不可接受的句子。请注意,这个任务比情绪分析之类的事情要困难得多!(采用随机采样)
df.loc[df.label == 0].sample(5)[['sentence', 'label']]
# Get the lists of sentences and their labels.
sentences = df.sentence.values
labels = df.label.values
数据预处理,转换为BERT训练格式
将给BERT传输的文本,首先要分割标记,然后建立索引和tokenizer词汇表的映射。
tokenization必须用BERT的tokenizer,下面下载的是bert-base-uncased
版本的
from transformers import BertTokenizer
# Load the BERT tokenizer.
print('Loading BERT tokenizer...')
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased', do_lower_case=True)
下面是应用到一个句子中的实例
# Print the original sentence.
print(' Original: ', sentences[0])
# Print the sentence split into tokens.
print('Tokenized: ', tokenizer.tokenize(sentences[0]))
# Print the sentence mapped to token ids.
print('Token IDs: ', tokenizer.convert_tokens_to_ids(tokenizer.tokenize(sentences[0])))
转换所有句子时,使用 tokenize.encode
函数对每一步进行处理,并不是用tokenize
和 convert_tokens_to_ids
分开处理
不过,在此之前,我们需要讨论一下BERT的一些格式要求。
上面的代码省略了格式化步骤。
*Side Note:在作者看来,要求BERT格式输入是"over-specified" …我们被要求向它提供一些似乎多余的信息,或者它们可以很容易地从数据中推断出来,而不需要我们明确提供。但事实就是如此,而且我怀疑,一旦我对BERT的内在原理有了更深的理解,它就会变得更有意义。
被输入数据要求:
[SEP]
在每个句子的末尾,都需要添加特殊的[SEP]
标记。
这个token是two-sentence任务的产物,其中BERT被给予两个独立的句子,并被要求确定一些事情例如,句子A中的问题的答案可以在句子B中找到吗?)
我还不确定当我们只有单句输入时为什么仍然需要标记,但它是!
[CLS]
For classification tasks, we must prepend the special [CLS]
token to the beginning of every sentence.
This token has special significance. BERT consists of 12 Transformer layers. Each transformer takes in a list of token embeddings, and produces the same number of embeddings on the output (but with the feature values changed, of course!).
对于分类任务,我们必须在每个句子的开头添加特殊的[CLS]
标记。
这个标志有特殊的意义。BERT有12个Transformer layers。每个transformer接受一个token embeddings列表,并在输出上生成相同数量的embeddings(当然,特征值发生了变化!)
在第12个transformer的输出,仅仅第一个embedding是用到classifier(对应的[CLS]token)
“The first token of every sequence is always a special classification token (
[CLS]
).The final hidden state corresponding to this token is used as the aggregate sequence representation for classification tasks.”(from the BERT paper)
上面是原论文
你可能会想去在最后一层加入pooling策略,但是,这是不需要的。因为BERT被训练为只使用这个[CLS]tokens进行分类,我们知道模型已经被激励将分类步骤所需的一切编码到每个的768个值的嵌入向量中。它已经帮我们搞定了!
我们数据集中的句子显然有不同的长度,那么BERT如何处理这个问题呢?
两个约束条件:
填充是用[PAD]
字符,在BERT的词汇表索引为0。下图演示了填充到8个令牌的“MAX_LEN”。
“Attention Mask” 只是一个由1和0组成的数组,表示哪些标记是填充,哪些不是(看起来有点多余,不是吗?!)这个mask机制告诉BERT的“Self-Attention”机制,不要把这些PAD符号合并到它对句子的解释中。
然而,最大长度确实影响训练和评估速度。
For example, with a Tesla K80:
MAX_LEN = 128 --> Training epochs take ~5:28 each
MAX_LEN = 64 --> Training epochs take ~2:57 each
transformers库提供了encode
函数,它将为我们处理大多数解析和数据准备步骤。
在我们准备编码我们的文本之前,我们需要决定填充/截断的最大句子长度。
下面的单元格将对数据集执行一次标记化传递(tokenization pass),以测量最大句子长度。
max_len = 0
# For every sentence...
for sent in sentences:
# Tokenize the text and add `[CLS]` and `[SEP]` tokens. 对每个单词加上[CLS]和[SEP]
input_ids = tokenizer.encode(sent, add_special_tokens=True)
# Update the maximum sentence length.
max_len = max(max_len, len(input_ids))
print('Max sentence length: ', max_len)
# 总共有8551个句子,这些最长47
为了防止出现更长的测试句子,最大长度设置为64。
现在进行tokenization
tokenizer.encode_plus
函数会组合多个步骤
[CLS]
和 [SEP]
[PAD]
tokens。前四个特性在tokenizer.encode
中,但是,我使用了tokenizer.encode_plus
来获得第五个(attention masks)。文档地址here.
# 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.
)
# encoded_dict字典形式返回有三类 input_ids,attention_mask,token_type_ids
# 分别加入到列表input_ids和attention_mask中
# 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])
9/1划分
from torch.utils.data import TensorDataset, random_split
# Combine the training inputs into a TensorDataset.
dataset = TensorDataset(input_ids, attention_masks, labels)
# Create a 90-10 train-validation split.
# Calculate the number of samples to include in each set.
train_size = int(0.9 * len(dataset))
val_size = len(dataset) - train_size
# Divide the dataset by randomly selecting samples.
train_dataset, val_dataset = random_split(dataset, [train_size, val_size])
print('{:>5,} training samples'.format(train_size))
print('{:>5,} validation samples'.format(val_size))
#7,695 training samples
#856 validation samples
我们还将使用torch DataLoader类为数据集创建一个迭代器。这有助于在训练期间节省内存,因为与for循环不同,使用迭代器时,整个数据集不需要加载到内存中。DataLoader
from torch.utils.data import DataLoader, RandomSampler, SequentialSampler
# The DataLoader needs to know our batch size for training, so we specify it
# here. For fine-tuning BERT on a specific task, the authors recommend a batch
# size of 16 or 32.
# DataLoader要指定batch_size大小 推荐16或者32
batch_size = 32
# Create the DataLoaders for our training and validation sets.
# We'll take training samples in random order. # 随机采样训练集
train_dataloader = DataLoader(
train_dataset, # The training samples.
sampler = RandomSampler(train_dataset), # Select batches randomly
batch_size = batch_size # Trains with this batch size.
)
# For validation the order doesn't matter, so we'll just read them sequentially. 验证集不用管顺序
validation_dataloader = DataLoader(
val_dataset, # The validation samples.
sampler = SequentialSampler(val_dataset), # Pull out batches sequentially.
batch_size = batch_size # Evaluate with this batch size.
)
这是第三课的在Cloab代码,我再这里算是做一个个人笔记,后半部分的争取明天中午前弄完,不能的可以留邮箱,我把notebook发过去,全部整完后我再传百度云。