2021大数据与计算智能大赛:客服通话文本摘要提取(paddle 版) baseline

一、赛题背景

客服中心每天都需要接通大量的客户来电,客户来电需要进行语音转文本,同时对文本进行概括,提取客户核心诉求,但是人工总结会增加客服工作量,降低工作效率,因此期望使用AI算法进行自动的文本摘要生成。

1.1 赛题任务

对客户通话数据进行摘要提取,因属于特定领域的通话数据,所以同宽泛性文本摘要提取存在一定的异同,主要的难点如下:

  1. 语音通话通过第三方服务转写为文本内容,存在一定的转写错误;
  2. 文本长度不固定,长短不一,可能存在文本长度过长的现象;
  3. 因为是具体领域的客服通话文本,专业词汇可能较多。

1.2 赛题数据简介

数据来源于客服中心通话文本数据库,首先是对通话进行录音,然后使用第三方服务进行语音转文本,文本常规情况下主要用于数据分析,用于支撑客服中心的各种客服人员指标评定和关键字分析。

1.2.1 赛题数据说明

文件划分为训练集和评测集

训练集

  • 文件名:train.csv
  • 文件格式:UTF-8
  • 文件内字段分隔符:|
  • 文件内字段:
  • Id:索引,评测用
  • content:通话文本
  • abstract:摘要

评测集

  • 文件名:evaluation_public.csv
  • 文件格式:UTF-8
  • 文件内字段分隔符:|
  • 文件内字段:
  • id:索引,评测用
  • content:通话文本

1.2.2 提交示例

  • 文件命名:result.csv
  • 文件内数据分隔符:|
  • 文件编码:UTF-8
  • 文件内数据格式:每一行为一条数据,每一行包含两部分,分别为:索引、预测的摘要,索引和预测的摘要使用‘|’进行分割,为防止中文列名导致某些异常,csv文件中两者使用英文id、ret表示

其中,id 列表示结果id,使用评测集数据中的id,ret列表示标注结果,对应预测的摘要
注意:必须包含id和ret

提交的csv样例如下(仅列举两行):
id|ret
100|预测的摘要1
101|预测的摘要2

1.2.3 评测标准

本任务采用ROUGE(Recall-Oriented Understudy for Gisting Evaluation)评价指标。
ROUGE指标将自动生成的摘要与参考摘要进行比较,其中ROUGE-1衡量unigram匹配情况,ROUGE-2衡量bigram匹配情况,ROUGE-L记录最长公共子序列,三者都只采用f-score。且总分的计算方式为:



0.2∗f-score (R1)+0.4∗f-score (R2)+0.4∗f-score (RL)

1.2.4 相关赛题地址

二、项目介绍

2.1 项目意义

文本摘要的目标是自动地将输入文本转换成简短摘要,为用户提供简明扼要的内容描述,是缓解文本信息过载的一个重要手段。 文本摘要也是自然语言生成领域中的一个重要任务,有很多应用场景,如新闻摘要、论文摘要、财报摘要、传记摘要、专利摘要、对话摘要、评论摘要、观点摘要、电影摘要、文章标题生成、商品名生成、自动报告生成、搜索结果预览等。

对于文本摘要任务,大致可以分为两种方法,抽取型文本摘要和抽象型文本摘要。

  • 抽取型摘要:这种方法依赖于从文本中提取几个部分,例如短语、句子,把它们组合起来创建摘要。因此,这种抽取型的方法最重要的是识别出适合总结文本的句子。
  • 生成式摘要:这种方法应用先进的NLP技术生成一篇全新的总结。可能总结中的文本甚至没有在原文中出现。

本项目主要采用生成式摘要的方式,借助飞桨的平台,通过对PEGASUS模型进行微调,实现了快速实现、 开箱即用,并基于FasterGeneration进行推理加速,能够提供更高性能的推理体验。

2.2 模型介绍

PEGASUS: Pre-training with Extracted Gap-sentences for Abstractive Summarization 是Google在2020年ICML会议上提出的工作。它针对文本摘要任务设计了无监督预训练任务(Gap Sentence Generation,简称GSG),即随机遮盖文档中的几个完整句子,让模型生成被遮盖的句子。该预训练任务能够很好地与实际地文本摘要任务匹配,从而使得预训练后的模型经过简单的微调后达到较好的摘要生成效果。

语音通话 java 语音通话费流量吗?_paddle

本项目是基于预训练语言模型PEGASUS,具有以下优势:

  • 效果领先。在LCSTS上效果达到SOTA。
  • 开箱即用。本项目提供TaskFlow接口,无需训练,仅需几行代码便可预测。
  • 高性能推理。本项目基于FasterGeneration进行推理加速,能够提供更高性能的推理体验

语音通话 java 语音通话费流量吗?_语音通话 java_02

2.3 具体方案流程

  • 数据分析: 通过进行词频分析,挖掘高频的无意义词句
  • 数据处理:读取训练集、验证集、进行相关的数据处理
  • 模型构建:设计基于PEGASUS的文本摘要模型;
  • 训练配置:实例化模型,配置超参,指定模型迭代的优化算法;
  • 模型训练和评估:执行多轮训练不断调整参数,以达到较好的效果;
  • 模型推理:选取一条文本数据,通过模型推理出生成结果。
  • 生成验证结果:基于训练好的模型,生成对应的验证结果。

2.4 相关运行环境

  • paddle版本 :2.4.2
  • 运行内存:32G
  • CPU:4 Cores
  • GPU:Tesla V100
  • python版本:3.7.4

三、详细方案实现

3.1 数据分析

3.1.1 安装相关库

# 安装paddlenlp 相关框架,且需先更新pip组件
!pip install --upgrade pip
!pip install rouge==1.0.1 -i https://pypi.tuna.tsinghua.edu.cn/simple
!pip install paddlenlp==2.4.2 -i https://pypi.tuna.tsinghua.edu.cn/simple
!pip install pypinyin


from IPython.display  import clear_output
clear_output()
print("环境安装成功!请重启内核!!")
环境安装成功!请重启内核!!

3.1.2 数据读取

#解压数据文件
!unzip  -d /home/aistudio/data/data111659/ /home/aistudio/data/data111659/train_dataset.zip
Archive:  /home/aistudio/data/data111659/train_dataset.zip
  inflating: /home/aistudio/data/data111659/train_dataset.csv
#计算最大的内容和摘要长度
def max_text_len(self):
    for i, col in enumerate(self):
        data[col] = data[col].astype(str)
        n_max = data[col].str.len().max()
        print(i, col, n_max)
import pandas as pd
import sklearn
#读取训练文件数据
txt_path='/home/aistudio/data/data111659/train_dataset.csv'

#读取训练数据文件
data=pd.read_csv(txt_path,encoding='utf-8',sep='|')

#打印查看数据样例
data.head(5)



id

content

abstract

0

0

【坐席】您好,实习话务员为您,【客户】服务,喂,你好,是这样的,我那个我的电话因为上次突然把...

用户来电反映手机号卡收费问题,用户称现在怎么欠费167.65,我方经查询用户有10月欠费76...

1

1

【客户】嗯,【坐席】,很高兴为您服务,【客户】喂,你好,你好,我想问一下,我没有去过这个吉林...

用户来电反映查询漫游地,显示有吉林,用户称没有去过附近,要求核实回复,请处理谢谢

2

2

【客户】嗯,【坐席】,您好,实习话务员为您服务,【客户】喂你好,然后我这某某人卡,然后有有两...

用户来电想要取消20元语音特惠包(200分钟另得100条短信)-立即生效,我方取消不成功,用...

3

3

【坐席】很高兴为您服务,您好?【客户】,唉,您好,那个,我这手机号上个月就是因为我每个月固定...

用户来电反映自己手机号码在7月份24.10元 8月份元 11月份85.90元 12月份300...

4

4

【坐席】您好,高兴为您服务,【客户】,唉,你好那个,我家里这个***不太好,【坐席】信号不好...

非常感谢您向我们提供这些宝贵信息,我们已经对您反应的问题进行记录反馈,同时我公司也会持续优化...

#查看最大的内容和文本长度
print(max_text_len(data.columns.tolist()))
0 id 5
1 content 24413
2 abstract 1002
None
#构造文件读取函数
def read_file(filename,num=2):
    lines = []
    with open(filename, 'r', encoding='utf-8') as f:
        next(f)
        if num==3:
            for line in f:
                lines.append({"id":line.split("|")[0].strip(),"content":line.split("|")[1].strip(),"abstract":line.split("|")[2].strip()})
        else:
            for line in f:
                lines.append({"id":line.split("|")[0].strip(),"content":line.split("|")[1].strip()})
    return lines

3.1.3 进行分词,统计词频

from paddlenlp import Taskflow
import time
from collections import defaultdict
#也可使用pos_tagging 模型
seg = Taskflow("word_segmentation", mode="accurate",batch_size=12)
texts = []
data_list=read_file(txt_path)
for f in data_list:
    texts.append(f['content'])
inputs_length = len(texts)
print("1. 句子数量:", inputs_length)

tic_seg = time.time()
# 快速分词
results = seg(texts)

time_diff = time.time() - tic_seg

print("2. 平均速率:%.2f句/s" % (inputs_length/time_diff))

# 词频统计
word_counts = defaultdict(int)
for result in results:
    for word in result:
        word_counts[word] += 1
[2022-12-13 17:54:21,399] [    INFO] - Downloading model_state.pdparams from https://bj.bcebos.com/paddlenlp/taskflow/knowledge_mining/wordtag_v1.3/model_state.pdparams
100%|██████████| 367M/367M [00:32<00:00, 11.8MB/s] 
[2022-12-13 17:54:55,035] [    INFO] - Downloading model_config.json from https://bj.bcebos.com/paddlenlp/taskflow/knowledge_mining/wordtag_v1.1/model_config.json
100%|██████████| 482/482 [00:00<00:00, 413kB/s]
[2022-12-13 17:54:55,164] [    INFO] - Downloading tags.txt from https://bj.bcebos.com/paddlenlp/taskflow/knowledge_mining/wordtag_v1.1/tags.txt
100%|██████████| 7.58k/7.58k [00:00<00:00, 8.40MB/s]
[2022-12-13 17:54:55,230] [    INFO] - Downloading https://bj.bcebos.com/paddlenlp/models/transformers/ernie_ctm/vocab.txt and saved to /home/aistudio/.paddlenlp/models/wordtag
[2022-12-13 17:54:55,232] [    INFO] - Downloading vocab.txt from https://bj.bcebos.com/paddlenlp/models/transformers/ernie_ctm/vocab.txt
100%|██████████| 89.6k/89.6k [00:00<00:00, 2.20MB/s]
[2022-12-13 17:54:55,350] [    INFO] - tokenizer config file saved in /home/aistudio/.paddlenlp/models/wordtag/tokenizer_config.json
[2022-12-13 17:54:55,352] [    INFO] - Special tokens file saved in /home/aistudio/.paddlenlp/models/wordtag/special_tokens_map.json
W1213 17:54:55.363752   491 gpu_resources.cc:61] Please NOTE: device: 0, GPU Compute Capability: 8.0, Driver API Version: 11.2, Runtime API Version: 11.2
W1213 17:54:55.366914   491 gpu_resources.cc:91] device: 0, cuDNN Version: 8.2.
[2022-12-13 17:54:58,292] [    INFO] - Converting to the inference model cost a little time.
[2022-12-13 17:55:04,400] [    INFO] - The inference model save in the path:/home/aistudio/.paddlenlp/taskflow/wordtag/static/inference


1. 句子数量: 25001
2. 平均速率:19.40句/s
# 打印频次最高的前50个单词及其对应词频
print("Top 50 Words:", sorted(word_counts.items(), key=lambda d: d[1], reverse=True)[:50])
Top 50 Words: [(',', 2538402), ('【', 868249), ('】', 868137), ('我', 793268), ('的', 717031), ('您', 666798), ('坐席', 442013), ('客户', 427701), ('是', 354173), ('这个', 344600), ('了', 315022), ('嗯', 294778), ('?', 276631), ('给', 233054), ('那', 212989), ('就', 171918), ('那个', 169982), ('你', 166162), ('一下', 153766), ('就是', 153271), ('说', 139643), ('这', 136162), ('话', 133832), ('啊', 123162), ('这边', 121750), ('对', 114874), ('吧', 113451), ('有', 108419), ('然后', 105620), ('现在', 99305), ('吗', 98499), ('一个', 86232), ('流量', 83464), ('没有', 83105), ('呢', 81665), ('帮', 81323), ('*', 80929), ('它', 77532), ('都', 75983), ('可以', 69983), ('在', 67155), ('他', 66466), ('好', 65793), ('先生', 65328), ('问题', 65300), ('你们', 64302), ('用', 57593), ('也', 57580), ('看', 56245), ('电话', 53484)]

通过上述的词频分析,可以看到存在大量的无意义字符,如:【】、? ,以及无意义的助词:坐席、客户、您好、你好
这类词语可进行去除,用以提高信息的密度
通过查看样例数据,发现如:很高兴为您服务、实习话务员为您服务 这样的短句是无意义的,也可一并去掉

#去掉无意义助词:您好、你好、【坐席】、【客户】、这个、那个、然后、一个、喂、很
#去掉无意义短句:实习话务员、为您服务、高兴
data['content']=data['content'].str.replace('【坐席】','')\
.str.replace('【客户】','').str.replace('您好,','')\
.str.replace('你好,','').str.replace('喂','').str.replace('嗯,','')\
.str.replace('这个','').str.replace('那个','').str.replace('然后,','')\
.str.replace('一个','').str.replace('喂','').str.replace('?','')\
.str.replace('很','').str.replace('为您服务,','').str.replace('高兴','').str.replace('实习话务员','')
#查看去掉后的数据情况
data.head(5)



id

content

abstract

0

0

为您,服务,,是这样的,我我的电话因为上次突然把给我停机了,我不知道啥意思,然后我就好长时间...

用户来电反映手机号卡收费问题,用户称现在怎么欠费167.65,我方经查询用户有10月欠费76...

1

1

,,我想问一下,我没有去过吉林松原市,为什么我的以前吧,数据查询码里头会有松,原是码,请问您...

用户来电反映查询漫游地,显示有吉林,用户称没有去过附近,要求核实回复,请处理谢谢

2

2

,然后我这某某人卡,然后有有两个增值业务,我想取消一下,您稍等,我帮您看一下是什么,就是语音...

用户来电想要取消20元语音特惠包(200分钟另得100条短信)-立即生效,我方取消不成功,用...

3

3

您好,唉,,我这手机号上个月就是因为我每个月固定的,应该39套餐是吧,上个月我看扣了300,...

用户来电反映自己手机号码在7月份24.10元 8月份元 11月份85.90元 12月份300...

4

4

,唉,你好,我家里***不太好,信号不好是吗宽带和座机是绑在一起的吗它是您是在这地方信号不好...

非常感谢您向我们提供这些宝贵信息,我们已经对您反应的问题进行记录反馈,同时我公司也会持续优化...

# 查看去掉后的内容和摘要长度
print(max_text_len(data.columns.tolist()))
0 id 5
1 content 20845
2 abstract 1002
None

3.2 数据处理

# 构造训练集和测试集
#通过文本切分的方式,按8:2 的方式来随机切分
df=sklearn.utils.shuffle(data) #随机打乱
train_data = df.sample(frac=0.8, random_state=0, axis=0)
dev_data = df[~df.index.isin(train_data.index)]

# #将训练数据写入到文件中
train_data.to_csv('train_data.csv',  index=False,encoding='utf-8',sep ='|',header =['id','content','abstract'])
# #将测试数据写入文件中
dev_data.to_csv('dev_data.csv',  index=False,encoding='utf-8',sep ='|',header =['id','content','abstract'])

3.3 数据读取

from paddlenlp.datasets import load_dataset

def read(data_path):
    with open(data_path, 'r', encoding='utf-8') as f:
        # 跳过列名
        next(f)
        for line in f:
            content, abstract = line.strip('\t').split("|")[1].strip(),line.strip('\t').split("|")[2].strip()
            content = line.strip('\t').split("|")[1].strip()
            abstract = line.strip('\t').split("|")[2].strip()
            
            yield {'content': content, 'abstract': abstract}

# data_path为read()方法的参数
train_dataset  = load_dataset(read, data_path='train_data.csv',lazy=False, split="train")
dev_dataset  = load_dataset(read, data_path='dev_data.csv',lazy=False, split="dev")
#查看训练集和测试集的划分情况
print(len(train_dataset))
print(len(dev_dataset))
20001
5000

3.4 模型加载

from paddlenlp.transformers import PegasusForConditionalGeneration, PegasusChineseTokenizer
from paddlenlp.transformers import LinearDecayWithWarmup
from paddle.io import BatchSampler, DistributedBatchSampler, DataLoader
from paddlenlp.data import DataCollatorForSeq2Seq

#创建Tokenizer,用于分词,将token映射成id。
1# 初始化分词器
tokenizer = PegasusChineseTokenizer.from_pretrained('IDEA-CCNL/Randeng-Pegasus-238M-Summary-Chinese')
# 初始化模型,'IDEA-CCNL/Randeng-Pegasus-238M-Summary-Chinese/'IDEA-CCNL/Randeng-Pegasus-538M-Summary-Chinese
model = PegasusForConditionalGeneration.from_pretrained('IDEA-CCNL/Randeng-Pegasus-238M-Summary-Chinese')
# 组装 Batch 数据 & Padding
batchify_fn = DataCollatorForSeq2Seq(tokenizer=tokenizer, model=model)
[2022-12-14 15:29:51,500] [    INFO] - Already cached /home/aistudio/.paddlenlp/models/IDEA-CCNL/Randeng-Pegasus-238M-Summary-Chinese/vocab.txt
[2022-12-14 15:29:51,503] [    INFO] - Already cached /home/aistudio/.paddlenlp/models/IDEA-CCNL/Randeng-Pegasus-238M-Summary-Chinese/added_tokens.json
[2022-12-14 15:29:51,505] [    INFO] - Already cached /home/aistudio/.paddlenlp/models/IDEA-CCNL/Randeng-Pegasus-238M-Summary-Chinese/special_tokens_map.json
[2022-12-14 15:29:51,507] [    INFO] - Already cached /home/aistudio/.paddlenlp/models/IDEA-CCNL/Randeng-Pegasus-238M-Summary-Chinese/tokenizer_config.json
[2022-12-14 15:29:51,565] [    INFO] - Already cached /home/aistudio/.paddlenlp/models/IDEA-CCNL/Randeng-Pegasus-238M-Summary-Chinese/model_state.pdparams
[2022-12-14 15:29:51,568] [    INFO] - Already cached /home/aistudio/.paddlenlp/models/IDEA-CCNL/Randeng-Pegasus-238M-Summary-Chinese/model_config.json

3.5 数据格式转换

3.5.1 加载相关的库

#安装需要的相关库
import os
import json
import argparse
import random
import time
import paddle
import paddlenlp
import distutils.util
from pprint import pprint
from functools import partial
from tqdm import tqdm
import numpy as np
import math
from datasets import load_dataset
import contextlib
from rouge import Rouge
from visualdl import LogWriter


import pandas as pd 
from paddlenlp.datasets import MapDataset
import paddle.nn as nn
from paddlenlp.utils.log import logger
from paddlenlp.metrics import BLEU
from paddlenlp.data import Tuple, Pad

3.5.2 定义转换器

#定义convert_example,将content和title文本映射成int类型的id,同时构造labels。
def convert_example(example, text_column, summary_column, tokenizer,
                    max_source_length, max_target_length):
    """
    构造模型的输入.
    """
    inputs = example[text_column]
    targets = example[summary_column]
    # 分词
    model_inputs = tokenizer(inputs,
                             max_length=max_source_length,
                             padding=False,
                             truncation=True,
                             return_attention_mask=True)
    labels = tokenizer(targets,
                       max_length=max_target_length,
                       padding=False,
                       truncation=True)
    # 得到labels,后续通过DataCollatorForSeq2Seq进行移位
    model_inputs["labels"] = labels["input_ids"]
    return model_inputs
#由于预训练模型限制,这里把文本最大长度设置为512
# 文本的最大长度
max_source_length = 512
# 摘要的最大长度
max_target_length = 64
# 摘要的最小长度
min_target_length = 0
#使用partial函数指定默认参数,使用map函数转换数据。map函数把原来的文本根据词汇表的编号转换成了相应的id,为了便于理解,这里把训练集合的部分样本展示出来。
# 定义转换器
trans_func = partial(convert_example,
                     text_column='content',
                     summary_column='abstract',
                     tokenizer=tokenizer,
                     max_source_length=max_source_length,
                     max_target_length=max_target_length)
                     
# train_dataset和dev_dataset分别转换
train_dataset = train_dataset.map(trans_func,
                                  batched=False,
                                  lazy =False)

dev_dataset = dev_dataset.map(trans_func,
                              batched=False,
                            lazy =False)
# 输出训练集的前 2 条样本
# for idx, example in enumerate(train_dataset):
#     if idx < 2:
#         print(example)

3.5.3 构造Dataloader

train_data_loader  = paddle.io.DataLoader(
    dataset=train_dataset,
    batch_size=8,
    return_list=True,
    collate_fn=batchify_fn)
dev_batch_sampler  = BatchSampler(
    dataset=dev_dataset,
    batch_size=8,
    shuffle=False)




# 构造测试Dataloader
dev_data_loader = DataLoader(dataset=dev_dataset,
                             batch_sampler=dev_batch_sampler,
                             num_workers=0,
                             collate_fn=batchify_fn,
                             return_list=True)

3.6 模型训练配置

# 学习率预热比例
warmup = 0.02
# 学习率
learning_rate = 5e-5
# 训练轮次
num_epochs =30
# 训练总步数
num_training_steps = len(train_data_loader) * num_epochs
# AdamW优化器参数epsilon
adam_epsilon = 1e-6
# AdamW优化器参数weight_decay
weight_decay=0.01
# 训练中,每100个log_steps打印一次日志
log_steps = 100
# 训练中,每隔eval_steps进行一次模型评估
eval_steps = 1000

# 训练模型保存路径
output_dir = 'checkpoints'
# 解码beam size
num_beams = 4

log_writer = LogWriter('visualdl_log_dir')
lr_scheduler = LinearDecayWithWarmup(learning_rate, num_training_steps, warmup)

# LayerNorm参数不参与weight_decay
decay_params = [
    p.name for n, p in model.named_parameters()
    if not any(nd in n for nd in ["bias", "norm"])
]

# 优化器AdamW
optimizer = paddle.optimizer.AdamW(
    learning_rate=lr_scheduler,
    beta1=0.9,
    beta2=0.999,
    epsilon=adam_epsilon,
    parameters=model.parameters(),
    weight_decay=weight_decay,
    apply_decay_param_fun=lambda x: x in decay_params)

3.7 构造模型评估函数

# 计算训练评估参数Rouge-1,Rouge-2,Rouge-L
def compute_metrics(preds, targets):
    assert len(preds) == len(targets), (
        'The length of pred_responses should be equal to the length of '
        'target_responses. But received {} and {}.'.format(
            len(preds), len(targets)))
    rouge = Rouge()
    
    scores = []
    for pred, target in zip(preds, targets):
        try:
            score = rouge.get_scores(' '.join(pred), ' '.join(target))
            scores.append([
                score[0]['rouge-1']['f'], score[0]['rouge-2']['f'],
                score[0]['rouge-l']['f']
            ])
        except ValueError:
            scores.append([0, 0, 0])
    rouge1 = np.mean([i[0] for i in scores])
    rouge2 = np.mean([i[1] for i in scores])
    rougel = np.mean([i[2] for i in scores])
    
    print('\n' + '*' * 15)
    print('The auto evaluation result is:')
    print('rouge-1:', round(rouge1*100, 2))
    print('rouge-2:', round(rouge2*100, 2))
    print('rouge-L:', round(rougel*100, 2))
   
    return rouge1, rouge2, rougel
# 模型评估函数
@paddle.no_grad()
def evaluate(model, data_loader, tokenizer, min_target_length,
             max_target_length):
    model.eval()
    all_preds = []
    all_labels = []
    model = model._layers if isinstance(model, paddle.DataParallel) else model
    for batch in tqdm(data_loader, total=len(data_loader), desc="Eval step"):
        labels = batch.pop('labels').numpy()
        # 模型生成
        preds = model.generate(input_ids=batch['input_ids'],
                               attention_mask=batch['attention_mask'],
                               min_length=min_target_length,
                               max_length=max_target_length,
                               diversity_rate='beam_search',
                               num_beams=num_beams,
                               use_cache=True)[0]
        # tokenizer将id转为string
        all_preds.extend(
            tokenizer.batch_decode(preds.numpy(),
                                   skip_special_tokens=True,
                                   clean_up_tokenization_spaces=False))
        labels = np.where(labels != -100, labels, tokenizer.pad_token_id)
        all_labels.extend(
            tokenizer.batch_decode(labels,
                                   skip_special_tokens=True,
                                   clean_up_tokenization_spaces=False))
    rouge1, rouge2, rougel= compute_metrics(all_preds, all_labels)
    model.train()
    return rouge1, rouge2, rougel

3.8 模型训练

3.8.1 定义训练函数

def train(model, train_data_loader):
    global_step = 0
    best_rougel = 0
    tic_train = time.time()
    for epoch in range(num_epochs):
        for step, batch in enumerate(train_data_loader):
            global_step += 1
            # 模型前向训练,计算loss
            lm_logits, _, loss = model(**batch)
            loss.backward()
            optimizer.step()
            lr_scheduler.step()
            optimizer.clear_grad()
            if global_step % log_steps == 0:
                logger.info(
                    "global step %d/%d, epoch: %d, batch: %d, rank_id: %s, loss: %f, lr: %.10f, speed: %.4f step/s"
                    % (global_step, num_training_steps, epoch, step,
                        paddle.distributed.get_rank(), loss, optimizer.get_lr(),
                        log_steps / (time.time() - tic_train)))
                log_writer.add_scalar("train_loss", loss.numpy(), global_step)
                tic_train = time.time()
            if global_step % eval_steps== 0 or global_step == num_training_steps:
                tic_eval = time.time()
                rouge1, rouge2, rougel = evaluate(model, dev_data_loader, tokenizer,
                            min_target_length, max_target_length)
                logger.info("eval done total : %s s" % (time.time() - tic_eval))
                log_writer.add_scalar("eval_rouge1", rouge1, global_step)
                log_writer.add_scalar("eval_rouge2", rouge2, global_step)
                log_writer.add_scalar("eval_rougel", rougel, global_step)
                if best_rougel < rougel:
                    best_rougel = rougel
                    if paddle.distributed.get_rank() == 0:
                        if not os.path.exists(output_dir):
                            os.makedirs(output_dir)
                        # Need better way to get inner model of DataParallel
                        model_to_save = model._layers if isinstance(
                            model, paddle.DataParallel) else model
                        model_to_save.save_pretrained(output_dir)
                        tokenizer.save_pretrained(output_dir)

3.8.2 进行训练

# 调用模型训练
train(model, train_data_loader)

语音通话 java 语音通话费流量吗?_语音通话 java_03

3.8.3 可视化训练过程

语音通话 java 语音通话费流量吗?_语音通话 java_04


语音通话 java 语音通话费流量吗?_初级_05


语音通话 java 语音通话费流量吗?_bc_06


语音通话 java 语音通话费流量吗?_数据_07

3.9 模型推理

3.9.1 构建推理函数

# 模型推理,针对单条文本,生成摘要
def infer(text, model, tokenizer):
    tokenized = tokenizer(text, 
                          truncation=True, 
                          max_length=max_source_length, 
                          return_tensors='pd')
    preds, _ = model.generate(input_ids=tokenized['input_ids'],
                              max_length=max_target_length,
                              min_length=min_target_length,
                              decode_strategy='beam_search',
                              num_beams=num_beams)
    #print(tokenizer.decode(preds[0], skip_special_tokens=True, clean_up_tokenization_spaces=False))
    return(tokenizer.decode(preds[0], skip_special_tokens=True, clean_up_tokenization_spaces=False))

3.9.2 模型加载

# 加载训练好的模型
model = PegasusForConditionalGeneration.from_pretrained('checkpoints')
model.eval()
tokenizer = PegasusChineseTokenizer.from_pretrained('checkpoints')

3.9.3 解压测评集文件

#解压数据文件
!unzip  -d /home/aistudio/data/data111659/ /home/aistudio/data/data111659/test_dataset.zip
Archive:  /home/aistudio/data/data111659/test_dataset.zip
  inflating: /home/aistudio/data/data111659/test_dataset.csv

3.9.4 查看测评集文件情况

validation_file='/home/aistudio/data/data111659/test_dataset.csv'
validation_data=pd.read_csv(validation_file,encoding='utf-8',sep='|',names=['id','content'])
text=validation_data.iat[1,1]
print(text)
【坐席】您好,实习话务员为您服务,【客户】,唉,你好那个,我刚收到咱们那个话费通知的短信了,我看了一下说我上个月消费了二十一块多吧,然后有五元是我的包月费,然后剩下有16块多的那个其他的消费,我这个没有进行过任何的,就是上网啊,通话的,我想查查那个什么费用啊,【坐席】嗯,稍等我看一下1月啊,二十一块8,上网流量费,15块9毛3,国内通话费0.15元,到达电话打了一分钟流量的话,应该是超出了一部分,用了15块9毛3的流量费,【客户】对,我这个肯定没有用流量,因为我这个是退休在家,我家里有WIFI,我也不出去玩,一星之前我刚激活,根本连买菜都不用,我出去就是这个,这个流量是怎么出来的钱啊,你们这肯定有积分问题,【坐席】用了五十五十九兆流量,然后扣了19块15块9毛7的费用【客户】59兆流量六块钱,【坐席】电话那您对流量超出不认可的话可以给您进行上报反馈,后期会有专人给您回电处理,【客户】嗯,他最后给我回电,怎么处理呀?我要求这返就是返费,因为我肯定没消费这个,比如说电话电话有过吗?你使过我电话吗?电话我也没打过呀,我家里人也没用,我电话打过,我们每个人都有手续啊,【坐席】现在这边无法给您进行核实,只能看到您使用的超出,嗯,您是没有使用的话,我这边给您记录上报,后期会有专人给您【客户】你你给我上报余额,实际话费,反正就这15块几吧,我肯定没花点钱,我肯定要交的,但是这个我我绝对没用过,因为我就没出过门激活,【坐席】好,那我这边给您记录上报,后期会有专人给您回复,保持电话畅通啊,【客户】嗯,他什么时候联系我呀?还是直接就给我发短信,【坐席】嗯,24小时,你回复电话,【客户】噢噢,行好了行,谢谢唉,【坐席】嗯,那感谢您来电,稍后按两个一,谢谢【客户】嗯

3.9.5 进行推理

#推理
infer(text, model, tokenizer)
23374

r(text, model, tokenizer)

23374


## 3.10 生成测评集


```python
#读取测评集文件
validation_lines=read_file(validation_file)
#生成测评集数据
result_data=pd.DataFrame()
for line in validation_lines:
    validation_id=line['id']
    content=line['content']
    ret=infer(content,model, tokenizer)
    result={"id":validation_id,"ret":ret}
    result_data=result_data.append(result,ignore_index=True)
    print("数据生成进行中,当前进行ID:",validation_id)


print("数据生成已完成")
result_data.to_csv('result.csv',  index=False,encoding='utf-8',sep ='|',header =['id','ret'])

四、项目总结

4.1 提交结果

语音通话 java 语音通话费流量吗?_bc_08

4.2 优化思路

这个项目还有很多值得优化的地方:

  1. 从训练配置上,提高训练轮数和调整学习率可进一步优化模型
  2. 从数据上:
  • 文本音频转文字存在部分翻译错误的地方,通过对这部分文本进行纠错,能进一步提高准确率
  • 此外,还可以进一步对无意义的词句进行去除来进一步提高信息的密度,从而达到更好的效果

4.3 作者介绍

本人是AI达人特训营第二期项目中的一名学员,非常有幸能与大家分享自己的所思所想。

作者:范远展