• 语音助手是这样子的(二)


    前一节我们介绍了语音助手的基本框架与核心技术,本节我们将优先介绍使用OpenDial来设计对话的管理与流程。OpenDial的功能很强大,可以实现NLU、DM以及NLG的所有功能,在官网上也有一系列的教程。在本系列博客中,我们仅仅使用OpenDial来进行对话管理(包括NLG),而把NLU剥离出来单独实现。

    再来看场景

    前一节中我们给出了一个设置闹钟的场景,现在让我们重新使用OpenDial的视角来看一下这个场景。

    用户:设置闹钟

    Siri:请问您需要设置几点的闹钟?

    用户:明天早上六点的

    Siri:好的,已经把闹钟设置到了明天早上六点

    在这个场景中,如果我们稍作抽象则可以这样来看,“设置闹钟”可以抽象为一个动作叫做SetAlarm,而这个动作是来自用户的,我们定义其为a_u(action of user),而“设置闹钟”是这个动作的一种语言表达u_u(utterance of user),语言的多样性允许用户使用其他的表述如“请为我设置一个闹钟”等等。同样,Siri的答复和操作也可以抽象,系统的答复“请问您需要设置几点的闹钟?”是u_m(utterance of machine)的一种语言表达,而询问设置闹钟的具体时间可以抽象为一个叫做RequestTime的动作,即a_m(action of machine)是RequestTime。

    如上的这些抽象其实是按照OpenDial的规范进行描述的,那么接下来我们就来研究如何把这样的一个对话场景设置到OpenDial中。

    OpenDial

    OpenDial是一个使用概率规则与贝叶斯网络实现的开源对对话系统引擎。读者可以参考OpenDial用户手册中的介绍完整地学习OpenDial的使用,这里我们只针对本文中的场景介绍其简单的使用与配置。

    首先,启动OpenDial的可视化工具(.scriptsopendial.bat),新建一个领域“Domian -> New”,文件命名为Alarm.xml,点击保存后在“Domain Editor”的标签页里可以设计对话状态了。

    OpenDial Example 1

    OpenDial的配置文件按如下的规范进行构造,一个场景被认为是一个domain,一个domain中包含多个model,每个model由一个trigger触发,通常我们设计三个model分别对应NLU,DM和NLG。NLU的model由u_u触发,DM的model由a_u触发,NLG的model由a_m触发。所以,按照一次对话是由NLU -> DM -> NLG这样的流程形成的思想,我们很自然地可以想象到u_u通过一定的规则(rule)条件(case)触发NLU model,同时在NLU model中设置a_u变量,这样a_u就可以根据一定的规则条件触发DM model,此时只需再在DM model中设置a_m变量,就可以顺利地到达NLG model,从而把需要响应用户的回复设置到u_m中。所以,样例场景就可以配置为如下的形式:

    <?xml version="1.0" encoding="GB2312"?>
     <domain>
      <model trigger="u_u">
        <rule>
          <case>
            <condition>
              <if var="u_u" relation="=" value="设置闹钟"/>
            </condition>
            <effect prob="1">
              <set var="a_u" value="SetAlarm"/>
            </effect>
          </case>
          <case>
            <condition>
              <if var="u_u" relation="=" value="明天早上六点的"/>
            </condition>
            <effect prob="1">
              <set var="a_u" value="InformTime"/>
            </effect>
          </case>
        </rule>
      </model>
      <model trigger="a_u">
        <rule>
          <case>
            <condition>
              <if var="a_u" relation="=" value="SetAlarm"/>
            </condition>
            <effect prob="1">
              <set var="a_m" value="RequestTime"/>
            </effect>
          </case>
          <case>
            <condition>
              <if var="a_u" relation="=" value="InformTime"/>
            </condition>
            <effect prob="1">
              <set var="a_m" value="ToDoSetAlarm"/>
            </effect>
          </case>
        </rule>
      </model>
      <model trigger="a_m">
        <rule>
          <case>
            <condition>
              <if var="a_m" relation="=" value="RequestTime"/>
            </condition>
            <effect prob="1">
              <set var="u_m" value="请问您需要设置几点的闹钟?"/>
            </effect>
          </case>
          <case>
            <condition>
              <if var="a_m" relation="=" value="ToDoSetAlarm"/>
            </condition>
            <effect prob="1">
              <set var="u_m" value="好的,已经把闹钟设置到了明天早上六点"/>
            </effect>
          </case>
        </rule>
      </model>
    </domain>
    

    保存如上的配置,在OpenDial工具中切换到“Interaction”标签页,就可以按照场景中的方式与OpenDial进行对话了。
    OpenDial Example 1

    OpenDial还有很多功能,比如支持正则表达式、可以设置变量的概率分布等,在这里我们不多介绍,感兴趣的读者可以参考官方指南。

    归一化表达

    我们说u_u由于语言表达的多样性存在很丰富的实例,我们不可能把所有的表达都枚举到OpenDial的配置中,而且诸如“明天早上六点”这样的时间信息,是需要自动传递到下一轮对话的,而不应该是固定写死在配置文件中的。所以,我们将使用一种称为“槽位填充(Slot Filling)”的思想来解决上述的问题。

    槽位是来自于自然语言处理中经常使用的规则模板的一个概念,它是对相同语义概念的表达的一种归一化表示。相对的,如果能填充到槽位里的表达就叫做相应的实例化表述。我们直观地看一个例子:

    【D:set】【D:clock】   =>   设置闹钟

    【F:time】的   =>    明天早上六点的

    我们用【】括起来的部分就是槽位,【D:set】表示的是可以表达“set”这个含义的词,我们需要收集“设置”、“设定”等同义词并且把这些词都定义为“set”,同理可以理解【D:clock】;【F:time】是可以识别时间的一个函数。那么,显然除了例子中的实例表述可以归一化为上面定义的槽位模板,“设定闹铃”也是可以被归一化为相同的槽位模板,“晚上七点的”也一样。所以,多种表述在这里可以被归一化为用槽位模板表达的形式,这样我们可以一定程度上减少在OpenDial中配置太多样的u_u。反过来,当用户输入的实例被泛化为槽位模板的表达时,就意味着每个槽位被相应的字符串填充了,此时我们取出槽位对应的字符串就实现了对关键信息的提取,那么a_m所要采取的动作就有了依据,而且u_m生成的回复也有了参考。

    本节,我们介绍了借助OpenDial实现对话管理的功能,重点讲解了如何配置该模块的xml文件,最后引入了归一化表达的思想,就是想实现NLU与DM的分离,使OpenDial只专注于DM。下一节,我们将详细介绍实现NLU的方法。

  • 相关阅读:
    freopen stdout 真的更快?
    【评分】第二次作业——个人项目实战
    【评分】第二次作业-数独-第一次测试成绩
    姑娘你大胆地往前走——答大二学生XCL之八问
    第二次作业-数独-初步测试日志
    第二次作业——个人项目实战
    关于C#的随机数
    必须展示窗口才能截图怎么办,伪后台截图思路
    Winform 奇怪的 英文字体错乱显示问题
    wpf 解决 WPF SelectionChanged事件向上传递造成重复执行不想执行的函数的问题
  • 原文地址:https://www.cnblogs.com/rouseway/p/8150983.html
Copyright © 2020-2023  润新知