许多完善的软件项目已经使用普通文本配置和资源文件很多年了,但没有出现什么重大问题。随着项目的扩展和复杂性的增加,对更高严谨性和更强适应性的需求也随之增加。通过 XML 以及使用具体标准的 XML 应用程序,您可以得到以下获益:跨项目和跨平台兼容性、稳健性,以及在 Unicode 等领域的可扩展性。
通过将普通文本文件转化为相关的开源标准,您还可以提高灵活性和可靠性。语音识别工作中的词典就是本文使用的一个很好的例子。不管您的开源项目是否转而使用 XML 格式的资源文件,您都可以在不损失功能的情况下在您的工作中运用 XML 标准。
在本文中,我们将学习如何轻松地在普通文本和 Pronunciation Lexicon Specification (PLS) 格式之间进行转换。几个示例展示如何存储 PLS 格式的自定义词典,并将数据提取为所需的普通文件。
词典是在语音识别工具中使用的单词列表。它们包含的信息涉及到如何打印或用图形显示单词,它如何使用音素发音。经常与 Hidden Markov Model Toolkit (HTK) 一同使用的词典在语音控制项目中得到广泛使用(参阅 参考资料)。清单 1 是来自一个 VoxForge HTK 词典的一段摘录。
清单 1. 清单 1 来自一个 VoxForge HTK 词典的一段摘录
AGENCY [AGENCY] ey jh ih n s iy AGENDA [AGENDA] ax jh eh n d ax AGENT [AGENT] ey jh ih n t AGENTS [AGENTS] ey jh ih n t s AGER [AGER] ey g er AGES [AGES] ey jh ih z |
清单 1 中的文件包含三个以制表符分隔的字段:
- 一般描述单词的标签
- 当您希望打印或在屏幕上显示一个单词时围绕单词的方括号(字素)
- 来自 Arpabet 集(参阅 参考资料)的一系列单一空格分隔的音素,描述单词的读音
在上述示例中使用英语发音,多半包含在 American Standard Code for Information Interchange (ASCII) 字符中。
CMU Sphinx 项目以类似的方式(参阅 参考资料)将词典(或字典)存储在 CMU Sphinx 上下文中。清单 2 给出一段摘录。
清单 2. 来自一个 CMU Sphinx 词典的一段摘录
agency EY JH AH N S IY agenda AH JH EH N D AH agendas AH JH EH N D AH Z agent EY JH AH N T agents EY JH AH N T S ager EY JH ER |
在 清单 2 中只有两个字段:单词/字素及其音素。两个词典示例有一些细微差别:
- 单词和音素是完全不同的类型。
- 音素有一些微妙的差异。
- 对待标点符号(逗号和惊叹号等)的方式稍有不同。
您可以在目前下载的 PocketSphinx 中的 cmu07a.dic 文件中看到整个字典(参阅 参考资料)。
由于词典给出特定单词的发音,您可能需要编辑文件以适应特定的人或方言。久而久之就可以在自定义词典中建立起知识资产了。使用文本编辑器很容易就可以编辑普通文件,但也易于引入错误,比如:使用文件标准以外的分隔符,插入非 ASCII 字符,以错误的顺序放置字段,不当地对字段进行排序,在需要的地方缺少方括号,等等。
普通文件还有一点不足之处。在您构建自定义文件时,始终与其他语音项目不兼容。而标准 XML 格式(比如 PLS)的词典,一旦被两个项目识别到,在两者中都能立即相互兼容。
语音词典规范 (Pronunciation Lexicon Specification)
PLSA 有一个简单的基本格式,如 清单 3 所示。
清单 3. 清单 3 基本 PLS 格式
<?xml version="1.0" encoding="UTF-8"?>
<lexicon version="1.0"
xmlns="http://www.w3.org/2005/01/pronunciation-lexicon"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.w3.org/2005/01/pronunciation-lexicon
http://www.w3.org/TR/2007/CR-pronunciation-lexicon-20071212/pls.xsd"
alphabet="ipa" xml:lang="en-US">
<lexeme ...>
<grapheme>...</grapheme>
<phoneme ...>...</phoneme>
</lexeme>
</lexicon>
|
XML 描述可包含多个 lexeme 子元素的 root 元素 lexicon。每个 lexeme 可以包含多个 grapheme 元素和多个 phoneme 元素。规范允许您覆盖 alphabet 属性,但不允许您覆盖 xml:lang 语言属性。要将词素存储为不同的语言,您非常需要分离 PLS 词典文件。这个词典中的默认字母系统是 ipa,它是指表示发音的 International Phonetic Alphabet (IPA) 系统(参阅 参考资料)。IPA 音素表示法是多字节 Unicode 字符。HTK 和 Sphinx 都使用纯 ASCII 代码。本文稍后会讨论这个重要考虑因素。
使用 PLS 规范的优势在于,它添加了更加严谨的结构,能够让您存储更多信息,比如词性和具体字母。词性细节在英语中很重要,因为一些拼写起来相同的单词(同形异义词)发音不同,具体视语法角色而定。例如,perfect 在作为形容词和作为动词时发音不同,因为重音在不同的地方。属性中存储的额外信息能够让您根据需要从整个文件提取特定记录。使用这一方法,您可以在多个 phoneme 元素中搜索一个特定的字母。
将 PLS 词典看作是一个词典信息数据库,从中您可以提取与所使用的语音工具相关的详细信息。清单 4 是一个 PLS 格式的示例。
清单 4. 清单 4 一个 PLS 格式的单词
<?xml version="1.0" encoding="UTF-8"?>
<lexicon version="1.0"
xmlns="http://www.w3.org/2005/01/pronunciation-lexicon"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.w3.org/2005/01/pronunciation-lexicon
http://www.w3.org/TR/2007/CR-pronunciation-lexicon-20071212/pls.xsd"
alphabet="ipa" xml:lang="en">
<lexeme role="noun">
<grapheme>agency</grapheme>
<phoneme alphabet="x-htk-voxforge">ey jh ih n s iy</phoneme>
<phoneme alphabet="x-cmusphinx">EY JH AH N S IY</phoneme>
</lexeme>
</lexicon>
|
清单 4 中的示例仅存储一个可能有两个音素表示的单词。您可以使用 alphabet 属性过滤出其中一个 phoneme 字符串。lexeme 元素显示 noun 的 role 属性。这里面虽然信息量大,但在本例中是冗余的,因为该单词仅作为一个名词使用,没有复杂的发音情况。
通过并排放置两个不同来源的 phoneme 表示,您已经可以分辨出细微的差别了。该信息在解决语音识别问题方面会很有帮助。
CMU Sphinx 和 HTK 都不能直接使用 PLS 词典,但是 HTK 工具包的 simon(参阅 参考资料)前端可以。如果您直接使用 HTK 或 Sphinx,一定要确保可以轻松实现普通文件和 PLS 之间的来回转换,而不丢失任何信息。
以下几节展示如何使用 Python 实现普通文件与 PLS 之间的来回转换。假定您在普通词典文件中有自定义信息。
清单 5 中的代码使用 Python,但是您可以通过许多其他方式完成相同的工作。(例如,参阅 参考资料 中有关 Extensible Stylesheet Language Transformations (XSLT) 的 developerWorks 教程)。有些人希望使用可在每一小步检查 XML 稳健性的库,获得更多有关问题出处的即时反馈,特别是当源文件较大且易于包含错误和不一致性时。下面的示例将检查留到最后一步,这意味着一定的置信水平,表示普通文件状态良好。
清单 5. 清单 5 转化为 PLS
from elementtree.ElementTree import parse
import string as str
import sys
import cgi
#
# call with
# python flat2pls.py vox
# or
# python flat2pls.py spx
#
if len(sys.argv) == 2:
src = sys.argv[1]
else:
exit("wrong args")
#
outfile = "mylex"+src+".pls"
print "out is "+outfile
out = open(outfile,"w")
out.write('<?xml version="1.0" encoding="UTF-8"?>\n\
<lexicon version="1.0"\n \
xmlns="http://www.w3.org/2005/01/pronunciation-lexicon"\n\
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n \
xsi:schemaLocation="http://www.w3.org/2005/01/pronunciation-lexicon\n \
http://www.w3.org/TR/2007/CR-pronunciation-lexicon-20071212/pls.xsd"\n\
alphabet="ipa" xml:lang="en">')
# now the lexemes
if src == "vox":
f = open("vf.lex","r")
for line in f:
line = str.strip(line)
word = str.split(line,"\t")
#gr = str.strip(word[1],"[]")
gr = cgi.escape(word[0])
out.write('\n\
<lexeme>\n\
<grapheme>'+gr+'</grapheme>\n\
<phoneme alphabet="x-htk-voxforge">'+word[2]+'</phoneme>\n\
</lexeme>')
else: # src is sphinx
f = open("cmu.dic","r")
for line in f:
line = str.strip(line)
word = str.split(line,"\t")
gr = cgi.escape(word[0])
out.write('\n\
<lexeme>\n\
<grapheme>'+gr+'</grapheme>\n\
<phoneme alphabet="x-cmusphinx">'+word[1]+'</phoneme>\n\
</lexeme>')
# ended lexemes
out.write('\n</lexicon>\n')
out.close()
# now check the output is ok
tree = parse(outfile)
lexicon = tree.getroot()
mylexcount = 0
for lexeme in lexicon:
mylexcount += 1
print 'Found %(number)d lexemes' % {"number":mylexcount}
|
清单 5 首先从 XML 解析库 elementtree 和一些支持的库导入模块(参阅 参考资料)开始。导入不同版本上的 ElementTree 会涉及到稍微不同的语法,具体取决于您安装模块的方式。示例代码来自 openSUSE,其中模块是从源头安装的,但 Ubuntu 可能需要 from xml.etree.ElementTree import parse。模块 str 支持一些字符串操作,sys 提供给您对文件的访问权限,且 cgi 提供处理 XML 数据所必要的一个非常重要的转义例程。代码打算获取一个命令行界面 (CLI) 参数,告诉它是否要从 CMU Sphinx 格式或 HTK/VoxForge 转换。然后示例代码打开输出文件,并编写适合 PLS 的 XML 序言。由于在此阶段您不存储任何 Unicode 字符,所以只需打开文件仅进行纯 ASCII 访问就行了。
此时,清单 5 中的代码是:
- 逐行处理源文件,将字段分为独立的字符串,编写
lexeme、grapheme和phoneme组件。 - 标识
phoneme,如果传入的数据来自 VoxForge 词典,那么使用属性alphabet="x-htk-voxforge",如果数据来自 Sphinx,那么使用alphabet="x-cmusphinx"。
当导入第一个字段时,它可能包含在 XML 中引起问题的字符,比如连字符 (&),除非使用 cgi.escape() 进行转义。
最后是代码:
- 编写关闭标签
- 关闭 PLS 文件,然后将其重新加载为一个 XML 文件
- 阅读整个文件,计算
lexeme元素 - 报告词素的计数
如果报告了计数,那么 XML 显得可靠且符合语法规则。
清单 6 是来自 VoxForge HTK 词典的一段结果摘录
清单 6. 清单 6 VoxForge HTK 词典结果摘录
... <lexeme> <grapheme>AGENDA</grapheme> <phoneme alphabet="x-htk-voxforge">ax jh eh n d ax</phoneme> </lexeme> <lexeme> <grapheme>AGENT</grapheme> <phoneme alphabet="x-htk-voxforge">ey jh ih n t</phoneme> </lexeme> ... |
您需要知道的一点是,您可以轻松实现从 PLS 格式到普通文件的转换。清单 7 中的代码假定您有存储为 PLS 格式文件的词典,而且您的语音识别项目只能使用 HTK 或 CMU Sphinx 格式的普通文件。
清单 7. 从 PLS 转换
from elementtree.ElementTree import parse
import string as str
import sys
#
# call with
# python pls2flat.py x-htk-voxforge > mylexicon
# or
# python pls2flat.py x-cmusphinx > mylexicon.dic
#
if len(sys.argv) > 1:
alpha = sys.argv[1]
#
if alpha == "x-htk-voxforge":
tree = parse("mylexvox.pls")
else:
tree = parse("mylexspx.pls")
lexicon = tree.getroot()
for lexeme in lexicon:
for child in lexeme:
#print child.tag
if child.tag[-8:] == 'grapheme':
if alpha == 'x-htk-voxforge':
gr = str.upper(child.text)
print gr,"\t","["+gr+"]","\t",
else:
gr = child.text
print gr,"\t",
if child.tag[-7:] == 'phoneme':
if child.get('alphabet') == alpha:
print child.text
|
这一段简短的脚本使用 elementtree 库来解析 PLS XML 文件。它建立 root 元素,然后迭代子词素,寻找 grapheme 和 phoneme,并将值写到相关格式的文本文件中。脚本请求标签中的最后 8 个字符,即 grapheme,因为标签会有一个命名空间前缀返回。它为 HTK 重新创建三个字段,为 CMU Sphinx 创建两个字段。
清单 8 中的脚本使用两个 PLS 文件来创建一个常见的 PLS 文件,其中包含两个原始文件的信息。它还将 VoxForge phoneme 字符串转换为 Unicode 并将 Unicode 版本存储在用 alphabet="ipa" 属性标识的一个独立的 phoneme 元素中。
清单 8. 清单 8 合并 Unicode
#! /usr/bin/python -u
# -*- coding: utf-8 -*-
#
# challenge is to merge two pls files
# given two pls files, merge them into one
#
import elementtree.ElementTree as ET
from elementtree.ElementTree import parse
import string as str
import codecs
import cgi
#
treevox = ET.parse("mylexvox.pls")
treespx = ET.parse("mylexspx.pls")
#
lexvox = treevox.getroot()
lexspx = treespx.getroot()
#
phons = { 'aa':u'ɑ','ae':u'æ','ah':u'ʌ','ao':u'ɔ','ar':u'ɛr','aw':u'aʊ',
'ax':u'ə','ay':u'aɪ','b':u'b','ch':u'tʃ','d':u'd','dh':u'ð','eh':u'ɛ',
'el':u'ɔl','en':u'ɑn','er':u'ər','ey':u'eɪ','f':u'f',
'g':u'ɡ','hh':u'h','ih':u'ɪ','ir':u'ɪr','iy':u'i','jh':u'dʒ','k':u'k','l':u'l',
'm':u'm','n':u'n','ng':u'ŋ','ow':u'oʊ','oy':u'ɔɪ','p':u'p','r':u'r','s':u's',
'sh':u'ʃ','t':u't','th':u'θ','uh':u'ʊ','ur':u'ʊr','uw':u'u','v':u'v',
'w':u'w','y':u'j','z':u'z','zh':u'ʒ','sil':'' }
#
def to_utf(s):
myp = str.split(s)
myipa = []
for p in myp:
myipa.append(phons[p])
return str.join(myipa,'')
#
outfile = "my2lexmrg.pls"
out = codecs.open(outfile, encoding='utf-8', mode='w')
#
out.write('<?xml version="1.0" encoding="UTF-8"?>\n\
<lexicon version="1.0"\n \
xmlns="http://www.w3.org/2005/01/pronunciation-lexicon"\n\
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n \
xsi:schemaLocation="http://www.w3.org/2005/01/pronunciation-lexicon\n \
http://www.w3.org/TR/2007/CR-pronunciation-lexicon-20071212/pls.xsd"\n\
alphabet="ipa" xml:lang="en">')
#
# scan the two pls, create dictionary
voxdict = {}
for lexeme in lexvox:
gr = str.lower(lexeme[0].text)
ph = lexeme[1].text
voxdict[gr] = {ph,}
#
for lexeme in lexspx:
gr = lexeme[0].text
ph = lexeme[1].text
if gr in voxdict:
voxdict[gr].add(ph)
else:
voxdict[gr] = {ph,}
#
for gr in sorted(voxdict.iterkeys()):
out.write('\n\
<lexeme>\n\
<grapheme>'+cgi.escape(gr)+'</grapheme>')
#print "%s: %s" % (key, voxdict[key])
for ph in sorted(voxdict[gr]):
alph = 'x-htk-voxforge' if ph.islower() else 'x-cmusphinx'
out.write('\n\
<phoneme alphabet="'+alph+'">'+ph+'</phoneme>')
if ph.islower():
phipa = to_utf(ph)
out.write(u'\n\
<phoneme alphabet="ipa">'+phipa+'</phoneme>')
out.write('\n\
</lexeme>')
# done, close files
out.write('\n</lexicon>')
out.close()
# now check the output is ok
tree = parse(outfile)
lexicon = tree.getroot()
mylexcount = 0
for lexeme in lexicon:
mylexcount += 1
print 'Found %(number)d lexemes' % {"number":mylexcount}
|
首先以 hashbang (#!) 表达式开始,后接标识 Python 解释器的一个特殊指示符。在第二行上,该代码包含 Unicode 字符。然后脚本导入大量模块,包括 elementtree、codecs 和 cgi,这些模块在处理 Unicode 时很有用。您告诉解释器两个 PLS 文件在哪里,并指向其 root 元素。
phons 变量存储一个特殊的字典,其中包含从 CMU Arpabet 代码到等价的 Unicode 组合的一个映射。该字典将现有的 phoneme 字符串转换为一个 Unicode 版本。您可以根据自己的需求随意修改映射,例如,您可能觉得 Unicode 中 'aa' 的等价词是 u'ɑ:',这拉长了 a 的发音。
一个定义好的函数 to_utf() 将 ASCII Arpabet 字符串转化为 Unicode。基础工作的最后一部分是打开一个文件来存储输出,确保该文件知道它要准备好接受 Unicode,并将 PLS 序言写到其中。
现在准备处理文件,方式就是创建两个特殊的内部 Python 字典,一个是各个 PLS 文件,使用 elementtree 库扫描它们。假设 grapheme 将是第一个子元素,而 phoneme 是词素的第二个子元素。脚本将来自第一个文件的所有记录添加到新的合并字典。在扫描第二个文件时,如果键已经存在于新的合并字典中,您可以添加其音素集。如果不存在,在合并的字典中创建一个新的键项。在循环结束时,新的合并字典同时包含来自原始文件和相关的一个或两个 phoneme 字符串的键。
从刚才创建的合并编写新的 PLS 文件。在扫描整个字典时,添加 alphabet 属性来区分两个 phoneme。写出现有的音素之后,创建一个新的 phoneme 字符串,它是 CMU Arpabet 字符串的 Unicode 等价字符串,您可以根据您的需要从 HTK 或 Sphinx 版本(或两者)获取它。
最后,结束 root 元素,关闭文件,并再次解析它,就像之前检查它符合语法规则一样。
结果类似于 清单 9。
清单 9. 清单 9 合并结果
...
<lexeme>
<grapheme>agenda</grapheme>
<phoneme alphabet="x-cmusphinx">AH JH EH N D AH</phoneme>
<phoneme alphabet="x-htk-voxforge">ax jh eh n d ax</phoneme>
<phoneme alphabet="ipa">ədʒɛndə</phoneme>
</lexeme>
<lexeme>
<grapheme>agendas</grapheme>
<phoneme alphabet="x-cmusphinx">AH JH EH N D AH Z</phoneme>
</lexeme>
<lexeme>
<grapheme>agent</grapheme>
<phoneme alphabet="x-cmusphinx">EY JH AH N T</phoneme>
<phoneme alphabet="x-htk-voxforge">ey jh ih n t</phoneme>
<phoneme alphabet="ipa">eɪdʒɪnt</phoneme>
</lexeme>
<lexeme>
<grapheme>agent's</grapheme>
<phoneme alphabet="x-cmusphinx">EY JH AH N T S</phoneme>
</lexeme>
...
|
有了合并的 PLS 字典,您可以应用 Extensible Stylesheet Language (XSL) 或其他任何程序来生成需要的结果,不管是普通文件还是新的特定 PLS 文件。在理论上,您也可以在该文件中存储其他 phoneme 字符串。但是,这是对 PLS 规范的一个非标准使用。
Kai Schott 已经做了大量 PLS 相关工作,而且已经准备好了几个不同语言版本的文件以供下载,特别是德语版本(参阅 参考资料)。
尽管您可以从普通文件获得大量信息,以下问题仍然未得到解决。
- 从多个字素中选择
- 有时您需要处理同一语言中一个单词的多个拼写。对于两个拼写法,角色和音素是相同的,因此在同一个词素中有多个字素。但是,在 PLS 中您无法添加一个属性到
grapheme元素,就像phoneme元素的alphabet属性。 - 缩略词
- 词典常常包含缩略词。PLS 在一个名为
<alias>的lexeme的子元素中处理这些。要从一个普通文件自动构建一个 PLS,您需要一种方式来将缩略词与词典中真正的单词区分开来。普通文件不一定有这个信息。 - 角色/词性
- 与缩略词一样,普通文件中不提供词性信息来将
role属性构建到 PLS 中。
在本文中,您了解了如何非常简单地进行普通文件和 PLS 格式文件之间的转换。存储 PLS 格式的词典有潜在的优势。开源项目可能使用、也可能没有使用 XML 格式的资源文件。这是项目经理要做出的决策。他们自己就可以评估资源,并且以他们在社区中普遍接受的最佳方式应用它们。
同时,您可以在自己的工作中运用 XML 标准,而不损失任何功能。对于在语音识别工作中使用的词典和字典,您可以通过对 PLS 格式的自定义词典评分来增加跨项目能力、可靠性和实用性。您可以轻松按需提取数据到所需的普通文件。
学习
- W3C Pronunciation Lexicon Specification (PLS):获取更多关于词素、字素和音素的信息。
- International Phonetic Alphabet:在 Wikipedia 上阅读更多关于这个注音字母系统的信息,包括 Arpabet 和 Unicode 等价字符串。
- HTK:了解如何使用 Hidden Markov Model Toolkit (HTK) 便携式工具包构建和操作隐马尔科夫模型。
- CMU Sphinx:探讨来自卡耐基梅隆大学的语音识别开源工具包。
- ElementTree:阅读更多有关 Python 的 ElementTree XML 库的信息。
- simon:更多了解这个语音识别平台。
- 测试 simon,Kai Schott 的博客:查找几个已经准备好的各个语言版本的 PLS 文件。
- 使用 XSLT 作为分析工具,第 1 部分:使用 XSLT 分析非 XML 数据(Chuck White,developerWorks,2003 年 12 月):介绍如何使用 XSLT 分析 XML。
- 本文作者的更多文章(Colin Beckingham,developerWorks,2009 年 3 月至今):阅读有关 XML、语音识别、XQuery、PHP 和其他技术的文章。
-
XML 新手入门 获得学习 XML 所需的资源。
- developerWorks XML 技术专区:寻找提高您在 XML 领域技能所需的资源,包括 DTD、模式和 XSLT。参见 XML 技术文档库,获得广泛的技术文章和技巧、教程、标准和 IBM 红皮书。
- IBM XML 认证:了解如何才能成为一名 IBM 认证的 XML 和相关技术的开发人员。
- developerWorks 技术活动 和 网络广播:随时关注这些活动中的技术。
- Twitter 上的 developerWorks:立即加入并关注 developerWorks 推文。
- developerWorks 播客:聆听针对软件开发人员的有趣访谈和讨论。
- developerWorks 演示中心:观看面向初学者的产品安装和设置演示,以及为经验丰富的开发人员提供的高级功能。
获得产品和技术
- VoxForge:创建声模型,进一步探究如何组合一个语音识别模型。
- CMU Sphinx:获取语音识别工具包。
- Python:获取有关这一编程语言的更多信息,包括下载链接。
- IBM 产品评估试用版软件:下载或 IBM SOA 人员沙箱,并开始使用来自 DB2®、Lotus®、Rational®、Tivoli® 和 WebSphere® 的应用程序开发工具和中间件产品。
讨论
- developerWorks 概要信息:现在就创建您的概要信息并 建立关注清单。
- XML 专区讨论论坛:参与任何一个 XML 相关讨论。
- developerWorks 中文社区:查看开发人员推动的博客、论坛、组和维基,并与其他 developerWorks 用户交流。
Colin Beckingham 居住在加拿大安大略省,是一位自由研究人员、作家和程序员。他拥有金斯顿皇后大学和温莎大学的学位,对园艺、赛马、教育、公共服务、零售和旅游/观光领域都有涉猎。他是数据库应用程序的作者,也是大量报纸、杂志和在线文章的撰稿人,他的研究兴趣包括 Linux 上的开源编程以及语音控制应用程序。您可通过 colbec@start.ca 与 Colin 联系。