基于bert特征提取的FAQ问答系统构建

本文介绍了如何构建一个基于Elasticsearch(ES)的FAQ问答系统。首先,通过Docker安装ES和Kibana,然后利用IK分词器处理中文。接着,使用Python操作ES,创建索引并批量插入数据,包括标准问题、相似问题、答案和预计算的BERT向量。查询时,通过余弦相似度计算找到最相关的问题。总结来说,这是一个快速落地的FAQ问答系统基础方案,后续可优化知识库和查询准确性。
摘要由CSDN通过智能技术生成

faq的问答系统是目前用的比较广泛的问答系统,由于它落地简单,并且大部分场景都需要,构建一个faq问答系统可以作为一个baseline快速应用到实际场景中。下面就介绍如何快速构建一个faq问答的baseline。

一、环境的搭建

faq的核心技术是信息检索,信息检索的常用工具则是es,es既可以对faq知识库存储,又可以快速查询文本。同时也有配套的可视化工具kibana,方便数据查询和管理。此外,es的社区也很活跃,文档和讨论的问题也方便使用。

elasticsearch+kibana安装

使用docker容器拉取镜像文件,注意elasticsearch和kibana的版本要一致,本人使用的版本均为7.8.1的版本。

启动容器,根据官网的教程启动docker容器,选用单个节点

docker run -p 9200:9200 -p 9300:9300 --name es7.8\
-e "discovery.type=single-node" \
-e ES_JAVA_OPS="-Xms512m -Xmx512m" \
-d elasticsearch:7.8.1

如果是中文检索,需要安装中文分词器,先到GitHub - medcl/elasticsearch-analysis-ik: The IK Analysis plugin integrates Lucene IK analyzer into elasticsearch, support customized dictionary.上下载对应版本的ik分词器,然后进入容器解压到对应文件夹。

安装完es后再安装kibana

docker run --link es7.8.1:elasticsearch -p 5601:5601 -d kibana:7.8.1

二、python操作es

创建索引

es = Elasticsearch('http://127.0.0.1:9200')
base_info_index_mappings = {
    "settings":{
        "index.analysis.analyzer.default.type": "ik_max_word"
    },
    "mappings":{
        "properties": {
            "id": {
                "type": "keyword"
            },
            "standard_question": {
                "type": "text",
                "analyzer": "ik_max_word",
                "search_analyzer": "ik_max_word"
            },
            "sim_question": {
                "type": "text",
                "analyzer": "ik_max_word",
                "search_analyzer": "ik_max_word"
            },
            "answer": {
                "type": "text",
                "analyzer": "ik_max_word",
                "search_analyzer": "ik_max_word"
            },
            "query_vector": {
                "type": "dense_vector",
                "dims": 768
            },
            "update_date": {
                "type": "date",
                "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd"
            },
            "create_time": {
                "type": "date",
                "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd"
            },
        }
    }
}

批量插入数据

def insert_data(index_name, items, batch_size=100):
    '''
    批量插入数据到索引中
    :param index_name:
    :param items:
    :param batch_size:
    :return:
    '''
    batch = []
    err_count = 0
    for i, columns in enumerate(items):
        [sim_question, std_question, answer, vector] = columns
        id = uuid.uuid1()
        update_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        create_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')

        try:
            body = {
                '_index': index_name,
                '_source': {
                    'id': id,
                    'standard_question': std_question,
                    'sim_question': sim_question,
                    'answer': answer,
                    'query_vector': vector,
                    'update_time': update_time,
                    'create_time': create_time
                }
            }
        except Exception as e:
            err_count += 1
            print(e)
        if len(batch)<batch_size:
            batch.append(body)
            cur_id = id
        else:
            helpers.bulk(es, batch)
            print('总共有{}条数据,存储到第{}条数据'.format(len(batch), i))
    if len(batch)>0:
        helpers.bulk(es, batch)
        print('最终id是{}'.format(cur_id))
    print('err_count:{}'.format(err_count))
    return '插入数据完成'

其中的query_vector通过bert模型提前计算出768维向量并存入es的dense_vector格式数据字段中。关于bert向量计算可参考下部分代码

    def extract_vectors(self, model, text):
        '''
        抽取句子向量
        :param text:
        :return:
        '''
        batch_token_ids, batch_segment_ids, batch_mask = [], [], []
        word_ids, segment_ids, word_mask = encode(text)
        batch_token_ids.append(word_ids)
        batch_segment_ids.append(segment_ids)
        batch_mask.append(word_mask)
        inputs = dict(
            input_word_ids=batch_token_ids,
            input_mask=batch_mask,
            input_type_ids=batch_segment_ids,
        )
        infer_input = {
            "input_word_ids": tf.convert_to_tensor(inputs['input_word_ids']),
            "input_mask": tf.convert_to_tensor(inputs['input_mask']),
            "input_type_ids": tf.convert_to_tensor(inputs['input_type_ids']),
        }

        outputs = model(infer_input)
        encoder_outputs = outputs[0]
        last_encoder = encoder_outputs[-1]

        mean_vecor = tf.reduce_mean(last_encoder[:, 1:len(text) + 1, :], axis=1)
        print(mean_vecor.shape)
        mean_vecor_norm = tf.norm(mean_vecor, ord=2, axis=0)
        mean_vecor = mean_vecor / mean_vecor_norm
        print(mean_vecor.numpy()[0].shape)
        return mean_vecor.numpy()[0].tolist()

三、查询

es可以计算向量的余弦相似度,通过向量检索的形式可以找出最相近的问题,检索代码如下:

script_query = {
    "script_score": {
        "query": {
            "match_all": {
        },
        },
        "script": {
            "source": "cosineSimilarity(params.query_vector, 'query_vector') + 1.0",
            "params": {"query_vector": query_vector}
        }
    }
}
body = {
    "_source": ["standard_question", "sim_question", "answer"],
    "size": 5,
    "query": script_query,
}
res = search_data_by_body('faq_test_index', body)

其中query_vector为实时用bert抽取的查询文本向量。该方法速度很快,满足上线要求。

四、总结

以上就是一个faq问答系统的baseline,有一个比较重要的步骤是在之前就做好了,就是知识库的构建,需要某一业务场景的常用的faq问题及答案的总结。在baseline的基础上,后续可以不断丰富知识库,也可以通过精排、意图分类等方法提高查询的准确率。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值