基于BERT的中文文本分类:将Bert预训练模型进行微调以应用于中文文本分类

基于BERT的中文文本分类:将Bert预训练模型进行微调以应用于中文文本分类

本文章是作者对基于BERT的中文文本分类的开源项目进行多次的运行测试、一步步调试过程中写下的分析文档,主要是针对该深度学习项目的四个关键文件(或四个模块)进行的分析和总结。
如有疏漏请在评论区指出,谢谢!

项目地址:https://github.com/649453932/Bert-Chinese-Text-Classification-Pytorch
预训练模型下载地址
bert_Chinese: 模型 https://s3.amazonaws.com/models.huggingface.co/bert/bert-base-chinese.tar.gz
词表 https://s3.amazonaws.com/models.huggingface.co/bert/bert-base-chinese-vocab.txt
你能学到什么:掌握实际深度学习项目的数据处理、训练、评估测试等的完整流程;理解微调以及如何对一个预训练模型进行微调。

中文新闻文本分类任务:

  • 文本长度在20到30之间。一共10个类别,每类2万条。数据以字为单位输入模型。

  • 类别:财经、房产、股票、教育、科技、社会、时政、体育、游戏、娱乐。

  • 数据集:训练集:验证集:测试集:18:1:1 万

  • 训练机器:单GPU:2080Ti , 训练时间:30分钟。

1.模型文件:bert.py

注意:这不是什么BERT原始模型,而是将bert预训练模型作为嵌入层,然后后面搭配基本的全连接层 的 一个简单网络

  • from pytorch_pretrained_bert import BertModel, BertTokenizer。即 模型架构类 BertModel 和 输入文本预处理类 BertTokenizer 都是bert官方包里面已经建好的,可以直接使用。

  • bert模型文件里面主要定义了两个类:模型类Model 和 配置类 Config

  • class Model:自己基于bert建的主体模型:在bert.py 里的 Model类,包含 init 和 forward 两个函数。 由于只是简单基于bert做中文文本分类应用,所以模型只有两层,一个就是直接用bert的 预训练嵌入层 的嵌入层(bert, 用于得到输入文本嵌入),以及一个全连接层(fc,用于作用于bert嵌入向量改变输出维度 为预测类别总数),这两个都首先在init方法里面进行初始化:

    • init 初始化方法:模型基本架构搭建的地方

    • belf.bert = BertModel.from_pretrained(config.bert_path) # 这个BertModel很关键,内部会自动去创建bert模型

      • self.fc = nn.Linear(config.hidden_size, config.num_classes)
      def __init__(self, config):
          super(Model, self).__init__()
          # layer1:一个就是直接用bert的 预训练嵌入层 的嵌入层(bert, 用于得到输入文本嵌入)
          self.bert = BertModel.from_pretrained(config.bert_path)  # 这个BertModel很关键,内部会自动去创建bert模型
          for param in self.bert.parameters():
              param.requires_grad = True
          # layer2: 一个全连接层(fc,用于作用于bert嵌入向量改变输出维度 为预测类别总数)
        self.fc = nn.Linear(config.hidden_size, config.num_classes)
      
    • forward 前向传播方法:模型从数据输入到输出过程中的基本流程架构

      def forward(self, x):
          context = x[0]  # 输入的句子
          mask = x[2]     # 对padding部分进行mask,和句子一个size,padding部分用0表示,如:[1, 1, 1, 1, 0, 0]
          # 提取 输入的嵌入向量
          _, pooled = self.bert(context, attention_mask=mask, output_all_encoded_layers=False)
          # 对 嵌入向量 通过一个全连接层,输出为 长度为类别总数num_classes 的类别分布向量
          out = self.fc(pooled)
        return out
      
  • class Config:模型的 数据、超参数 配置

    def __init__(self, dataset):
        self.model_name = 'bert'							 # 模型名称
        self.train_path = dataset + '/data/train.txt'        # 训练集
        self.dev_path = dataset + '/data/dev.txt'            # 验证集
        self.test_path = dataset + '/data/test.txt'          # 测试集
        self.class_list = [x.strip() for x in open(
            dataset + '/data/class.txt').readlines()]        # 类别名单
        self.save_path = dataset+'/saved_dict/' + self.model_name + '.ckpt'           # 模型训练结果     
        self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')     # 设备  有cuda用cuda训练,否则用cpu训练
    
        self.require_improvement = 1000   # 若超过1000batch效果还没提升,则提前结束训练
        self.num_classes = len(self.class_list)      # 类别数
        self.num_epochs = 3                          # epoch数
        self.batch_size = 128                        # mini-batch大小
        self.pad_size = 32                           # 每句话处理成的长度(短填长切)
        self.learning_rate = 5e-5                    # 学习率
        self.bert_path = './bert_pretrain'
        self.tokenizer = BertTokenizer.from_pretrained(self.bert_path)
      self.hidden_size = 768
    
  • bert.py 将在 执行train 或 main 或 run 或 其他的 入口函数时,建立对应的config 和 模型对象,建立对象时 需要传入 init需要的参数。具体是 先通过传入dataset参数创建 config对象,然后通过传入这个config对象 创建 具体的模型对象 (这个项目就是 run.py里面 )

2.模型入口文件:run.py

  • 运行方式例如:python run.py --model bert (指定模型为bert,会进行 训练 和 测试,也可以先配置模型参数再点击运行或调试)

  • 在run.py 的 入口main函数中:

    • 首先会定义好数据集

      • dataset = ‘THUCNews’
    • 然后提取 运行指令中指定的模型(如果你的项目目录只有一个模型,那就不指定,这里应该是直接指定的 你自己定义的模型)

      • model_name = args.model # bert
      • x = import_module(‘models.’ + model_name) # 找到具体的 模型文件,比如bert.py,或者bert_CNN.py
    • 根据数据集 和 名称,建立相关模型类的设置Config对象:(有的模型不是这样设置config的)

      • config = x.Config(dataset) # 获取模型文件的 模型设置config,包含了数据集路径、batch_size、epoch超参数 等
    • 设置随机种子,保证 每次使用同样的设置,训练测试结果不变

      • np.random.seed(1)
        torch.manual_seed(1)
        torch.cuda.manual_seed_all(1)
        torch.backends.cudnn.deterministic = True  # 保证每次结果一样
        
    • 开始 加载数据,由于config里面提供了数据路径,所以可以通过config获取数据:

      # 读取数据的时候,首先全部通过utils工具类里面的 build_dataset 方法 从给定路径的数据集中加载数据 为 dataset
      train_data, dev_data, test_data = build_dataset(config)  # 断点1:读取数据,处理数据
      # 然后通过config里面的 batch_size, device 转换成 iterator形式(dataloader?)
      train_iter = build_iterator(train_data, config)
      dev_iter = build_iterator(dev_data, config)
      test_iter = build_iterator(test_data, config)
      
    • 根据 config 构建出模型模型 并 开始训练

      • model = x.Model(config).to(config.device) # 断点2:构建模型
      • train(config, model, train_iter, dev_iter, test_iter) # 断点3:训练模型
    • 在 run.py的main函数中,先根据对应的配置参数创建出模型,然后再使用参数和数据集对 创建出的基本模型 进行训练。 这需要 调用 train_eval.py 文件 的 train方法

3.模型训练评估文件:train_eval.py

  • 模型训练评估脚本文件 包含了 很多方法:核心就是 train方法 和 evaluate方法
    • def init_network(model, method=‘xavier’, exclude=‘embedding’, seed=123) # 权重初始化,默认为xavier的初始化权重,给 模型里面的 权重参数weight 和 偏置参数bias 进行初始化。

    • def train(config, model, train_iter, dev_iter, test_iter) # 训练模型,总共训练num_epochs次,每次epoch 都反向传播更新参数 len(train_iter) / batch_size 次(个batch),如果验证集loss超过1000个batch 没下降,结束所有训练。 在训练完全结束后,会使用当前训练好的模型做测试,即调用test 方法,输出模型在 测试集上的效果。 在计算模型在验证集上的效果时,需要调用这个文件里面的 评估函数 evaluate。

    • def test(config, model, test_iter) # 测试模型,训练完毕后会调用测试函数,在测试集上测试模型结果,包括在测试集下的 损失loss、精确率acc(accuracy指的是正确预测的样本数占总预测样本数的比值,它不考虑预测的样本是正例还是负例)、准确率Precision、召回率Recall、F1值F1-Score;这里的测试必须需要传入之前模型训练中用到的config,实际上是为了后面 模型的数据输出 到 实际的类别输出 的映射,把模型的预测分布 转化为 实际的 类别标签。 在计算模型在测试集上的效果时,需要调用这个文件里面的 评估函数 evaluate。

    • def evaluate(config, model, data_iter, test=False) # 模型评估方法,唯一的模型评估方法,在用验证集和测试集计算模型的评估结果时,都是调用的这个方法在计算。 如果是验证集,则只会计算acc 和 loss,如果是测试集,还会额外计算 Precision, Recall and F1-Score,以及 真实类别结果 和 预测类别结果 的交叉矩阵 confusion 。 这里采用的损失计算函数,就是 多分类里面常用的 交叉熵损失函数,直接调用的 torch.nn.functional.cross_entropy(outputs, labels) 方法

      损失函数:直接调用的 torch.nn.functional.cross_entropy(outputs, labels) 损失计算方法。

4.方法工具文件:utils.py

  • 方法工具脚本文件 包含了 很多 数据处理方法,以及一些其他的辅助方法。核心的是将原始数据集构造成更方便后续读取数据的 dataset形式数据的 build_dataset方法(dataset实现,其实也可以用DataSet实现的形式) 以及 将 创建好的dataset转化为 iterator 形式的 DatasetIterater(Dataloader类实现,然后用 )。 DataSet类 和 Dataloader类 实现 只能处理一份数据,由于这里dataset方法中是一次性处理三份数据,即一次性构造完训练集、验证集、测试集 的 dataset,所以这里用的是dataset方法,它在run.py的main函数中会执行一次:train_data, dev_data, test_data = build_dataset(config)。 构造完dataset后,再通过 DatasetIterater类 分别构造出 训练集、验证集、测试集 的 iterator形式数据,从而方便后续模型训练。 此处为了统一,脚本文件中也创建了 基于dataset构造对应的iterator形式数据的build_iterator方法,在方法内部只需要 返回 基于DatasetIterater类创建的iterator对象即可。

  • def build_dataset(config) # 将原始数据集构造成更方便后续读取数据的 dataset形式数据。一次性处理三份数据,即一次性构造完训练集、验证集、测试集 的 dataset,处理完毕后将通过 build_iterator方法 再分别构造 三份数据 分别对应的 iterator形式的数据。 它在run.py的main函数中会一次调用创建三种数据:train_data, dev_data, test_data = build_dataset(config)

  • class DatasetIterater(object) # 是一种Dataloader类,提供了将 dataset形式的数据转化为dataloader形式数据 的 各种所需方法,从而使得,通过传入dataset参数来创建DatasetIterater类对象,就可以创建出 该dataset对应的 iterator数据。

  • build_iterator(dataset, config) # 将dataset数据 构造成 模型训练需要 的 dataloader形式数据。 由于是通过DatasetIterater创建数据,所以 一次只能处理一份数据。所以它在run.py的main函数中会使用三次(分别基于 训练集、验证集、测试集 的dataset数据 构造 出对应的 iterator数据):

    train_iter = build_iterator(train_dataset, config)
    dev_iter = build_iterator(dev_dataset, config)
    test_iter = build_iterator(test_dataset, config)
    
  • def get_time_dif(start_time) # 计算某一段代码执行完毕 所花费的时间。是一种计算时间的辅助函数

  • Dataset

    • Dataset是一个抽象类,用于表示数据集。在PyTorch和TensorFlow等深度学习框架中,通常需要将原始数据转换为Dataset对象,以便后续进行数据处理和加载。
    • Dataset类通常需要实现两个方法:len()方法返回数据集的大小,getitem(index)方法用于根据索引获取数据样本
    • 通过自定义Dataset类,可以根据具体需求对数据进行预处理、增强等操作,然后传递给DataLoader进行加载
  • DataLoader

    • DataLoader是用于批量加载数据的工具,它可以从Dataset中按照设定的batch size和shuffle方式加载数据。
    • DataLoader会自动对数据集进行批量划分,并提供多线程加载等功能,以加快训练过程
    • 通过DataLoader加载的数据可以直接用于模型的训练、验证和测试
  • 在实际应用中,通常会先将原始数据处理成Dataset对象,然后通过DataLoader来批量加载数据,以供模型训练。这样可以更高效地管理和处理大规模的训练数据集。

代码分析完毕。本文章对于深度学习项目初学者来讲会很有帮助,但要深入理解项目、尝试自己改变模型等还需要自己对项目进行更详细的分析和理解。

若文章有误或需要改进的可以私信我或者评论区指出,谢谢!

如果觉得本文章有用的话,可以点赞/关注/收藏支持一下(#^ ∨ ^#) ,谢谢!

  • 51
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值