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不能识别出阴阳怪气啊

posted @ 2024-01-25 23:40  xcyle  阅读(126)  评论(1编辑  收藏  举报