[学习日志]使用pytorch 和 bert 实现一个简单的文本分类任务

项目简介

最近在学习pytorch和Bert,所以做了一个这样完全新手向的入门项目来练习。

由于之前在网上学习发现现存的教程比较少,所以记录一下自己的学习过程,加深印象,也希望能帮到别的学习者吧,能涨粉丝就更好了。

项目模块化之类的做的并不好,感觉机器学习项目那么小没啥分包的必要,分了一堆包只会降低代码的可读性,反而不利于新手学习(其实是懒得分)

数据集和任务介绍

数据集就是谷歌官方在bert教程中提供得一个GLUE数据集,名字叫MRPC
下载地址:https://github.com/google-research/bert
[学习日志]使用pytorch 和 bert 实现一个简单的文本分类任务_第1张图片
任务目标就是判断两句输入是否是同义句子

项目结构

[学习日志]使用pytorch 和 bert 实现一个简单的文本分类任务_第2张图片
很简单的结构,下面会尽量分模块详细说明。
如果不想看模块,最后会直接把三个文件的内容都发出来供参考。

数据预处理模块 MRPCDataset

from torch.utils.data import Dataset
from itertools import islice


class MRPCDataset(Dataset):
     def __init__(self):
        file = open('data/msr_paraphrase_train.txt', 'r', encoding="utf-8")
        data = []
        for line in islice(file, 1, None):
            content = line.split(sep='\t')
            data.append(content)

        self.data = data

     def __getitem__(self, index):
     	 #把标签从char转成int,不然做损失的时候会报错
         if self.data[index][0] == '0':
             label = 0
         else:
             label = 1
		 
		 #这里要手动拼接句子,中间加一个拼接token
         sentences = self.data[index][3] + '[SEP]' + self.data[index][4]
         return sentences, label

     def __len__(self):
         return len(self.data)

Dataset就是pytorch封装的用来加载数据的接口,将其继承后并重写__getitem__方法就可以实现一个自定义的读取器。

似乎不用dataset类也能自己实现读取器,但是感觉官方的应该会有加载速度的优化,所以还是选了官方的。

模型搭建部分

bert模型

from transformers import BertTokenizer, BertModel
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
bert_model = BertModel.from_pretrained("bert-base-uncased")
bert_model.to(device)

因为谷歌官方用的是tensorflow的模型,这里使用的是hugging face提供的接口进行的加载的(民间SOTA)。

还挺好用的,两行就把预训练的模型加载进来了。

全连接分类层

import torch

class FCModel(torch.nn.Module):
    def __init__(self):
        super(FCModel, self).__init__()
        self.fc = torch.nn.Linear(in_features=768, out_features=1)


    def forward(self, input):
        score = self.fc(input)
        result = torch.sigmoid(score)
        return result

这里是把模型封装成类并继承了Module,这样比较方便后面的换GPU、传参数等操作。其实模型很简单,就是一个全连接+sigmoid

优化方法模块

#定义优化器&损失函数
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
bert_optimizer = torch.optim.Adam(bert_model.parameters(), lr=0.00001)
crit = torch.nn.BCELoss()

这里就是定义优化器和损失函数,但是有一个巨大的坑。
全连接层和bert模型的学习率是不一样的,因为bert只需要微调,所以学习率应该很低才对。这也是为什么要把全连接层独立封装出来。

开始不懂这一点,调了一天也没收敛,后来多亏大佬指点。

训练模块

#定义训练方法
def train():
    #记录统计信息
    epoch_loss, epoch_acc = 0., 0.
    total_len = 0

    #分batch进行训练
    for i, data in enumerate(train_loader):
        bert_model.train()
        model.train()

		#获取训练数据
        sentence, label = data
        label = label.cuda()

		#数据喂给模型,并把模型拼起来
        encoding = tokenizer(sentence, return_tensors='pt', padding=True, truncation=True)
        bert_output = bert_model(**encoding.to(device))
        pooler_output = bert_output.pooler_output
        predict = model(pooler_output).squeeze()

		#计算损失和准确度
        loss = crit(predict, label.float())
        acc = binary_accuracy(predict, label) #自定义方法

        #gd
        optimizer.zero_grad() #把梯度重置为零
        bert_optimizer.zero_grad()
        loss.backward() #求导
        optimizer.step() #更新模型
        bert_optimizer.step()

        epoch_loss += loss * len(label)
        epoch_acc += acc * len(label)
        total_len += len(label)

        print("batch %d loss:%f accuracy:%f" % (i, loss, acc))

    return epoch_loss/total_len, epoch_acc/total_len

这里就都是一些常规的操作:喂数据、记录训练信息、求导、更新…
因为只是截取部分源码,所以会出现一些没声明过的变量,比如model啥的,最好配合完整代码看。

运行模块

#开始训练
Num_Epoch = 3
index = 0
for epoch in range(Num_Epoch):
    epoch_loss, epoch_acc = train()
    index += 1
    print("EPOCH %d loss:%f accuracy:%f" % (index, epoch_loss, epoch_acc))

这里也没什么好说的,就是定一下训练轮次。

完整项目应该还有验证模块,懒得写了

和训练模块大同小异

训练结果

因为在图书馆,跑代码机器会发出很大的噪音影响别的同学学习,所以就不跑一遍再截图了。(其实也是懒得跑)

我之前自己跑3轮训练,准确率能到达88%左右,提供参考。
(谷歌官方教程里也说这个数据集差不多也就跑90%,不用太较真)

完整项目代码

train.py

import torch
from torch.utils.data import DataLoader
from FCModel import FCModel
from MRPCDataset import MRPCDataset
from transformers import BertTokenizer, BertModel

#载入数据预处理模块
mrpcDataset = MRPCDataset()
train_loader = DataLoader(dataset=mrpcDataset, batch_size=32, shuffle=True)
print("数据载入完成")

#设置运行设备
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print("设备配置完成")

#加载bert模型
tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
bert_model = BertModel.from_pretrained("bert-base-uncased")
bert_model.to(device)
print("bert层模型创建完成")

#创建模型对象
model = FCModel()
model = model.to(device)
print("全连接层模型创建完成")

#定义优化器&损失函数
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
bert_optimizer = torch.optim.Adam(bert_model.parameters(), lr=0.001)
crit = torch.nn.BCELoss()

#计算准确率的公式
def binary_accuracy(predict, label):
    rounded_predict = torch.round(predict)
    correct = (rounded_predict == label).float()
    accuracy = correct.sum() / len(correct)
    return accuracy

#定义训练方法
def train():
    #记录统计信息
    epoch_loss, epoch_acc = 0., 0.
    total_len = 0

    #分batch进行训练
    for i, data in enumerate(train_loader):
        bert_model.train()
        model.train()

        sentence, label = data
        label = label.cuda()

        encoding = tokenizer(sentence, return_tensors='pt', padding=True, truncation=True)
        bert_output = bert_model(**encoding.to(device))
        pooler_output = bert_output.pooler_output
        predict = model(pooler_output).squeeze()

        loss = crit(predict, label.float())
        acc = binary_accuracy(predict, label)

        #gd
        optimizer.zero_grad() #把梯度重置为零
        bert_optimizer.zero_grad()
        loss.backward() #求导
        optimizer.step() #更新模型
        bert_optimizer.step()

        epoch_loss += loss * len(label)
        epoch_acc += acc * len(label)
        total_len += len(label)

        print("batch %d loss:%f accuracy:%f" % (i, loss, acc))

    return epoch_loss/total_len, epoch_acc/total_len

#开始训练
Num_Epoch = 3
index = 0
for epoch in range(Num_Epoch):
    epoch_loss, epoch_acc = train()
    index += 1
    print("EPOCH %d loss:%f accuracy:%f" % (index, epoch_loss, epoch_acc))



MRPCDataset.py

from torch.utils.data import Dataset
from itertools import islice


class MRPCDataset(Dataset):
     def __init__(self):
        file = open('data/msr_paraphrase_train.txt', 'r', encoding="utf-8")
        data = []
        for line in islice(file, 1, None):
            content = line.split(sep='\t')
            data.append(content)

        self.data = data

     def __getitem__(self, index):
         if self.data[index][0] == '0':
             label = 0
         else:
             label = 1

         sentences = self.data[index][3] + '[SEP]' + self.data[index][4]
         return sentences, label

     def __len__(self):
         return len(self.data)

FCModel.py

import torch

class FCModel(torch.nn.Module):
    def __init__(self):
        super(FCModel, self).__init__()
        self.fc = torch.nn.Linear(in_features=768, out_features=1)


    def forward(self, input):
        score = self.fc(input)
        result = torch.sigmoid(score)
        return result

你可能感兴趣的:(自然语言处理,pytorch)