使用Bert模型进行文字分类。

一、数据处理

读取数据,得到训练集和验证集,并获得对应的加载器。

1、read_file函数,读文件,得到真实标签和数据内容

def read_file(path):
    data = []#列表
    label = []
    with open(path, "r", encoding="utf-8") as f:
        for i, line in enumerate(f):#行号、行内数据
            if i == 0:#第一行数据无用
                continue
            if i > 200 and i< 11000:#只取1-200和7500-7767行数据(包含序号为0/1,表示错误或正确)
                continue
            line = line.strip("\n")#移除字符串末尾的换行符
            line = line.split(",", 1)  #把这句话,按照,分割, 1表示分割次数
            data.append(line[1])#将符号后面的部分加到line列表第二个元素(数据内容)
            label.append(line[0])#将符号前面部分加到line列表第一个元素(序号)
    print("读了%d的数据"%len(data))
    return data, label

除去文件第一行的字段名,从纯数据中读文件,文件内容太多。将标签为0//1的数据都取一部分。

并由line.split()函数根据逗号实现分割,将标签和数据分开存放。

2、数据集

class wmDataset(Dataset):
    def __init__(self, data, label):
        self.X = data
        self.Y = torch.LongTensor([int(i) for i in label])

    def __getitem__(self, item):
        return self.X[item], self.Y[item]

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

将read_file函数得到的数据和标签在实例化的时候,自动调用初始化函数,保存为实例属性,在之后训练测试时使用。

__getitem__()函数可以通过dataset[i]获得第i个文本

__len__()得到标签长度

3、加载器

def get_data_loader(path, batchsize, val_size=0.2):          #读入数据,分割数据。
    data, label = read_file(path)#数据和序号
    train_x, val_x, train_y, val_y = train_test_split(data, label, test_size=val_size, shuffle=True, stratify=label)
    train_set = wmDataset(train_x, train_y)#训练集
    val_set = wmDataset(val_x, val_y)#测试集
    train_loader = DataLoader(train_set, batchsize, shuffle=True)#训练加载器
    val_loader = DataLoader(val_set, batchsize, shuffle=True)#测试加载器
    return train_loader, val_loader

二、模型

class myBertModel(nn.Module):
    def __init__(self, bert_path, num_class, device):
        super(myBertModel, self).__init__()

        self.bert = BertModel.from_pretrained(bert_path)#加载模型和预训练好的参数
        # config = BertConfig.from_pretrained(bert_path)#仅加载模型,不加载参数
        # self.bert = BertModel(config)#加载自己的参数

        self.device = device#将设备信息保存为当前类实例化属性
        self.cls_head = nn.Linear(768, num_class)#分类头
        self.tokenizer = BertTokenizer.from_pretrained(bert_path)#加载与Bert模型相匹配的分词器

    def forward(self, text):
        input = self.tokenizer(text, return_tensors="pt", truncation=True, padding="max_length", max_length=128)
        #对输入文本进行处理,将其转化为模型要求的输入格式
        #return_tensors="pt"要求返回值为张量、truncation=True自动截断超过max_length的文本(有分类头和逗号),padding若不够则输出张量自动填充至max_length
        #分词后会化成input_ids、token_type_ids、attention_mask三个字段
        #多头自注意力机制(Multi - HeadSelf - Attention)
        #残差连接 + LayerNorm
        #前馈神经网络(Feed - ForwardNetwork)(FFN)
        #残差连接 + LayerNorm
        input_ids = input["input_ids"].to(self.device)
        token_type_ids = input['token_type_ids'].to(self.device)
        attention_mask = input['attention_mask'].to(self.device)
        sequence_out, pooler_out = self.bert(input_ids=input_ids,#序列输出(自注意力和前馈神经网络的最后一次输出)和池化输出
                        token_type_ids=token_type_ids,
                        attention_mask=attention_mask,
                        return_dict=False)      #返回元组,便于解包
        #position_ids(隐式生成),没传,bert也会自动生成
        #input_ids、token_type_ids、position_ids为batchsize*128通过词嵌入表、段嵌入表、位置嵌入表(Embedding)后相加变为16*128*768
        pred = self.cls_head(pooler_out)#16*768->16*2
        return pred

三、训练

import torch
import time
import matplotlib.pyplot as plt
import numpy as np
from tqdm import tqdm
import matplotlib
matplotlib.use('TkAgg')

def train_val(para):
########################################################
    model = para['model']
    train_loader =para['train_loader']
    val_loader = para['val_loader']
    scheduler = para['scheduler']
    optimizer = para['optimizer']
    loss = para['loss']
    epoch = para['epoch']
    device = para['device']
    save_path = para['save_path']
    max_acc = para['max_acc']
    val_epoch = para['val_epoch']

#################################################
    plt_train_loss = []
    plt_train_acc = []
    plt_val_loss = []
    plt_val_acc = []
    val_rel = []

    for i in range(epoch):
        start_time = time.time()
        model.train()
        train_loss = 0.0
        train_acc = 0.0
        val_acc = 0.0
        val_loss = 0.0
        for batch in tqdm(train_loader):#batch为train_loader中的dataset对象,一次batchsize个
            optimizer.zero_grad()#梯度清0,是防止梯度累加,毕竟是相乘,如果下一次的梯度还带有上一次会越来越大,例如每次称重前归0
            text, labels = batch[0], batch[1].to(device)
            pred = model(text)#得到预测值
            bat_loss = loss(pred, labels)
            bat_loss.backward()#反向回传
            torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)#梯度裁切,梯度太大变为1,给称重设最大限度
            optimizer.step()#更新参数
            scheduler.step()              #scheduler     调整学习率
            train_loss += bat_loss.item()    #.detach 表示去掉梯度
            train_acc += np.sum(np.argmax(pred.cpu().data.numpy(),axis=1)== labels.cpu().numpy())#得到预测的标签总值
        plt_train_loss . append(train_loss/train_loader.dataset.__len__())
        plt_train_acc.append(train_acc/train_loader.dataset.__len__())#得到准确率
        if i % val_epoch == 0:#每val_epoch验证一次
            model.eval()
            with torch.no_grad():
                for batch in tqdm(val_loader):#batch为val_loader中的dataset对象
                    val_text, val_labels = batch[0], batch[1].to(device)
                    val_pred = model(val_text)
                    val_bat_loss = loss(val_pred, val_labels)
                    val_loss += val_bat_loss.cpu().item()

                    val_acc += np.sum(np.argmax(val_pred.cpu().data.numpy(), axis=1) == val_labels.cpu().numpy())
                    val_rel.append(val_pred)

            if val_acc > max_acc:#保存最佳模型
                torch.save(model, save_path+str(epoch)+"ckpt")
                max_acc = val_acc
            plt_val_loss.append(val_loss/val_loader.dataset.__len__())
            plt_val_acc.append(val_acc/val_loader.dataset.__len__())
            print('[%03d/%03d] %2.2f sec(s) TrainAcc : %3.6f TrainLoss : %3.6f | valAcc: %3.6f valLoss: %3.6f  ' % \
                  (i, epoch, time.time()-start_time, plt_train_acc[-1], plt_train_loss[-1], plt_val_acc[-1], plt_val_loss[-1])
                  )
            if i % 50 == 0:
                torch.save(model, save_path+'-epoch:'+str(i)+ '-%.2f'%plt_val_acc[-1])
        else:
            plt_val_loss.append(plt_val_loss[-1])
            plt_val_acc.append(plt_val_acc[-1])
            print('[%03d/%03d] %2.2f sec(s) TrainAcc : %3.6f TrainLoss : %3.6f   ' % \
                  (i, epoch, time.time()-start_time, plt_train_acc[-1], plt_train_loss[-1])
                  )
    plt.plot(plt_train_loss)
    plt.plot(plt_val_loss)
    plt.title('loss')
    plt.legend(['train', 'val'])
    plt.show()

    plt.plot(plt_train_acc)
    plt.plot(plt_val_acc)
    plt.title('Accuracy')
    plt.legend(['train', 'val'])
    plt.savefig('acc.png')
    plt.show()

更多推荐