目录
问答(QA)系统中最关键的挑战之一是标记数据的稀缺性,这是因为对目标领域获取问答对或常见问答对(FAQ)的成本很高,需要消耗大量的人力和时间。由于上述制约,这导致检索式问答系统落地困难,解决此问题的一种方法是依据问题上下文或大量非结构化文本自动生成的QA问答对。
在此背景下,无监督检索式问答系统(即问答对自动生成智能检索式问答),基于PaddleNLP问题生成、UIE、检索式问答,支持以非结构化文本形式为上下文自动生成QA问答对,生成的问答对语料可以通过无监督的方式构建检索式问答系统。
若开发者已有FAQ语料,请参考supervised_qa。
具体来说,本项目具有以下优势:
-
低成本
- 可通过自动生成的方式快速大量合成QA语料,大大降低人力成本
- 可控性好,合成语料和语义检索问答解耦合,可以人工筛查和删除合成的问答对,也可以添加人工标注的问答对
-
低门槛
- 手把手搭建无监督检索式问答系统
- 无需相似Query-Query Pair标注数据也能构建问答系统
-
效果好
- 可通过自动问答对生成提升问答对语料覆盖度,缓解中长尾问题覆盖较少的问题
- 业界领先的检索预训练模型: RocketQA Dual Encoder
- 针对无标注数据场景的领先解决方案: 检索预训练模型 + 增强的无监督语义索引微调
-
端到端
- 提供包括问答语料生成、索引库构建、模型服务部署、WebUI可视化一整套端到端智能问答系统能力
- 支持对Txt、Word、PDF、Image多源数据上传,同时支持离线、在线QA语料生成和ANN数据库更新
本项目的流程图如下,对于给定的非结构化文本,我们首先通过答案抽取、问题生成、以及往返过滤模块,得到大量语料相关的问答对。针对这些得到的问答对,用户可以通过可以人工筛查和删除的方式来调整生成的问答对,也可以进一步添加人工标注的问答对。随后开发者就可以通过语义索引模块,来构建向量索引库。在构造完索引库之后,我们就可以通过召回模块和排序模块对问答对进行查询,得到最终的查询结果。
由于涉及较多的模块,本项目将基于PaddleNLP Pipelines进行模块的组合和项目的构建。PaddleNLP Pipelines是一个端到端NLP流水线系统框架,它可以通过插拔式组件产线化设计来构建一个完整的无监督问答系统。具体来说,我们的技术方案包含以下方面:
答案抽取:我们基于UIE训练了一个答案抽取模型,该答案抽取模型接收“答案”作为提示词,该模型可以用来对潜在的答案信息进行挖掘抽取,我们同时提供了训练好的模型权重uie-base-answer-extractor
。
问题生成:我们基于中文预训练语言模型UNIMO-Text、模版策略和大规模多领域问题生成数据集训练了一个通用点问题生成预训练模型unimo-text-1.0-question-generation
。
往返过滤:我们采用过生成(overgenerate)的策略生成大量的潜在答案和问题,并通过往返过滤的方式针对生成的过量问答对进行过滤得到最终的问答对。我们的往返过滤模块需要训练一个有条件抽取式问答模型3。
语义索引:针对给定问答对语料,我们基于RocketQA(即rocketqa-zh-base-query-encoder
)对问答对进行语义向量化,并通过ElasticSearch的ANN服务构建索引库。
召回排序:给定用户查询,我们基于RocketQA的query-encoder和cross-encoder分别进行召回和排序操作,得到目标的问答对,从而返回给用户查询结果。
Pipelines:由于本项目设计的模块较多,我们使用PaddleNLP Pipelines进行模块的组合和项目的构建。大体来说,我们的Pipelines包含两个具体的pipeline和三个服务。两个pipeline分别是qa_generation_pipeline和dense_faq_pipeline;三个服务分别是基于ElasticSearch的ANN在线索引库服务,基于RestAPI的模型后端服务以及基于Streamlit的前端WebUI服务。
基于Pipelines构建问答系统需要安装paddle-pipelines依赖,使用pip安装命令如下:
# pip一键安装
pip install --upgrade paddle-pipelines -i https://pypi.tuna.tsinghua.edu.cn/simple
或者进入pipelines目录下,针对源码进行安装:
# 源码进行安装
cd PaddleNLP/pipelines/
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
python setup.py install
我们以提供的纯文本文件source_file.txt为例,系统将每一条都视为一个上下文并基于此生成多个问答对,并基于此构建索引库,该文件可直接下载放入data
,开发者也可以使用自己的文件。
开发者可以通过如下命令快速体验无监督智能检索问答系统的效果,系统将自动根据提供的纯文本文件构建问答对语料库,并基于生成的问答对语料库构造索引库。 我们建议在GPU环境下运行本示例,运行速度较快,运行命令如下:
# GPU环境下运行示例
# 设置1个空闲的GPU卡,此处假设0卡为空闲GPU
export CUDA_VISIBLE_DEVICES=0
python run_pipelines_example.py --device gpu --source_file data/source_file.txt --doc_dir data/my_data --index_name faiss_index --retriever_batch_size 16
关键参数释义如下:
device
: 使用的设备,默认为'gpu',可选择['cpu', 'gpu']。source_file
: 源文件路径,指定该路径将自动为其生成问答对至doc_dir
。doc_dir
: 生成的问答对语料保存的位置,系统将根据该位置自动构建检索数据库,默认为'data/my_data'。index_name
: FAISS的ANN索引名称,默认为'faiss_index'。retriever_batch_size
: 构建ANN索引时的批量大小,默认为16。
如果只有CPU机器,可以通过--device参数指定cpu即可, 运行耗时较长,运行命令如下:
# CPU环境下运行示例
unset CUDA_VISIBLE_DEVICES
python run_pipelines_example.py --device cpu --source_file data/source_file.txt --doc_dir data/my_data --index_name faiss_index --retriever_batch_size 16
开发者可以基于Pipelines进一步构建Web可视化的无监督检索式问答系统,其效果如下,
这一部分介绍如何离线构建问答对语料,同时我们我们也在Pipeline中集成了在线问答对语料。
我们以提供的纯文本文件source_file.txt为例,系统将每一条都视为一个上下文并基于此生成多个问答对,随后系统将根据这些问答对构建索引库,该文件可直接下载放入data
,开发者也可以使用自己的文件。
对于标准场景的问答对可以直接使用提供的预训练模型实现零样本(zero-shot)问答对生成。对于细分场景开发者可以根据个人需求训练自定义模型,加载自定义模型进行问答对生成,以进一步提升效果。
生成问答对语料的命令如下:
export CUDA_VISIBLE_DEVICES=0
python -u run_qa_pairs_generation.py \
--source_file_path=data/source_file.txt \
--target_file_path=data/target_file.json \
--answer_generation_model_path=uie-base-answer-extractor-v1 \
--question_generation_model_path=unimo-text-1.0-question-generation \
--filtration_model_path=uie-base-qa-filter-v1 \
--batch_size=8 \
--a_max_answer_candidates=10 \
--a_prompt='答案' \
--a_position_prob=0.01 \
--q_num_return_sequences=3 \
--q_max_question_length=50 \
--q_decode_strategy=sampling \
--q_top_k=5 \
--q_top_p=1 \
--do_filtration \
--f_filtration_position_prob=0.01 \
--do_debug
关键参数释义如下:
-
source_file_path
源文件路径,源文件中每一行代表一条待生成问答对的上下文文本。 -
target_file_path
目标文件路径,生成的目标文件为json格式。 -
answer_generation_model_path
要加载的答案抽取模型的路径,可以是PaddleNLP提供的预训练模型,或者是本地模型checkpoint路径。如果使用PaddleNLP提供的预训练模型,可以选择下面其中之一。可选预训练模型 uie-base-answer-extractor-v1 -
question_generation_model_path
要加载的问题生成模型的路径,可以是PaddleNLP提供的预训练模型,或者是本地模型checkpoint路径。如果使用PaddleNLP提供的预训练模型,可以选择下面其中之一。可选预训练模型 unimo-text-1.0-question-generation unimo-text-1.0-dureader_qg unimo-text-1.0-question-generation-dureader_qg -
filtration_model_path
要加载的过滤模型的路径,可以是PaddleNLP提供的预训练模型,或者是本地模型checkpoint路径。如果使用PaddleNLP提供的预训练模型,可以选择下面其中之一。可选预训练模型 uie-base-qa-filter-v1 -
batch_size
使用taskflow时的批处理大小,请结合机器情况进行调整,默认为8。 -
a_max_answer_candidates
答案抽取阶段,每个输入的最大返回答案候选数,默认为5。 -
a_prompt
答案抽取阶段,使用的提示词,以","分隔,默认为"答案"。 -
a_position_prob
答案抽取阶段,置信度阈值,默认为0.01。 -
q_num_return_sequences
问题生成阶段,返回问题候选数,在使用"beam_search"解码策略时它应该小于q_num_beams
,默认为3。 -
q_max_question_length
问题生成阶段,最大解码长度,默认为50。 -
q_decode_strategy
问题生成阶段,解码策略,默认为"sampling"。 -
q_top_k
问题生成阶段,使用"sampling"解码策略时的top k值,默认为5。 -
q_top_p
问题生成阶段,使用"sampling"解码策略时的top p值,默认为0。 -
q_num_beams
问题生成阶段,使用"beam_search"解码策略时的beam大小,默认为6。 -
do_filtration
是否进行过滤。 -
f_filtration_position_prob
过滤阶段,过滤置信度阈值,默认为0.1。 -
do_debug
是否进入调试状态,调试状态下将输出过滤掉的生成问答对。
执行以下脚本对生成的问答对进行转换,得到语义索引所需要的语料train.csv、dev.csv、q_corpus.csv、qa_pair.csv:
python -u run_corpus_preparation.py \
--source_file_path data/target_file.json \
--target_dir_path data/my_corpus
关键参数释义如下:
source_file_path
指示了要转换的训练数据集文件或测试数据集文件,文件格式要求见从本地文件创建数据集部分。指示了要转换的问答对json文件路径,生成的目标文件为json格式target_dir_path
输出数据的目标文件夹,默认为"data/my_corpus"。test_sample_num
构建检索系统时保留的测试样本数目,默认为0。train_sample_num
构建检索系统时保留的有监督训练样本数目,默认为0。all_sample_num
构建检索系统时保留的总样本数目,默认为None,表示保留除了前test_sample_num
+train_sample_num
个样本外的所有样本。
本项目提供了基于Pipelines的低成本构建问答对自动生成智能检索问答系统的能力。开发者只需要提供非结构化的纯文本,就可以使用本项目预制的问答对生成模块生成大量的问答对,并基于此快速搭建一个针对自己业务的检索问答系统,并可以提供Web可视化产品服务。Web可视化产品服务支持问答检索、在线问答对生成,在线文件上传和解析,在线索引库更新等功能,用户也可根据需要自行调整。具体的构建流程请参考Pipelines-无监督智能检索问答系统。
除了使用预置模型外,用户也可以训练并接入自己训练的模型,我们提供了从答案抽取、问题生成、往返过滤的过滤模型,到语义索引、召回、排序各个阶段的定制化训练方案。
这一部分介绍如何准备和预处理答案抽取、问题生成、过滤模块微调所需的数据。关于如何准备通过无监督方式训练自定义语义索引模型所需的问答对数据,见离线问答对语料构建。
在许多情况下,我们需要使用本地数据集来微调模型从而得到定制化的能力,让生成的问答对更接近于理想分布,本项目支持使用固定格式本地数据集文件进行微调。
这里我们提供预先标注好的文件样例train.json和dev.json,开发者可直接下载放入data
目录,此外也可自行构建本地数据集,具体来说,本地数据集主要包含以下文件:
data
├── train.json # 训练数据集文件
├── dev.json # 开发数据集文件
└── test.json # 可选,待预测数据文件
本地数据集文件格式如下:
# train.json/dev.json/test.json文件格式:
{
"context": <context_text>,
"answer": <answer_text>,
"question": <question_text>,
}
...
本地数据集文件具体样例如下:
train.json/dev.json/test.json文件样例:
{
"context": "欠条是永久有效的,未约定还款期限的借款合同纠纷,诉讼时效自债权人主张债权之日起计算,时效为2年。 根据《中华人民共和国民法通则》第一百三十五条:向人民法院请求保护民事权利的诉讼时效期间为二年,法律另有规定的除外。 第一百三十七条:诉讼时效期间从知道或者应当知道权利被侵害时起计算。但是,从权利被侵害之日起超过二十年的,人民法院不予保护。有特殊情况的,人民法院可以延长诉讼时效期间。 第六十二条第(四)项:履行期限不明确的,债务人可以随时履行,债权人也可以随时要求履行,但应当给对方必要的准备时间。",
"answer": "永久有效",
"question": "欠条的有效期是多久"
}
...
执行以下脚本对数据集进行数据预处理,得到接下来答案抽取、问题生成、过滤模块模型微调所需要的数据,注意这里答案抽取、问题生成、过滤模块的微调数据来源于相同的数据集。
python -u run_data_preprocess.py \
--source_file_path data/train.json \
--target_dir data/finetune \
--do_answer_prompt
python -u run_data_preprocess.py \
--source_file_path data/dev.json \
--target_dir data/finetune \
--do_answer_prompt
关键参数释义如下:
source_file_path
指示了要转换的训练数据集文件或测试数据集文件,文件格式要求见自定义数据部分。target_dir
输出数据的目标文件夹,默认为"data/finetune"。do_answer_prompt
表示在构造答案抽取数据时是否添加"答案"提示词。do_len_prompt
表示在构造答案抽取数据时是否添加长度提示词。do_domain_prompt
表示在构造答案抽取数据时是否添加领域提示词。domain
表示添加的领域提示词,在do_domain_prompt
时有效。
NOTE: 预处理后的微调用数据将分别位于target_dir
下的answer_extraction、question_generation、filtration三个子文件夹中。
运行如下命令即可在样例训练集上微调答案抽取模型,用户可以选择基于uie-base-answer-extractor
进行微调,或者基于uie-base
等从头开始微调。
# GPU启动,参数`--gpus`指定训练所用的GPU卡号,可以是单卡,也可以多卡
# 例如使用1号和2号卡,则:`--gpu 1,2`
unset CUDA_VISIBLE_DEVICES
python -u -m paddle.distributed.launch --gpus "1,2" --log_dir log/answer_extraction finetune/answer_extraction_and_roundtrip_filtration/finetune.py \
--train_path=data/finetune/answer_extraction/train.json \
--dev_path=data/finetune/answer_extraction/dev.json \
--save_dir=log/answer_extraction/checkpoints \
--learning_rate=1e-5 \
--batch_size=16 \
--max_seq_len=512 \
--num_epochs=30 \
--model=uie-base \
--seed=1000 \
--logging_steps=100 \
--valid_steps=100 \
--device=gpu
关键参数释义如下:
train_path
: 训练集文件路径。dev_path
: 验证集文件路径。save_dir
: 模型存储路径,默认为log/answer_extration/checkpoints
。learning_rate
: 学习率,默认为1e-5。batch_size
: 批处理大小,请结合机器情况进行调整,默认为16。max_seq_len
: 文本最大切分长度,输入超过最大长度时会对输入文本进行自动切分,默认为512。num_epochs
: 训练轮数,默认为30。model
: 选择模型,程序会基于选择的模型进行模型微调,可选有uie-base-answer-extractor
,uie-base
,uie-medium
,uie-mini
,uie-micro
和uie-nano
,默认为uie-base
。init_from_ckpt
: 用于初始化的模型参数的路径。seed
: 随机种子,默认为1000.logging_steps
: 日志打印的间隔steps数,默认10。valid_steps
: evaluate的间隔steps数,默认100。device
: 选用什么设备进行训练,可选cpu或gpu。
通过运行以下命令在样例验证集上进行模型评估:
python finetune/answer_extraction_and_roundtrip_filtration/evaluate.py \
--model_path=log/answer_extraction/checkpoints/model_best \
--test_path=data/finetune/answer_extraction/dev.json \
--batch_size=16 \
--max_seq_len=512 \
--limit=0.01
关键参数释义如下:
model_path
: 进行评估的模型文件夹路径,路径下需包含模型权重文件model_state.pdparams
及配置文件model_config.json
。test_path
: 进行评估的测试集文件。batch_size
: 批处理大小,请结合机器情况进行调整,默认为16。max_seq_len
: 文本最大切分长度,输入超过最大长度时会对输入文本进行自动切分,默认为512。model
: 选择所使用的模型,可选有uie-base
,uie-medium
,uie-mini
,uie-micro
和uie-nano
,默认为uie-base
。debug
: 是否开启debug模式对每个正例类别分别进行评估,该模式仅用于模型调试,默认关闭。limit
: SpanEvaluator测评指标的limit
,当概率数组中的最后一个维度大于该值时将返回相应的文本片段;当limit设置为0.01时表示关注模型的召回率,也即答案的覆盖率。
运行如下命令即可在样例训练集上微调问题生成模型,并在样例验证集上进行验证。
# GPU启动,参数`--gpus`指定训练所用的GPU卡号,可以是单卡,也可以多卡
# 例如使用1号和2号卡,则:`--gpu 1,2`
unset CUDA_VISIBLE_DEVICES
python -u -m paddle.distributed.launch --gpus "1,2" --log_dir log/question_generation finetune/question_generation/train.py \
--train_file=data/finetune/question_generation/train.json \
--predict_file=data/finetune/question_generation/dev.json \
--save_dir=log/question_generation/checkpoints \
--output_path=log/question_generation/predict.txt \
--dataset_name=dureader_qg \
--model_name_or_path="unimo-text-1.0" \
--logging_steps=100 \
--save_steps=500 \
--epochs=20 \
--batch_size=16 \
--learning_rate=1e-5 \
--warmup_proportion=0.02 \
--weight_decay=0.01 \
--max_seq_len=512 \
--max_target_len=30 \
--do_train \
--do_predict \
--max_dec_len=20 \
--min_dec_len=3 \
--num_return_sequences=1 \
--template=1 \
--device=gpu
关键参数释义如下:
-
gpus
指示了训练所用的GPU,使用多卡训练可以指定多个GPU卡号,例如 --gpus "0,1"。 -
dataset_name
数据集名称,用来指定数据集格式,默认为dureader_qg
。 -
train_file
本地训练数据地址,数据格式必须与dataset_name
所指数据集格式相同,默认为None。 -
predict_file
本地测试数据地址,数据格式必须与dataset_name
所指数据集格式相同,默认为None。 -
model_name_or_path
指示了finetune使用的具体预训练模型,可以是PaddleNLP提供的预训练模型,或者是本地的预训练模型。如果使用本地的预训练模型,可以配置本地模型的目录地址,例如: ./checkpoints/model_xx/,目录中需包含paddle预训练模型model_state.pdparams。如果使用PaddleNLP提供的预训练模型,可以选择下面其中之一,默认为unimo-text-1.0
。可选预训练模型 unimo-text-1.0 unimo-text-1.0-large unimo-text-1.0-question-generation -
save_dir
表示模型的保存路径。 -
output_path
表示预测结果的保存路径。 -
logging_steps
表示日志打印间隔。 -
save_steps
表示模型保存及评估间隔。 -
seed
表示随机数生成器的种子。 -
epochs
表示训练轮数。 -
batch_size
表示每次迭代每张卡上的样本数目。 -
learning_rate
表示基础学习率大小,将于learning rate scheduler产生的值相乘作为当前学习率。 -
weight_decay
表示AdamW优化器中使用的weight_decay的系数。 -
warmup_proportion
表示学习率逐渐升高到基础学习率(即上面配置的learning_rate)所需要的迭代数占总步数的比例。 -
max_seq_len
模型输入序列的最大长度。 -
max_target_len
模型训练时标签的最大长度。 -
min_dec_len
模型生成序列的最小长度。 -
max_dec_len
模型生成序列的最大长度。 -
do_train
是否进行训练。 -
do_predict
是否进行预测,在验证集上会自动评估。 -
device
表示使用的设备,从gpu和cpu中选择。 -
template
表示使用的模版,从[0, 1, 2, 3, 4]中选择,0表示不选择模版,1表示使用默认模版。
程序运行时将会自动进行训练和验证,训练过程中会自动保存模型在指定的save_dir
中。
【注意】 如需恢复模型训练,model_name_or_path
配置本地模型的目录地址即可。
运行如下命令即可在样例训练集上微调答案抽取模型,用户可以选择基于uie-base-qa-filter
进行微调,或者基于uie-base
等从头开始微调。
# GPU启动,参数`--gpus`指定训练所用的GPU卡号,可以是单卡,也可以多卡
# 例如使用1号和2号卡,则:`--gpu 1,2`
unset CUDA_VISIBLE_DEVICES
python -u -m paddle.distributed.launch --gpus "1,2" --log_dir log/filtration finetune/answer_extraction_and_roundtrip_filtration/finetune.py \
--train_path=data/finetune/filtration/train.json \
--dev_path=data/finetune/filtration/dev.json \
--save_dir=log/filtration/checkpoints \
--learning_rate=1e-5 \
--batch_size=16 \
--max_seq_len=512 \
--num_epochs=30 \
--model=uie-base \
--seed=1000 \
--logging_steps=100 \
--valid_steps=100 \
--device=gpu
关键参数释义如下:
train_path
: 训练集文件路径。dev_path
: 验证集文件路径。save_dir
: 模型存储路径,默认为log/filtration/checkpoints
。learning_rate
: 学习率,默认为1e-5。batch_size
: 批处理大小,请结合机器情况进行调整,默认为16。max_seq_len
: 文本最大切分长度,输入超过最大长度时会对输入文本进行自动切分,默认为512。num_epochs
: 训练轮数,默认为30。model
: 选择模型,程序会基于选择的模型进行模型微调,可选有uie-base-qa-filter
,uie-base
,uie-medium
,uie-mini
,uie-micro
和uie-nano
,默认为uie-base
。init_from_ckpt
: 用于初始化的模型参数的路径。seed
: 随机种子,默认为1000.logging_steps
: 日志打印的间隔steps数,默认10。valid_steps
: evaluate的间隔steps数,默认100。device
: 选用什么设备进行训练,可选cpu或gpu。
通过运行以下命令在样例验证集上进行模型评估:
python finetune/answer_extraction_and_roundtrip_filtration/evaluate.py \
--model_path=log/filtration/checkpoints/model_best \
--test_path=data/finetune/filtration/dev.json \
--batch_size=16 \
--max_seq_len=512 \
--limit=0.5
关键参数释义如下:
model_path
: 进行评估的模型文件夹路径,路径下需包含模型权重文件model_state.pdparams
及配置文件model_config.json
。test_path
: 进行评估的测试集文件。batch_size
: 批处理大小,请结合机器情况进行调整,默认为16。max_seq_len
: 文本最大切分长度,输入超过最大长度时会对输入文本进行自动切分,默认为512。model
: 选择所使用的模型,可选有uie-base
,uie-medium
,uie-mini
,uie-micro
和uie-nano
,默认为uie-base
。debug
: 是否开启debug模式对每个正例类别分别进行评估,该模式仅用于模型调试,默认关闭。limit
: SpanEvaluator测评指标的limit
,当概率数组中的最后一个维度大于该值时将返回相应的文本片段。
我们的语义索引和召回模型是基于RocketQA的QueryEncoder训练的双塔模型,该模型用于语义索引和召回阶段,分别进行语义向量抽取和相似度召回。除使用预置模型外,如果用户想训练并接入自己的模型,模型训练可以参考FAQ Finance。
我们的排序模型是基于RocketQA的CrossEncoder训练的单塔模型,该模型用于搜索的排序阶段,对召回的结果进行重新排序的作用。关于排序的定制训练,可以参考CrossEncoder。
[1] Zheng, Chujie, and Minlie Huang. "Exploring prompt-based few-shot learning for grounded dialog generation." arXiv preprint arXiv:2109.06513 (2021).
[2] Li, Wei, et al. "Unimo: Towards unified-modal understanding and generation via cross-modal contrastive learning." arXiv preprint arXiv:2012.15409 (2020).
[3] Puri, Raul, et al. "Training question answering models from synthetic data." arXiv preprint arXiv:2002.09599 (2020).
[4] Lewis, Patrick, et al. "Paq: 65 million probably-asked questions and what you can do with them." Transactions of the Association for Computational Linguistics 9 (2021): 1098-1115.
[5] Alberti, Chris, et al. "Synthetic QA corpora generation with roundtrip consistency." arXiv preprint arXiv:1906.05416 (2019).