调整音频模型以实现更好的语音识别

使用一些策略和工具微调音频输入的准确性

处理一个准备不充足的音频模型可能会令人感到沮丧,特别是对于语音识别领域的初学者,他们习惯使用自己的口音相关模型。不像键盘和鼠标输入那样行动相对比较积极且易于操作系统解释,将音频输入到语音识别器就不那么积极了,严重依赖音频模型的广度和深度。编程人员可以通过提供工具来简化分析识别错误过程。一个合理的目标是,将错误率从 5/10 减少到不到 1/1000:了解如何使用 Python 和 PostgreSQL 构造的工具。

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

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



2012 年 7 月 23 日

术语

  • 语法:可供语音识别器选择的单词或单词组合的有限集
  • 字素:单词书写或打印的方法
  • 词素:单词书写或打印方法的一般性描述
  • 词典:一个词素列表;在本文中是一个文件,每行含一个单词的拼写和发音
  • 音素:一个声音的标记;几个音素串合起来代表一个单词的读音
  • PLS:参阅 参考资料 的 Pronunciation Lexicon Specification 部分,获取关于这些术语的详细信息

跟随 VoxForge 教程学习,使用诸如 Hidden Markov Model Toolkit (HTK)、Julius 和 CMU Sphinx(参阅 参考资料 获取这些工具的详细信息)等工具,就可以轻松地创建一个简单的口音相关 (speaker-dependent) 的音频模型实现语音识别。只需几个小时,您就可以拥有属于自己的 2001: A Space Odyssey HAL 9000 电脑,它可以响应您的命令并和您交谈,如果您离开工作站去浴室,几乎不可能发现自己的房门被锁上了。

然而,有时候,令初学者感到惊讶的是,他们第一个模型识别的准确度简直无法接受。他们很容易就放弃了对声音控制的进一步研究,认为该领域还没有发展到一个尚可接受的准确率,这完全脱离了现实。

此外,甚至更严重的是,您发现的错误越多,您就越难集中精力地对着麦克风认真讲话以调整明显停止不前的语音识别器,就越有可能喊坏嗓子,就像您试图以不自然的方式控制声带所得到预期效果那样。我们有充分的理由争取以轻松的输入获得高精度的语音识别。

对于这个问题,简单添加更多音频样本可能是一个有效的解决办法。此外,另一个可行方法是采用预配置的口音相关模型(参阅 VoxForge 和 CMU Sphinx 参考资料,获取完成这一过程的指南)。第三种方法是通过编程工具将精力集中于低识别精度上,并设计针对基础问题的修正。

编程人员在减少低识别精度过程中起着重要作用。描述您工作环境的工具可以帮助确定识别问题的本质,并鼓励用户迅速聚焦基础问题和潜在解决方案。

HTK 提供许多工具供模型创建阶段所用,而 Julius 则提供关于识别阶段的描述性消息。但这种方法有其局限性:例如,HTK 和 Julius 编写者并不是以您需要的语法为前提进行的开发,所以您要负责处理您所选的语法引起的错误。

下一节讨论一些可能出现的具体问题,以及一些关于是否存在一种特殊的工具可以帮助确定和解决该问题的示例和评论。

问题类型

查看可能干扰识别精确度的错误类型,将会发现许多错误原因:

  • 我们所熟悉的 “无用输入,无用输出” 概念通常导致出现模糊模型 问题。HTK 不能正确地创建模型,除非供该音频模型创建过程所用的样例提示是纯净且具有相关性的。例如,您可能以错误的提示录制了 .wav 文件;这个 .wav 文件是可以接受的,但是只能由 HTK 解密,并且有一定困难(可能因为体积差异);删除单词之间短暂停顿出现的声音;或点击、突然弹出、以及背景声音入侵。解决方案是调查音频文件问题,也有可能是重新录制。
  • 如果您想要使用一个口音相关模型,用别人而不是您自己的声音训练的模型,会弹出狭隘模型。这种情况下,得到的结果可能会很差。此外,如果您使用有线耳机自己训练一个模型,然后使用生成的模型以蓝牙耳机发出命令,结果是识别准确度降低 50%,尽管使用的是同一个声音。进一步来看,使用同一个耳机但是连接不同的机器,结果会再次令您感到失望。您可以将其称之为模型宽度不足,可以通过添加特定音频文件到个人、设备或者出现问题的平台来进行补救。
  • 音频模型既需要宽度也需要广度。在不同的时间,一个人可以以不同方式说相同一件事,因为受到情绪状态、通风条件等的影响。即使操作平台、扬声器、或耳机设备没有变化,但如果说话人感冒了,感到不耐烦或者是微醉,声音多多少少会有改变,足够混淆在不同条件下训练的模型,导致生成一个浅模型 (1)。可以通过频繁查词典获得关于单词发音的准确描述,根据需要添加和编辑更多词条,来增加模型深度。
  • 浅模型 (2) 类似于前一个但也并不完全一样。在同一天,同一个声音,相同的条件,也有一些单词发音不一样,可能是根据上下文也或是一时兴致。例如,Tanzania:读为 [Tanza-niya] 或 [Tan-Zania],或者两者均可?Schedule 读 [sked-ule],还是 [shed-ule]?status 读 [stay-tus],还是 [stattus]?您可以强迫自己保持一致或者向词典添加更多词条。另一个微妙的问题是录音中单词间的长短停顿。
  • 该模型是基于音素的,是识别器期望听到的独立声音表现。音素存储在词典中,音素中的不准确或不一致问题及其相关声音可能导致错误。您可以通过识别或更正词典中的词条消除所用单词的不正确音素分解或取消其他发音 问题。
  • 不幸的是,在正常发音中,很多语言提供的音素使用平衡都比较差,结果是在语法中出现一个不完整的音素平衡。这些音素无法正确表示,因为它在这种语言中使用频率很低,您甚至期盼出现问题,因为练习它们的机会很有限。在我的英语语法中,音素 [ax]、[n]、[s]、[t] 往往频繁使用,另一种极端是,音素 [oy]、[ar]、[el]、[ur] 很少使用。解决方案是识别问题规则,然后调整语法,使用同义词,观察 HDMan 输出,甚至发明您自己的非标准术语练习极少使用的音素。
  • 音素密切相关的词素问题来自日常用语,强迫我们使用识别器很难区分的单词,因此语法命令选择就是一门精巧的艺术。我的模型有时候会混淆 pipenine,我的解决方案是将 pipe 改为复合词 pipe_symbolvertical_bar

工具

如果您的语法只有很少的几个规则,而且其中一个始终显示识别问题,那您可以相当快速地把精力集中在这个问题上。然而,对于较多的语法,您需要一个系统的方法测试模型的准确性

工具 1:识别问题单词

工具 1 是一个测试例程,识别有问题的词组,并将它们存储起来以备日后检查修订。总之,优势是不需要添加更多样例,只需要添加那些语法规则(识别器难于识别)的一些示例,这样节省时间。

在这里,机器读取提示文件,将词条存储在数组中并随机打乱,然后在屏幕上或者通过音频逐个为您呈现这些提示,要求您向识别器读出该提示。然后,脚本比较 Julius 的输出和所听到的,如果不一样,将问题记录下来稍后分析,继续下一个提示。

该脚本使用 Python(参阅 参考资料),并将问题存储在 PostgreSQL(参阅 参考资料)后端。使用 PHP、Perl、MySQL,或其他工具也可以完成同样的工作。此项选择对您的工作环境提出几点假设:首先,您拥有一个平面文件 “prompts”,由一个可测试语法规则列表和相关音频文件名组成。这些按照文件名以及后面的提示符排列,提示可以是一个词也可以是两个词。这里有一个示例。使用该工具,您将不能使用音频文件名,因此一个仅含样例提示的独立文件可以正常工作:

*/mysample1     ONE
*/mysample2     COMPUTER     QUIT

另外,您有一个 PostgreSQL 表,设置 4 个字段:一个 ID 号(用来确保记录的惟一性)和 3 个可变字符(一个存储您将要测试的设备,另一个存储计算机所要求的,第三个存储识别器所听到的)。在 清单 1 中,testprom.py 是脚本名称,headset 是您所使用耳机的一个标识符,100 是在脚本停止前您想要测试的提示数量。Julius 输出被输入到该脚本。

清单 1. 使用 Python 和 PostgreSQL 测试您的提示
#
# call with:
# julius (your options) -quiet -C julian.jconf | python testprom.py headset 100
#
import sys
import string as str
import os
import random
import psycopg2
# database setup
conn = psycopg2.connect("host='xxx' user='yyy' password='zzz' dbname='qqq'")
cur = conn.cursor()
# get the command line arguments
device = sys.argv[1]
limit = int(sys.argv[2]) # convert CLI string to integer
# get the prompts
proms = []
f = open('prompts', 'r')
for prompt in f:
  words = str.split(prompt)
  if len(words) == 2:
    thisprom = words[1]
  if len(words) == 3:
    thisprom = words[1]+' '+words[2]
  if thisprom not in proms:
    proms.append(thisprom)
f.close()
random.shuffle(proms)
# run the tests
i = 0
challengeprom = "please say something"
print challengeprom
while i < limit:
  line = sys.stdin.readline()
  if line[:4] == "sent":
    line = str.replace(line,"sentence1: <s> ","")
    line = str.replace(line," </s>","")
    heardprom = str.strip(line)
    if heardprom == challengeprom or i == 0:
      print "OK"
    else:
      sql = "insert into gramerrors (device, prompt, heard) values (%s,%s,%s)"
      data = (device,challengeprom,heardprom)
      cur.execute(sql, data)
      conn.commit()
      print "problem recorded: <"+challengeprom+'> <'+heardprom+'>'
    #
    challengeprom = proms[i]
    print challengeprom
    i += 1
# tidy up
cur.close()
conn.close()

清单 1 中(从根本上说,它是一个高度专业化的对话管理器),您首先要导入所需库,然后建立 PostgreSQL 后台和连接,读取命令行参数。然后,打开提示文件进行读取,并逐行读取,在一个列表中存储提示,但只有当该列表中没有时才存储。读取完成后随机打乱列表。

接下来,脚本从列表顶部开始遍历打乱的提示,逐个在屏幕上显示(也可以读出 Festival,请参阅 参考资料),然后等待语音识别器的输出,当您对着麦克风说话时将被触发;julius 解码它所听到的。然后,脚本比较预期提示和实际听到的提示。理想的情况是,比较结果相同,脚本处理下一个提示。如果出现问题,脚本在处理下一个提示之前会先在后台中记录设备、预期提示,以及实际提示。当达到界限或者文件读完时停止。

结果是一个提示表格,记录出现问题的提示以及引发此问题的设备。如果您经常使用多台设备,该表格的信息量将是非常大。如果您在不同的时间使用不同的设备进行多个测试,您需要构建一个图片,记录何处出现问题。总是同一个设备吗?同样的提示吗?Julius 持续输出同样的错误语法规则?这将指引您进行进一步分析。

工具 2:查看现有音频样例

也许,您的测试显示一个提示始终有问题。您首先会怀疑该提示的一个或多个音频文件出问题了,需要替换。可能是该音频文件与提示不匹配,或者卷太高而导致失真,或者太低,HTK 提取有用数据时出现问题了。您需要的这些信息都在提示文件中,清单 2 中的脚本遍历了选择的提示,然后打开该音频。

清单 2. 查看音频样例
#
# called with "$ python audioreview.py word1 word2"
#
import sys
import string as str
import os
f = open('prompts', 'r')
path = './wavs/'
for line in f:
  words = str.split(line)
  if words[1] == sys.argv[1] and words[2] == sys.argv[2]:
    mywav = words[0] + '.wav'
    wavfil = path + mywav[2:]
    os.system("aplay " + wavfil)

脚本导入少量模块,然后打开提示文件读取。接着一次读一行,将提示中的单词与命令行中指定的单词相比较。如果是同一个,脚本会使用一个系统调用来在音频播放器中播放音频相关文件(在例中是 aplay)。播放每个文件时脚本会暂停。在本例中,靠耳朵就可以听出视频文件是否需要替换。该脚本是一个方便的工具可以快速检查视频。如果发现有问题的音频文件,只需要使用正常录音流程就可以替换。

工具 3:检查词典中的音素选择

词典 是一个单词及其音素表示列表。清单 3 提供一个示例,显示词典中出现的内容。

清单 3. 样例词典词条
BLACK     [BLACK]     b l ae k
BLUE     [BLUE]     b l uw
BRAVO     [BRAVO]     b r aa v ow
BROWN     [BROWN]     b r aw n

该词典每行包含 3 个字段:单词(词素),打印时的表示(字素),最后是有序音素集表示单词的读音。在本例中,这 3 个字段由 5 个空格分隔:需要注意的是,该分隔符允许 清单 4 中的 split 方法在最后一个字段中作为一个独立单元保存该音素,因为该字段包含单行间距组件。

要检查一个单词,可以使用像 清单 4 这样的脚本。

清单 4. 针对一个单词检查词典音素
#
# called with "$ python scanlex.py word"
#
import sys
import string as str
f = open('lextest', 'r')
for line in f:
  words = str.split(line," "*5)
  if words[0] == sys.argv[1]:
    print words

该脚本一开始就导入所需的模块,然后打开词典文件进行读取。逐行读取文件,并打印第一个字段就是搜素单词的词条,正如命令行调用中所指定的。对于给定单词,可能出现多个词条,每个都有不同的音素表示。词典是按字母顺序排序的,因此在读完最后一个 “单词” 时,添加代码停止处理文件是完全合理的。最近,我发现了一关于单词 seven 的问题;当我以些许非标准音素表示 [s eh v ih n] 向字典添加一个新词条时,这个问题就完全消失了。

工具 4:针对该语法检查词典选择

工具 4 是对工具 3 的扩展,通过扫描提示列表以及按照该语法打印每个单词的词典音素表示。此外,它也会查询 Festival 获取其单词表示,这可以作为一个向导显示您的词典版本是否接近正确。

清单 5. 针对语法单词检查词典
#
# called with "$ python checkphons.py"
#
import sys
import string as str
import os
with open('prompts', 'r') as f:
  wdlist = []
  for line in f:
    words = str.split(line)
    if words[1] not in wdlist:
      wdlist.append(words[1])
    if words[2] not in wdlist:
      wdlist.append(words[2])
with open('lextest', 'r') as f:
  lexwd = []
  lexphon = []
  for line in f:
    lexs = str.split(line,' '*5)
    lexwd.append(str.strip(lexs[0]))
    lexphon.append(str.strip(lexs[2]))
for wd in wdlist:
  #print wd
  if wd in lexwd:
    pos = lexwd.index(wd)
    print wd, lexphon[pos]
    cmd = ("/home/colin/downloads/festival/bin/festival -b \
	      '(format t \"%l\\n\" (lex.lookup \""+wd+"\") ) '")
    os.system(cmd)

工具 4 通过阅读提示文件以及在一个列表中存储单词来工作。然后对词典中的单词和音素进行同样的操作。在最后阶段,它会扫描提示词列表,并从词典中输出此单词的音素描述。对于每个单词,它会调用 Festival,并打印它认为这个单词听起来应该是的 Festival 版本进行比较。在根据 清单 3 中的最小词典运行该脚本时,会出现此处的示例输出。

BLACK b l ae k
("black" nil (((b l ae k) 1)))
BLUE b l uw
("blue" nil (((b l uw) 1)))
BROWN b r aw n
("brown" nil (((b r aw n) 1)))
BRAVO b r aa v ow
("bravo" nil (((b r aa v) 1) ((ow) 0)))

注意,检查音素时,HTK HDMan 工具提供了该语法中使用的音素数量总结,作为 VoxForge 教程的输出部分来生成。对于较大的语法,您可以使用与工具 3 同样的方法进行过滤。


结束语

使用本文中的工具,对我而言,识别失败比率是千分之一(来自我自己在极为安静的条件进行的实验)作为一个目标非常合理。只需要添加更多来自各种不同资源的示例提示,有助于消减模型中的差异,只要语法和词典中没有严重的缺陷。对用于构建模型的单词和音频文件多点关注和照顾,就有可能更迅速地实现这一目标。结果是一个更令人满意的产品,与您的设备有一个更有效的通信方式,您也可以更放松声带。

参考资料

学习

获得产品和技术

  • 了解有关 HTK(Hidden Markov Model Toolkit)更多的信息,它是一个便携工具包,可用于构建和控制隐藏的 Markov 模型。
  • 了解关于 Carnegie Mellon 的 CMU Sphinx 语音工具包的更多信息。
  • 了解有关 Julius 语音识别引擎的更多信息。
  • 了解有关 Festival 文字-话音切换引擎的更多信息。
  • 了解有关 PostgreSQL 关系型数据库系统的更多信息。
  • 以最适合您的方式 IBM 产品评估试用版软件:下载产品试用版,在线试用产品,在云环境下试用产品,或者在 IBM SOA 人员沙箱 中花费几个小时来学习如何高效实现面向服务架构。

讨论

条评论

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=Open source
ArticleID=827074
ArticleTitle=调整音频模型以实现更好的语音识别
publish-date=07232012