该文档为datawhale情感分析组队学习的笔记
Github地址:team-learning-nlp/Emotional_Analysis at master · datawhalechina/team-learning-nlp (github.com)
- 本文使用 BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding 论文中的 BERT模型。
- 我们将固定(而不训练)transformer,只训练从transformer产生的表示中学习的模型的其余部分。 在这种情况下,我们将使用双向GRU继续提取从Bert embedding后的特征。最后在fc层上输出最终的结果。
1. 数据预处理
import torch
import random
import numpy as np
SEED = 1234
random.seed(SEED)
np.random.seed(SEED)
torch.manual_seed(SEED)
torch.backends.cudnn.deterministic = True
1.1 bert-base-uncased
Transformer 已经用特定的词汇进行了训练,这意味着我们需要使用完全相同的词汇进行训练,并以与 Transformer 最初训练时相同的方式标记我们的数据。
幸运的是,transformers 库为每个提供的transformer 模型都有分词器。 在这种情况下,我们使用忽略大小写的 BERT 模型(即每个单词都会小写)。 我们通过加载预训练的“bert-base-uncased”标记器来实现这一点。
from transformers import BertTokenizer
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
tokenizer
有一个vocab
属性,它包含我们将使用的实际词汇。 我们可以通过检查其长度来检查其中有多少单词。
len(tokenizer.vocab)
# 30522
- 使用
tokenizer.tokenize
方法对字符串进行分词,并统一大小写。
tokens = tokenizer.tokenize('Hello WORLD how ARE yoU?')
print(tokens)
# ['hello', 'world', 'how', 'are', 'you', '?']
- 我们可以使用我们的词汇表使用
tokenizer.convert_tokens_to_ids
来数字化标记。下面的tokens是我们之前上面进行了分词和统一大小写之后的list。
indexes = tokenizer.convert_tokens_to_ids(tokens)
print(indexes)
# [7592, 2088, 2129, 2024, 2017, 1029]
1.2 特殊token
- cls:放在第一个句子的首位,经过BERT得到的表征向量C可以用于后续的分类任务(可以理解为用于下游的分类任务)
init_token = tokenizer.cls_token
eos_token = tokenizer.sep_token
pad_token = tokenizer.pad_token
unk_token = tokenizer.unk_token
print(init_token, eos_token, pad_token, unk_token)
- 我们可以通过反转词汇表来获得特殊tokens的索引
init_token_idx = tokenizer.convert_tokens_to_ids(init_token)
eos_token_idx = tokenizer.convert_tokens_to_ids(eos_token)
pad_token_idx = tokenizer.convert_tokens_to_ids(pad_token)
unk_token_idx = tokenizer.convert_tokens_to_ids(unk_token)
print(init_token_idx, eos_token_idx, pad_token_idx, unk_token_idx)
- 或者通过tokenizer的方法直接获取
init_token_idx = tokenizer.cls_token_id
eos_token_idx = tokenizer.sep_token_id
pad_token_idx = tokenizer.pad_token_id
unk_token_idx = tokenizer.unk_token_id
print(init_token_idx, eos_token_idx, pad_token_idx, unk_token_idx)
# 101 102 0 100
1.3 max_input_length
- 我们需要处理的另一件事是模型是在具有定义的最大长度的序列上训练的——它不知道如何处理比训练更长的序列。 我们可以通过检查我们想要使用的转换器版本的
max_model_input_sizes
来获得这些输入大小的最大长度。
max_input_length = tokenizer.max_model_input_sizes['bert-base-uncased']
print(max_input_length)
# 512
1.4 tokenize_and_cut()
- 之前我们使用了
spaCy
标记器来标记我们的示例。 然而,我们现在需要定义一个函数,我们将把它传递给我们的TEXT
字段,它将为我们处理所有的标记化。 它还会将token
的数量减少到最大长度。 请注意,我们的最大长度比实际最大长度小 2。 这是因为我们需要向每个序列附加两个标记,一个在开头,一个在结尾。
def tokenize_and_cut(sentence):
tokens = tokenizer.tokenize(sentence)
tokens = tokens[:max_input_length-2]
return tokens
1.5 定义标签字段
- 现在我们开始定义我们的字段
- transformer期望将batch维度放在第一维上,所以我们设置了
batch_first = True
。 - 现在我们已经有了文本的词汇数据,由transformer提供,我们设置
use_vocab = False
来告诉 torchtext 已经不需要切分数据了。 - 我们将
tokenize_and_cut
函数作为标记器传递。 preprocessing
参数是一个函数,这是我们将token转换为其索引的地方。- 最后,我们定义特殊的token——注意我们将它们定义为它们的索引值而不是它们的字符串值,即“100”而不是“[UNK]”这是因为序列已经被转换为索引。
from torchtext.legacy import data
TEXT = data.Field(batch_first = True,
use_vocab = False,
tokenize = tokenize_and_cut,
preprocessing = tokenizer.convert_tokens_to_ids,
init_token = init_token_idx,
eos_token = eos_token_idx,
pad_token = pad_token_idx,
unk_token = unk_token_idx