Task4-基于深度学习的文本分类3-基于Bert预训练和微调进行文本分类

因为天池这个比赛的数据集是脱敏的,无法利用其它已经预训练好的模型,所以需要针对这个数据集自己从头预训练一个模型。

我们利用Huggingface的transformer包,按照自己的需求从头开始预训练一个模型,然后将该模型应用于下游任务。

完整代码见:NLP-hands-on/天池-零基础入门NLP at main · ifwind/NLP-hands-on (github.com)

注意:利用Huggingface做预训练需要安装wandb包,如果报错可参考:[wandb.errors.UsageError: api_key not configured (no-tty). call wandb.login(key=[your_api_key\])_](https://blog.csdn.net/hhhhhhhhhhwwwwwwwwww/article/details/116124285)

预训练模型

利用Huggingface的transformer包进行预训练主要包括以下几个步骤:

  1. 用数据集训练Tokenizer;
  2. 加载数据及数据预处理;
  3. 设定预训练模型参数,初始化预训练模型;
  4. 设定训练参数,加载训练器;
  5. 训练并保存模型。

用数据集训练Tokenizer

Tokenizer是分词器,分词方式有很多种,可以按照空格直接切分、也可以在按词组划分等,可以查看HuggingFace关于tokenizers的官方文档。

Huggingface中,Tokenizer的训练方式为:

  1. 根据tokenizers.models实例化一个Tokenizer对象tokenizer
  2. tokenizers.trainers中选模型相应的训练器实例化,得到trainer
  3. tokenizers.pre_tokenizers 选定一个预训练分词器,对tokenizer的预训练分词器实例化;
  4. 利用tokenizer.train()结合trainer对语料(注意,语料为一行一句)进行训练;
  5. 利用tokenizer.save()保存tokenizer

因为天池这个比赛的数据集是脱敏的,词都是用数字进行表示,没有办法训练wordpiece等复杂形式的分词器,只能用空格分隔,在wordlevel进行分词。

因此,我们利用tokenizers.models中的WordLevel模型,对应tokenizers.trainers中的WordLevelTrainer,选择预训练分词器为Whitespace训练分词器。

另外,在训练Tokenizer时,可以利用上全部的语料(包括训练集和最终的测试集)。

完整代码如下:

import joblib
from config import *
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.model_selection import StratifiedKFold
import os
def data_preprocess():rawdata = pd.read_csv(data_file, sep='\t', encoding='UTF-8')#用正则表达式按标点替换文本import rerawdata['words']=rawdata['text'].apply(lambda x: re.sub('3750|900|648',"",x))del rawdata['text']#预测final_test_data = pd.read_csv(final_test_data_file, sep='\t', encoding='UTF-8')final_test_data['words'] = final_test_data['text'].apply(lambda x: re.sub('3750|900|648',"",x))del final_test_data['text']all_value= rawdata['words'].append(final_test_data['words'])all_value.columns=['text']all_value.to_csv('../alldata.csv',index=False)data_preprocess()from tokenizers import Tokenizer
from tokenizers.models import BPE,WordLevel
tokenizer= Tokenizer(WordLevel(unk_token="[UNK]"))from tokenizers.trainers import BpeTrainer,WordLevelTrainer
#加入一些特殊字符
trainer = WordLevelTrainer(special_tokens=["[UNK]", "[CLS]", "[SEP]", "[PAD]", "[MASK]"])#空格分词器
from tokenizers.pre_tokenizers import Whitespace
tokenizer.pre_tokenizer = Whitespace()#保存语料库文件
tokenizer.train(['../alldata.csv'], trainer)
tokenizer.mask_token='[MASK]'
tokenizer.save("../tokenizer-my-Whitespace.json")

加载数据及数据预处理

在预训练模型时,利用的是普通的、不带标签的句子,因此,在预训练模型时,同样采用全部的语料(包括训练集和最终的测试集,也就是前面的alldata.csv)。

使用Huggingface的datasets包的load_dataset函数加载数据,这里用的是csv的数据格式,关于其他格式的数据可以参考之前的一篇博客:加载数据。

输入模型之前,还需要对句子进行分词,转换成word id,此外,还需要padding长度不足的句子、获取对padding部分掩码的矩阵等等操作,这些由tokenizer进行处理,如果还需要其他预处理操作,都可以通过定义一个函数,将预处理操作封装起来,然后利用dataset.map()进行处理。

另外我们还在这里实例化了一个数据收集器data_collator,将经预处理的输入分batch再次处理后喂给模型。这是为了告诉Trainer如何从预处理的输入数据中构造batch。

from transformers import PreTrainedTokenizerFast
#注意这里用了另外一种方式加载Tokenizer
tokenizer = PreTrainedTokenizerFast(tokenizer_file="tokenizer-my-Whitespace.json")
tokenizer.mask_token='[MASK]'
tokenizer.pad_token='[PAD]'
#加载数据
from datasets import load_dataset
dataset = load_dataset('csv', data_files={'train':'alldata.csv'},cache_dir='Bert_pre_train\\') #这里的cache_dir考虑用绝对路径,不然可能会卡住
#预处理函数
def preprocess_function(examples):return tokenizer(examples['text'], truncation=True,max_length=512)
encoded_dataset = dataset.map(preprocess_function, batched=True)
#数据收集器
from transformers import DataCollatorForLanguageModeling
data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=True, mlm_probability=0.15 #mlm表示是否使用masked language model;mlm_probability表示mask的几率
)

设定预训练模型参数,初始化预训练模型

这里选择了XLNet模型作为预训练模型的基础架构,然后根据调整模型的参数,也可以选 Roberta、ALBert等模型,参考官方文档进行配置。

from transformers import RobertaConfig,AlbertConfig,XLNetConfig
# 将模型的配置参数载入
config_kwargs = {"d_model": 512,"n_head": 4,"vocab_size": tokenizer.get_vocab_size(), # 自己设置词汇大小"embedding_size":64,"bi_data":True,"n_layer":8
}
config = XLNetConfig(**config_kwargs)
# 载入预训练模型,这里其实是根据某个模型结构调整config然后创建模型
from transformers import RobertaForMaskedLM,AlbertForMaskedLM,XLNetLMHeadModelmodel = XLNetLMHeadModel(config=config)

设定训练参数,加载训练器

from transformers import Trainer, TrainingArgumentstraining_args = TrainingArguments(output_dir="./BERT",overwrite_output_dir=True,num_train_epochs=1, #训练epoch次数per_gpu_train_batch_size=12, #训练时的batchsizesave_steps=10_000, #每10000步保存一次模型save_total_limit=2,#最多保存两次模型prediction_loss_only=True,
)trainer = Trainer(model=model,args=training_args,data_collator=data_collator, #数据收集器在这里train_dataset=encoded_dataset["train"] #注意这里选择的是预处理后的数据集
)

训练并保存模型

#开始训练
trainer.train()
#保存模型
trainer.save_model("./BERT")

微调模型进行分类

主要包括以下几个步骤:

  1. 训练集划分;
  2. 数据预处理;
  3. 加载预训练模型、设置微调参数;
  4. 微调训练下游任务模型并保存。

训练集划分

去掉可能的标点符号,并把当前竞赛给的训练集划分为三个部分:训练集、验证集、测试集。其中,训练集用于训练,验证集用于调参,测试集用于评估线下和线上的模型效果。

这里首先用train_test_split(注意使用分层抽样)把训练集划分为训练集和测试集(9:1),然后再将训练集进一步划分为训练集和开发集(9:1)。

import joblib
from config import *
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.model_selection import StratifiedKFold
import os
def data_preprocess():rawdata = pd.read_csv(data_file, sep='\t', encoding='UTF-8')#用正则表达式按标点替换文本import rerawdata['words']=rawdata['text'].apply(lambda x: re.sub('3750|900|648',"",x))del rawdata['text']#数据划分#如果之前已经做了就直接加载if os.path.exists(test_index_file) and os.path.exists(train_index_file):test_index=joblib.load(test_index_file)train_index=joblib.load(train_index_file)else:rawdata.reset_index(inplace=True, drop=True)X = list(rawdata.index)y = rawdata['label']X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1,stratify=y)  # stratify=y表示分层抽样,根据不同类别的样本占比进行抽样test_index = {'X_test': X_test, 'y_test': y_test}joblib.dump(test_index, 'test_index.pkl')train_index = {'X_train': X_train, 'y_train': y_train}joblib.dump(train_index, 'train_index.pkl')train_x=rawdata.loc[train_index['X_train']]train_y=rawdata.loc[train_index['X_train']]['label'].valuesX_train, X_test, y_train, y_test = train_test_split(train_x, train_y, test_size=0.1,stratify=train_y)#训练集X_train.columns=['label', 'text']X_train.to_csv('train_data.csv',index=False)#开发集X_test.columns=['label', 'text']X_test.to_csv('dev_data.csv',index=False)#测试集test_x=rawdata.loc[test_index['X_test']]test_x.columns=['label', 'text']test_x.to_csv('test_data.csv',index=False)

数据预处理

和预训练模型时一致,加载数据集后还需要对数据进行预处理,注意上一步中加入了label这个字段,作为句子的分类标签。

Huggingface封装了模型的有效字段,如果字段名称对不上会在trainerself.get_train_dataloader()去掉无效字段,有效字段可以在debug模式下,在transformertrainer.pyself.args.remove_unused_columns中查看。

from datasets import load_dataset
dataset = load_dataset('csv', data_files={'train':'train_data.csv','dev':'dev_data.csv', 'test':'test_data.csv'},cache_dir='fine-tune\\') #这里的cache_dir考虑用绝对路径,不然可能会卡住
from transformers import PreTrainedTokenizerFast
#注意这里用了另外一种方式加载Tokenizer
tokenizer = PreTrainedTokenizerFast(tokenizer_file="tokenizer-my-Whitespace.json")
tokenizer.mask_token='[MASK]'
tokenizer.pad_token='[PAD]'def preprocess_function(examples):return tokenizer(examples['text'], truncation=True,max_length=512)
encoded_dataset = dataset.map(preprocess_function, batched=True)

加载预训练模型

model_checkpoint = "BERT" #所选择的预训练模型num_labels = 14
from transformers import XLNetForSequenceClassification, TrainingArguments, Trainer
model = XLNetForSequenceClassification.from_pretrained(model_checkpoint, num_labels=num_labels)

设置微调参数

metric_name = "acc"args = TrainingArguments(    "test-glue",    evaluation_strategy = "epoch", #每个epcoh会做一次验证评估;    save_strategy = "epoch",    learning_rate=2e-5,    per_device_train_batch_size=batch_size,    per_device_eval_batch_size=batch_size*4,    num_train_epochs=5,    weight_decay=0.01,    load_best_model_at_end=True,    metric_for_best_model=metric_name, #根据哪个评价指标选最优模型    save_steps=10_000,    save_total_limit=2,)from datasets import load_metricimport numpy as npdef compute_metrics(eval_pred):    metric = load_metric('f1')    predictions, labels = eval_pred    predictions = np.argmax(predictions, axis=1)    return metric.compute(predictions=predictions, references=labels,average='macro')trainer = Trainer(    model,    args,    train_dataset=encoded_dataset["train"],    eval_dataset=encoded_dataset["dev"],    tokenizer=tokenizer,    compute_metrics=compute_metrics)

微调训练下游任务模型并保存

trainer.train()trainer.save_model("./test-glue")

评估模型

这里先用之前训练过程中保存的test-glue/checkpoint-13500模型进行评估,看一下训练效果,F1可以达到91.39左右。

model_checkpoint = "test-glue/checkpoint-13500"#"BERT" #所选择的预训练模型num_labels = 14from transformers import XLNetForSequenceClassification, TrainingArguments, Trainermodel = XLNetForSequenceClassification.from_pretrained(model_checkpoint, num_labels=num_labels)batch_size = 12metric_name = "acc"args = TrainingArguments(    "test-glue",    evaluation_strategy = "epoch", #每个epcoh会做一次验证评估;    save_strategy = "epoch",    learning_rate=2e-5,    per_device_train_batch_size=batch_size,    per_device_eval_batch_size=batch_size*4,    num_train_epochs=5,    weight_decay=0.01,    load_best_model_at_end=True,    metric_for_best_model=metric_name, #根据哪个评价指标选最优模型    save_steps=10_000,    save_total_limit=2,)from datasets import load_metricimport numpy as npdef compute_metrics(eval_pred):    metric = load_metric('f1')    predictions, labels = eval_pred    predictions = np.argmax(predictions, axis=1)    return metric.compute(predictions=predictions, references=labels,average='macro')trainer = Trainer(    model,    args,    train_dataset=encoded_dataset["train"],    eval_dataset=encoded_dataset["dev"],    tokenizer=tokenizer,    compute_metrics=compute_metrics)trainer.evaluate()

参考资料

ALBERT — transformers 4.11.3 documentation (huggingface.co)

[BERT相关——(5)Pre-train Model | 冬于的博客 (ifwind.github.io)](https://ifwind.github.io/2021/08/22/BERT相关——(5)Pre-train Model/#重新配置模型)

BERT实战——(1)文本分类 | 冬于的博客 (ifwind.github.io)

阅读源码-理解pytorch_pretrained_bert中BertTokenizer工作方式_枪枪枪的博客-CSDN博客

NLP学习1 - 使用Huggingface Transformers框架从头训练语言模型 - 简书 (jianshu.com)

天池零基础入门NLP竞赛实战:Task4-基于深度学习的文本分类3-基于Bert预训练和微调进行文本分类相关推荐

  1. 天池零基础入门NLP竞赛实战:Task1Task2 数据读取与数据分析

    Task1&Task2 数据读取与数据分析 赛题数据是文本数据,每个新闻是不定长的,使用csv格式进行存储.因此可以直接用Pandas完成数据读取的操作. import pandas as p ...

  2. 天池零基础入门NLP - 新闻文本分类Top1方案的bert4torch复现

    天池有些长期比赛可以练习玩玩(还可以继续提交),于是试了下简单的新闻文本分类任务,Top1的解决方案思路是"预训练+fgm+交叉验证模型融合",代码是基于bert4keras的,本 ...

  3. 阿里云天池 零基础入门NLP - 新闻文本分类 2种做法,F1=0.87

    problem 1.赛题理解 数据集: 在NLP_data_list_0715.csv中,有三个链接. 分别可以下载训练集,测试集A,测试样例. f1_score介绍: F1分数(F1-score)是 ...

  4. 零基础入门NLP - 天池新闻文本分类Task3笔记

    零基础入门NLP - 天池新闻文本分类 以下以Datawhale与天池举办的新闻文本分类这个NLP赛题做的NLP入门Task2笔记 赛题链接:https://tianchi.aliyun.com/co ...

  5. 零基础入门NLP之新闻文本分类挑战赛——赛题理解

    假期还有两周左右就结束了,正巧,Datawhale联合天池发布了零基础入门NLP的学习,于是报名参加了零基础入门NLP-新闻文本分类. 本人之前刚接触NLP没多久,记录一下学习的历程,供和我一样的小白 ...

  6. 【初学者入门】零基础入门NLP - 新闻文本分类

    序言 从今天开始入门学习NLP,虽然有点晚,但是我觉得任何时候都值得开始,尤其是面对你去感兴趣的事情.今天的任务是 [零基础入门NLP - 新闻文本分类],这是天池大赛中的入门级算法比赛,入口链接请自 ...

  7. Task01——零基础入门NLP - 新闻文本分类之赛题理解

    本篇目标 首先本篇文章会对赛题进行介绍以及个人对赛题的理解,带大家接触NLP的预处理.模型构建和模型训练等知识点. 赛题介绍 赛题名称:零基础入门NLP - 新闻文本分类 赛题任务:赛题以自然语言处理 ...

  8. 零基础入门NLP - 新闻文本分类

    本文是对阿里云新人竞赛中的"零基础入门NLP - 新闻文本分类"解体过程进行的记录,目前仅使用了textCNN模型进行预测,后续还会考虑使用LSTM进行对比. 赛题数据 赛题以新闻 ...

  9. 零基础入门NLP - 新闻文本分类,正式赛第一名方案分享

    零基础入门NLP - 新闻文本分类,正式赛第一名方案分享:https://mp.weixin.qq.com/s/7WpZUqdlItBToLYuRLm44g

最新文章

  1. Linux 终端推荐 Terminator
  2. POJ 1470 Closest Common Ancestors (最近公共祖先LCA 的离线算法Tarjan)
  3. 10 Reasons Why Your Projects Should Use the Dojo
  4. 手把手教你安装鸿蒙和运行第一个Demo(js)版
  5. mysql命令行查看表的触发器_Mysql事项,视图,函数,触发器命令(详解)
  6. #我要上首页# 新版博客首页来了,做明星博主还会远吗?
  7. HDU多校4 - 6808 Go Running(最小点覆盖+网络流优化)
  8. python输入一个人的名字_怎样用c语言做到输入一个人的名字才会输出一个心?
  9. python读取多个文件夹_如何从python中的文件夹中读取多个NetCDF文件
  10. mysql官网下载下来的免安装版怎么配置_Windows下的免安装版MySQL配置
  11. 关于php车服务论文,「PHP」行车服务app后端代码简析
  12. vue 刷新嵌套路由_vue router嵌套路由在history模式下刷新无法渲染页面问题的解决方法...
  13. 同事乱用 Redis 卡爆,我真是醉了
  14. Android 开发的现状及发展前景
  15. 【Fortran】STOP语句
  16. 108个Mac电脑快捷键大全
  17. 给网站添加优质内容的25种方式
  18. 就业双方合同、三方合同、用人单位类别、编制
  19. 程序员新人面临最尴尬的事:需要工作积累经验,需要有经验才能找到工作!到底怎么办?...
  20. 【SNA】社会网络分析三 图论与图学习

热门文章

  1. 逆向工程入门:IDAwindows本地动态调试,linux远程动态调试及虚拟机配置
  2. lcd1602温度报警 c语言,【新人发帖】51单片机接DS18B20测量及LCD1602显示当前温度值...
  3. win10下双系统安装以及Ubuntu双系统启动时卡死的解决方法
  4. C# surface 屏幕键盘
  5. Ci520 13.56MHz非接触式读写器芯片(A卡,低成本)
  6. ClickHouse - LowCardinality 数据类型的神秘之旅
  7. npm 创建 package.json 文件
  8. 拨号上网“769”错误的原因之一
  9. 小白都能学会的新建数据库,无非就是这七招
  10. 十九、文章内容展示页面设计