(某乎吞了我的帖子,发这里)DeepsetAI-haystack中文场景下使用(三)让haystack能用在中文任务上!

2023.12.5 deepsetAI发布了最新beta版本的haystack2.0 项目整体结构大改。 而其分词分句功能目前没有加入,也许以后都不会加入。 因此本教程适用于haystack2.0版本之前的。请根据需要下载版本,并查看官方文档。 在2.0版本之前,haystack也存在很多实用性组件,不代表2.0版本会比之前功能更多。

写在开头,本系列文章,目的在将deepsetAI-haystack的nlp在中文领域应用讲解透彻,以实用角度出发,从安装部署,到实际应用,全讲。个人觉得,应该能在nlp领域广泛应用,可以用来解决训练集数据问题,大模型外接知识库问题,等。

前情提要

在上一篇内容中,我们安装好了官方版本的haystack,并测试了一个简单的例子,给了一些文档,将文档预处理之后,写入到内存中作为document_store,并通过bm25关键词检索,对用户输入的query问题检索到top_k个答案,并调用QA模型对检索出的文档进行解析。

如果还没看过的小伙伴,可以看我上一篇文章:

DeepsetAI-haystack中文场景下使用(二)haystack的安装

当然,调用QA模型有一个特点,就是回答基本都是言简意赅。 但是可以将搜索结果处理后接入大模型解析。这个会在以后的文章中更新。 最重要的是检索的精度问题。 以及检索速度的问题。

今天我们根据官方教程中,基础使用的另一个例子来看一下中文情况下,表现有多差。

并且我们会将haystack的两个py文件进行替换,让其支持中文!

下面开始

利用现有的常见问题解答,来回答问题。

官方教程链接:Utilizing Existing FAQs for Question Answering | Haystack

为了在中文语境上测试该教程,我收集了一些常见的医疗 问题—答案 的数据集。

度盘链接:

链接:https://pan.baidu.com/s/1CZ0Pthwy_jERXJSWPOceNQ

提取码:1234

里面有2个数据集,一个是官方问答数据集,是英文的。另一个是中文的医疗问答类数据集。

英文部分,自己可以跑一下。本篇主要测试中文问答。

中文医疗问答数据集大致是这个样子。

department,title,ask,answer
心血管科,高血压患者能吃党参吗?,我有高血压这两天女婿来的时候给我拿了些党参泡水喝,您好高血压可以吃党参吗?,高血压病人可以口服党参的。党参有降血脂,降血压的作用,可以彻底消除血液中的垃圾,从而对冠心病以及心血管疾病的患者都有一定的稳定预防工作作用,因此平时口服党参能远离三高的危害。另外党参除了益气养血,降低中枢神经作用,调整消化系统功能,健脾补肺的功能。感谢您的进行咨询,期望我的解释对你有所帮助。

包含了问题所属科室,问题的标题,问题详细描述,以及医生的解答。

该任务完成的是,根据语义匹配,匹配到用户输入的问题,在该数据集中最相似的问题,并把该问题的答案返还给用户。

代码如下:

import logging

logging.basicConfig(format="%(levelname)s - %(name)s -  %(message)s", level=logging.WARNING)
logging.getLogger("haystack").setLevel(logging.INFO)

from haystack.nodes import EmbeddingRetriever
from haystack.document_stores import InMemoryDocumentStore

# 创建一个存储在内存中的document_store
document_store = InMemoryDocumentStore()

from haystack.nodes import EmbeddingRetriever

retriever = EmbeddingRetriever(
    document_store=document_store,
    embedding_model="sentence-transformers/paraphrase-multilingual-mpnet-base-v2",
    use_gpu=True,
    scale_score=False,
)

# retriever = EmbeddingRetriever(
#     document_store=document_store,
#     embedding_model="uer/sbert-base-chinese-nli",
#     use_gpu=True,
#     scale_score=False,
# )


import pandas as pd

from haystack.utils import fetch_archive_from_http

# 下载数据集,这里我处理好了,记得修改好路径
doc_dir = "../data/tutorial4"
# s3_url = "https://s3.eu-central-1.amazonaws.com/deepset.ai-farm-qa/datasets/documents/small_faq_covid.csv.zip"
# fetch_archive_from_http(url=s3_url, output_dir=doc_dir)

# 读取csv文件
df = pd.read_csv(f"{doc_dir}/样例_内科5000-6000.csv")

# 去除空值
df.fillna(value="", inplace=True)
df["ask"] = df["ask"].apply(lambda x: x.strip())
print(df.head())

#将问题ask,经过embedding模型,映射为tensor,并写入dataframe的新的一列
questions = list(df["ask"].values)
df["embedding"] = retriever.embed_queries(queries=questions).tolist()
df = df.rename(columns={"ask": "content"})

# 将df数据转换为字典,因为document_store的写入接口,接收的数据是字典格式的
docs_to_index = df.to_dict(orient="records")
document_store.write_documents(docs_to_index)

# 创建FAQ管道,并添加管道组件检索器retriever
from haystack.pipelines import FAQPipeline
pipe = FAQPipeline(retriever=retriever)

from haystack.utils import print_answers

# 开始提问
prediction = pipe.run(query="癫痫用什么药物", params={"Retriever": {"top_k": 1}})
# 打印答案
print_answers(prediction, details="medium")

注意修改代码中的路径。

这里使用的embedding模型是一个文本相似度模型,支持50多种语言,包括中文。sentence-transformers/paraphrase-multilingual-mpnet-base-v2 · Hugging Face

各位也可以根据数据集中的问题来进行提问,但是注意,要换一个问法,我们需要让haystack进行多语义提问,而不是关键词搜索。 例如,在数据集中的问题是:癫痫会表现出哪些症状?,我们提问的时候可以提问,癫痫的表现是什么样的? 如果匹配到可以的答案,那么就可以了。

ps:数据embedding以及写入内存的过程比较耗时,可以将提问部分自行修改为while Ture, 等待接收问题。 这样可以省去每次执行脚本都要重新embedding写入,因为脚本执行结束,退出后,内存中存储的document_store会被释放。 本章后面会介绍一个持久化存储document_store的方法,其实也是调用数据库。


如果你已经测试了很多,发现,这不是基本可以用吗,中文的表现也不是很差啊,语义检索的精度,来自于文本相似度模型的表现啊。 根本不需要修改代码进行替换。

别急, 上一个文章说过,haystack之所以不支持中文,是因为分词分句的问题,英文的分词直接空格切分就好了,而中文要使用jieba。 在上面这个官方教程中,根本没使用到分词,分句。 并且问题ask的长度不长,如果按照title字段进行搜索,也就20个字符不到。 并且,基于已有问题搜索答案,本身实用性不是那么高,多是应用于已有固定解决方法或者答案。 比方说人工客服回复客户,常见的问题,基本都写好了。 也算是一种人工标注。

重点来了,如果只有文档,没有答案呢?我还要根据文档去找到答案。

各位首先想到的是langchain对吧。

其实haystack比langchain早了一两年。

接下来这个例子实用性较高,可扩展能力较高,请仔细阅读。

拿小板凳坐好。

动图封面


官方教程 Better Retrieval with Embedding Retrieval | Haystack

任务说明:我有一大堆文档,没有人工标注,没有答案,就是一大堆文档。 我要在里面搜索一些内容的答案。

英文测试内容,去上面的链接自己跑。 如果有google_colab,可以直接点这里,链接一个免费的T4显卡,全部代码块执行,就可以。

准备一些中文数据集,百科词条

测试用的一共13个百科词条。文档长度不确定。

另外还有一个大的百科词条,一共636个文档。

度盘下载:

测试用数据data_test

链接:https://pan.baidu.com/s/1pOJrV_fmKBS2EjiGiZ_27w

提取码:1234

完整数据:

链接:https://pan.baidu.com/s/1uA2QpGYHf0SP2TtrPeuu4w

提取码:1234


数据准备好了,代码如下:

import os
import logging
import time
logging.basicConfig(format="%(levelname)s - %(name)s -  %(message)s", level=logging.WARNING)
logging.getLogger("haystack").setLevel(logging.INFO)

# 写入库

from haystack.document_stores import FAISSDocumentStore
from haystack.pipelines.standard_pipelines import TextIndexingPipeline
#创建库以及索引
document_store = FAISSDocumentStore(faiss_index_factory_str="Flat",embedding_dim=768)

#加载已有的document_store
# document_store = FAISSDocumentStore.load(index_path="wiki_faiss_index.faiss", config_path="wiki_faiss_index.json")

doc_dir = 'F:\MC-PROJECT\CUDA_Preject/test_haystack\wiki\data_test'


files_to_index = [doc_dir + "/" + f for f in os.listdir(doc_dir)]
indexing_pipeline = TextIndexingPipeline(document_store)
indexing_pipeline.run_batch(file_paths=files_to_index)
# print(document_store.get_all_documents())

from haystack.nodes import EmbeddingRetriever

retriever = EmbeddingRetriever(
    document_store=document_store,
    embedding_model="shibing624/text2vec-base-chinese",
)

document_store.update_embeddings(retriever)

document_store.save(index_path="wiki_faiss_index.faiss")


from haystack.nodes import FARMReader
reader = FARMReader(model_name_or_path="uer/roberta-base-chinese-extractive-qa", use_gpu=True,context_window_size=300,max_seq_len=512)

# 下面这个IDEA研究院的模型太大
# reader = FARMReader(model_name_or_path="IDEA-CCNL/Randeng-T5-784M-QA-Chinese", use_gpu=True)


from haystack.utils import print_answers
from haystack.pipelines import ExtractiveQAPipeline

pipe = ExtractiveQAPipeline(reader, retriever)




while True:
    q = input('输入问题吧:')
    st_time = time.time()
    prediction = pipe.run(
        query=q,
        params={
            "Retriever": {"top_k": 10},
            "Reader": {"top_k": 5}
        }
    )
    # print(prediction)
    print_answers(prediction, details="all")
    end_time = time.time()

    print("计算时间为:{}".format(end_time - st_time))

说明:

  1. 首先,路径问题,不需要多说,自行修改。
  2. 使用的文本相似度模型为shibing624/text2vec-base-chinese,huggingface上开源,自行查看参数
  3. QA模型为uer/roberta-base-chinese-extractive-qa,同样HF上开源。
  4. 矢量存储库使用的是faiss,存储在本地,faiss的参数可修改地方很多,可以自行查看官方API文档,在我前面的文章有地址链接。
  5. 我写了while True循环,可以重复的提问。

执行成功后,应该是下面的样子:

我相信各位自行测试过的小伙伴,会发现,测试的结果有好有坏,有的还行,有的几乎不贴题。

如果你中间停止了脚本,再次执行一定会报以下错误。

注意看该脚本的同目录下,是不是多了这三个文件。

看代码这个部分:

from haystack.document_stores import FAISSDocumentStore
from haystack.pipelines.standard_pipelines import TextIndexingPipeline
#创建库以及索引
document_store = FAISSDocumentStore(faiss_index_factory_str="Flat",embedding_dim=768)

#加载已有的document_store
# document_store = FAISSDocumentStore.load(index_path="wiki_faiss_index.faiss", config_path="wiki_faiss_index.json")

解释: 创建document_store,创建的是faiss存储,因此,在同目录下,会出现后缀为db的文件,是将document_store进行本地化存储,同时另外两个,以faiss和json为后缀的文件,是faiss存储的索引以及配置文件。faiss存储也支持远程,需要填写地址以及存储名称,haystack会在远程服务器创建faiss持久化存储。

因此想要解决这个问题,你可以将这三个文件删除,删除后重新执行,就不会报错,但是又要进行一次文档处理,以及embedding的过程。如果文档量很大。 比如那个636个百科词条的文档,耗时会将近半小时。

那么另一种解决方法,就是将创建faiss的document_store代码注释掉,并把下面的document_store放开,代表不重新创建document_store,转而加载已有的document_store。

这样,就不需要等待重新写入这种漫长的过程了。


修改代码使其支持中文语境

分析一下,其中文表现效果时好时坏的原因。

分句,分词。 在文档预处理时,haystack使用了自定义的管道组件:

files_to_index = [doc_dir + "/" + f for f in os.listdir(doc_dir)]
indexing_pipeline = TextIndexingPipeline(document_store)
indexing_pipeline.run_batch(file_paths=files_to_index)

如果有兴趣ctrl跳转进TextIndexingPipeline,你会在里面看到preprocessor组件,这个就是对文档进行切分。

其规则会根据空行切分,也会根据英语的句号进行切分,也会根据回车符\n切分,等等。

但是,现在使用的是中文,因此只有\n,或者空行,生效。

看一下数据。你会发现。假设我们需要将文档切分成,300个字符的小文档。 同时尊重句子边界(也就是在不超过300字符的情况下,保证句子完整。 也就是说,如果下一个句子加进来,小文档的长度超过了300个字符,则省去下一个句子, 来保证不超过300个字符,同时不让每个小文档的最后一个句子,只说了一半话,就被截断了)

preprocessor类的接口如下,有兴趣请查一下官方接口文档,文档预处理,对于检索的准确度有一定影响。举个简单的例子,模型支持输入512个字符的话,如果文档切分长度为600,那么会被截断的。 诸如此类。

各位明显会看到language里面的参数是'en'。

别急,马上就是如何修改,使其支持中文。


我修改好了文件,并上传至github。只需要将我提供的文件,替换haystack同名原文件即可。

GitHub - mc112611/haystack-chinese: :mag: 基于deepsetAI的开源项目haystack进行修改,使其支持中文场景下的任务

里面两个preprocessor.py和txt.py都替换原文件,原文件的位置如下

preprocessor.py:你的解释器环境+\Lib\site-packages\haystack\nodes\preprocessor
txt.py:你的解释器环境\Lib\site-packages\haystack\nodes\file_converter

大家会在上面的目录下,找到同名文件,替换掉即可。

我们看一下替换后的preprocessor预处理器。

首先在语种加入了zh

参数等设置为默认。实际上,preprocessor为文档预处理器,这个组件是可以单独被调用的,用法不在官方教程中,需要自己去研究haystack的官方文档,以及接口文档。 可以作为管道组件添加到pipeline中的任意位置。本篇文章不提供这些复杂用法的教程。

替换完毕之后就可以支持中文了。

再执行一次,会发现表现效果好了很多,文档切分也更舒适了。

记得删除那三个faiss文件,因为这次我们要重新对文档进行处理。

PS:当然,文档质量也影响检索的结果,这里我提供的wiki文档,没有做数据清洗,会有一些符号等。如果有小伙伴想实际应用,注意这一点哦。

最佳化,等内容,在官方文档都有非常详细的使用说明。 个人总结起来肯定不如官方做的全面细致。 当然,我会尽可能在后面的文章中去写一些, 但是,一切请以官方文档为准。

个人提供这个修改源码的文件,基本上在我个人测试官方支持功能的时候,基本上没遇到问题。如果有问题,欢迎交流。


到这里,基本上haystack已经可以应用于中文场景了。

最大的问题解决了,剩下的就是照着官方教程,以及官方文档,去实际操作,包括接入翻译器,接入文本转语音,接入web搜索引擎,接入聊天应用程序,或者接入文本到图像的图文搜索。

下一章,我会更新如何微调模型,当应用某些特定领域时候,训练文本相似度模型,QA模型,使用哪些,或者微调这类模型。 有兴趣的可以看一下 sentence_transformer ,做文本相似度模型最好的库。

  • 16
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值