运行虚拟环境:
$ 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交互结果展示:
模型只能在输入完整、实体明确的情况下,能够有比较好的返回结果,对于通俗的或不在该领域的一些用语,返回结果不是很好。所以接下来就是去优化模型吧!