NLP 之四:双向预训练模型
利用预训练好的模型进行微调(Fine-tune),可以获得比传统模型的巨大提升。
此时学习率一般是正常的十分之一(\(10^{-5}\) 左右)。也可以保持预训练的参数不变。
Transformer 架构
- Encoder-only:擅长分类任务
- Decoder-only:擅长生成任务
- Encoder-only:混合情况(例如文本翻译、总结)
在无监督训练中,Encoder 和 Decoder 最大的区别是,前者可以从上下文同时学习,而后者则只能从上文学习。
前者也被称为双向预训练模型,BERT 便是其中的经典之作。
BERT:Encoder-only
为了同时从上下文学习,我们不能再使用直接使用注意力机制预测文本每一个词。因为这样当叠了两层 Attention 后,模型就可以从每个词自己提取出信息,直接预测出自己。取而代之的是以下的两种训练策略:
- 掩码语言模型(MLM):随机一定规模(一般是15%)的词替换为【MASK】,用别的词预测它们。
- 下一句预测 (NSP) :选两个句子,让模型判断其中一个是不是紧跟在另一个后面。
不过后者效果并不好,并且基本在一两轮训练后正确率就会接近100%。
IMDB 情感分类:BERT 微调实例
参考了网上的代码。
数据处理
正常下载下来是这样的,我们先处理成 [text,label] 的形式。(貌似这里 train 和 test 比例是1:1,正常来说会让它们变成大约9:1,不过我懒了)
import os
import random
def init(aim_set, data_dir, type):
for i in os.listdir(data_dir):
file_path = os.path.join(data_dir, i)
with open(file_path, "r", encoding="utf-8") as file:
aim_set.append([file.read(), type])
train_data = []
test_data = []
init(train_data, "aclImdb/train/pos/", 1)
init(train_data, "aclImdb/train/neg/", 0)
init(test_data, "aclImdb/test/pos/", 1)
init(test_data, "aclImdb/test/neg/", 0)
random.shuffle(train_data)
random.shuffle(test_data)
将数据转化成类 Dataset&Dataloader 的格式。
tokenizer = BertTokenizer.from_pretrained("./bert-base-uncased/", truncation=True, do_lower_case=True)
class My_Dataset(Dataset):
def __init__(self, dataframe, tokenizer, max_len):
self.tokenizer = tokenizer
self.text = [row[0] for row in dataframe]
self.targets = [row[1] for row in dataframe]
self.max_len = max_len
def __len__(self):
return len(self.text)
def __getitem__(self, index):
text = str(self.text[index])
text = " ".join(text.split())
inputs = self.tokenizer.encode_plus(
text,
add_special_tokens = True,
max_length = self.max_len,
pad_to_max_length = True,
return_token_type_ids = True
)
return {
"ids": torch.tensor(inputs["input_ids"], dtype=torch.long),
"mask": torch.tensor(inputs["attention_mask"], dtype=torch.long),
"token_type_ids": torch.tensor(inputs["token_type_ids"], dtype=torch.long),
"targets": torch.tensor(self.targets[index], dtype=torch.float)
}
train_set = My_Dataset(train_data, tokenizer, MAX_LEN)
test_set = My_Dataset(test_data, tokenizer, MAX_LEN)
train_params = {"batch_size": TRAIN_BATCH_SIZE, "shuffle": True}
test_params = {"batch_size": VALID_BATCH_SIZE, "shuffle": True}
train_loader = DataLoader(train_set, **train_params)
test_loader = DataLoader(test_set, **test_params)
模型搭建
from transformers import BertModel
class BertClass(nn.Module):
def __init__(self):
super(BertClass, self).__init__()
self.BERT = BertModel.from_pretrained("./bert-base-uncased/")
self.l1 = torch.nn.Linear(768, 768)
self.ReLU = torch.nn.ReLU()
self.l2 = torch.nn.Linear(768, 2)
def forward(self, input_ids, attention_mask, token_type_ids):
output_BERT = self.BERT(input_ids=input_ids, attention_mask=attention_mask, token_type_ids=token_type_ids)
hidden_state = output_BERT[0]
x = hidden_state[:, 0]
return self.l2(self.ReLU(self.l1(x)))
微调训练
model = BertClass()
model.to(device)
loss_function = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(params=model.parameters(), lr=LEARNING_RATE)
def calc(preds, targets):
n_correct = (preds == targets).sum().item()
return n_correct
def train(epoch, train_loader):
tr_loss = 0
n_correct = 0
nb_tr_steps = 0
nb_tr_examples = 0
model.train()
for _, data in tqdm(enumerate(train_loader, 0)):
ids = data["ids"].to(device, dtype=torch.long)
mask = data["mask"].to(device, dtype=torch.long)
token_type_ids = data["token_type_ids"].to(device, dtype=torch.long)
targets = data["targets"].to(device, dtype=torch.long)
outputs = model(ids, mask, token_type_ids)
loss = loss_function(outputs, targets)
tr_loss += loss.item()
big_val, big_idx = torch.max(outputs.data, dim=1)
n_correct += calc(big_idx, targets)
nb_tr_steps += 1
nb_tr_examples += targets.size(0)
optimizer.zero_grad()
loss.backward()
optimizer.step()
epoch_loss = tr_loss / nb_tr_steps
epoch_accu = (n_correct * 100) / nb_tr_examples
print(f"The Total Accuracy for Epoch {epoch}: {(n_correct * 100) / nb_tr_examples}")
print(f"Training Loss Epoch: {epoch_loss}")
print(f"Training Accuracy Epoch: {epoch_accu}")
train(epoch, train_loader)
torch.save(model, model_dir)
结果
用 bert-base-uncased 微调可以在测试集上达到93%的正确率;如果权重初始随机,只靠 imdb 数据训练一轮,甚至只有50%左右;本地对 bert 简单预训练(mlm)则有85%。
为什么BERT不能识别出阴阳怪气啊