首页 chatbot
文章
取消

chatbot

对话型应用

对话型应用也既是 chatbot 的中文翻译。

首先,对话型应用是智能应用,是当今最热门的「机器学习」(AI) 技术的典型应用。

所以,chatbot(对话型应用)是「智能应用」。何谓「智能应用」?

具体来说是一种基于 SL 的「智能应用」:

  • SL(Supervised learning):在一组数据集(输入数据+输出数据,也就是训练数据)之上建立的数学模型。之后可以使用建立的「数学模型」对新输入的数据产出结果数据
    • 例如:「分类模型」(垃圾邮件识别就是其典型应用)

对话型应用和机器学习

既然对话型应用是「智能应用」,那么先介绍一下「机器学习」和 chatbot 相关的内容。

对话型应用主要使用的是「机器学习」子方向 NLP 方面的模型。 具体可以参考 NLP 相关内容

实施「机器学习」的过程分 3 个步骤:training、inference、evaluation。

整个实施过程就是模型不断调优,不断迭代;所以工程上要提供全链路的支持。

如何评估「分类」算法?主要是体现在一些概念上:

  • ACC(Accuracy):算法得到正确结果总数 / 数据集总数;也就是:(TP+TN)/(数据集总数)
  • Recall:算法得到的正确的正例 / 数据集中应该的正例;也就是 TP / TP + FN
  • Precision:算法得到的正确的正例 / 算法算出来的所有正例;也就是 TP / TP + FP
  • F1 score:Recall 和 Precision 的「调和平均」;综合 recall 和 precision 对算法评价
  • overfitting:算法过于简单、选用的网络架构不适用于当前应用等……
  • underfitting:泛化(Generalization)能力不足

最后,再提一下在对话型应用中比较重要的 Transfer learning。为什么重要:

  • TL 可以用相对来说不大的训练数据得到好的训练效果
  • TL 的速度也比较快

在正式介绍 chatbot 更多细节之前,先用一个小节简要介绍一下 NLP 的相关内容。

NLP

NLP 是「机器学习」(ML,或 AI)的一个子方向,重点关注计算机和人的互动。

NLP 技术的发展分为 2 个阶段:传统方法和现代方法。

传统的 NLP 方法面临 2 大难于解决的问题:

  • how we represent textual information during the computing process
  • how we can build models for text

第一个问题,传统的 NLP 使用的都是比较初级的方法:one-hot encoding 表示 word 和 phrase;用 BoW(bag of words)表示句子和段落。

第二个问题,传统建模方法普遍重度依赖人工来构造特征:

  • TF-IDF:用于表示 word 的重要性
  • topic modeling:用于获取文档主题的相关信息
  • 也需要人工把很多语言学相关的信息构造成特征

在用上面的方法得到各种特征之后,传统的方法用传统的「机器学习」算法来建立模型;例如得到以下的分类模型:SGD、NN、SVC、RF、AdaBoost……

传统的方法优点是训练起来比较快,不要求要有大数据集。缺点是复杂的场景效果不好,而且需要花大量的时间在人工构造特征和模型调参上。

而现代方法能比较好的解决这 2 个问题。

现代的 NLP 方法:

  • word2vec(CBOW)解决了第一个问题,第一步就是把 words 先转化为 words embeddings。
  • 在解决了第一个问题之后,第二个问题也有了标准的解法:DNNs 模型(把 DL 应用到 NLP 领域,替代了传统的 ML 方法)

word2vec 基础上的改进(基于当前的上下文来提供 words embeddings):

  • LSTM(RNN)
  • GPT/BERT(Transform,attention mechanisms)

总结:实际场景中,有多种方法可供候选。

对话型应用按功能分类

从功能上,对话型应用可以归为 3 种类型,或者是这 3 种类型的混合体:

  • 任务型
  • 问答型
  • 闲聊型

需要在做需求调研时收集多的用例来印证用户的需求属于哪一种。

但问题在于,不同的「业务方」对于对话型应用的认知并不一致,「业务方」期望通过对话型应用获得什么价值也并不一致(甚至有可能「业务方」自己也不能精确描述出自己想要什么;甚至有可能「业务方」的需求根本不需要用对话型应用就能解决)。

所以,所有工作的前提要在产品上和「业务方」进行多轮沟通,之后才有可能获得正确的答案。

这里我们重点介绍「任务型」;同时也会附带一些「问答型」相关的内容;但基本不会介绍「闲聊型」。

对话型应用架构

chatbot 应用在架构上一般由 NLU、DM、NLG 3 大部分组成:

  • NLU:解释用户输入
  • DM:对话状态管理
  • NLG:对话响应生成

接下来重点介绍这 3 大部分。

NLU

NLU 模块在接收到用户输入的内容(文本)后,要完成 2 大任务:

  • 意图识别
  • fill slot(填槽)

NLP 的角度来看,NLU 模块的「意图识别」(或「意图分类」)是典型的「文本分类」任务;而填槽是典型的 ER(实体识别)任务。

NLU 模块一般工作步骤为(现在只是简介,后面还会细说):

  • 预处理。例如:分句,token 化,分词,coreference resolution 等等……
  • feature engineering。例如传统的 number_of_tokens, symbols_ in_between, and bag_of_words_in_between
  • 「分类」和 ER。可以使用传统的 ML 方法和现代的 DL 方法:
    • 使用传统方法的话。传统的 ML 分类方法有:linear classification,support-vector machines(SVMs)。而传统的 ER 方法有:sequential labeling models(例如 hidden Markov model (HMM) 和 CRF)
    • 使用现代 DL 方法的话。「分类」和 ER 都可以 use word2vec to do UL on a large corpus to embed hidden features of words into word embeddings and input them into DNN models such as convolutional neural networks (CNNs) or RNNs

总之,既可以用模型也可以用规则来做 NLU。模型的好处是能获得更高的 recall,从而能覆盖到更多样的用户输入;规则的好处是利用精度更高规则生成的特征可以训练出更好的模型。

具体细节如下:

  • 「意图分类」的方法主要有:TextCNN、TextRNN、Transformer(以及这些方法的变种)
  • ER 任务的方法主要有:CRF、BiLSTM-CRF、Transformer(以及这些方法的变种)

NLU 是 pipeline 结构,流水线由几个组件组成的:

  • 语言模型组件
  • 分词组件
  • 特征组件;无论是「意图识别」,还是「实体提取」都需要先有 features。而生成 features 可以采用多种方法的组合,例如:
    • 传统 NLP 方法
    • 现代的 word2vec 方法
    • 基于人工定义的的规则生成 features(高精度)。规则可以是「字典」或者「正则表达式」
  • 实体提取组件;也可以是多种方法:
    • 传统的分类模型(SVMs)
    • 也可能是前面提到的现代分类模型
    • 还可能是基于规则的方法
  • 意图分类组件。可能的方法有:
    • 传统的 ER 模型(HMM)
    • 现代 ER 模型,
    • 基于规则的方法
  • 另外,对话型应用也会接入一个「知识库」,在做「意图分类」时,也会判断(这个判断也是由算法模型+规则综合决定)是否需要从「知识库」里面查询有用的信息给用户(FAQ),如果判断出是要返回「知识库」的结果,DM 会调用知识库查询,然后返回
  • NLU 结果产出组件:产出 JSON 格式的 NLU 结果,其中包括「意图」和「槽位)

NLU 的 pipeline 是顺序执行的;流水线上的组件可插拔(不同的业务场景使用不同类型的组件来工作);但注意,流水线上的有些组件是互斥的(用了一个,就不能用另一个);有些组件可以并行执行。

NLU 实战

下面给一些 NLU 实战例子。

下面配置分别给出了训练所需要的数据(YAML 格式)

  • 「意图」训练数据
  • 「同义词」列表
  • 「字典」(可枚举类型)定义。利用「字典」可以获得更多的 features,模型从而可以得到更加精准的结果
  • 正则表达式定义。利用「正则表达式」可以按照模式匹配规则生成更加精准的 features 来改进 ER 和意图识别的效果

总之,「字典」和「正则表达式」都是规则。这些规则有 2 个用途:

1)可以获得更多的 features,利用这些 features 可以提高 ER 和意图识别的效果。但注意,某个规则返回的 features 只会作为 model 的候选,是否要真正的把这个 features 作为 ER 和意图识别的结果,最终还是取决于 model 根据具体的上下文来做决断

2)直接利用这些规则来提取实体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
nlu:
  - intent: greet
  examples: |
    - hey
    - hello
    - hi
    - hello there
    - good morning
    - good evening
    - morning
    - hey there
    - let's go
    - hey dude
    - good afternoon
  - intent: goodbye
  examples: |
    - ByeBye
    - bye
  - intent: weather
  example: |
    - What's the weather like [tomorrow]{"entity": "date"} in [New York]{"entity": "city", "value": "New York City"}?
  - intent: medicine
  examples: |
    - What medicine should I take if I have a [cold](disease)
    - I am [constipated](disease), what medicine should I take?
  - synonym: bike
    examples: |
      - bicycle
      - mountain bike
      - road bike
      - folding bike
  - lookup: city
    examples: |
      - New York
      - Chicago
      - San Francisco
      - Huston
  - regex: help
    examples: |
      - \bhelp\b

下面是 2 个 NLU 结果的例子(JSON 格式):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
  "text": "show me chinese restaurants",
  "intent": "restaurant_search",
  "entities": [
    {
      "start": 8,
      "end": 15,
      "value": "chinese",
      "entity": "cuisine",
      "extractor": "CRFEntityExtractor",
      "confidence": 0.854,
      "processors": []
    }
  ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
  "intent": {
    "name": "greet",
    "confidence": 0.9968444108963013
  },
  "entities": [],
  "intent_ranking": [
    {
      "name": "greet",
      "confidence": 0.9968444108963013
    },
    {
      "name": "mood_great",
      "confidence": 0.00005138086999068037
    }
  ],
  "text": "hello"
}

DM

Dialogue Management(以后简称 DM)负责记录对话上下文,并相应的对下一步的行动做出选择。

DM 依据对话先前的轮次,决定当前的动作。DM 对于「任务型」对话特别重要,因为「任务型」对话往往需要多轮对话才能完成一个任务。

DM 的任务就是管控对话流程:

  • 当 DM 判断出所有的条件都已经满足时,调用业务接口,或进行查询,获得业务上要求的结果
    • 另外,DM 也可以接入一个知识库(或知识图谱),当用户的意图是 FAQ 时,DM 会查询知识库获取 Answer
  • 反之,当 DM 判断出条件还缺失时,返回提示给用户

所以,DM 需要维护对话 session,或者说 context(其中包含了该多轮对话的所有历史信息)。

具体来说,DM 负责 4 个工作:

  • 对话状态跟踪:根据前一轮次的对话状态和前一轮次的系统动作,更新当前轮次的对话状态
  • 对话策略(dialogue policy):负责根据当前对话状态,相应的产出对话的下一步行动
  • 对话动作(dialogue action):基于对话策略的决定,完成真正的任务动作(一般是取调用业务接口来完成)
  • 对话结果输出:把系统的操作结果以对用户友好的方式返回

配置 DM

因为 DM 如何运行依赖于业务方如何配置 DM。所以,在深入 DM 如果运行之前,先看一下如何配置 DM。

DM 的配置是按 domain 为单位的。

关于 domain 这里我们有 2 个前提:

  • 一个 chatbot 中只有一个 domain
  • 在一个 domain 内,在对话过程中,有可能在该 domain 下的不同 intents 之间切换
    • 当然,在哪些 intents 中切换,甚至不出现切换,是根据「具体业务」来决定的

定义 chatbot 的 DM(单 domain 维度)需要提供 6 种信息 intents、entities、slots、responses、actions、forms。

这 6 种信息简要说明如下(这里先简要介绍,后面会深入它们的细节):

  • intents:该 domain 内,所有可能的 intents(用户想要完成的意图)。intents 由 NLU 识别;这里要注意的是,虽然一个 domain 中有多个 intents 存在,但需要特别指出:
    • 多轮对话中,可能 DM 并不一定会在所有的这些 intents 中切换。可以在哪些 intents 中切换,需要 DM 来决定
    • 考虑一种极端情况:一旦 intent 确定后,就不能再切换到其他 intent 中去
  • entities:该 domain 内所有可能的 entities,也就是用户想要提供的关键实体信息。entities 由 NLU 识别
  • slots:在对话过程中,chatbot 需要跟踪和记录的信息
    • 和 entities 不同,slots 不是由 NLU 填写,而是由 DM 负责填写
    • slots 除了「名字」之外,还需要指明其「类型」和「取值范围」;就「取值范围」来说,有「可枚举」和「不可枚举」2 种
    • 可以通过配置,把 NLU 阶段获得的 entities 填到 slots 中
    • 可以通过配置,把 action 的输出填到 slots 中
  • actions:chatbot 可以实施的动作;actions 就是 DM 的产出物:每一轮,DM 都要产出一个 action 作为结果。action 的更多相关信息,可以参考:对话流程配置
  • forms:定义必须收集的 slots 信息,以及如何填这些 slot 的规则。更多 forms 的配置可以参考:对话流程配置
  • responses:chatbot 响应模板;对话过程中,响应模板的实例化往往由 NLG 模块(可以是一个外部的 NLG 服务)来负责;而 DM 只负责把响应返回给用户

接下来的几个小节会分别介绍「对话流程配置」,以及 DM 的这 6 种关键信息的细节。

对话流程配置

在定义好以上信息之后,配置 DM 的重点配置对话流程配置,也就是要提供对话 stories。

The story is a high-level semantic way of recording conversations. It records not only the expressions from users, but also the correct state change within the system.

例一

配置 Story 需要配置「用户」和 chatbot 之间的对话流程(也就是对话的 steps)。给一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
stories:
  - story: This is the description of one story
    steps:
      - intent: greet
      - action: action_ask_how_can_help
      - slot_was_set:
        - asked_for_help: true
      - intent: inform
        entities:
          - location: "New York"
          - price: "cheap"
      - action: action_on_it
      - action: action_ask_cuisine
      - intent: inform
        entities:
          - cuisine: "Italian"
      - action: restaurant_form
      - active_loop: restaurant_form

上面例子的说明:

  • stories 由多个 story 组成
  • 每个 story 由 2 个字段组成:store 和 steps。store 字段是该 store 的文字描述;steps 表达人机之间的「对话流程」
    • 所谓的「对话流程」就是每次「用户」发送一个消息给「chatbot」,然后「chatbot」做相应的「动作」,再等待下一次「用户」的消息,如此反复,就形成了一个「对话流程」

我们重点描述一下上面例子中的 steps 的定义:

  • 用 intent + entities 字段描述「用户」发送过来的消息。可以参考上面例子中的:
1
2
3
4
      - intent: inform
        entities:
          - location: "New York"
          - price: "cheap"
  • 然后针对这个「用户」发送过来的消息,给出「chatbot」相应的「动作」,以及这个「动作」返回的「事件」。参考上面的例子,对于用户的输入,「chatbot」可能执行 2 个动作:
      - action: action_on_it
      - action: action_ask_cuisine
  • 「chatbot」在执行了「动作」之后,也会改变「对话」的「状态」。「对话状态」的改变也就是「事件」,「事件」是「动作」的结果
    • 所以,在定义「动作」时,也需要定义其对应的「事件」(「对话状态」会怎么改变)
  • 典型的「对话事件」有:「slot events」和「loop events」

  • slot events 会改变 slot 的状态。例如:
1
2
      - slot_was_set:
        - asked_for_help: true
  • loop events 可以被设置为「生效」或「无效」状态。下面是一个「生效」的例子:
1
      - active_loop: restaurant_form
例二
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
stories:
  - story: Process starts
    steps:
      - intent: greet
      - action: action_ask_user_question
      - checkpoint: check_asked_question
  - story: Handle user's confirmation
    steps:
      - checkpoint: check_asked_question
      - intent: affirm
      - action: action_handle_affirmation
      - checkpoint: check_flow_finished
  - story: Handle user's denial
    steps:
      - checkpoint: check_asked_question
      - intent: deny
      - action: action_handle_denial
      - checkpoint: check_flow_finished
  - story: Process ends
    steps:
      - checkpoint: check_flow_finished
      - intent: goodbye
      - action: utter_goodbye

这个例子中,使用 checkpoint 字段实现了 story 之间的跳转:

  • 「Process starts」这个 story 可以跳转到另外 2 个 story:
    • 当用户回复「肯定」时,跳转到「Handle user’s confirmation」story
    • 当用户回复「否定」时,跳转到「Handle user’s denial」story
  • 最后,再都跳转到「Process ends」story

使用 checkpoint 字段可以简化 stories 的配置,但也不能过度使用,否则会造成配置过于复杂,难于维护。

actions

专门说一下 actions 的细节(actions 的输入和输出)。

  • 每个 action 的输入包括:用户输入、对话状态、对话历史
  • 每个 action 的输出可以包含以下 4 种产出物中的一种或多种:
    • 1)用户响应
    • 2)forms(收集必须的 slots 信息)
    • 3)对话状态修改;这里说的对话状态可以被修改为:「等待用户输入而不是由 DM 来判断 action」、「清空session,重启对话」、「对话开始」、「退回到上一轮对话状态」、「NLU 识别失败时,转到请用户再说一次的状态」……
    • 4)外部接口调用

也专门说一下 forms 的细节:

  • forms 定义必须收集的 slots 信息,以及如何填这些 slot 的规则。例如,可以是把 NLU 阶段得到的实体 map 到 slot
  • 除此之外,还要定义什么场景下「激活 form 开始工作」。例如,可以设置为一旦某个 intent 下,自动激活某个 form

最后讨论 intents 如何切换的细节:

  • 可以由人工来定义 intents 直接的切换规则。例如,在一个 step 结束时,可以指定一个规则,在这个 step 完成之后,切换到另外一个 story 去,而这个新的 story 可能是另外一个 intent。
  • 但人工定义 intents 切换规则的一个问题是,随着规则越来越多,切换规则也就越来越复杂,后期难以维护。

slots

slots 用于记录对话过程中的关键信息,一般是 KV 对的形式。

例如,一个天气查询的对话任务,对话过程中,「chatbot」必须收集 2 个关键信息,也就是 2 个 slots:地点、时间。

同时,slots 需要有「值」和「类型」(类型规定了这个值的取值范围及其支持的运算类型)。常用的 slots 类型有:text、bool、float、list、any……

slots 的值可以影响到「对话」的「运行流程」。

slots 的值可以从用户的输入获得(例如 intents 或 entities);也可以从 actions 执行的结果获得(例如调用外部服务获取天气信息)。

form

form 定义了该 intent 所需要的所有 slots。

简单例子:

1
2
3
4
5
6
7
8
forms:
  weather_form:
    address:
      - entity: address
        type: from_entity
    date-time:
      - entity: date-time
        type: from_entity

该 form 指明了需要 2 个必须的 slots:address 和 date-time。

但如何把 NLU 阶段收集到的信息(intents + 实体)填入到 form 指明的 slots 呢?我们可以在对话流程配置中定义填入规则(也就是所谓的「slot mapping functions」)。上面例子中的「slot mapping functions」就是:from_entity,也就是直接把 NLU 阶段识别到的「同名实体」填入 slots。

在配置好 form 之后,还需要在对话流程配置中指明什么时候启用该 form,开始 slots 的收集工作(默认 form 是不工作的,不然 DM 执行起来后就开始收集 slots 的工作)。

fallback

chatbot 不能处理的「用户输入」始终存在,所以我们专门用一节来说明一下当遇到不能处理的「用户输入」时,chatbot 要怎么工作,也就是所谓的 fallback

有 2 类 fallback:NLU Fallback,和 Policy Fallback。

针对 NLU Fallback,我们可以对 chatbot 的 NLU 进行配置,例如:

1
2
3
- FallbackClassifier
  threshold: 0.6
  ambiguity_threshold: 0.1

主要就是设置了 2 个 threshold:NLU 识别出 intents 的阈值为 0.6;识别出不止一个 intents 的模糊阈值为:0.1。一旦触发了这 2 个 thresholds 的阈值,NLU 就会把当前 intent 识别为一个特殊 intent:Fallback。

然后在根据事先配置好的,和 Fallback 意图对应的 action,chatbot 就会执行对应的 Fallback 动作,例如:

1
2
3
4
5
rules:
  - rule: Ask user to speak again
    steps:
    - intent: nlu_fallback
    - action: utter_please_rephrase

也就是当 fallback 时,utter_please_rephrase 这个 action 被触发。

而 Policy Fallback 就是在对话流程配置时,直接指定对话执行到某一步时,触发 fallback。这种场景较简单,就不细说了。

执行 DM

在了解了 DM 的配置后,再说明一下 DM 运行时的细节。

DM 运行时的核心是 Policy 模块。

我们会把 DM 配置好的各个 stores 转换成「对话状态」,然后得到这些「对话状态」的特征。

而 Policy 模块工作时按照「对话状态」的特征来预测对话的下一个 action。常用的 Policy 实现有:

  • TEDPolicy:Transformer Embedding Dialogue
  • MemoizationPolicy:把配置存在「字典」中,然后通过查字典的方式来采取相应的 action
  • AugmentedMemoizationPolicy:MemoizationPolicy 的基础上加上随机步骤,使得 DM 运行起来合理
  • RulePolicy:DM 完全基于各种跳转配置来运行

工程上,以上的 Policy 可能会被同时使用,但会根据优先级计算出真正采用哪个 actions。各个 policies 的优先级为:

RulePolicy > MemoizationPolicy > TEDPolicy

RulePolicy

例一:

1
2
3
4
5
rules:
- rule: mapping from some_intent to some_action
  steps:
  - intent: some_intent
  - action: some_action

也就是一旦 NLU 识别出特定的 intent 就触发指定的 action。

NLG

NLG 负责返回给用户响应内容,一般来说是按文本的形式返回。

NLG 目前项目中的做法是按照业务规则把要返回给用户信息提入到实现配置好的 template 作为响应,回复给用户。

现在也在探索不用规则,而是通过训练得到模型来决定返回什么样的响应给用户。

本文由作者按照 CC BY 4.0 进行授权