基于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来批量加载数据,以供模型训练。这样可以更高效地管理和处理大规模的训练数据集。
代码分析完毕。本文章对于深度学习项目初学者来讲会很有帮助,但要深入理解项目、尝试自己改变模型等还需要自己对项目进行更详细的分析和理解。
若文章有误或需要改进的可以私信我或者评论区指出,谢谢!
如果觉得本文章有用的话,可以点赞/关注/收藏支持一下(#^ ∨ ^#) ,谢谢!