分享

Kaggle从零到实践:Bert中文多项选择

 520jefferson 2021-07-14

机器阅读理解最大的挑战就是回答需要外部先验知识的问题,文本将使用Bert模型来完成C3(中文多项选择题)。

C3数据集一共有13369篇文章和19577个问题,其中的60%用是训练集,20%是开发集,20%是测试集。

图片

步骤1:查看数据样例

C3数据集的案例如下,模型需要对对话和问题进行理解,最后从待选选项中识别出正确的答案。

对话:
男:你今天晚上有时间吗?我们一起去看电影吧?
女:你喜欢恐怖片和爱情片,但是我喜欢喜剧片,科幻片一般。

问题:女的最喜欢哪种电影?
可选择项:['恐怖片''爱情片''喜剧片''科幻片']
正确答案:喜剧片

步骤2:定义数据读取格式

接下来完成具体的数据读取格式转换,首先读取Bert Tokenizer。

import torch
from transformers import BertTokenizer
tokenizer = BertTokenizer.from_pretrained('bert-base-chinese', num_choices=4)

然后定义具体的batch数据处理,需要将问题和待选项进行处理。

def collate_fn(data): #将文章问题选项拼在一起后,得到分词后的数字id,输出的size是(batch, n_choices, max_len)
    input_ids, attention_mask, token_type_ids = [], [], []
    for x in data:
        text = tokenizer(x[1], text_pair=x[0], padding='max_length', truncation=True, 
                         max_length=128, return_tensors='pt')
        input_ids.append(text['input_ids'].tolist())
        attention_mask.append(text['attention_mask'].tolist())
        token_type_ids.append(text['token_type_ids'].tolist())
    input_ids = torch.tensor(input_ids)
    attention_mask = torch.tensor(attention_mask)
    token_type_ids = torch.tensor(token_type_ids)
    label = torch.tensor([x[-1] for x in data])
    return input_ids, attention_mask, token_type_ids, label

最后定义Dataset,需要将多选项转为单个选择和问题的匹配过程。

import torch
from torch.utils.data import Dataset, DataLoader, TensorDataset

class TextDataset(Dataset):
    def __init__(self, data, labels):
        self.data = data
        self.labels = labels
        
    def __getitem__(self, idx):
        label = self.labels[idx]
        question = self.data[idx][1][0]['question']
        content = '。'.join(self.data[idx][0])
        choice = self.data[idx][1][0]['choice']
        if len(choice) < 4: #如果选项不满四个,就补“不知道”
            for i in range(4-len(choice)):
                choice.append('不知道')
        
        content = [content for i in range(len(choice))]
        pair = [question + ' ' + i for i in choice]
        
        return content, pair, label
    
    def __len__(self):
        return len(self.labels)

train_dataset = TextDataset(train, train_label)
test_dataset = TextDataset(val, val_label)

步骤3:定义Bert模型

这里可以直接使用BertForMultipleChoice来完成Finetune过程。

import torch
from transformers import BertForMultipleChoice, AdamW, get_linear_schedule_with_warmup
model = BertForMultipleChoice.from_pretrained('bert-base-chinese')

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model.to(device)

步骤4:模型训练与验证

当定义好数据集、模型后,接下来是万年不变的模型正向传播和反向传播代码。

from tqdm import tqdm

def train():
    model.train()
    total_train_loss = 0
    iter_num = 0
    total_iter = len(train_loader)
    for idx, (input_ids, attention_mask, token_type_ids, labels) in enumerate(train_loader):
        optim.zero_grad()
        
        input_ids = input_ids.to(device)
        attention_mask = attention_mask.to(device)
        labels = labels.to(device)
        outputs = model(input_ids, attention_mask=attention_mask, labels=labels)

        loss = outputs.loss
        
        if idx % 20 == 0:
            with torch.no_grad():
                print((outputs[1].argmax(1).data == labels.data).float().mean().item(), loss.item())
        
        total_train_loss += loss.item()
        loss.backward()
        torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
        optim.step()
        scheduler.step()

        iter_num += 1
        if(iter_num % 100 ==0):
            print('epoth: %d, iter_num: %d, loss: %.4f, %.2f%%' % (epoch, iter_num, loss.item(), iter_num/total_iter*100))
        
    print('Epoch: %d, Average training loss: %.4f'%(epoch, total_train_loss/len(train_loader)))
    
def validation():
    model.eval()
    total_eval_accuracy = 0
    total_eval_loss = 0
    for (input_ids, attention_mask, token_type_ids, labels) in test_dataloader:
        with torch.no_grad():
            input_ids = input_ids.to(device)
            attention_mask = attention_mask.to(device)
            labels = labels.to(device)
            outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
        loss = outputs.loss
        logits = outputs[1]

        total_eval_loss += loss.item()
        logits = logits.detach().cpu().numpy()
        label_ids = labels.to('cpu').numpy()
        total_eval_accuracy += (outputs[1].argmax(1).data == labels.data).float().mean().item()
        
    avg_val_accuracy = total_eval_accuracy / len(test_dataloader)
    print('Accuracy: %.4f' % (avg_val_accuracy))
    print('Average testing loss: %.4f'%(total_eval_loss/len(test_dataloader)))
    print('-------------------------------')

for epoch in range(4):
    print('------------Epoch: %d ----------------' % epoch)
    validation()
    train()

通过上述代码运行完成,可以在C3数据集上取得60%+的精度,妈妈再也不用担心我不会多项选择了!

AINLP
AINLP
一个有趣有AI的自然语言处理公众号:关注AI、NLP、机器学习、推荐系统、计算广告等相关技术。公众号可直接对话双语聊天机器人,尝试自动对联、作诗机、藏头诗生成器,调戏夸夸机器人、彩虹屁生成器,使用中英翻译,查询相似词,测试NLP相关工具包。
331篇原创内容
公众号

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多