BERT中⽂任务实战(⽂本分类、预测下⼀句)踩坑记录
⽂章⽬录
⼀、概述
最近参与了两个项⽬开发,主要内容是基于NLP和深度学习的⽂本处理任务。其中有两个⼦模块⽤到⽂本分类和预测下⼀句模型,刚好前段时间⾃⼰折腾学习了⼀点BERT,就打算实践⼀下,记录下遇到的问题和解决⽅案。
⼆、Bert简介
2.1 简要说明
关于BERT的介绍,已经有铺天盖地的⽂章和博客或深或浅的介绍了,这⾥暂时先不展开详细介绍,后⾯会写⼀篇论⽂翻译和理解的博客,这⾥暂时简要带过,这⾥我们只简单说明下为什么我们选择在⾃⼰数据集上fine-tuning,然后⽤于预测。
2.2 fine-tune原理
在BERT论⽂中,作者说明了BERT的fine-tune原理。
BERT模型⾸先会对input进⾏编码,转为模型需要的编码格式,使⽤辅助标记符[CLS]和[SEP]来表⽰句⼦的开始和分隔。然后根据输⼊得到对应的embedding,这⾥的embedding是三种embedding的和,分别是token、segment、position级别。
得到整体的embedding后即使⽤相关模型进⾏学习,最终根据不同任务的分类层得到结果。
图a表⽰句⼦对的分类任务,例如预测下⼀句、语义相似度等任务,输⼊是两个句⼦A和B,中间⽤[SEP]分隔,最终得到的class label就表⽰是否下⼀句或者是否是语义相似的。
图b表⽰单句分类任务,如常见的⽂本分类、情感分析等。输⼊就是⼀个单独的句⼦,最终的class label就是表⽰句⼦属于哪⼀类,或者属于什么情感。
图c表⽰问答任务,主要⽤于SQuAD数据集,输⼊是⼀个问题和问题对应的段落,⽤[SEP]分隔,这⾥输出的结果就不是某个class label⽽是答案在给定段落的开始和终⽌位置,主要⽤于阅读理解任务。
图d表⽰单个句⼦标注任务,例如常见的命名实体识别任务,输⼊就是⼀个单独的句⼦,输出是句⼦中每个token对应的类别标注。
这⾥我主要是使⽤了前两个任务的⽅法,分别fine-tune了预测下⼀句模型和单句分类模型。
三、在项⽬数据集上fine-tune教程
因为之前主要使⽤的框架是Pytorch,因此fine-tune的代码也主要参考了。
先做的是⽂本分类任务,主要参考了,可以看到,整个脚本代码有接近1000⾏,但是其实在我们的fine-tune过程中,需要理解的关键部分主要是DataProcessor类。我们的fine-tune说的直⽩⼀点,就是
把我们⾃⼰的数据整理好,转换成BERT模型能够读取的输⼊,只要模型读到了inputs,后续的各种内部转换表⽰其实已经不需要我们关注了(但是最好还是要理解,学习最⽜的模型的原理和思路)。
python怎么读取json文件3.1整体流程
⾸先简要介绍下fine-tune的整体流程,如下图所⽰:
我们⾸先需要先进⾏⼀些预处理,就是把训练集、验证集、测试集标签化。接着会调⽤我们⾃定义的继承DataProcessor类的MyPro类,这⼀步就是实现将我们的训练数据,转换成模型能够获取的标准输⼊格式。这⾥是转换成论⽂定义的⼀种InputExamples格式,相关代码:
class InputExample(object):
"""A single training/test example for simple sequence classification."""
def__init__(self, guid, text_a, text_b=None, label=None):
"""⽣成⼀个InputExample.
Args:
guid: 每个example的独有id.
text_a: 字符串,也就是输⼊的未分割的句⼦A,对于单句分类任务来说,text_a是必须有的
text_b: 可选的输⼊字符串,单句分类任务不需要这⼀项,在预测下⼀句或者阅读理解任务中需要输⼊text_b,
text_a和text_b中间使⽤[SEP]分隔
label: 也是可选的字符串,就是对应的⽂本或句⼦的标签,在训练和验证的时候需要指定,但是在测试的时候可以不选
"""
self.guid = guid
<_a = text_a
<_b = text_b
self.label = label
接着会调⽤convert_examples_to_features()将所有的InputExamples转为⼀种train_features格式,相关代码:
"""A single set of features of data.
Args:
input_ids:  token的id,在chinese模式中就是每个分词的id,对应⼀个word vector,就是之前提到的混合embedding
input_mask:  真实字符对应1,补全字符对应0,在padding的时候可能会补0,需要记录⼀下真实的输⼊字符,模型的attention机制只关注这些字符
segment_ids:  句⼦标识符,第⼀句全为0,第⼆句全为1,主要是⽤于区分单句任务或者是句⼦对任务,
但其实我们通过使⽤[SEP]已经起到了区分作⽤,这⾥主要还是为了便于模型识别计算
label_id: 将Label_list转化为相应的id表⽰,这⾥的Label_list可以是数字,可以是字符串,这⾥统⼀转换成label_id便于后续加载到tensor中
"""
def__init__(self, input_ids, input_mask, segment_ids, label_id):
self.input_ids = input_ids
self.input_mask = input_mask
self.segment_ids = segment_ids
self.label_id = label_id
后⾯的就是pytorch中的相关操作,先将train_features转为TensorDataset格式,再使⽤DataLoader将TensorDataset送到模型中进⾏训练。
3.2 ⾃定义DataProcessor
接着重点讲⼀下fine-tune任务最重要的DataProcessor类和我们怎么继承DataProcessor类。
原始的DataProcessor类定义了获取训练集、验证集、测试集对应的examples⽅法,获取label的⽅法,⾄于在此基础之上的其他⽅法我们都可以⾃⼰再定义
class DataProcessor(object):
"""Base class for data converters for sequence classification data sets."""
def get_train_examples(self, data_dir):
"""Gets a collection of `InputExample`s for the train set."""
raise NotImplementedError()
def get_dev_examples(self, data_dir):
"""Gets a collection of `InputExample`s for the dev set."""
raise NotImplementedError()
def get_test_examples(self, data_dir):
"""Gets a collection of `InputExample`s for the test set."""
raise NotImplementedError()
def get_labels(self):
"""Gets the list of labels for this data set."""
raise NotImplementedError()
#  这⾥的_read_json并不是DataProcessor类中必须包含的,是我⾃⼰定义的读取json⽂件内容转为字典,再添加到list中⽤于转化为InputExample格式的辅助函数
@classmethod
def_read_json(cls, input_file, quotechar=None):
"""Reads a tab separated value file."""
dicts =[]
with codecs.open(input_file,'r','utf-8')as infs:
for inf in infs:
inf = inf.strip()
dicts.append(json.loads(inf))
return dicts
⽽我们需要继承重写的MyPro类,则必须要实现DataProcessor类中定义的⼏个⽅法,⾄于如何实现,以何种类型存储、读取,这些都是我们可以⾃⼰⾃定义的。我这⾥是使⽤json格式来存储获取的。相关代码如下:
"""⾃定义数据读取⽅法,针对json⽂件
data_dir是我们的训练集、验证集、测试集存放的⽂件路径,因为之前是在Linux环境后来改到Windows环境,增加了⼀个路径转化过程
_create_examples函数的作⽤是读取上⾯的_read_json函数返回的⽂件内容,这⾥的json⽂件格式为{'content':content, 'label':label},其中content表⽰⽂本内容,也就是每个句⼦,label表⽰句⼦对应的标签。然后依次转化成模型需要的InputExample格式
Returns:
examples: 数据集,包含index、中⽂⽂本、类别三个部分
"""
def get_train_examples(self, data_dir):
return self._create_examples(
self._read_json(os.path.join(data_dir,"train.json").replace('\\','/')),'train')
def get_dev_examples(self, data_dir):
return self._create_examples(
self._read_json(os.path.join(data_dir,"val.json").replace('\\','/')),'dev')
def get_test_examples(self, data_dir):
return self._create_examples(
self._read_json(os.path.join(data_dir,"test.json").replace('\\','/')),'test')
def get_labels(self):
return[0,1]
def_create_examples(self, dicts, set_type):
examples =[]
for(i, infor)in enumerate(dicts):
guid ="%s-%s"%(set_type, i)
text_a = infor['content']
label = infor['label']
examples.append(InputExample(guid=guid, text_a=text_a, label=label))
return examples
再次说明,我们fine-tune任务,其实就是把我们的项⽬数据,转换成BERT模型可以读取的输⼊格式。我们的关键任务就是在上⾯的⾃定义Processor中,⾃定义⼀些⽂件读取、转换函数,把我们的⽂本数据,输⼊到模型中,进⾏微调。输⼊⽂件类型可以是csv,可以是json,也可以是txt等,只要我们在继承的Processor类中定义好我们的读取和转换函数,把⽂件中的⽂本数据,转成InputExamples格式,其实我们的微调任务已经完成了⼀⼤半。⾄于在上⾯数据处理过程中涉及到的其他代码,例如padding,convert_example_to_features这些函数,这⾥就不⼀⼀展开细讲,后续如果有时间可以再写⼀个详细的源码解析博客。
3.3 参数设置
接下来,在我们已经处理好数据,能够将数据送到模型中去,我们就要准备开始fine-tune and train。huggingface的代码中,关于train 部分的特别长⼀段,我也做了⼀些简要的注释说明,但是限于篇幅这⾥就不全贴了,重点需要说明⼀下相关的参数设置。
main函数⼀开始就是⼀⼤段的参数设置,我们需要重点关注的主要包括以下⼏个参数:
data_dir: 输⼊数据的⽂件⽬录,⾥⾯应该包含train val test三个⽂件分别⽤于训练、验证、测试
bert_model: 所使⽤的bert预训练模型,这⾥我们⼀般⽤到的是bert-base-chinese
task_name:训练任务的名称,其实就是⽤来获取我们为每个任务⾃定义的Processor类
model_save_pth: 训练完的模型参数的保存地址
max_seq_length:字符串的最⼤长度,越长需要越多的计算量,⼀般设置64或128
do_train/do_eval:是否训练或验证
train_batch_size:训练时的batch⼤⼩,⼀般设置为64
learning_rate:学习率,论⽂推荐了⼏个,5e-5, 3e-5, 2e-5
no_cuda:是否使⽤GPU加速,如果设置为False,双重否定表肯定,表⽰使⽤GPU训练模型,这⾥就会埋下⼀个坑
local_rank:这个参数可以不改,默认设置为-1,但是主要也涉及到⼀个坑

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。