知识图谱应用实例


知识图谱的构建可以参考这篇笔记:知识图谱构建实例

这一个实例demo仅基于规则完成问题的匹配,并未涉及任何文本分类或文本生成等算法,所以其适用范围比较窄。

1. 前期准备

导入相关的包

import json
import ahocorasick
from py2neo import Graph

准备字典

with open('dict.josn', 'r', encoding='utf8') as f:
    words_dict = json.load(f)

# 实体词典
region_words = set()

for v in words_dict.values():
    region_words |= set(v)

# 否定词
deny_words = ['否', '非', '不', '无', '弗', '勿', '毋', '未', '没', '莫', '没有', '防止', '不再', '不会', '不能', '忌', '禁止',
              '防止', '难以', '忘记', '忽视', '放弃', '拒绝', '杜绝', '不是', '并未', '并无', '仍未', '难以出现', '切勿', '不要',
              '不可', '别', '管住', '注意', '小心', '少']

# 问句疑问词词库
question_words_dict = {
    'cause'        : ['原因', '成因', '为什么', '怎么会', '怎样才', '咋样才', '怎样会', '如何会', '为啥', '为何', '如何才会', '怎么才会', '会导致', '会造成'],
    'prevent'      : ['预防', '防范', '抵制', '抵御', '防止', '躲避', '逃避', '避开', '免得', '逃开', '避开', '避掉', '躲开', '躲掉', '绕开',
                      '怎样才能不', '怎么才能不', '咋样才能不', '咋才能不', '如何才能不',
                      '怎样才不', '怎么才不', '咋样才不', '咋才不', '如何才不',
                      '怎样才可以不', '怎么才可以不', '咋样才可以不', '咋才可以不', '如何可以不',
                      '怎样才可不', '怎么才可不', '咋样才可不', '咋才可不', '如何可不'],
    'cure_lasttime': ['周期', '多久', '多长时间', '多少时间', '几天', '几年', '多少天', '多少小时', '几个小时', '多少年'],
    'cure_way'     : ['怎么治疗', '如何医治', '怎么医治', '怎么治', '怎么医', '如何治', '医治方式', '疗法', '咋治', '怎么办', '咋办', '咋治'],
    'cured_prob'   : ['多大概率能治好', '多大几率能治好', '治好希望大么', '几率', '几成', '比例', '可能性', '能治', '可治', '可以治', '可以医'],
    'easy_get'     : ['易感人群', '容易感染', '易发人群', '什么人', '哪些人', '感染', '染上', '得上'],
    'acompany'     : ['并发症', '并发', '一起发生', '一并发生', '一起出现', '一并出现', '一同发生', '一同出现', '伴随发生', '伴随', '共现'],
    'food'         : ['饮食', '饮用', '吃', '食', '伙食', '膳食', '喝', '菜', '忌口', '补品', '保健品', '食谱', '菜谱', '食用', '食物', '补品'],
    'drug'         : ['药', '药品', '用药', '胶囊', '口服液', '炎片'],
    'check'        : ['检查', '检查项目', '查出', '检查', '测出', '试出'],
    'belongs_to'   : ['属于什么科', '属于', '什么科', '科室'],
    'symptom'      : ['症状', '表征', '现象', '症候', '表现'],
    'cure'         : ['治疗什么', '治啥', '治疗啥', '医治啥', '治愈啥', '主治啥', '主治什么', '有什么用', '有何用', '用处', '用途',
                      '有什么好处', '有什么益处', '有何益处', '用来', '用来做啥', '用来作甚', '需要', '要'],
}

# 问题类型-sql字典表
disease_info_key = ['desc', 'prevent', 'cause', 'easy_get', 'cure_way', 'cure_lasttime', 'cured_prob']
edges_tuple_dict = {
    'acompany'      : ['diseases', 'diseases', '并发症'],  # 疾病并发关系
    'not_eat'       : ['diseases', 'foods', '忌吃'],  # 疾病-忌吃食物关系
    'do_eat'        : ['diseases', 'foods', '宜吃'],  # 疾病-宜吃食物关系
    'recommand_eat' : ['diseases', 'foods', '推荐食谱'],  # 疾病-推荐吃食物关系
    'common_drug'   : ['diseases', 'drugs', '常用药品'],  # 疾病-通用药品关系
    'recommand_drug': ['diseases', 'drugs', '好评药品'],  # 疾病-热门药品关系
    'production'    : ['producers', 'drugs', '生产药品'],  # 厂商-药物关系
    'check'         : ['diseases', 'checks', '诊断检查'],  # 疾病-检查关系
    'belongs_to'    : ['departments', 'departments', '属于'],  # 科室-科室关系
    'category'      : ['diseases', 'departments', '所属科室'],  # 疾病与科室之间的关系
    'symptom'       : ['diseases', 'symptoms', '症状'],  # 疾病症状关系
}

# 构建词典
word_type_dict = dict()
for wd in region_words:
    word_type_dict[wd] = []
    for k, v in words_dict.items():
        if wd in v:
            word_type_dict[wd].append(k)

构建AC自动机(AC自动机原理可参考这篇笔记:数据结构-匹配树

# 构建ac自动机
region_tree = ahocorasick.Automaton()
for index, word in enumerate(region_words):
    region_tree.add_word(word, (index, word))

region_tree.make_automaton()

2. 意图识别

def question_classifier(question):
    def check_words(words, sentence):
        for word in words:
            if word in sentence:
                return True
        return False

    # 抽取主体
    region_wds = [_[1][1] for _ in region_tree.iter(question)]
    stop_wds = [wd1 for wd1 in region_wds for wd2 in region_wds if wd1 in wd2 and wd1 != wd2]
    final_wds = [i for i in region_wds if i not in stop_wds]
    medical_dict = {i: word_type_dict.get(i) for i in final_wds}

    # 判断意图
    question_types = []

    types = []
    for type_ in medical_dict.values():
        types += type_

    for type_ in types:
        for k, v in question_words_dict.items():
            if check_words(v, question):
                # 已知疾病
                if type_ == 'diseases':
                    # 找食物
                    if k == 'food':
                        if check_words(deny_words, question):
                            question_types.append((type_, 'foods', ('忌吃',), 0))
                        else:
                            question_types.append((type_, 'foods', ('宜吃', '推荐食谱'), 0))

                    elif k == 'drug':
                        question_types.append((type_, 'drugs', ('常用药品', '好评药品'), 0))

                    elif k in disease_info_key:
                        question_types.append((type_, k))

                    else:
                        tmp = edges_tuple_dict.get(k, [])
                        if tmp:
                            question_types.append((tmp[0], tmp[1], (tmp[2],), 0))

                # 已知症状,找疾病
                elif type_ == 'symptoms' and k == 'symptom':
                    question_types.append(('diseases', 'symptoms', ('症状',), 1))

                # 已知食物,找疾病
                elif type_ == 'foods' and k in ['food', 'cure']:
                    if check_words(deny_words, question):
                        question_types.append(('diseases', 'foods', ('忌吃',), 1))
                    else:
                        question_types.append(('diseases', 'foods', ('宜吃', '推荐食谱'), 1))

                # 已知药品,找疾病
                elif type_ == 'drugs' and k == 'cure':
                    question_types.append(('diseases', 'drugs', ('常用药品', '好评药品'), 1))

                # 已知检查项目,找疾病
                elif type_ == 'checks' and k in ['check', 'cure']:
                    question_types.append(('diseases', 'checks', ('诊断检查',), 1))

    # 若没有查到相关的外部查询信息,那么则将该疾病的描述信息返回
    if not question_types and 'diseases' in types:
        question_types = [('diseases', 'desc')]

    # 若没有查到相关的外部查询信息,那么则将该疾病的描述信息返回
    if not question_types and 'symptoms' in types:
        question_types = [('diseases', 'symptoms', ('症状',), 1)]

    # 将多个分类结果进行合并处理,组装成一个字典
    res_classify = {
        'args'          : medical_dict,
        'question_types': question_types
    }

    return res_classify

3. 生成答案

生成查询语句

def gen_query(res_classify):
    entity_dict = dict()
    for arg, types in res_classify['args'].items():
        for type_ in types:
            entity_dict[type_] = entity_dict.get(type_, [])
            entity_dict[type_].append(arg)

    question_types = res_classify['question_types']

    sqls = []
    for question_type in question_types:
        sql = []

        if len(question_type) == 2:
            node, attr = question_type
            sql += [f"MATCH (m:{node}) where m.name = '{query_name}' return m.name, m.{attr}" for query_name in entity_dict.get(node, [])]

        else:
            p, q, edges, idx = question_type
            query_node = 'n' if idx else 'm'
            for edge in edges:
                sql += [f"MATCH (m:{p})-[r:{edge}]->(n:{q}) where {query_node}.name = '{query_name}' return m.name, r.name, n.name" for query_name in entity_dict.get(question_type[idx], [])]

        if sql:
            sqls.append({
                'sql'          : sql,
                'question_type': question_type
            })

    return sqls

定义每个类型的问题输出对应的答句的文本格式

question_answer_dict = {
    ('diseases', 'symptoms', ('症状',), 0)      : '{0}的症状包括:{1}',
    ('diseases', 'symptoms', ('症状',), 1)      : '症状{0}可能染上的疾病有:{1}',
    ('diseases', 'cause')                     : '{0}可能的成因有:{1}',
    ('diseases', 'prevent')                   : '{0}的预防措施包括:{1}',
    ('diseases', 'cure_lasttime')             : '{0}治疗可能持续的周期为:{1}',
    ('diseases', 'cure_way')                  : '{0}可以尝试如下治疗:{1}',
    ('diseases', 'cured_prob')                : '{0}治愈的概率为(仅供参考):{1}',
    ('diseases', 'easy_get')                  : '{0}的易感人群包括:{1}',
    ('diseases', 'desc')                      : '{0},熟悉一下:{1}',
    ('diseases', 'diseases', ('并发症',), 0)     : '{0}的症状包括:{1}',
    ('diseases', 'foods', ('忌吃',), 0)         : '{0}忌食的食物包括有:{1}',
    ('diseases', 'foods', ('宜吃', '推荐食谱'), 0)  : '{0}宜食的食物包括有:{1}\n推荐食谱包括有:{2}',
    ('diseases', 'foods', ('忌吃',), 1)         : '患有{1}的人最好不要吃{0}',
    ('diseases', 'foods', ('宜吃', '推荐食谱'), 1)  : '患有{1}的人建议多试试{0}',
    ('diseases', 'drugs', ('常用药品', '好评药品'), 0): '{0}通常的使用的药品包括:{1}',
    ('diseases', 'drugs', ('常用药品', '好评药品'), 1): '{0}主治的疾病有{1},可以试试',
    ('diseases', 'checks', ('诊断检查',), 0)      : '{0}通常可以通过以下方式检查出来:{1}',
    ('diseases', 'checks', ('诊断检查',), 1)      : '通常可以通过{0}检查出来的疾病有{1}',
}

从知识图谱中搜索答案

def answer_search(sqls, num_limit=20):
    final_answers = []
    for sql_ in sqls:
        question_type = sql_['question_type']
        queries = sql_['sql']
        answers = []

        for query in queries:
            ress = graph.run(query).data()
            answers += ress

        if not answers:
            break

        answers_type = question_answer_dict[question_type]

        if len(question_type) == 2:
            node, attr = question_type
            desc = []
            for answer in answers:
                if isinstance(answer[f'm.{attr}'], str):
                    desc.append(answer[f'm.{attr}'])
                else:
                    desc += answer[f'm.{attr}']

            final_answer = answers_type.format(
                answers[0]['m.name'],
                ';'.join(set(desc[:num_limit])))

        else:
            if question_type == ('diseases', 'diseases', ('并发症',), 0):
                desc1 = [answers['n.name'] for answers in answers]
                desc2 = [answers['m.name'] for answers in answers]
                subject = answers[0]['m.name']
                desc = [i for i in desc1 + desc2 if i != subject]

                final_answer = answers_type.format(
                    subject,
                    ';'.join(set(desc[:num_limit]))
                )

            elif question_type == ('diseases', 'foods', ('宜吃', '推荐食谱'), 0):
                final_answer = answers_type.format(
                    answers[0]['m.name'],
                    ';'.join(set([answer['n.name'] for answer in answers if answer['r.name'] == '宜吃'][:num_limit])),
                    ';'.join(set([answer['n.name'] for answer in answers if answer['r.name'] == '推荐食谱'][:num_limit]))
                )

            else:
                p, q, edges, idx = question_type
                first_node = 'n' if idx else 'm'
                second_node = 'm' if idx else 'n'

                final_answer = answers_type.format(
                    answers[0][f'{first_node}.name'],
                    ';'.join(set([answer[f'{second_node}.name'] for answer in answers][:num_limit]))
                )

        if final_answer:
            final_answers.append(final_answer)

    return final_answers

4. 结果展示

def main(question):
    res_classify = question_classifier(question)
    sqls = gen_query(res_classify)
    final_answers = answer_search(sqls)
    
    if not final_answers:
        answer = '对不起,我无法理解你的问题~'
    else:
        answer = '\n'.join(final_answers)
    
    return answer
  
print(main('头痛'))
"""
头痛,熟悉一下:头痛是临床常见症状之一,通常指局限于头颅上半部,包括眉弓、耳轮上缘和枕外隆突连线上的疼痛,病因较复杂,可由颅内病变,颅外头颈部病变,头颈部以外躯体疾病及神经官能症、精神病等引起。
"""

print(main('头痛怎么办'))
"""
头痛可以尝试如下治疗:药物治疗;康复治疗
"""

print(main('头痛吃点啥'))
"""
头痛宜食的食物包括有:鸭蛋;鸡蛋;芝麻;鸭肉
推荐食谱包括有:凉拌莲藕;清拌茄子;紫茄子粥;冬瓜汤;红豆莲藕粥;三鲜冬瓜汤;冬瓜粥;油焖茄子
"""

评论
  目录