看,不用键盘!使用固定文法的语音输入和响应

使用 SRGS 指导构建语音识别声学模型文法和使用此文法的对话管理器

对于为语音识别模型定义非自然语言文法,存在多种普通文本、特定于应用程序的格式。程序员不仅使用语音识别文法规范(Speech Recognition Grammar Specification,SRGS)来以一种开放标准结构表达这些格式,也用它来为解释识别模型生成的输出所必需的对话管理器定义规则。本文在一种特定于非自然语言的文法上下文中,使用 PHP 探究 SRGS 和语音识别的语义解释(Semantic Interpretation for Speech Recognition,SISR)之类的方法。

Colin Beckingham, 研究人员, 自由职业者

Colin Beckingham 居住在加拿大安大略省,是一位自由研究人员、作家和程序员。他拥有金斯顿皇后大学的学位,对园艺、赛马、教育、公共服务、零售和旅游/观光领域都有涉猎。他是数据库应用程序的作者,也是大量报纸、杂志和在线文章的撰稿人,他的研究兴趣包括 Linux ® 上的开源编程以及语音控制应用程序。



2010 年 12 月 06 日

语音识别的现状

常用缩写词

  • DTD:文档类型定义
  • HTK:隐马尔可夫模型工具包
  • URI:统一资源标识符
  • W3C:万维网联盟
  • XML:可扩展标记语言

随着计算机尺寸的变小和便携性的提高,不使用键盘或鼠标来与之交互的需求也越来越强烈。语音是一种备选方案。大体上来说,语音通信使用的带宽比视频交互使用的带宽要小得多。在人们的印象中一幅图片等同于千言万语,因此通过计算机显示器响应机械外围输入比起接受音频输入并以类似方式进行响应更易于为人所接受。

成功的技术已经存在,但是仅限于用语音向较小的计算机发出指令。目的是让计算机对语音做出响应,并根据命令采取特定的操作。达到此目的的一般过程是,构建(或采纳)一个模型,在识别过程中对该模型应用一个口头命令,然后在对话管理器中决定一个操作。从仅用少量命令识别各种语音方面来讲,模型的范围很广,您也可以根据特定的文法训练您的模型。文法使得复杂的解译和交互成为可能。图 1 展示了语音识别和解译开发流程。

图 1. 语音识别和解译开发流程
语音识别和解译开发流程图

一定要对自然语言处理 (NLP) 和特定的 n 元语法加以区别。后者直接了当地说明识别器预期会听到什么,前者则擅长于通过丢弃一些接收到的语音元素并重新排列其他元素将自然语言解码成一种更简单的结构。尽管诸如 SRGS 和 SISR 之类的技术主要偏向于自然语言的处理,但是程序员们也在寻找将这些工具用于其他类型文法的途径。

利用一组 2-gram(或二元语法)示例作为例子,本文使用了定义固定文法的 SRGS 方法,并解决了对话管理器中出现的未登录词(out-of-vocabulary,OOV)提示和上下文等问题。


编程角度

从编程角度来看,有一些问题很重要。首先,您可以用很多不同的方式表达一种文法,具体取决于您使用的是哪个语音识别器应用程序。其次,这些文法与使用它们的对话管理器不太协调。对话管理器很重要,因为它们提供处理智能响应和 OOV 识别等问题的方式。再次,因为您是在谈论旨在做某项特定工作的特定文法,所以您必须为每种文法重写或采纳模型和对话管理器。您越能自动生成它们,就越好。

智能响应意味着计算机将上下文考虑在内。如果您的问题是询问温度,并且前一个问题是关于计算机的,那么您需要的肯定是计算机的温度。

OOV 是小文法中的一个常见问题。简单来说,一些提示很重要,另外一些提示对于构建模型是必需的,但是对于以后的处理不重要。

假设存在清晰的规则,那么通过使用脚本(比如 Bash、Perl 和 PHP)或常规编程语言(比如 C 和 C++),自动生成是很直观的。SRGS 旨在封装这些规则。


一个示例

清单 1 是一个普通文本文法的原型。

清单 1. 文法原型
COMPUTER WAKE
COMPUTER STATUS
COMPUTER SLEEP

清单 1 中的文法相当简单而明确。它告诉计算机,它只听到三种可能的提示。每个提示以单词 COMPUTER 打头,后面可以是 WAKESTATUSSLEEP。其他命令都不可能。语音识别器只有一项工作,就是从三个选项中选择它认为最接近所听到内容的选项,并将该命令传递到下一阶段。例如,如果我说 MAKE COFFEE,那么它返回 COMPUTER 加上三个备选单词中的一个。对话管理器应该应用一些智能。例如,如果听到 COMPUTER SLEEP,那么它应该不再响应任何命令,直到它听到 COMPUTER WAKE。只有在 WAKE 状态时(此时,它可以宣布处理器温度、可用的磁盘空间以及所有其他有关的内容),它才应该响应 COMPUTER STATUS。无论如何它不是一个实际的文法 — 通过这么小的文法构建语音模型时,您很快就会遇到样例不够的问题。此原型仅用于演示基本原理。

训练一台计算机识别语音以及向它听到的内容应用文法规则,是一个相当直观的过程,即使在开源世界也是如此。关于如何使用固定词汇表获得高效语音识别系统的完整指南,请参考 VoxForge 网站。VoxForge 教程使用的工具有剑桥大学的 HTK 和日本名古屋大学的 Julius 语音识别引擎。参考资料 中有到所有这些网站的链接。

利用 HTK 构建一个音频模型需要您用特定的格式表达文法,如 清单 2 所示。

清单 2. HTK 格式的文法
$major =  COMPUTER ;
$minor = WAKE | STATUS | SLEEP ;
( SENT-START ( $major $minor ) SENT-END )

利用 Julius 引擎完成此过程需要一种稍微不同的格式,如 清单 3 所示。

清单 3. Julius 格式的文法
S : NS_B SENT NS_E
SENT: MAJOR MINOR
MAJOR: COMPUTER
MINOR: WAKE STATUS SLEEP

从编程的角度来看,HTK 和 Julius 格式的结构类似,但是二者也有不同之处,不可相互交换。

清单 4 展示了一个用 PHP 编写的基本对话管理器,可以处理该文法。

清单 4. 一个普通对话管理器
<?php
...
function dm($prompt_heard) {
global $wake_state; // FALSE is asleep so do not respond, TRUE is awake
  $parts = explode(" ",$prompt_heard);
  $minor = $parts[1];
  switch ($minor) {
    case 'WAKE':
      $wake_state = TRUE ;
    break;
    case 'SLEEP':
      $wake_state = FALSE ;
    break;
    case 'STATUS':
      if ($wake_state) {
        announce_status();
      } else {
        // do nothing
      }
    break;
    default:
      // OOV - any other prompt, just ignore it.
    break;
  }
}
?>

这个 PHP 函数将来自识别器的结果作为一个参数传递到 $prompt_heard 中。$wake_state 变量被声明为一个全局变量,在整个对话管理器中有效。explode() 分解听到的提示部分。在本例中,您知道第一部分会是 COMPUTER,所以下面的 switch 只关注 minor 部分。如果是 WAKE,那么将清醒状态设置为 TRUE。如果是 SLEEP,那么将清醒状态设置为 FALSE。如果 minor 部分是 STATUS,那么宣布结果,但是只有在清醒状态是 TRUE 时才这么做。您可以使用类似于 developerWorks 文章 “PHP bees and audio honey: Accessible agent-based audio alerts and feedback”(参见 参考资料)中探究的过程向扬声器宣布状态。默认的部分将捕获您为体验识别器而添加的任何附加提示 — 对于未识别的提示不做任何事情,但是有可能写到日志中以备以后进行故障诊断。

从编程效率角度来讲,如果您可以哪怕是部分地自动生成文法和对话管理器,也会节省时间和精力,还能提高准确性。创建更高级别文法来做这件事(参见 参考资料)在很程度上属于 NLP 领域。


SRGS 如何提供帮助

SRGS 旨在表达与 清单 2清单 3 中相同的思想,但是使用的是 XML 提供的更为严格的结构,如 清单 5 所示。

清单 5. SRGS 格式的文法原型
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE grammar PUBLIC "-//W3C//DTD GRAMMAR 1.0//EN"
                  "http://www.w3.org/TR/speech-grammar/grammar.dtd">
<grammar xmlns="http://www.w3.org/2001/06/grammar" xml:lang="en"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xsi:schemaLocation="http://www.w3.org/2001/06/grammar 
                             http://www.w3.org/TR/speech-grammar/grammar.xsd"
         version="1.0" 
         mode="voice" 
         root="myroot">
<meta name="author" content="Colin Beckingham"/>
<rule id="myroot" scope="public">
  <example> COMPUTER WAKE </example>
  <example> COMPUTER SLEEP </example>
  <example> COMPUTER STATUS </example>
  <ruleref uri="#command1"/>
  <!-- ruleref uri="#command2"/ -->
</rule>
<rule id="command1">
  <ruleref uri="#major"/> <ruleref uri="#minor"/>
</rule>
<rule id="major">
   <one-of>
      <item> COMPUTER </item>
    </one-of>
</rule>
<rule id="minor">
    <one-of>
      <item> WAKE </item>
      <item> SLEEP </item>
      <item> STATUS </item>
    </one-of>
</rule>
</grammar>

清单 5 中首先展示常规的 XML 声明,然后是一个定位 DTD 的 DOCTYPE 语句,在本例中是指向与文法相关的详细信息。然后再是根元素 <grammar>。这个 grammar 元素包含很多重要的属性,比如说名称空间、模式(在本例中是 voice,因为数据的目的地是语音识别器)以及根规则的 ID(这是寻找匹配提示时开始的地方,在本例中是 myroot)。myroot 规则包含指向其他规则的 rulerefs。DTD 支持用于信息的 <example> 元素。然后是 ID 为 command1 的规则。后面是另外两个规则,即 majorminormajor 规则包含一个单词 COMPUTERminor 规则包含备选的 WAKESLEEPSTATUS。最后是 <one-of> 结构的 <item> 元素,表示一次能够且只能够应用一个规则。使用根规则的 <ruleref> 元素,major 和 minor 规则成了整个规则结构的一部分。

简要来说,规则遵循以下结构:

  1. 文法属性 root 指向根规则 myroot
  2. myroot 规则包含一个或多个 rulerefs,每个都有一个指向另一规则(本例中是 command1,它是一个 n 元语法的 master rule)的 URI。command2ruleref 被注释掉了,因为它只是一个还不存在的主规则的占位符。
  3. 主规则 command1 包含几个 ruleref,表示使用了其他规则(前面是 major,后面是 minor)。

HTK 和 Julius 的模型生成过程不是 W3C 术语中的用户代理,因为它们当前不直接读取 SRGS 格式,而是使用 SRGS 来为脚本定义文法调用,以从 SRGS 转换成其他格式。此外,由于没有足够的信息,HTK 和 Julius 都不生成对话管理器。那么对话管理器如何可以猜出 SLEEP 意味着停止响应呢?


从 SRGS 转换成 HTK 或 Julius

清单 6 是一个用 PHP 编写的简单转换器,它把 SRGS 版本的文法转换成等价的基本 HTK 或 Julius 格式的文法。

清单 6. 转换器
<?php
// test translator: SRGS to HTK/Julius
$xml = simplexml_load_file("mysrgs.xml");
$roote = trim($xml['root']);
// find the root rule, get the basic rulerefs
foreach ($xml->rule as $rule) { 
  if ($rule['id'] == $roote) { 
    foreach ($rule->ruleref as $rref) {
      $rulerefs[] = substr($rref['uri'],1);
} } }
// find the rules indicated by the rulerefs
foreach ($rulerefs as $ruleid) {
  foreach ($xml->rule as $rule) {
    if ($rule['id'] == $ruleid) {
      $i = 0;
      foreach ($rule->ruleref as $rref) {
        $myrules[$ruleid][$i] = substr($rref['uri'],1);
        $i++;
} } } }
// load the words array
foreach ($myrules as $mr=>$myr) {
  foreach ($myr as $md=>$myd) {
    foreach ($xml->rule as $rule) {
      if ($rule['id'] == $myd) {
        foreach ($rule->{'one-of'}->item as $item) {
          $words[$mr][$md][] = $item;
} } } } }
// now the output
$varnamestr .= "";
$jvarnamestr = "";
foreach ($words as $k=>$v) { // master
  $jvarnamestr .= "\nSENT: ";
  foreach ($v as $kk=>$vv) { // sub
    $i = 0;
    foreach ($vv as $wd) { // word
      $j = count($vv);
      $htkwds[$k][$kk] .= " $wd ";
      $i++;
      $jwds[$k][$kk] .= " $wd ";
      if ($i < $j) $htkwds[$k][$kk] .= "|";
    }
    // htk
    $varname = "$".$k."_".$kk."";
    $htkv .= "$varname = ".$htkwds[$k][$kk].";\n";
    $varnamestr .= " ".$varname;
    // julius
    $jvarname = strtoupper($k."_".$kk);
    $jvarwordstr .= "$jvarname: ".$jwds[$k][$kk]."\n";
    $jvarnamestr .= " ".$jvarname." ";
  }
  $varnamestr = $varnamestr." |";
}
// output as HTK
echo "\n-------------------------\n";
echo "HTK Version\n-------------------------\n";
$varnamestr = substr($varnamestr,0,-2);
$htk = $htkv."( SENT-START (".$varnamestr." ) SENT-END )";
echo "$htk\n-------------------------\n";
// output as Julius
echo "Julius Version\n-------------------------\n";
$julius = "S : NS_B SENT NS_E";
$julius .= "$jvarnamestr\n";
$julius .= "$jvarwordstr";
echo "$julius-------------------------\n";
// end
echo "Done\n\n";
?>

清单 6 中程序的整体目标是扫描 SRGS 文档,并用表示提示结构的 SimpleXML 对象填充一个多维数组。数组完成时,程序就从这个数组生成所需的字符串变量,并输出为 HTK 和 Julius 格式。数组是用一系列 foreach 语句填充的,该语句挑出根规则、主规则以及主规则引用的规则。结果是一个数组,其中第一个键值是主规则的名称,第二个键值是位置 — 0(主要部分)或 1(次要部分)。$i$j 变量是计数器,控制垂直竖线 (|) 的添加,这个竖线是 HTK 格式中的一个 OR 符号。最后,输出使用从主规则的单词和 ID 创建的变量。清单 7 是一个样例会话的输出。

清单 7. 转换器输出
> php mytrans.php

-------------------------
HTK Version
-------------------------
$command_0 =  COMPUTER ;
$command_1 =  WAKE | SLEEP | STATUS ;
$zoo_0 =  ANIMALS ;
$zoo_1 =  TIGER | LION | LEOPARD ;
( SENT-START ( $command_0 $command_1 | $zoo_0 $zoo_1 ) SENT-END )
-------------------------
Julius Version
-------------------------
S : NS_B SENT NS_E
SENT:  COMMAND_0  COMMAND_1
SENT:  ZOO_0  ZOO_1
COMMAND_0:  COMPUTER
COMMAND_1:  WAKE  SLEEP  STATUS
ZOO_0:  ANIMALS
ZOO_1:  TIGER  LION  LEOPARD
-------------------------
Done

此代码带您回到清单 23 的格式。出于测试目的,添加了第二个主规则,以确保它处理多个主规则。注意,这是一个基本的转换器,不处理重复(例如,就像电话号码,可以重复一组数字)。您仍然需要先根据所选的词汇表、词典和音素结构定义其他文件,然后才可以构建模型,但是这至少给了您一个起点。请参考 参考资料 中的 VoxForge 教程,以获得进一步的指导。


对话管理器生成器

程序员现在转向对话管理器。如果您至少可以从 SRGS 来源生成一部分对话,那也是不错的。如果您使用一种上下文无关的文法,那么 n 元语法的结构(参见 参考资料)可能是您所需要的。在这个当前的 n 元语法情景中,文法是固定的。文法包含四个单词,并且使用这些单词只给出三种可能的答案。

尽管仍然严格保持标准,但是 SRGS 定义允许您添加两个在生成对话管理器过程中有益的细节。首先,它允许向 <item> 元素添加一个 weight 属性(作为一个整数或小数)。其次,它允许向规则添加 <tag> 元素(作为可包含任意字符串的子元素)。这些通常是 ECMAScript (JavaScript) 表达式。它们通常用于向浏览器中的 NLP 解析器发出 SISR 指令,但是在本例中,它们可能适用于您向对话管理器生成器发送提示。

您已经有了一些来自文法的信息:二元语法格式需要两个 switch 语句,这两个语句是嵌套在主要部分中的次要部分。这非常直观。但是上下文和 OOV 比这稍微复杂一点。此提议使用 weight 属性来处理 OOV 和 tag 元素,以处理上下文。

清单 8. 带有对话管理器指令的增强的 SRGS
...
<item weight="1"> COMPUTER <tag>$context = "tech";</tag></item>
...
<item weight=""> WAKE <tag>$wake_state = TRUE;</tag></item>
<item weight=""> SLEEP <tag>$wake_state = FALSE;</tag></item>
<item weight=""> STATUS <tag>
      if ($wake_state) {
        announce_status();
      }</tag></item>
...      
<item weight="0"> ANIMALS <tag></tag></item>
...

注意,主 COMPUTER 权值为 1,但是 ANIMALS 权值为 0。在此上下文中,您想要 COMPUTER * 被识别,但是 ANIMALS * 被作为 OOV 忽略。此外,tag 元素包含生成器可以插入的 PHP 代码片段。

清单 9 中所示对话管理器生成器的目标是构建一个类似于 清单 4 中的 dm() 函数。

清单 9. 对话管理器生成器
<?php
// Dialog manager generator
// Colin Beckingham, 2010
// test dmgen
$xml = simplexml_load_file("mysrgs.xml");
$roote = trim($xml['root']);
// find the root rule, get the basic rulerefs
foreach ($xml->rule as $rule) { 
  if ($rule['id'] == $roote) { 
    foreach ($rule->ruleref as $rref) {
      $rulerefs[] = substr($rref['uri'],1);
} } }
// find the rules indicated by the rulerefs
foreach ($rulerefs as $ruleid) {
  foreach ($xml->rule as $rule) {
    if ($rule['id'] == $ruleid) {
      $i = 0;
      foreach ($rule->ruleref as $rref) {
        $myrules[$ruleid][$i] = substr($rref['uri'],1);
        $i++;
} } } }
// load the words array
foreach ($myrules as $mr=>$myr) {
  foreach ($myr as $md=>$myd) {
    foreach ($xml->rule as $rule) {
      if ($rule['id'] == $myd) {
        foreach ($rule->{'one-of'}->item as $item) {
          $words[$mr][$md][] = $item;
} } } } }
$dmout1 = "<?php
...
function dm(\$prompt_heard) {
global \$wake_state; // FALSE is asleep so do not respond, TRUE is awake
  \$parts = explode(\" \",\$prompt_heard);
  \$major = \$parts[0];
  \$minor = \$parts[1];
  switch (\$major) {";
    foreach ($words as $mw) {
      $mww = $mw[0][0]->attributes();
      if ($mww->weight == 1) {
$maj = "    case '".trim($mw[0][0])."':\n      ";
$maj .= "".$mw[0][0]->tag."";
$min1 = "\n      switch (\$minor) {";
        $ins .= "\n$maj$min1";
        foreach ($mw[1] as $mm) {
$min2 = "\n        case '".trim($mm)."':\n";
$min3 = "        ".$mm->tag."\n";
$min4 = "        break;";
        $ins .= "$min2$min3$min4";
      }
$min5 = "\n        case default:\n        break;\n      }";
        $ins .= $min5;
      }
    }
$dmout2= "
    default:
      // OOV - any other prompt, just ignore it.
    break;
  }
}
?>
";
echo "$dmout1$ins$dmout2\n";
?>

清单 9 中代码的前半部分跟 清单 6 中转换器的代码完全相同。这种重复是有意的,因为文法和对话管理器的生成最终都可以在同一个程序中完成。已经利用 SimpleXML 对象建立了 $words 数组,现在您可以扫描这些对象,并找出条目以及权值和标记的值。输出介绍性静态代码之后,对话管理器生成器遍历 SimpleXML 对象,期间,在需要时会以 PHP 代码格式呈现输出以及嵌套 switch 语句。

清单 10 展示了来自测试源 SRGS 文件的一些示例输出,该文件包含三个主规则,其中一个规则应该被作为测试数据忽略掉。

清单 10. 生成器输出
> php dmgen2.php

<?php                                                          
...                                                            
function dm($prompt_heard) {                                   
global $wake_state; // FALSE is asleep so do not respond, TRUE is awake
  $parts = explode(" ",$prompt_heard);                                 
  $major = $parts[0];                                                  
  $minor = $parts[1];                                                  
  switch ($major) {                                                    
    case 'COMPUTER':                                                   
      $context = "tech";                                               
      switch ($minor) {                                                
        case 'WAKE':                                                   
          $wake_state = TRUE;
        break;
        case 'SLEEP':
          $wake_state = FALSE;
        break;
        case 'STATUS':
          if ($wake_state) { announce_status(); }
        break;
        case default:
        break;
      }
    case 'APPLES':
      $context = "fruit";
      switch ($minor) {
        case 'PIPPIN':
          pippin apple
        break;
        case 'DELICIOUS':
          delicious apple
        break;
        case 'SPARTAN':
          spartan apple
        break;
        case default:
        break;
      }
    default:
      // OOV - any other prompt, just ignore it.
    break;
  }
}
?>

产生该输出的文件可在 下载 处得到。


结束语

利用 SRGS 可以声明固定文法的需求及其在 NLP 中的常见角色,为文法和对话管理器文件的生成提供一个中心位置。通过使用 weight 属性来定义一个主规则是否被检测,以及使用 <tag> 元素来指示对话管理器在检测到特定提示时采取什么动作,文法和对话管理器的自动生成更为严格而高效。

只通过语音与计算机交互相比起使用各种硬件输入设备和利用可视反馈监视计算机状态来说,是一项更为艰难的工作。作为语音程序员,一个主要的目标应该是让用户通过语音与计算机的交互更简单 — 尤其是对那些除了用语音和耳朵之外别无选择的人。尽管这里给出的文法和对话管理器生成器还远远谈不上简单,但是它们可以使得制造简单、易用的工具更为容易。


下载

描述名字大小
样例文件,使用 PHP 体验 SRGS 和 SISRsrgsphp.zip3KB

参考资料

学习

获得产品和技术

  • Julius:是语音相关研究人员和开发人员使用的一个高效的、双道的大词汇连续语音识别(LVCSR)解码软件。基于单词的 N 元语法和上下文相关的 HMM。
  • HTK:这是一个用于构建和操纵隐马尔可夫模型的便携式工具包。HTK 主要用于语音识别研究,也用于语音合成、字符识别和 DNA 排列等研究。
  • IBM 产品评估试用版软件:下载或 在线试用 IBM SOA Sandbox,并开始使用来自 DB2®、Lotus®、Rational®、Tivoli® 和 WebSphere® 的应用程序开发工具和中间件产品。

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=XML, Open source
ArticleID=594131
ArticleTitle=看,不用键盘!使用固定文法的语音输入和响应
publish-date=12062010