mirror of
https://github.com/cmusphinx/sphinxtrain.git
synced 2026-06-16 13:14:30 +00:00
186 lines
6.1 KiB
Python
186 lines
6.1 KiB
Python
#!/usr/bin/env python
|
|
|
|
# Copyright (c) 2010 Carnegie Mellon University
|
|
#
|
|
# You may copy and modify this freely under the same terms as
|
|
# Sphinx-III
|
|
"""
|
|
Find lattice word error rate using OpenFST.
|
|
"""
|
|
|
|
__author__ = "David Huggins-Daines <dhdaines@gmail.com>"
|
|
__version__ = "$Revision$"
|
|
|
|
import sys
|
|
import os
|
|
import re
|
|
import openfst
|
|
from cmusphinx import fstutils, lattice, lat2fsg
|
|
|
|
|
|
def is_filler(sym):
|
|
if sym == '<s>' or sym == '</s>':
|
|
return False
|
|
return ((sym[0] == '<' and sym[-1] == '>')
|
|
or (sym[0] == '+' and sym[-1] == '+'))
|
|
|
|
|
|
linere = re.compile(r"^\s*(?:<s>)?\s*([^(]+)(?:</s>)?\s*(?:\(([^)]+)\))\s*?")
|
|
|
|
|
|
def get_utt(line):
|
|
m = linere.match(line)
|
|
if m:
|
|
return m.groups()
|
|
else:
|
|
return (None, None)
|
|
|
|
|
|
class LevenshteinModel(openfst.StdVectorFst):
|
|
def __init__(self, symtab, scost=1, icost=1, dcost=1):
|
|
openfst.StdVectorFst.__init__(self)
|
|
st = self.AddState()
|
|
self.SetStart(st)
|
|
self.SetFinal(st, 0)
|
|
sigma = symtab.Find("σ")
|
|
for c, val in symtab:
|
|
if val in (openfst.epsilon, sigma):
|
|
continue
|
|
# Translation arc
|
|
self.AddArc(st, val, val, 0, st)
|
|
# Insertion/Deletion arcs
|
|
self.AddArc(st, 0, val, icost, st)
|
|
self.AddArc(st, val, 0, dcost, st)
|
|
# Substitution arcs
|
|
for cc, vv in symtab:
|
|
if vv in (openfst.epsilon, sigma, val):
|
|
continue
|
|
self.AddArc(st, val, vv, scost, st)
|
|
self.SetInputSymbols(symtab)
|
|
self.SetOutputSymbols(symtab)
|
|
|
|
|
|
class CompoundWordModel(openfst.StdVectorFst):
|
|
def __init__(self, isyms, osyms):
|
|
openfst.StdVectorFst.__init__(self)
|
|
st = self.AddState()
|
|
self.SetStart(st)
|
|
self.SetFinal(st, 0)
|
|
sigma = osyms.Find("σ")
|
|
for c, val in osyms:
|
|
if val in (openfst.epsilon, sigma):
|
|
continue
|
|
# Translation arc
|
|
self.AddArc(st, val, val, 0, st)
|
|
# Compound word separator
|
|
if '_' not in c:
|
|
continue
|
|
parts = c.split('_')
|
|
prev = st
|
|
nx = self.AddState()
|
|
# Transduce word sequence from input
|
|
isym = isyms.AddSymbol(parts[0])
|
|
self.AddArc(prev, isym, openfst.epsilon, 0, nx)
|
|
prev = nx
|
|
for p in parts[1:-1]:
|
|
nx = self.AddState()
|
|
isym = isyms.AddSymbol(p)
|
|
self.AddArc(prev, isym, openfst.epsilon, 0, nx)
|
|
prev = nx
|
|
isym = isyms.AddSymbol(parts[-1])
|
|
# Finally insert compound into output
|
|
self.AddArc(prev, isym, val, 0, st)
|
|
self.SetInputSymbols(isyms)
|
|
self.SetOutputSymbols(osyms)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
from optparse import OptionParser
|
|
parser = OptionParser(usage="%prog CTL REF LATDIR")
|
|
parser.add_option("--prune", type="float")
|
|
opts, args = parser.parse_args(sys.argv[1:])
|
|
|
|
ctl, ref, latdir = args
|
|
ctl = open(ctl)
|
|
ref = open(ref)
|
|
wordcount = 0
|
|
errcount = 0
|
|
for c, r in zip(ctl, ref):
|
|
# Normalize reference, etc.
|
|
ref, refid = get_utt(r)
|
|
c = c.strip()
|
|
r = ref.split()
|
|
if len(r) == 0 or r[0] != '<s>':
|
|
r.insert(0, '<s>')
|
|
if r[-1] != '</s>':
|
|
r.append('</s>')
|
|
r = [x for x in r if not is_filler(x)]
|
|
# Turn it into an FSM
|
|
rfst = fstutils.sent2fst(r)
|
|
# Get the hypothesis lattice
|
|
try:
|
|
l = lattice.Dag(os.path.join(latdir, c + ".lat"))
|
|
except IOError:
|
|
try:
|
|
l = lattice.Dag(os.path.join(latdir, c + ".lat.gz"))
|
|
except IOError:
|
|
l = lattice.Dag(htk_file=os.path.join(latdir, c + ".slf"))
|
|
if opts.prune is not None:
|
|
l.posterior_prune(-opts.prune)
|
|
# Convert it to an FSM
|
|
lfst = lat2fsg.build_lattice_fsg(l,
|
|
rfst.OutputSymbols(),
|
|
addsyms=True,
|
|
determinize=False,
|
|
baseword=lattice.baseword_noclass)
|
|
openfst.ArcSortInput(lfst)
|
|
# Apply Levenshtein model to the input
|
|
errfst = LevenshteinModel(rfst.OutputSymbols())
|
|
openfst.ArcSortInput(errfst)
|
|
# Apply compound word model based on the lattice
|
|
compfst = CompoundWordModel(errfst.OutputSymbols(),
|
|
lfst.InputSymbols())
|
|
# Precompose and project it to the lattice so compound words
|
|
# are split in the alignment
|
|
xlat = openfst.StdVectorFst()
|
|
openfst.Compose(compfst, lfst, xlat)
|
|
openfst.ProjectInput(xlat)
|
|
openfst.ArcSortInput(xlat)
|
|
# Compose everything together
|
|
cfst = openfst.StdComposeFst(rfst, errfst)
|
|
cfst = openfst.StdComposeFst(cfst, xlat)
|
|
# Do bestpath search
|
|
ofst = openfst.StdVectorFst()
|
|
openfst.ShortestPath(cfst, ofst, 1)
|
|
st = ofst.Start()
|
|
err = 0
|
|
bt = []
|
|
while st != -1 and ofst.NumArcs(st):
|
|
a = ofst.GetArc(st, 0)
|
|
isym = ofst.InputSymbols().Find(a.ilabel)
|
|
osym = ofst.OutputSymbols().Find(a.olabel)
|
|
if isym == '</s>':
|
|
break
|
|
if a.ilabel == openfst.epsilon:
|
|
isym = '*INS*'
|
|
if a.olabel == openfst.epsilon:
|
|
osym = '*DEL*'
|
|
bt.append((isym, osym))
|
|
err += a.weight.Value()
|
|
st = a.nextstate
|
|
maxlen = [max([len(y) for y in x]) for x in bt]
|
|
nwords = len(r) - 2
|
|
refid = '(%s)' % refid
|
|
c = '(%s)' % c
|
|
print(" ".join(["%*s" % (m, x[0]) for m, x in zip(maxlen, bt)]), refid)
|
|
print(" ".join(["%*s" % (m, x[1]) for m, x in zip(maxlen, bt)]), c)
|
|
if nwords:
|
|
print("Error: %.2f%%" % (float(err) / nwords * 100))
|
|
else:
|
|
print("Error: %.2f%%" % (float(err) * 100))
|
|
print()
|
|
wordcount += nwords
|
|
errcount += err
|
|
|
|
print("TOTAL Error: %.2f%%" % (float(errcount) / wordcount * 100))
|