rasa问答系统(一)

运行虚拟环境:

$ sudo cd chitchat_assistant    --项目路径
$ sudo virtualenv env
$ sudo source env/bin/activate

配置文件

config.yml

language: "zh"
policies:
  - name: "TEDPolicy"
    epochs: 120
    featurizer:
      - name: MaxHistoryTrackerFeaturizer
        max_history: 5
        state_featurizer:
          - name: BinarySingleStateFeaturizer
  - name: "rasa.core.policies.memoization.MemoizationPolicy"
    max_history: 5
  - name: "rasa.core.policies.mapping_policy.MappingPolicy"
  - name: "rasa.core.policies.fallback.FallbackPolicy"
    nlu_threshold: 0.4
    core_threshold: 0.3
    fallback_action_name: 'action_donknow'

pipeline:
- name: "MitieNLP" --训练词向量的组件
  model: "data/total_word_feature_extractor_zh.dat" --词向量矩阵
- name: "JiebaTokenizer" --分词、token组件
  dictionary_path: "data/jieba_userdict" --分词后的字典
- name: "MitieEntityExtractor" --实体识别组件
- name: "EntitySynonymMapper"--
- name: "RegexFeaturizer"--模式匹配组件
- name: "MitieFeaturizer"--特征提取组件
- name: "SklearnIntentClassifier"--意图识别组件(文本分类)

language:'zh'代表中文,'en'代表英文
policies:nlu和core模型训练的参数
pipline :定义了从输入到输出文本经历了哪些处理过程(components)
),分词、特征提取、实体识别、意图识别、模式匹配。每个组件输出的结果成为下一个组件的输入(因此名pipline)或者作为结果输出。可以自定义组件,如- name:"sentiment.SentimentAnalyzer",模板代码:https://www.jianshu.com/p/0db6173b0931

data/nlu.md (nlu.json或nlu.yml)

a example(json格式):

{"text":"怎么医Goodpasture综合征时呢", --用户输入的文本
 "intent":"search_treat",--识别用户的意图
 "entities":[{"end":17,--实体的结束位置
              "entity":"disease",--实体的类别
              "start":3,--实体的开始位置
              "value":"Goodpasture综合征"--实体本身
             }]
}

nlu.md就是nlu模型训练用的数据,可以用.json或.yml的格式存储。
data/stories.md

## story_2_search_food_simple
* greet                     --用户的意图是greet
   - utter_greet                --机器回答的动作应该是greet
* search_food{"disease": "百日咳"}      --用户的意图的search_food
   - action_search_food      --机器回答的动作应该是search_food
* bye       --用户的意图是bye
   - utter_goodbye    --机器回答的动作应该是goodbye

当机器判断出用户的意图,会根据不同的意图做出不同类别的反应,utter_greet、action_search_food、utter_goodbye就是机器回答的类别,但还不是具体的话。具体的话需要在domain.yml中定义。
domain.yml

slots:
  disease:
    type: text
  symptom:
    type: text
  dept:
    type: text
  drug:
    type: text
  food:
    type: text
  sure:
    type: unfeaturized
  pre_disease:
    type: unfeaturized


intents:
- first:
    triggers: action_first
- greet
- bye
- affirmative
#  - search_disease
- search_treat
- search_food
- search_symptom
- search_cause
- search_neopathy
- search_drug
- search_prevention
- search_drug_func
- search_disease_treat_time
- search_easy_get
- search_disease_dept

entities:
- disease
- symptom
- dept
- drug
- food

responses:
  utter_first:
  - text: 您好,我是您的医疗助手Friende,我是个机器人,请问有什么可以帮您?
  utter_greet:
  - text: 您好~
  - text: 您好呀~
  utter_goodbye:
  - text: 再见,祝您身体健康~
  - text: 拜拜,希望我有帮到您~
  utter_howcanhelp:
  - text: '您可以这样向我提问: 头痛怎么办/ 什么人容易头痛/ 头痛吃什么药/ 头痛能治吗/ 头痛属于什么科/ 头孢地尼分散片用途/ 如何防止头痛/ 头痛要治多久/
      糖尿病有什么并发症/ 糖尿病有什么症状'
  utter_donknow:
  - text: 啊噢,我没有理解您说的话,我的理解力还需要更多的提升>_<。

actions:
- utter_first
- utter_donknow
- action_first
- action_donknow
- action_echo
- action_search_treat
- action_search_food
- action_search_symptom
- action_search_cause
- action_search_neopathy
- action_search_drug
- action_search_prevention
- action_search_drug_func
- action_search_disease_treat_time
- action_search_easy_get
- action_search_disease_dept
- utter_greet
- utter_howcanhelp
- utter_goodbye

比如在stories.md中,机器第一轮动作是utter_greet ,那么就在domain.md中找到utter_greet对应的具体回答:您好~您好呀~;机器第三轮动作是utter_goodbye,那么utter_goodbye具体的回答就是:再见,祝您身体健康~拜拜,希望我有帮到您~;那么第二轮动作是action_search_food ,在domain中没有具体的例子怎么办?这里就涉及到actions.py。
actions.py

import re
from typing import Text, Dict, Any
from rasa_sdk import Action, Tracker
from rasa_sdk.executor import CollectingDispatcher
from py2neo import Graph
from markdownify import markdownify as md


p = 'data/medical/lookup/Diseases.txt'
disease_names = [i.strip() for i in open(p, 'r', encoding='UTF-8').readlines()]
# default neo4j account should be user="neo4j", password="neo4j"
graph = Graph(host="119.45.121.125", http_port=7474, user="neo4j", password="123456")


def retrieve_disease_name(name):
    names = []
    name = '.*' + '.*'.join(list(name)) + '.*'
    pattern = re.compile(name)
    for i in disease_names:
        candidate = pattern.search(i)
        if candidate:
            names.append(candidate.group())
    return names


def make_button(title, payload):
    return {'title': title, 'payload': payload}


class ActionSearchFood(Action):
    def name(self) -> Text:
        return "action_search_food"

    def run(self,
            dispatcher: CollectingDispatcher,
            tracker: Tracker,
            domain: Dict[Text, Any]):
        disease = tracker.get_slot("disease")
        pre_disease = tracker.get_slot("sure")
        print("pre_disease::::" + str(pre_disease))
        
        possible_diseases = retrieve_disease_name(disease)
        """ search_food db action here """
        food = dict()
        if disease == pre_disease or len(possible_diseases) == 1:
            m = [x['m.name'] for x in graph.run("match (a:Disease{name: {disease}})-[:can_eat]->(m:Food) return m.name",
                          disease=disease).data()]
            food['can_eat'] = "、".join(m) if m else "暂无记录"

            m = [x['m.name'] for x in graph.run("match (a:Disease{name: {disease}})-[:not_eat]->(m:Food) return m.name",
                          disease=disease).data()]

            food['not_eat'] = "、".join(m) if m else "暂无记录"

            retmsg = "在患 {0} 期间,可以食用:{1},\n但不推荐食用:{2}".\
                            format(disease, food['can_eat'], food['not_eat'])

            dispatcher.utter_message(retmsg)
        elif len(possible_diseases) > 1:
            buttons = []
            for d in possible_diseases:
                buttons.append(make_button(d, '/search_food{{"disease":"{0}", "sure":"{1}"}}'.format(d, d)))
            dispatcher.utter_button_message("请点击选择想查询的疾病,若没有想要的,请忽略此消息", buttons)
        else:
            dispatcher.utter_message("知识库中暂无与 {0} 相关的饮食记录".format(disease))
        return []

actions负责将tracker传递过来的意图和实体,做出具体的反馈。第二轮机器的动作是action_search_food,search什么food呢?关于{"disease": "百日咳"} 的food。根据搜索graph,返回可能的结果。当然也可以不基于graph给出反馈,也可以基于生成模型来做。
这里用的是neo4j图数据库,需要下载neo4j(4.1.3),并且配置jdk环境(JDK 11.0.8)下载地址:https://www.oracle.com/java/technologies/javase-jdk11-downloads.html。在运行模型的时候需要打开neo4j:

$ cd /Users/zhangwanyu/neo4j-community-4.1.3 --到达neo4j目录
$ cd bin
$ ./neo4j start  
--不用的时候需要关闭掉图数据库
$ ./neo4j stop
rasa train

nlu core 同时训练(文件名称自定义的):
python -m rasa train --config config.yml --domain domain.yml --data data/ --out models
只训练nlu:
python -m rasa train nlu --config config.yml --out models
只训练core:
python -m rasa train core --domain domain.yml --out models

rasa shell

模型训练好之后,便可以启动rasa shell在终端交互,在此期间需要把neo4j连接上。
rasa shell [nlu,core]
退出rasa shell nlu:
ctrl + c
rasa shell展示了模型的效果,但是对于初学者一个个小细节都可能无法展示出来。我踩过的坑:
1、提示utter_greet无法调用:导致的原因是在domain文件中,老版的rasa使用的templates,新版用的responses。解决方案:修改domain文件,templates替换成responses。具体操作如下:

from ruamel import yaml

with open('/Users/zhangwanyu/chitchat_assistant/domain.yml', encoding="utf-8") as f:
    content = yaml.load(f, Loader=yaml.RoundTripLoader)
    content['responses']=content['templates']
    del content['templates']
    print(content)

with open('/Users/zhangwanyu/chitchat_assistant/domain2.yml', 'w', encoding="utf-8-sig") as nf:
    yaml.dump(content, nf, Dumper=yaml.RoundTripDumper,allow_unicode=True)

2、提示retrieve_disease_name(disease)中disease为空。导致的原因是neo4j数据库没有导入数据。解决方案:把数据写入数据库。
3、提示py2neo语法错误,报错如下:

py2neo.database.work.ClientError: [Statement.SyntaxError] The old parameter syntax `{param}` is no longer supported. Please use `$param` instead 

导致的原因是新版的neo4j的查询语法升级了,不再使用{param}。解决方案:把actions的查询语句的语法全改成$param格式。也可以改py2neo下的文件,但这样做不太安全。
4、提示所有的action都无法执行,导致的原因是domain文件的slots字段多了个/t,修改domain文件就行。注意修改domain文件后,最好重新训练模型,基于没有修改之前的模型,可能还是会出错。

rasa run actions

Starts an action server using the Rasa SDK。可以查看rasa shell执行的具体报错信息。
python -m rasa run actions --port 5055 --actions actions --debug
rasa shell交互结果展示:

模型只能在输入完整、实体明确的情况下,能够有比较好的返回结果,对于通俗的或不在该领域的一些用语,返回结果不是很好。所以接下来就是去优化模型吧!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。