mirror of
https://github.com/cmusphinx/sphinxtrain.git
synced 2026-06-16 13:14:30 +00:00
201 lines
6.8 KiB
Python
201 lines
6.8 KiB
Python
# Copyright (c) 2006 Carnegie Mellon University
|
|
#
|
|
# You may copy and modify this freely under the same terms as
|
|
# Sphinx-III
|
|
|
|
"""Read/write Sphinx-III model definition files.
|
|
|
|
This module reads and writes the text format model definiton (triphone
|
|
to senone mapping) files used by SphinxTrain, Sphinx-III, and
|
|
PocketSphinx.
|
|
"""
|
|
|
|
__author__ = "David Huggins-Daines <dhdaines@gmail.com>"
|
|
__version__ = "$Revision$"
|
|
|
|
from numpy import ones, empty
|
|
import io
|
|
|
|
|
|
def open(file):
|
|
return S3Mdef(file)
|
|
|
|
|
|
class S3Mdef:
|
|
"Read Sphinx-III format model definition files"
|
|
def __init__(self, filename=None):
|
|
self.info = {}
|
|
self.phoneset = {}
|
|
if filename is not None:
|
|
self.read(filename)
|
|
|
|
def read(self, filename):
|
|
fh = io.open(filename, "r")
|
|
|
|
while True:
|
|
version = fh.readline().rstrip()
|
|
if not version.startswith("#"):
|
|
break
|
|
if version != "0.3":
|
|
raise Exception("Model definition version %s is not 0.3" % version)
|
|
info = {}
|
|
while True:
|
|
spam = fh.readline().rstrip()
|
|
if spam.startswith("#"):
|
|
break
|
|
val, key = spam.split()
|
|
info[key] = int(val)
|
|
self.n_phone = info['n_base'] + info['n_tri']
|
|
self.n_ci = info['n_base']
|
|
self.n_tri = info['n_tri']
|
|
self.n_ci_sen = info['n_tied_ci_state']
|
|
self.n_sen = info['n_tied_state']
|
|
self.n_tmat = info['n_tied_tmat']
|
|
|
|
# Skip field description lines
|
|
spam = fh.readline()
|
|
spam = fh.readline()
|
|
|
|
ssidmap = {}
|
|
self.phonemap = {}
|
|
self.trimap = []
|
|
self.fillermap = empty(self.n_phone, 'b')
|
|
self.tmatmap = empty(self.n_phone, 'h')
|
|
self.sidmap = empty(self.n_sen, 'i')
|
|
self.cisidmap = empty(self.n_sen, 'i')
|
|
phoneid = 0
|
|
self.max_emit_state = 0
|
|
while True:
|
|
spam = fh.readline().rstrip()
|
|
if spam == "":
|
|
break
|
|
fields = spam.split()
|
|
base, lc, rc, wpos, attrib, tmat = fields[0:6]
|
|
sids = fields[6:-1]
|
|
self.max_emit_state = max(self.max_emit_state, len(sids))
|
|
|
|
# Build phone mappings
|
|
if lc == '-' and rc == '-' and wpos == '-':
|
|
self.phoneset[base] = phoneid
|
|
if wpos not in self.phonemap:
|
|
self.phonemap[wpos] = {}
|
|
if base not in self.phonemap[wpos]:
|
|
self.phonemap[wpos][base] = {}
|
|
if lc not in self.phonemap[wpos][base]:
|
|
self.phonemap[wpos][base][lc] = {}
|
|
self.phonemap[wpos][base][lc][rc] = phoneid
|
|
self.trimap.append((base, lc, rc, wpos))
|
|
self.fillermap[phoneid] = (attrib == 'filler')
|
|
self.tmatmap[phoneid] = int(tmat)
|
|
|
|
# Build senone sequence mapping
|
|
sseq = ",".join(sids)
|
|
if sseq not in ssidmap:
|
|
ssidmap[sseq] = []
|
|
ssidmap[sseq].append(phoneid)
|
|
for s in sids:
|
|
# FIXME: Note these will break for one-to-many mappings
|
|
self.sidmap[int(s)] = phoneid
|
|
self.cisidmap[int(s)] = self.phoneset[base]
|
|
phoneid = phoneid + 1
|
|
|
|
# Now invert the senone sequence mapping
|
|
self.sseqmap = empty(self.n_phone, 'i')
|
|
# Fill an array with -1 (which is the ID for non-emitting
|
|
# states)
|
|
self.sseq = -1 * ones((len(ssidmap), self.max_emit_state+1), 'i')
|
|
|
|
sseqid = 0
|
|
self.pidmap = []
|
|
for sseq, phones in ssidmap.items():
|
|
sids = list(map(int, sseq.split(',')))
|
|
self.sseq[sseqid, 0:len(sids)] = sids
|
|
self.pidmap.append(phones)
|
|
for p in phones:
|
|
self.sseqmap[p] = sseqid
|
|
sseqid = sseqid + 1
|
|
fh.close()
|
|
|
|
def is_ciphone(self, sid):
|
|
return sid >= 0 and sid < self.n_ci
|
|
|
|
def is_cisenone(self, sid):
|
|
return sid >= 0 and sid < self.n_ci_sen
|
|
|
|
def phone_id(self, ci, lc='-', rc='-', wpos=None):
|
|
if wpos is None:
|
|
if lc != '-':
|
|
# Try all word positions to find one that matches
|
|
for new_wpos, pmap in self.phonemap.items():
|
|
if ci in pmap and lc in pmap[ci] and rc in pmap[ci][lc]:
|
|
wpos = new_wpos
|
|
break
|
|
else:
|
|
wpos = '-' # context-independent phones have no wpos
|
|
if wpos == '-':
|
|
# It's context-indepedent so ignore lc, rc
|
|
return self.phonemap[wpos][ci]['-']['-']
|
|
else:
|
|
return self.phonemap[wpos][ci][lc][rc]
|
|
|
|
def phone_id_nearest(self, ci, lc='-', rc='-', wpos=None):
|
|
if wpos is None or wpos == '-':
|
|
return self.phone_id(ci, lc, rc, wpos)
|
|
else:
|
|
# First try to back off to a different word position
|
|
for new_wpos, pmap in self.phonemap.items():
|
|
if ci in pmap and lc in pmap[ci] and rc in pmap[ci][lc]:
|
|
return self.phonemap[new_wpos][ci][lc][rc]
|
|
# If not, try using silence in the left/right context
|
|
if wpos == 'e' and 'SIL' in self.phonemap[wpos][ci][lc]:
|
|
return self.phonemap[wpos][ci][lc]['SIL']
|
|
if wpos == 'b' \
|
|
and 'SIL' in self.phonemap[wpos][ci] \
|
|
and rc in self.phonemap[wpos][ci]['SIL']:
|
|
return self.phonemap[wpos][ci]['SIL'][rc]
|
|
# If not, try context-independent
|
|
return self.phonemap['-'][ci]['-']['-']
|
|
|
|
def phone_from_id(self, pid):
|
|
return self.trimap[pid]
|
|
|
|
# FIXME: This may be bogus, see def. of sidmap above
|
|
def phone_id_from_senone_id(self, sid):
|
|
return self.sidmap[sid]
|
|
|
|
# FIXME: This may be bogus, see def. of sidmap above
|
|
def phone_from_senone_id(self, sid):
|
|
return self.trimap[int(self.sidmap[sid])]
|
|
|
|
# FIXME: This may be bogus, see def. of sidmap above
|
|
def ciphone_id_from_senone_id(self, sid):
|
|
return self.cisidmap[sid]
|
|
|
|
# FIXME: This may be bogus, see def. of sidmap above
|
|
def ciphone_from_senone_id(self, sid):
|
|
return self.trimap[int(self.cisidmap[sid])][0]
|
|
|
|
def triphones(self, ci, lc, wpos=None):
|
|
if wpos is None:
|
|
out = []
|
|
for wpos in self.phonemap:
|
|
out.extend(self.triphones(ci, lc, wpos))
|
|
else:
|
|
try:
|
|
return [(ci, lc, rc, wpos) for rc in self.phonemap[wpos][ci][lc]]
|
|
except KeyError:
|
|
return []
|
|
return out
|
|
|
|
def pid2ssid(self, pid):
|
|
return int(self.sseqmap[pid])
|
|
|
|
def pid2sseq(self, pid):
|
|
return self.sseq[self.sseqmap[pid]]
|
|
|
|
def pid2tmat(self, pid):
|
|
return int(self.tmatmap[pid])
|
|
|
|
def is_filler(self, pid):
|
|
return int(self.fillermap[pid])
|