使⽤PaddleNLP识别垃圾邮件(⼆):⽤BERT做中⽂邮件内容分类
本⽂是《使⽤PaddleNLP识别垃圾邮件》系列第⼆篇,该系列持续更新中……
系列背景介绍:系列项⽬,针对当前企业⾯临的垃圾邮件问题,尝试使⽤深度学习的⽅法,探索多语⾔垃圾邮件的内容、标题提取与
分类识别。
该系列还有⼀个姊妹篇,,欢迎感兴趣的读者点进来交流评论
系列⽬录
使⽤PaddleNLP的⽂本分类LSTM模型,提取中⽂邮件内容判断邮件是否为垃圾邮件。
使⽤PaddleNLP的BERT预训练模型,根据提取的中⽂邮件内容判断邮件是否为垃圾邮件。
介绍在Python中解析eml邮件内容的办法:email模块和mmpi库
使⽤PaddleNLP的ELECTRA预训练模型,根据提取的英⽂邮件标题判断邮件是否为垃圾邮件。
升级到最新⾃定义数据集⽅法;
使⽤PaddleNLP模型库,⼤幅简化开发流程;
使⽤PaddleNLP的RoBERTa预训练模型,根据提取的中⽂邮件标题判断邮件是否为垃圾邮件;
完成完整的批量邮件分类部署流程。
项⽬思路
在项⽬对中⽂邮件内容提取的基础上,使⽤BERT进⾏finetune,⼒争在LSTM的98.5%的基线上进⼀步提升。
关于BERT模型的详细介绍与PaddleNLP对BERT模型的应⽤,在项⽬中进⾏了详细总结,欢迎感兴趣的读者交流指
导。
本项⽬参考了陆平⽼师的项⽬,但是由于PaddleNLP版本迭代的原因,个别地⽅需要修改,在本项⽬中,进⾏了调整和
说明。
数据集介绍
是⼀个公开的垃圾邮件语料库,由国际⽂本检索会议提供,分为英⽂数据集(trec06p)和中⽂数据集(trec06c),其中
所含的邮件均来源于真实邮件保留了邮件的原有格式和内容。
除TREC2006外,还有TREC2005和TREC2007的英⽂垃圾邮件数据集(对,没有中⽂),本项⽬中,仅使⽤TREC
200提供的中⽂数据集进⾏演⽰。TREC2005-2007的垃圾邮件数据集,均已整理在项⽬挂载的数据集中,感兴趣的读者
可以⾃⾏fork。
⽂件⽬录形式:delay和full分别是⼀种垃圾邮件过滤器的过滤机制,full⽬录下,是理想的邮件分类结果,我们可以视为研
究的标签。
trec06c
│
└───data
││000
││001
││...
│└───215
└───delay
││index
└───full
││index
邮件内容样本⽰例:
负责⼈您好我是深圳⾦海实业有限公司⼴州东莞等省市有分公司我司有良好的社会关系和实⼒因每⽉进项多出项少现有⼀部分发票可优惠对外代开税率较低
增值税发票为其它国税地税运输⼴告等普通发票为的税点还可以根据数⽬⼤⼩来衡量优惠的多少希望贵公司商家等来电商谈欢迎合作本公司郑重承诺所⽤票
据可到税务局验证或抵扣欢迎来电进⼀步商谈电话⼩时服务信箱联系⼈张海南顺祝商祺深圳市⾦海实业有限公司
GG⾮常好的朋友H在计划马上的西藏⾃助游(完全靠搭车的那种),我和H也是很早认识的朋友,他有⼥朋友,在⼀起10年了,感情很好。
GG对旅游兴趣不⼤。⽽且喜欢跟着旅⾏社的那种。所以肯定不去。
我在没有认识GG前,时常独⾃去⼀些地⽅,从南到北,觉得旅⾏不应该⽬的那么强。
⼀、环境配置
本项⽬基于Paddle2.0编写,如果你的环境不是本版本,请先参考官⽹Paddle2.0。
#导⼊相关的模块
importre
importjieba
importos
importrandom
importpaddle
importpaddlenlpasppnlp
portStack,Pad,Tuple
onalasF
fromvisualdlimportLogWriter
importnumpyasnp
fromfunctoolsimportpartial#partial()函数可以⽤来固定某些参数值,并返回⼀个新的callable对象
print(paddle.__version__)
2.0.2
⼆、数据加载
2.1数据集准备
#解压数据集
!tarxvfdata/data89631/
2.2提取邮件内容,划分训练集、验证集、测试集
本项⽬中,截取中⽂邮件提取内容的最后200个字符作为⽂本分类任务的输⼊,但是这⾥需要特别注意的是,个别邮件提取
结果为null,在BERT预训练模型的finetune任务中,如果输⼊为空会产⽣报错。
因此,在⽣成训练集、验证集、测试集前,要进⾏数据清洗。
#去掉⾮中⽂字符
defclean_str(string):
string=(r"[^u4e00-u9fff]","",string)
string=(r"s{2,}","",string)
()
#从指定路径读取邮件⽂件内容信息
defget_data_in_a_file(original_path,save_path='all_'):
email=''
f=open(original_path,'r',encoding='gb2312',errors='ignore')
#lines=nes()
forlineinf:
#去掉换⾏符
line=().strip('n')
#去掉⾮中⽂字符
line=clean_str(line)
email+=line
()
#只保留末尾200个字符
returnemail[-200:]
#读取标签⽂件信息
f=open('trec06c/full/index','r')
forlineinf:
str_list=("")
#设置垃圾邮件的标签为0
ifstr_list[0]=='spam':
label='0'
#设置正常邮件标签为1
elifstr_list[0]=='ham':
label='1'
text=get_data_in_a_file('trec06c/full/'+str(str_list[1].split("n")[0]))
withopen("all_","a+")asf:
(text+'t'+label+'n')
data_list_path="./"
withopen((data_list_path,'eval_'),'w',encoding='utf-8')asf_eval:
f_(0)
f_te()
withopen((data_list_path,'train_'),'w',encoding='utf-8')asf_train:
f_(0)
f_te()
withopen((data_list_path,'test_'),'w',encoding='utf-8')asf_test:
f_(0)
f_te()
withopen((data_list_path,'all_'),'r',encoding='utf-8')asf_data:
lines=f_nes()
i=0
withopen((data_list_path,'eval_'),'a',encoding='utf-8')asf_eval,open((data_list_path,'test_'),'a',encoding='utf-8')asf
_test,open((data_list_path,'train_'),'a',encoding='utf-8')asf_train:
forlineinlines:
#提取label信息
label=('t')[-1].replace('n','')
#提取输⼊⽂本信息
words=('t')[0]
#邮件⽂本提取结果中有⼤量空格,这⾥统⼀⽤逗号替换
words=e('',',')
labs=""
#数据清洗,如果输⼊⽂本内容为空,则视为脏数据予以提出,避免在BERT模型finetune时报错
iflen(words)>0:
#划分验证集
ifi%10==1:
labs=words+'t'+label+'n'
f_(labs)
#划分测试集
elifi%10==2:
labs=words+'t'+label+'n'
f_(labs)
#划分训练集
el:
labs=words+'t'+label+'n'
f_(labs)
i+=1
el:
pass
print("数据列表⽣成完成!")
数据列表⽣成完成!
2.3⾃定义数据集
在⽰例项⽬中,BERT模型finetune的数据集为公开中⽂情感分析数据集ChnSenticorp。使⽤PaddleNLP
的._datats⽅法即可以加载该数据集。
在本项⽬中,我们需要⾃定义数据集,并使⾃定义数据集后的数据格式与使
⽤_datats(['train','dev','test'])加载后完全⼀致。
classSelfDefinedDatat(t):
def__init__(lf,data):
super(SelfDefinedDatat,lf).__init__()
=data
def__getitem__(lf,idx):
[idx]
def__len__(lf):
returnlen()
defget_labels(lf):
return["0","1"]
deftxt_to_list(file_name):
res_list=[]
forlineinopen(file_name):
res_(().split('t'))
returnres_list
trainlst=txt_to_list('train_')
devlst=txt_to_list('eval_')
testlst=txt_to_list('test_')
train_ds,dev_ds,test_ds=_datats([trainlst,devlst,testlst])
#获得标签列表
label_list=train__labels()
#获得标签列表
label_list=train__labels()
#看看数据长什么样⼦,分别打印训练集、验证集、测试集的前3条数据。
print("训练集数据:{}n".format(train_ds[0:3]))
print("验证集数据:{}n".format(dev_ds[0:3]))
print("测试集数据:{}n".format(test_ds[0:3]))
print("训练集样本个数:{}".format(len(train_ds)))
print("验证集样本个数:{}".format(len(dev_ds)))
print("测试集样本个数:{}".format(len(test_ds)))
2.4数据预处理
#调⽤kenizer进⾏数据处理,tokenizer可以把原始输⼊⽂本转化成模型model可接受的输⼊数据格式。
tokenizer=_pretrained("bert-ba-chine")
#数据预处理
defconvert_example(example,tokenizer,label_list,max_q_length=256,is_test=Fal):
ifis_test:
text=example
el:
text,label=example
#⽅法能够完成切分token,映射tokenID以及拼接特殊token
encoded_inputs=(text=text,max_q_len=max_q_length)
input_ids=encoded_inputs["input_ids"]
#注意,在早前的PaddleNLP版本中,token_type_ids叫做gment_ids
gment_ids=encoded_inputs["token_type_ids"]
ifnotis_test:
label_map={}
for(i,l)inenumerate(label_list):
label_map[l]=i
label=label_map[label]
label=([label],dtype="int64")
returninput_ids,gment_ids,label
el:
returninput_ids,gment_ids
#数据迭代器构造⽅法
defcreate_dataloader(datat,trans_fn=None,mode='train',batch_size=1,u_gpu=Fal,pad_token_id=0,batchify_fn=None):
iftrans_fn:
datat=(trans_fn,lazy=True)
ifmode=='train'andu_gpu:
sampler=butedBatchSampler(datat=datat,batch_size=batch_size,shuffle=True)
el:
shuffle=Trueifmode=='train'elFal#如果不是训练集,则不打乱顺序
sampler=ampler(datat=datat,batch_size=batch_size,shuffle=shuffle)#⽣成⼀个取样器
dataloader=ader(datat,batch_sampler=sampler,return_list=True,collate_fn=batchify_fn)
returndataloader
#使⽤partial()来固定convert_example函数的tokenizer,label_list,max_q_length,is_test等参数值
trans_fn=partial(convert_example,tokenizer=tokenizer,label_list=label_list,max_q_length=128,is_test=Fal)
batchify_fn=lambdasamples,fn=Tuple(Pad(axis=0,pad_val=_token_id),Pad(axis=0,pad_val=_token_id),Stack(dtype="int64")
):[datafordatainfn(samples)]
#训练集迭代器
train_loader=create_dataloader(train_ds,mode='train',batch_size=64,batchify_fn=batchify_fn,trans_fn=trans_fn)
#验证集迭代器
dev_loader=create_dataloader(dev_ds,mode='dev',batch_size=64,batchify_fn=batchify_fn,trans_fn=trans_fn)
#测试集迭代器
test_loader=create_dataloader(test_ds,mode='test',batch_size=64,batchify_fn=batchify_fn,trans_fn=trans_fn)
[2021-05-2416:29:11,262][INFO]-Found/home/aistudio/.paddlenlp/models/bert-ba-chine/
三、模型训练
3.1加载BERT预训练模型
#加载预训练模型Bert⽤于⽂本分类任务的Fine-tune⽹络BertForSequenceClassification,它在BERT模型后接了⼀个全连接层进⾏分类。
#由于本任务中的垃圾邮件识别是⼆分类问题,设定num_class为2
model=_pretrained("bert-ba-chine",num_class=2)
3.2开始训练
为了看到训练过程,这⾥需要引⼊VisualDL记录训练log信息。添加的⽅式如下:
fromvisualdlimportLogWriter
withLogWriter(logdir="./log")aswriter:
_scalar(tag="train/loss",step=global_step,value=loss)
_scalar(tag="train/acc",step=global_step,value=acc)
_scalar(tag="eval/loss",step=epoch,value=eval_loss)
_scalar(tag="eval/acc",step=epoch,value=eval_acc)
具体实现详见下⽅代码。
#设置训练超参数
#学习率
learning_rate=1e-5
#训练轮次
epochs=10
#学习率预热⽐率
warmup_proption=0.1
#权重衰减系数
weight_decay=0.01
num_training_steps=len(train_loader)*epochs
num_warmup_steps=int(warmup_proption*num_training_steps)
defget_lr_factor(current_step):
ifcurrent_step
returnfloat(current_step)/float(max(1,num_warmup_steps))
el:
returnmax(0.0,
float(num_training_steps-current_step)/
float(max(1,num_training_steps-num_warmup_steps)))
#学习率调度器
lr_scheduler=Decay(learning_rate,lr_lambda=lambdacurrent_step:get_lr_factor(current_step))
#优化器
optimizer=(
learning_rate=lr_scheduler,
parameters=ters(),
weight_decay=weight_decay,
apply_decay_param_fun=lambdax:xin[
rn,_parameters()
ifnotany(ndinnforndin["bias","norm"])
])
#损失函数
criterion=ntropyLoss()
#评估函数
metric=cy()
#评估函数,设置返回值,便于VisualDL记录
defevaluate(model,criterion,metric,data_loader):
()
()
loss=[]
forbatchindata_loader:
input_ids,gment_ids,labels=batch
logits=model(input_ids,gment_ids)
loss=criterion(logits,labels)
(())
correct=e(logits,labels)
(correct)
accu=late()
print("evalloss:%.5f,accu:%.5f"%((loss),accu))
()
()
(loss),accu
#开始训练
global_step=0
withLogWriter(logdir="./log")aswriter:
forepochinrange(1,epochs+1):
forstep,batchinenumerate(train_loader,start=1):#从训练数据迭代器中取数据
input_ids,gment_ids,labels=batch
logits=model(input_ids,gment_ids)
loss=criterion(logits,labels)#计算损失
probs=x(logits,axis=1)
correct=e(probs,labels)
(correct)
acc=late()
global_step+=1
ifglobal_step%50==0:
print("globalstep%d,epoch:%d,batch:%d,loss:%.5f,acc:%.5f"%(global_step,epoch,step,loss,acc))
#记录训练过程
_scalar(tag="train/loss",step=global_step,value=loss)
_scalar(tag="train/acc",step=global_step,value=acc)
rd()
()
lr_()
_gradients()
eval_loss,eval_acc=evaluate(model,criterion,metric,dev_loader)
#记录评估过程
_scalar(tag="eval/loss",step=epoch,value=eval_loss)
_scalar(tag="eval/acc",step=epoch,value=eval_acc)
可以看到,使⽤BERT预训练模型进⾏finetune,在第2个epoch后验证集准确率已经达到99.4%以上,在第3个epoch就能
达到99.6%以上。
四、预测效果
完成上⾯的模型训练之后,可以得到⼀个能够通过中⽂邮件内容识别是否为垃圾邮件的模型。接下来查看模型在测试集上的泛化能⼒。
defpredict(model,data,tokenizer,label_map,batch_size=1):
examples=[]
fortextindata:
input_ids,gment_ids=convert_example(text,tokenizer,label_list=label_(),max_q_length=128,is_test=True)
((input_ids,gment_ids))
batchify_fn=lambdasamples,fn=Tuple(Pad(axis=0,pad_val=_token_id),Pad(axis=0,pad_val=_token_id)):fn(samples)
batches=[]
one_batch=[]
forexampleinexamples:
one_(example)
iflen(one_batch)==batch_size:
(one_batch)
one_batch=[]
ifone_batch:
(one_batch)
results=[]
()
forbatchinbatches:
input_ids,gment_ids=batchify_fn(batch)
input_ids=_tensor(input_ids)
gment_ids=_tensor(gment_ids)
logits=model(input_ids,gment_ids)
probs=x(logits,axis=1)
idx=(probs,axis=1).numpy()
idx=()
labels=[label_map[i]foriinidx]
(labels)
returnresults
data=['您好我公司有多余的发票可以向外代开,国税,地税,运输,⼴告,海关缴款书如果贵公司,⼚,有需要请来电洽谈,咨询联系电话,罗先⽣谢谢顺祝商祺']
label_map={0:'垃圾邮件',1:'正常邮件'}
predictions=predict(model,data,tokenizer,label_map,batch_size=32)
foridx,textinenumerate(data):
(labels)
returnresults
data=['您好我公司有多余的发票可以向外代开,国税,地税,运输,⼴告,海关缴款书如果贵公司,⼚,有需要请来电洽谈,咨询联系电话,罗先⽣谢谢顺祝商祺']
label_map={0:'垃圾邮件',1:'正常邮件'}
predictions=predict(model,data,tokenizer,label_map,batch_size=32)
foridx,textinenumerate(data):
print('预测内容:{}n邮件标签:{}'.format(text,predictions[idx]))
预测内容:您好我公司有多余的发票可以向外代开,国税,地税,运输,⼴告,海关缴款书如果贵公司,⼚,有需要请来电洽谈,咨询联系电话,罗先⽣谢谢顺祝商祺
邮件标签:垃圾邮件
预测效果良好,⼀个验证集准确率⾼达99.6%以上、基于BERT的中⽂邮件内容分类顺利出炉啦!
参考资料
by
⼩结
1.再次强调,平台上优质资源特别多,要善⽤搜索多交流~
任务中,数据清洗这⼀步骤特别重要。考虑到当前NLP任务和⽣产结合还是⽐较紧密,⽽⽣产环境的数据集往往脏数据特别多,通
过规则等进⾏处理,也可能出现数据异常。如果模型不能正常训练,第⼀步就是去排查数据格式!
3.个别⽂件中⽂邮件内容提取结果为空,在实际处理时,这些可以直接归⼊垃圾邮件再进⾏⼈⼯复核,当然,如果这些⽂件⽐较多,就要进
⼀步分析数据提取过程是否有问题了。
本文发布于:2022-12-31 15:31:26,感谢您对本站的认可!
本文链接:http://www.wtabcd.cn/fanwen/fan/90/66304.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |