Initial commit: FunASR Speech Recognition Toolkit
Update API Documentation / build-api-docs (push) Has been cancelled
Update API Documentation / build-api-docs (push) Has been cancelled
Add complete FunASR codebase including models, runtime, and documentation.
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
cmake_minimum_required(VERSION 3.10 FATAL_ERROR)
|
||||
|
||||
project(kaldi)
|
||||
|
||||
add_library(kaldi-util STATIC
|
||||
base/kaldi-error.cc
|
||||
base/kaldi-math.cc
|
||||
util/kaldi-io.cc
|
||||
util/parse-options.cc
|
||||
util/simple-io-funcs.cc
|
||||
util/text-utils.cc
|
||||
)
|
||||
#target_link_libraries(kaldi-util PUBLIC utils)
|
||||
|
||||
add_library(kaldi-decoder STATIC
|
||||
lat/determinize-lattice-pruned.cc
|
||||
lat/lattice-functions.cc
|
||||
decoder/lattice-faster-decoder.cc
|
||||
decoder/lattice-faster-online-decoder.cc
|
||||
)
|
||||
|
||||
if (WIN32)
|
||||
target_link_libraries(kaldi-decoder PUBLIC kaldi-util)
|
||||
else()
|
||||
target_link_libraries(kaldi-decoder PUBLIC kaldi-util dl)
|
||||
endif (WIN32)
|
||||
|
||||
|
||||
if (WIN32)
|
||||
target_compile_definitions (kaldi-decoder PUBLIC GLOG_NO_ABBREVIATED_SEVERITIES)
|
||||
endif (WIN32)
|
||||
|
||||
include_directories(${CMAKE_SOURCE_DIR}/build/third_party/glog)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/third_party/glog/src)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/third_party/gflags/src/include)
|
||||
if(TRUE)
|
||||
# Arpa binary
|
||||
add_executable(arpa2fst
|
||||
lm/arpa-file-parser.cc
|
||||
lm/arpa-lm-compiler.cc
|
||||
lmbin/arpa2fst.cc
|
||||
)
|
||||
|
||||
if (WIN32)
|
||||
target_link_libraries(arpa2fst PUBLIC kaldi-util fst)
|
||||
else()
|
||||
target_link_libraries(arpa2fst PUBLIC kaldi-util fst dl)
|
||||
endif (WIN32)
|
||||
|
||||
|
||||
# FST tools binary
|
||||
set(FST_BINS
|
||||
fstaddselfloops
|
||||
fstdeterminizestar
|
||||
fstisstochastic
|
||||
fstminimizeencoded
|
||||
fsttablecompose
|
||||
)
|
||||
|
||||
foreach(name IN LISTS FST_BINS)
|
||||
add_executable(${name}
|
||||
fstbin/${name}.cc
|
||||
fstext/kaldi-fst-io.cc
|
||||
)
|
||||
if (WIN32)
|
||||
target_link_libraries(${name} PUBLIC kaldi-util fst)
|
||||
else()
|
||||
target_link_libraries(${name} PUBLIC kaldi-util fst dl)
|
||||
endif (WIN32)
|
||||
endforeach()
|
||||
endif()
|
||||
@@ -0,0 +1,27 @@
|
||||
# Whenever make is run in this directory, call ./get_version.sh as the
|
||||
# first thing. This script regenereates ./version.h if necessary, e.g.
|
||||
# if it does not already exist or if the version number has changed.
|
||||
LOG := $(shell ./get_version.sh; echo " $$?")
|
||||
ifneq ($(strip $(LOG)), 0)
|
||||
RC := $(lastword $(LOG))
|
||||
OUT := $(wordlist 1,$(shell echo $$(($(words $(LOG))-1))),$(LOG))
|
||||
ifeq ($(RC),0)
|
||||
$(info $(OUT))
|
||||
else
|
||||
$(error $(OUT))
|
||||
endif
|
||||
endif
|
||||
|
||||
all:
|
||||
|
||||
include ../kaldi.mk
|
||||
|
||||
TESTFILES = kaldi-math-test io-funcs-test kaldi-error-test timer-test
|
||||
|
||||
OBJFILES = kaldi-math.o kaldi-error.o io-funcs.o kaldi-utils.o timer.o
|
||||
|
||||
LIBNAME = kaldi-base
|
||||
|
||||
ADDLIBS =
|
||||
|
||||
include ../makefiles/default_rules.mk
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright 2017 University of Southern California (Author: Dogan Can)
|
||||
|
||||
# See ../../COPYING for clarification regarding multiple authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
# WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
# MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
# See the Apache 2 License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
# Kaldi versioning is loosely based on the semantic versioning scheme. This
|
||||
# script tries to work out the version string from the partial version number
|
||||
# specified in src/.version along with the recent git history. By convention
|
||||
# src/.version specifies the first two components (MAJOR.MINOR) of the version
|
||||
# number. The third component (PATCH) is determined by counting how many
|
||||
# commits there are that are newer than than the last commit modifiying
|
||||
# src/.version. If there are uncommitted changes in the src/ directory, then
|
||||
# the version string is extended with a suffix (~N) specifiying the number of
|
||||
# files with uncommitted changes. The last component of the version string is
|
||||
# the abbreviated hash of the HEAD commit. If git history is not available or
|
||||
# if the file src/.short_version exists, then the version string defaults to
|
||||
# the number specified in src/.version.
|
||||
|
||||
set -e
|
||||
|
||||
# Change working directory to the directory where this script is located.
|
||||
cd "$(dirname ${BASH_SOURCE[0]})"
|
||||
|
||||
# Read the partial version number specified in the first line of src/.version.
|
||||
version=$(head -1 ../.version)
|
||||
|
||||
if [ -e ../.short_version ]; then
|
||||
echo "$0: File src/.short_version exists."
|
||||
echo "$0: Stopping the construction of full version number from git history."
|
||||
elif ! [[ $version =~ ^[0-9][0-9]*.[0-9][0-9]*$ ]]; then
|
||||
echo "$0: The version number \"$version\" specified in src/.version is not" \
|
||||
"in MAJOR.MINOR format."
|
||||
echo "$0: Stopping the construction of full version number from git history."
|
||||
elif ! which git >&/dev/null; then
|
||||
echo "$0: Git is not installed."
|
||||
echo "$0: Using the version number \"$version\" specified in src/.version."
|
||||
elif [ "$(git rev-parse --is-inside-work-tree 2>/dev/null)" != true ]; then
|
||||
echo "$0: Git history is not available."
|
||||
echo "$0: Using the version number \"$version\" specified in src/.version."
|
||||
else
|
||||
# Figure out patch number.
|
||||
version_commit=$(git log -1 --pretty=oneline ../.version | awk '{print $1}')
|
||||
patch_number=$(git rev-list ${version_commit}..HEAD | wc -l | awk '{print $1}')
|
||||
version="$version.$patch_number"
|
||||
|
||||
# Check for uncommitted changes in src/.
|
||||
uncommitted_changes=$(git diff-index HEAD -- .. | wc -l | awk '{print $1}')
|
||||
if [ $uncommitted_changes -gt 0 ]; then
|
||||
# Add suffix ~N if there are N files in src/ with uncommitted changes
|
||||
version="$version~$uncommitted_changes"
|
||||
fi
|
||||
|
||||
# Figure out HEAD commit SHA-1.
|
||||
head_commit=$(git log -1 --pretty=oneline | awk '{print $1}')
|
||||
head_commit_short=$(git log -1 --oneline --abbrev=4 | awk '{print $1}')
|
||||
version="$version-${head_commit_short}"
|
||||
fi
|
||||
|
||||
# Empty version number is not allowed.
|
||||
if [ -z "$version" ]; then
|
||||
version="?"
|
||||
fi
|
||||
|
||||
# Write version info to a temporary file.
|
||||
temp=$(mktemp /tmp/temp.XXXXXX)
|
||||
trap 'rm -f "$temp"' EXIT
|
||||
echo "// This file was automatically created by ./get_version.sh." > $temp
|
||||
echo "// It is only included by ./kaldi-error.cc." >> $temp
|
||||
echo "#define KALDI_VERSION \"$version\"" >> $temp
|
||||
if [ -n "$head_commit" ]; then
|
||||
echo "#define KALDI_GIT_HEAD \"$head_commit\"" >> $temp
|
||||
fi
|
||||
|
||||
# Overwrite ./version.h with the temporary file if they are different.
|
||||
if ! cmp -s $temp version.h; then
|
||||
cp $temp version.h
|
||||
chmod 644 version.h
|
||||
fi
|
||||
exit 0
|
||||
@@ -0,0 +1,327 @@
|
||||
// base/io-funcs-inl.h
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation; Saarland University;
|
||||
// Jan Silovsky; Yanmin Qian;
|
||||
// Johns Hopkins University (Author: Daniel Povey)
|
||||
// 2016 Xiaohui Zhang
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef KALDI_BASE_IO_FUNCS_INL_H_
|
||||
#define KALDI_BASE_IO_FUNCS_INL_H_ 1
|
||||
|
||||
// Do not include this file directly. It is included by base/io-funcs.h
|
||||
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
namespace kaldi {
|
||||
|
||||
// Template that covers integers.
|
||||
template<class T> void WriteBasicType(std::ostream &os,
|
||||
bool binary, T t) {
|
||||
// Compile time assertion that this is not called with a wrong type.
|
||||
KALDI_ASSERT_IS_INTEGER_TYPE(T);
|
||||
if (binary) {
|
||||
char len_c = (std::numeric_limits<T>::is_signed ? 1 : -1)
|
||||
* static_cast<char>(sizeof(t));
|
||||
os.put(len_c);
|
||||
os.write(reinterpret_cast<const char *>(&t), sizeof(t));
|
||||
} else {
|
||||
if (sizeof(t) == 1)
|
||||
os << static_cast<int16>(t) << " ";
|
||||
else
|
||||
os << t << " ";
|
||||
}
|
||||
if (os.fail()) {
|
||||
KALDI_ERR << "Write failure in WriteBasicType.";
|
||||
}
|
||||
}
|
||||
|
||||
// Template that covers integers.
|
||||
template<class T> inline void ReadBasicType(std::istream &is,
|
||||
bool binary, T *t) {
|
||||
KALDI_PARANOID_ASSERT(t != NULL);
|
||||
// Compile time assertion that this is not called with a wrong type.
|
||||
KALDI_ASSERT_IS_INTEGER_TYPE(T);
|
||||
if (binary) {
|
||||
int len_c_in = is.get();
|
||||
if (len_c_in == -1)
|
||||
KALDI_ERR << "ReadBasicType: encountered end of stream.";
|
||||
char len_c = static_cast<char>(len_c_in), len_c_expected
|
||||
= (std::numeric_limits<T>::is_signed ? 1 : -1)
|
||||
* static_cast<char>(sizeof(*t));
|
||||
if (len_c != len_c_expected) {
|
||||
KALDI_ERR << "ReadBasicType: did not get expected integer type, "
|
||||
<< static_cast<int>(len_c)
|
||||
<< " vs. " << static_cast<int>(len_c_expected)
|
||||
<< ". You can change this code to successfully"
|
||||
<< " read it later, if needed.";
|
||||
// insert code here to read "wrong" type. Might have a switch statement.
|
||||
}
|
||||
is.read(reinterpret_cast<char *>(t), sizeof(*t));
|
||||
} else {
|
||||
if (sizeof(*t) == 1) {
|
||||
int16 i;
|
||||
is >> i;
|
||||
*t = i;
|
||||
} else {
|
||||
is >> *t;
|
||||
}
|
||||
}
|
||||
if (is.fail()) {
|
||||
KALDI_ERR << "Read failure in ReadBasicType, file position is "
|
||||
<< is.tellg() << ", next char is " << is.peek();
|
||||
}
|
||||
}
|
||||
|
||||
// Template that covers integers.
|
||||
template<class T>
|
||||
inline void WriteIntegerPairVector(std::ostream &os, bool binary,
|
||||
const std::vector<std::pair<T, T> > &v) {
|
||||
// Compile time assertion that this is not called with a wrong type.
|
||||
KALDI_ASSERT_IS_INTEGER_TYPE(T);
|
||||
if (binary) {
|
||||
char sz = sizeof(T); // this is currently just a check.
|
||||
os.write(&sz, 1);
|
||||
int32 vecsz = static_cast<int32>(v.size());
|
||||
KALDI_ASSERT((size_t)vecsz == v.size());
|
||||
os.write(reinterpret_cast<const char *>(&vecsz), sizeof(vecsz));
|
||||
if (vecsz != 0) {
|
||||
os.write(reinterpret_cast<const char *>(&(v[0])), sizeof(T) * vecsz * 2);
|
||||
}
|
||||
} else {
|
||||
// focus here is on prettiness of text form rather than
|
||||
// efficiency of reading-in.
|
||||
// reading-in is dominated by low-level operations anyway:
|
||||
// for efficiency use binary.
|
||||
os << "[ ";
|
||||
typename std::vector<std::pair<T, T> >::const_iterator iter = v.begin(),
|
||||
end = v.end();
|
||||
for (; iter != end; ++iter) {
|
||||
if (sizeof(T) == 1)
|
||||
os << static_cast<int16>(iter->first) << ','
|
||||
<< static_cast<int16>(iter->second) << ' ';
|
||||
else
|
||||
os << iter->first << ','
|
||||
<< iter->second << ' ';
|
||||
}
|
||||
os << "]\n";
|
||||
}
|
||||
if (os.fail()) {
|
||||
KALDI_ERR << "Write failure in WriteIntegerPairVector.";
|
||||
}
|
||||
}
|
||||
|
||||
// Template that covers integers.
|
||||
template<class T>
|
||||
inline void ReadIntegerPairVector(std::istream &is, bool binary,
|
||||
std::vector<std::pair<T, T> > *v) {
|
||||
KALDI_ASSERT_IS_INTEGER_TYPE(T);
|
||||
KALDI_ASSERT(v != NULL);
|
||||
if (binary) {
|
||||
int sz = is.peek();
|
||||
if (sz == sizeof(T)) {
|
||||
is.get();
|
||||
} else { // this is currently just a check.
|
||||
KALDI_ERR << "ReadIntegerPairVector: expected to see type of size "
|
||||
<< sizeof(T) << ", saw instead " << sz << ", at file position "
|
||||
<< is.tellg();
|
||||
}
|
||||
int32 vecsz;
|
||||
is.read(reinterpret_cast<char *>(&vecsz), sizeof(vecsz));
|
||||
if (is.fail() || vecsz < 0) goto bad;
|
||||
v->resize(vecsz);
|
||||
if (vecsz > 0) {
|
||||
is.read(reinterpret_cast<char *>(&((*v)[0])), sizeof(T)*vecsz*2);
|
||||
}
|
||||
} else {
|
||||
std::vector<std::pair<T, T> > tmp_v; // use temporary so v doesn't use extra memory
|
||||
// due to resizing.
|
||||
is >> std::ws;
|
||||
if (is.peek() != static_cast<int>('[')) {
|
||||
KALDI_ERR << "ReadIntegerPairVector: expected to see [, saw "
|
||||
<< is.peek() << ", at file position " << is.tellg();
|
||||
}
|
||||
is.get(); // consume the '['.
|
||||
is >> std::ws; // consume whitespace.
|
||||
while (is.peek() != static_cast<int>(']')) {
|
||||
if (sizeof(T) == 1) { // read/write chars as numbers.
|
||||
int16 next_t1, next_t2;
|
||||
is >> next_t1;
|
||||
if (is.fail()) goto bad;
|
||||
if (is.peek() != static_cast<int>(','))
|
||||
KALDI_ERR << "ReadIntegerPairVector: expected to see ',', saw "
|
||||
<< is.peek() << ", at file position " << is.tellg();
|
||||
is.get(); // consume the ','.
|
||||
is >> next_t2 >> std::ws;
|
||||
if (is.fail()) goto bad;
|
||||
else
|
||||
tmp_v.push_back(std::make_pair<T, T>((T)next_t1, (T)next_t2));
|
||||
} else {
|
||||
T next_t1, next_t2;
|
||||
is >> next_t1;
|
||||
if (is.fail()) goto bad;
|
||||
if (is.peek() != static_cast<int>(','))
|
||||
KALDI_ERR << "ReadIntegerPairVector: expected to see ',', saw "
|
||||
<< is.peek() << ", at file position " << is.tellg();
|
||||
is.get(); // consume the ','.
|
||||
is >> next_t2 >> std::ws;
|
||||
if (is.fail()) goto bad;
|
||||
else
|
||||
tmp_v.push_back(std::pair<T, T>(next_t1, next_t2));
|
||||
}
|
||||
}
|
||||
is.get(); // get the final ']'.
|
||||
*v = tmp_v; // could use std::swap to use less temporary memory, but this
|
||||
// uses less permanent memory.
|
||||
}
|
||||
if (!is.fail()) return;
|
||||
bad:
|
||||
KALDI_ERR << "ReadIntegerPairVector: read failure at file position "
|
||||
<< is.tellg();
|
||||
}
|
||||
|
||||
template<class T> inline void WriteIntegerVector(std::ostream &os, bool binary,
|
||||
const std::vector<T> &v) {
|
||||
// Compile time assertion that this is not called with a wrong type.
|
||||
KALDI_ASSERT_IS_INTEGER_TYPE(T);
|
||||
if (binary) {
|
||||
char sz = sizeof(T); // this is currently just a check.
|
||||
os.write(&sz, 1);
|
||||
int32 vecsz = static_cast<int32>(v.size());
|
||||
KALDI_ASSERT((size_t)vecsz == v.size());
|
||||
os.write(reinterpret_cast<const char *>(&vecsz), sizeof(vecsz));
|
||||
if (vecsz != 0) {
|
||||
os.write(reinterpret_cast<const char *>(&(v[0])), sizeof(T)*vecsz);
|
||||
}
|
||||
} else {
|
||||
// focus here is on prettiness of text form rather than
|
||||
// efficiency of reading-in.
|
||||
// reading-in is dominated by low-level operations anyway:
|
||||
// for efficiency use binary.
|
||||
os << "[ ";
|
||||
typename std::vector<T>::const_iterator iter = v.begin(), end = v.end();
|
||||
for (; iter != end; ++iter) {
|
||||
if (sizeof(T) == 1)
|
||||
os << static_cast<int16>(*iter) << " ";
|
||||
else
|
||||
os << *iter << " ";
|
||||
}
|
||||
os << "]\n";
|
||||
}
|
||||
if (os.fail()) {
|
||||
KALDI_ERR << "Write failure in WriteIntegerVector.";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template<class T> inline void ReadIntegerVector(std::istream &is,
|
||||
bool binary,
|
||||
std::vector<T> *v) {
|
||||
KALDI_ASSERT_IS_INTEGER_TYPE(T);
|
||||
KALDI_ASSERT(v != NULL);
|
||||
if (binary) {
|
||||
int sz = is.peek();
|
||||
if (sz == sizeof(T)) {
|
||||
is.get();
|
||||
} else { // this is currently just a check.
|
||||
KALDI_ERR << "ReadIntegerVector: expected to see type of size "
|
||||
<< sizeof(T) << ", saw instead " << sz << ", at file position "
|
||||
<< is.tellg();
|
||||
}
|
||||
int32 vecsz;
|
||||
is.read(reinterpret_cast<char *>(&vecsz), sizeof(vecsz));
|
||||
if (is.fail() || vecsz < 0) goto bad;
|
||||
v->resize(vecsz);
|
||||
if (vecsz > 0) {
|
||||
is.read(reinterpret_cast<char *>(&((*v)[0])), sizeof(T)*vecsz);
|
||||
}
|
||||
} else {
|
||||
std::vector<T> tmp_v; // use temporary so v doesn't use extra memory
|
||||
// due to resizing.
|
||||
is >> std::ws;
|
||||
if (is.peek() != static_cast<int>('[')) {
|
||||
KALDI_ERR << "ReadIntegerVector: expected to see [, saw "
|
||||
<< is.peek() << ", at file position " << is.tellg();
|
||||
}
|
||||
is.get(); // consume the '['.
|
||||
is >> std::ws; // consume whitespace.
|
||||
while (is.peek() != static_cast<int>(']')) {
|
||||
if (sizeof(T) == 1) { // read/write chars as numbers.
|
||||
int16 next_t;
|
||||
is >> next_t >> std::ws;
|
||||
if (is.fail()) goto bad;
|
||||
else
|
||||
tmp_v.push_back((T)next_t);
|
||||
} else {
|
||||
T next_t;
|
||||
is >> next_t >> std::ws;
|
||||
if (is.fail()) goto bad;
|
||||
else
|
||||
tmp_v.push_back(next_t);
|
||||
}
|
||||
}
|
||||
is.get(); // get the final ']'.
|
||||
*v = tmp_v; // could use std::swap to use less temporary memory, but this
|
||||
// uses less permanent memory.
|
||||
}
|
||||
if (!is.fail()) return;
|
||||
bad:
|
||||
KALDI_ERR << "ReadIntegerVector: read failure at file position "
|
||||
<< is.tellg();
|
||||
}
|
||||
|
||||
|
||||
// Initialize an opened stream for writing by writing an optional binary
|
||||
// header and modifying the floating-point precision.
|
||||
inline void InitKaldiOutputStream(std::ostream &os, bool binary) {
|
||||
// This does not throw exceptions (does not check for errors).
|
||||
if (binary) {
|
||||
os.put('\0');
|
||||
os.put('B');
|
||||
}
|
||||
// Note, in non-binary mode we may at some point want to mess with
|
||||
// the precision a bit.
|
||||
// 7 is a bit more than the precision of float..
|
||||
if (os.precision() < 7)
|
||||
os.precision(7);
|
||||
}
|
||||
|
||||
/// Initialize an opened stream for reading by detecting the binary header and
|
||||
// setting the "binary" value appropriately.
|
||||
inline bool InitKaldiInputStream(std::istream &is, bool *binary) {
|
||||
// Sets the 'binary' variable.
|
||||
// Throws exception in the very unusual situation that stream
|
||||
// starts with '\0' but not then 'B'.
|
||||
|
||||
if (is.peek() == '\0') { // seems to be binary
|
||||
is.get();
|
||||
if (is.peek() != 'B') {
|
||||
return false;
|
||||
}
|
||||
is.get();
|
||||
*binary = true;
|
||||
return true;
|
||||
} else {
|
||||
*binary = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
} // end namespace kaldi.
|
||||
|
||||
#endif // KALDI_BASE_IO_FUNCS_INL_H_
|
||||
@@ -0,0 +1,160 @@
|
||||
// base/io-funcs-test.cc
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
#include "base/io-funcs.h"
|
||||
#include "base/kaldi-math.h"
|
||||
|
||||
namespace kaldi {
|
||||
|
||||
void UnitTestIo(bool binary) {
|
||||
{
|
||||
const char *filename = "tmpf";
|
||||
std::ofstream outfile(filename, std::ios_base::out | std::ios_base::binary);
|
||||
InitKaldiOutputStream(outfile, binary);
|
||||
if (!binary) outfile << "\t";
|
||||
int64 i1 = Rand() % 10000;
|
||||
WriteBasicType(outfile, binary, i1);
|
||||
uint16 i2 = Rand() % 10000;
|
||||
WriteBasicType(outfile, binary, i2);
|
||||
if (!binary) outfile << "\t";
|
||||
char c = Rand();
|
||||
WriteBasicType(outfile, binary, c);
|
||||
if (!binary && Rand()%2 == 0) outfile << " \n";
|
||||
std::vector<int32> vec1;
|
||||
WriteIntegerVector(outfile, binary, vec1);
|
||||
if (!binary && Rand()%2 == 0) outfile << " \n";
|
||||
std::vector<uint16> vec2;
|
||||
for (size_t i = 0; i < 10; i++) vec2.push_back(Rand()%100 - 10);
|
||||
WriteIntegerVector(outfile, binary, vec2);
|
||||
if (!binary) outfile << " \n";
|
||||
std::vector<char> vec3;
|
||||
|
||||
int32 size = RandInt(0, 10);
|
||||
for (size_t i = 0; i < size; i++) vec3.push_back(Rand()%100);
|
||||
WriteIntegerVector(outfile, binary, vec3);
|
||||
std::vector<std::pair<int32, int32> > vec4;
|
||||
WriteIntegerPairVector(outfile, binary, vec4);
|
||||
if (!binary && Rand()%2 == 0) outfile << " \n";
|
||||
std::vector<std::pair<uint16, uint16> > vec5;
|
||||
for (size_t i = 0; i < size; i++) vec5.push_back(std::make_pair<uint16, uint16>(Rand()%100 - 10, Rand()%100 - 10));
|
||||
WriteIntegerPairVector(outfile, binary, vec5);
|
||||
if (!binary) outfile << " \n";
|
||||
std::vector<std::pair<char, char> > vec6;
|
||||
for (size_t i = 0; i < size; i++) vec6.push_back(std::make_pair<char, char>(Rand()%100, Rand()%100));
|
||||
WriteIntegerPairVector(outfile, binary, vec6);
|
||||
if (!binary && Rand()%2 == 0) outfile << " \n";
|
||||
const char *token1 = "Hi";
|
||||
WriteToken(outfile, binary, token1);
|
||||
if (!binary) outfile << " \n";
|
||||
std::string token2 = "There.";
|
||||
WriteToken(outfile, binary, token2);
|
||||
if (!binary && Rand()%2 == 0) outfile << " \n";
|
||||
std::string token3 = "You.";
|
||||
WriteToken(outfile, binary, token3);
|
||||
if (!binary && Rand()%2 == 0) outfile << " ";
|
||||
float f1 = RandUniform();
|
||||
WriteBasicType(outfile, binary, f1);
|
||||
if (!binary && Rand()%2 == 0) outfile << "\t";
|
||||
float f2 = RandUniform();
|
||||
WriteBasicType(outfile, binary, f2);
|
||||
double d1 = RandUniform();
|
||||
WriteBasicType(outfile, binary, d1);
|
||||
if (!binary && Rand()%2 == 0) outfile << "\t";
|
||||
double d2 = RandUniform();
|
||||
WriteBasicType(outfile, binary, d2);
|
||||
if (!binary && Rand()%2 == 0) outfile << "\t";
|
||||
outfile.close();
|
||||
|
||||
{
|
||||
std::ifstream infile(filename, std::ios_base::in | std::ios_base::binary);
|
||||
bool binary_in;
|
||||
InitKaldiInputStream(infile, &binary_in);
|
||||
int64 i1_in;
|
||||
ReadBasicType(infile, binary_in, &i1_in);
|
||||
KALDI_ASSERT(i1_in == i1);
|
||||
uint16 i2_in;
|
||||
ReadBasicType(infile, binary_in, &i2_in);
|
||||
KALDI_ASSERT(i2_in == i2);
|
||||
char c_in;
|
||||
ReadBasicType(infile, binary_in, &c_in);
|
||||
KALDI_ASSERT(c_in == c);
|
||||
std::vector<int32> vec1_in;
|
||||
ReadIntegerVector(infile, binary_in, &vec1_in);
|
||||
KALDI_ASSERT(vec1_in == vec1);
|
||||
std::vector<uint16> vec2_in;
|
||||
ReadIntegerVector(infile, binary_in, &vec2_in);
|
||||
KALDI_ASSERT(vec2_in == vec2);
|
||||
std::vector<char> vec3_in;
|
||||
ReadIntegerVector(infile, binary_in, &vec3_in);
|
||||
KALDI_ASSERT(vec3_in == vec3);
|
||||
std::vector<std::pair<int32, int32> > vec4_in;
|
||||
ReadIntegerPairVector(infile, binary_in, &vec4_in);
|
||||
KALDI_ASSERT(vec4_in == vec4);
|
||||
std::vector<std::pair<uint16, uint16> > vec5_in;
|
||||
ReadIntegerPairVector(infile, binary_in, &vec5_in);
|
||||
KALDI_ASSERT(vec5_in == vec5);
|
||||
std::vector<std::pair<char, char> > vec6_in;
|
||||
ReadIntegerPairVector(infile, binary_in, &vec6_in);
|
||||
KALDI_ASSERT(vec6_in == vec6);
|
||||
std::string token1_in, token2_in;
|
||||
KALDI_ASSERT(Peek(infile, binary_in) == static_cast<int>(*token1));
|
||||
KALDI_ASSERT(PeekToken(infile, binary_in) == static_cast<int>(*token1));
|
||||
// Note:
|
||||
// the stuff with skipping over '<' is tested in ../util/kaldi-io-test.cc,
|
||||
// since we need to make sure it works with pipes.
|
||||
ReadToken(infile, binary_in, &token1_in);
|
||||
KALDI_ASSERT(token1_in == std::string(token1));
|
||||
ReadToken(infile, binary_in, &token2_in);
|
||||
KALDI_ASSERT(token2_in == std::string(token2));
|
||||
if (Rand() % 2 == 0)
|
||||
ExpectToken(infile, binary_in, token3.c_str());
|
||||
else
|
||||
ExpectToken(infile, binary_in, token3);
|
||||
float f1_in; // same type.
|
||||
ReadBasicType(infile, binary_in, &f1_in);
|
||||
AssertEqual(f1_in, f1);
|
||||
double f2_in; // wrong type.
|
||||
ReadBasicType(infile, binary_in, &f2_in);
|
||||
AssertEqual(f2_in, f2);
|
||||
double d1_in; // same type.
|
||||
ReadBasicType(infile, binary_in, &d1_in);
|
||||
AssertEqual(d1_in, d1);
|
||||
float d2_in; // wrong type.
|
||||
ReadBasicType(infile, binary_in, &d2_in);
|
||||
AssertEqual(d2_in, d2);
|
||||
KALDI_ASSERT(Peek(infile, binary_in) == -1);
|
||||
KALDI_ASSERT(PeekToken(infile, binary_in) == -1);
|
||||
}
|
||||
unlink(filename);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // end namespace kaldi.
|
||||
|
||||
int main() {
|
||||
using namespace kaldi;
|
||||
for (size_t i = 0; i < 10; i++) {
|
||||
UnitTestIo(false);
|
||||
UnitTestIo(true);
|
||||
}
|
||||
KALDI_ASSERT(1); // just to check that KALDI_ASSERT does not fail for 1.
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
// base/io-funcs.cc
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation; Saarland University
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "base/io-funcs.h"
|
||||
#include "base/kaldi-math.h"
|
||||
|
||||
namespace kaldi {
|
||||
|
||||
template<>
|
||||
void WriteBasicType<bool>(std::ostream &os, bool binary, bool b) {
|
||||
os << (b ? "T":"F");
|
||||
if (!binary) os << " ";
|
||||
if (os.fail())
|
||||
KALDI_ERR << "Write failure in WriteBasicType<bool>";
|
||||
}
|
||||
|
||||
template<>
|
||||
void ReadBasicType<bool>(std::istream &is, bool binary, bool *b) {
|
||||
KALDI_PARANOID_ASSERT(b != NULL);
|
||||
if (!binary) is >> std::ws; // eat up whitespace.
|
||||
char c = is.peek();
|
||||
if (c == 'T') {
|
||||
*b = true;
|
||||
is.get();
|
||||
} else if (c == 'F') {
|
||||
*b = false;
|
||||
is.get();
|
||||
} else {
|
||||
KALDI_ERR << "Read failure in ReadBasicType<bool>, file position is "
|
||||
<< is.tellg() << ", next char is " << CharToString(c);
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
void WriteBasicType<float>(std::ostream &os, bool binary, float f) {
|
||||
if (binary) {
|
||||
char c = sizeof(f);
|
||||
os.put(c);
|
||||
os.write(reinterpret_cast<const char *>(&f), sizeof(f));
|
||||
} else {
|
||||
os << f << " ";
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
void WriteBasicType<double>(std::ostream &os, bool binary, double f) {
|
||||
if (binary) {
|
||||
char c = sizeof(f);
|
||||
os.put(c);
|
||||
os.write(reinterpret_cast<const char *>(&f), sizeof(f));
|
||||
} else {
|
||||
os << f << " ";
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
void ReadBasicType<float>(std::istream &is, bool binary, float *f) {
|
||||
KALDI_PARANOID_ASSERT(f != NULL);
|
||||
if (binary) {
|
||||
double d;
|
||||
int c = is.peek();
|
||||
if (c == sizeof(*f)) {
|
||||
is.get();
|
||||
is.read(reinterpret_cast<char*>(f), sizeof(*f));
|
||||
} else if (c == sizeof(d)) {
|
||||
ReadBasicType(is, binary, &d);
|
||||
*f = d;
|
||||
} else {
|
||||
KALDI_ERR << "ReadBasicType: expected float, saw " << is.peek()
|
||||
<< ", at file position " << is.tellg();
|
||||
}
|
||||
} else {
|
||||
is >> *f;
|
||||
}
|
||||
if (is.fail()) {
|
||||
KALDI_ERR << "ReadBasicType: failed to read, at file position "
|
||||
<< is.tellg();
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
void ReadBasicType<double>(std::istream &is, bool binary, double *d) {
|
||||
KALDI_PARANOID_ASSERT(d != NULL);
|
||||
if (binary) {
|
||||
float f;
|
||||
int c = is.peek();
|
||||
if (c == sizeof(*d)) {
|
||||
is.get();
|
||||
is.read(reinterpret_cast<char*>(d), sizeof(*d));
|
||||
} else if (c == sizeof(f)) {
|
||||
ReadBasicType(is, binary, &f);
|
||||
*d = f;
|
||||
} else {
|
||||
KALDI_ERR << "ReadBasicType: expected float, saw " << is.peek()
|
||||
<< ", at file position " << is.tellg();
|
||||
}
|
||||
} else {
|
||||
is >> *d;
|
||||
}
|
||||
if (is.fail()) {
|
||||
KALDI_ERR << "ReadBasicType: failed to read, at file position "
|
||||
<< is.tellg();
|
||||
}
|
||||
}
|
||||
|
||||
void CheckToken(const char *token) {
|
||||
if (*token == '\0')
|
||||
KALDI_ERR << "Token is empty (not a valid token)";
|
||||
const char *orig_token = token;
|
||||
while (*token != '\0') {
|
||||
if (::isspace(*token))
|
||||
KALDI_ERR << "Token is not a valid token (contains space): '"
|
||||
<< orig_token << "'";
|
||||
token++;
|
||||
}
|
||||
}
|
||||
|
||||
void WriteToken(std::ostream &os, bool binary, const char *token) {
|
||||
// binary mode is ignored;
|
||||
// we use space as termination character in either case.
|
||||
KALDI_ASSERT(token != NULL);
|
||||
CheckToken(token); // make sure it's valid (can be read back)
|
||||
os << token << " ";
|
||||
if (os.fail()) {
|
||||
KALDI_ERR << "Write failure in WriteToken.";
|
||||
}
|
||||
}
|
||||
|
||||
int Peek(std::istream &is, bool binary) {
|
||||
if (!binary) is >> std::ws; // eat up whitespace.
|
||||
return is.peek();
|
||||
}
|
||||
|
||||
void WriteToken(std::ostream &os, bool binary, const std::string & token) {
|
||||
WriteToken(os, binary, token.c_str());
|
||||
}
|
||||
|
||||
void ReadToken(std::istream &is, bool binary, std::string *str) {
|
||||
KALDI_ASSERT(str != NULL);
|
||||
if (!binary) is >> std::ws; // consume whitespace.
|
||||
is >> *str;
|
||||
if (is.fail()) {
|
||||
KALDI_ERR << "ReadToken, failed to read token at file position "
|
||||
<< is.tellg();
|
||||
}
|
||||
if (!isspace(is.peek())) {
|
||||
KALDI_ERR << "ReadToken, expected space after token, saw instead "
|
||||
<< CharToString(static_cast<char>(is.peek()))
|
||||
<< ", at file position " << is.tellg();
|
||||
}
|
||||
is.get(); // consume the space.
|
||||
}
|
||||
|
||||
int PeekToken(std::istream &is, bool binary) {
|
||||
if (!binary) is >> std::ws; // consume whitespace.
|
||||
bool read_bracket;
|
||||
if (static_cast<char>(is.peek()) == '<') {
|
||||
read_bracket = true;
|
||||
is.get();
|
||||
} else {
|
||||
read_bracket = false;
|
||||
}
|
||||
int ans = is.peek();
|
||||
if (read_bracket) {
|
||||
if (!is.unget()) {
|
||||
// Clear the bad bit. This code can be (and is in fact) reached, since the
|
||||
// C++ standard does not guarantee that a call to unget() must succeed.
|
||||
is.clear();
|
||||
}
|
||||
}
|
||||
return ans;
|
||||
}
|
||||
|
||||
|
||||
void ExpectToken(std::istream &is, bool binary, const char *token) {
|
||||
int pos_at_start = is.tellg();
|
||||
KALDI_ASSERT(token != NULL);
|
||||
CheckToken(token); // make sure it's valid (can be read back)
|
||||
if (!binary) is >> std::ws; // consume whitespace.
|
||||
std::string str;
|
||||
is >> str;
|
||||
is.get(); // consume the space.
|
||||
if (is.fail()) {
|
||||
KALDI_ERR << "Failed to read token [started at file position "
|
||||
<< pos_at_start << "], expected " << token;
|
||||
}
|
||||
// The second half of the '&&' expression below is so that if we're expecting
|
||||
// "<Foo>", we will accept "Foo>" instead. This is so that the model-reading
|
||||
// code will tolerate errors in PeekToken where is.unget() failed; search for
|
||||
// is.clear() in PeekToken() for an explanation.
|
||||
if (strcmp(str.c_str(), token) != 0 &&
|
||||
!(token[0] == '<' && strcmp(str.c_str(), token + 1) == 0)) {
|
||||
KALDI_ERR << "Expected token \"" << token << "\", got instead \""
|
||||
<< str <<"\".";
|
||||
}
|
||||
}
|
||||
|
||||
void ExpectToken(std::istream &is, bool binary, const std::string &token) {
|
||||
ExpectToken(is, binary, token.c_str());
|
||||
}
|
||||
|
||||
} // end namespace kaldi
|
||||
@@ -0,0 +1,245 @@
|
||||
// base/io-funcs.h
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation; Saarland University;
|
||||
// Jan Silovsky; Yanmin Qian
|
||||
// 2016 Xiaohui Zhang
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef KALDI_BASE_IO_FUNCS_H_
|
||||
#define KALDI_BASE_IO_FUNCS_H_
|
||||
|
||||
// This header only contains some relatively low-level I/O functions.
|
||||
// The full Kaldi I/O declarations are in ../util/kaldi-io.h
|
||||
// and ../util/kaldi-table.h
|
||||
// They were put in util/ in order to avoid making the Matrix library
|
||||
// dependent on them.
|
||||
|
||||
#include <cctype>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include "base/kaldi-common.h"
|
||||
#include "base/io-funcs-inl.h"
|
||||
|
||||
namespace kaldi {
|
||||
|
||||
|
||||
|
||||
/*
|
||||
This comment describes the Kaldi approach to I/O. All objects can be written
|
||||
and read in two modes: binary and text. In addition we want to make the I/O
|
||||
work if we redefine the typedef "BaseFloat" between floats and doubles.
|
||||
We also want to have control over whitespace in text mode without affecting
|
||||
the meaning of the file, for pretty-printing purposes.
|
||||
|
||||
Errors are handled by throwing a KaldiFatalError exception.
|
||||
|
||||
For integer and floating-point types (and boolean values):
|
||||
|
||||
WriteBasicType(std::ostream &, bool binary, const T&);
|
||||
ReadBasicType(std::istream &, bool binary, T*);
|
||||
|
||||
and we expect these functions to be defined in such a way that they work when
|
||||
the type T changes between float and double, so you can read float into double
|
||||
and vice versa]. Note that for efficiency and space-saving reasons, the Vector
|
||||
and Matrix classes do not use these functions [but they preserve the type
|
||||
interchangeability in their own way]
|
||||
|
||||
For a class (or struct) C:
|
||||
class C {
|
||||
..
|
||||
Write(std::ostream &, bool binary, [possibly extra optional args for specific classes]) const;
|
||||
Read(std::istream &, bool binary, [possibly extra optional args for specific classes]);
|
||||
..
|
||||
}
|
||||
NOTE: The only actual optional args we used are the "add" arguments in
|
||||
Vector/Matrix classes, which specify whether we should sum the data already
|
||||
in the class with the data being read.
|
||||
|
||||
For types which are typedef's involving stl classes, I/O is as follows:
|
||||
typedef std::vector<std::pair<A, B> > MyTypedefName;
|
||||
|
||||
The user should define something like:
|
||||
|
||||
WriteMyTypedefName(std::ostream &, bool binary, const MyTypedefName &t);
|
||||
ReadMyTypedefName(std::ostream &, bool binary, MyTypedefName *t);
|
||||
|
||||
The user would have to write these functions.
|
||||
|
||||
For a type std::vector<T>:
|
||||
|
||||
void WriteIntegerVector(std::ostream &os, bool binary, const std::vector<T> &v);
|
||||
void ReadIntegerVector(std::istream &is, bool binary, std::vector<T> *v);
|
||||
|
||||
For other types, e.g. vectors of pairs, the user should create a routine of the
|
||||
type WriteMyTypedefName. This is to avoid introducing confusing templated functions;
|
||||
we could easily create templated functions to handle most of these cases but they
|
||||
would have to share the same name.
|
||||
|
||||
It also often happens that the user needs to write/read special tokens as part
|
||||
of a file. These might be class headers, or separators/identifiers in the class.
|
||||
We provide special functions for manipulating these. These special tokens must
|
||||
be nonempty and must not contain any whitespace.
|
||||
|
||||
void WriteToken(std::ostream &os, bool binary, const char*);
|
||||
void WriteToken(std::ostream &os, bool binary, const std::string & token);
|
||||
int Peek(std::istream &is, bool binary);
|
||||
void ReadToken(std::istream &is, bool binary, std::string *str);
|
||||
void PeekToken(std::istream &is, bool binary, std::string *str);
|
||||
|
||||
WriteToken writes the token and one space (whether in binary or text mode).
|
||||
|
||||
Peek returns the first character of the next token, by consuming whitespace
|
||||
(in text mode) and then returning the peek() character. It returns -1 at EOF;
|
||||
it doesn't throw. It's useful if a class can have various forms based on
|
||||
typedefs and virtual classes, and wants to know which version to read.
|
||||
|
||||
ReadToken allows the caller to obtain the next token. PeekToken works just
|
||||
like ReadToken, but seeks back to the beginning of the token. A subsequent
|
||||
call to ReadToken will read the same token again. This is useful when
|
||||
different object types are written to the same file; using PeekToken one can
|
||||
decide which of the objects to read.
|
||||
|
||||
There is currently no special functionality for writing/reading strings (where the strings
|
||||
contain data rather than "special tokens" that are whitespace-free and nonempty). This is
|
||||
because Kaldi is structured in such a way that strings don't appear, except as OpenFst symbol
|
||||
table entries (and these have their own format).
|
||||
|
||||
|
||||
NOTE: you should not call ReadIntegerType and WriteIntegerType with types,
|
||||
such as int and size_t, that are machine-independent -- at least not
|
||||
if you want your file formats to port between machines. Use int32 and
|
||||
int64 where necessary. There is no way to detect this using compile-time
|
||||
assertions because C++ only keeps track of the internal representation of
|
||||
the type.
|
||||
*/
|
||||
|
||||
/// \addtogroup io_funcs_basic
|
||||
/// @{
|
||||
|
||||
|
||||
/// WriteBasicType is the name of the write function for bool, integer types,
|
||||
/// and floating-point types. They all throw on error.
|
||||
template<class T> void WriteBasicType(std::ostream &os, bool binary, T t);
|
||||
|
||||
/// ReadBasicType is the name of the read function for bool, integer types,
|
||||
/// and floating-point types. They all throw on error.
|
||||
template<class T> void ReadBasicType(std::istream &is, bool binary, T *t);
|
||||
|
||||
|
||||
// Declare specialization for bool.
|
||||
template<>
|
||||
void WriteBasicType<bool>(std::ostream &os, bool binary, bool b);
|
||||
|
||||
template <>
|
||||
void ReadBasicType<bool>(std::istream &is, bool binary, bool *b);
|
||||
|
||||
// Declare specializations for float and double.
|
||||
template<>
|
||||
void WriteBasicType<float>(std::ostream &os, bool binary, float f);
|
||||
|
||||
template<>
|
||||
void WriteBasicType<double>(std::ostream &os, bool binary, double f);
|
||||
|
||||
template<>
|
||||
void ReadBasicType<float>(std::istream &is, bool binary, float *f);
|
||||
|
||||
template<>
|
||||
void ReadBasicType<double>(std::istream &is, bool binary, double *f);
|
||||
|
||||
// Define ReadBasicType that accepts an "add" parameter to add to
|
||||
// the destination. Caution: if used in Read functions, be careful
|
||||
// to initialize the parameters concerned to zero in the default
|
||||
// constructor.
|
||||
template<class T>
|
||||
inline void ReadBasicType(std::istream &is, bool binary, T *t, bool add) {
|
||||
if (!add) {
|
||||
ReadBasicType(is, binary, t);
|
||||
} else {
|
||||
T tmp = T(0);
|
||||
ReadBasicType(is, binary, &tmp);
|
||||
*t += tmp;
|
||||
}
|
||||
}
|
||||
|
||||
/// Function for writing STL vectors of integer types.
|
||||
template<class T> inline void WriteIntegerVector(std::ostream &os, bool binary,
|
||||
const std::vector<T> &v);
|
||||
|
||||
/// Function for reading STL vector of integer types.
|
||||
template<class T> inline void ReadIntegerVector(std::istream &is, bool binary,
|
||||
std::vector<T> *v);
|
||||
|
||||
/// Function for writing STL vectors of pairs of integer types.
|
||||
template<class T>
|
||||
inline void WriteIntegerPairVector(std::ostream &os, bool binary,
|
||||
const std::vector<std::pair<T, T> > &v);
|
||||
|
||||
/// Function for reading STL vector of pairs of integer types.
|
||||
template<class T>
|
||||
inline void ReadIntegerPairVector(std::istream &is, bool binary,
|
||||
std::vector<std::pair<T, T> > *v);
|
||||
|
||||
/// The WriteToken functions are for writing nonempty sequences of non-space
|
||||
/// characters. They are not for general strings.
|
||||
void WriteToken(std::ostream &os, bool binary, const char *token);
|
||||
void WriteToken(std::ostream &os, bool binary, const std::string & token);
|
||||
|
||||
/// Peek consumes whitespace (if binary == false) and then returns the peek()
|
||||
/// value of the stream.
|
||||
int Peek(std::istream &is, bool binary);
|
||||
|
||||
/// ReadToken gets the next token and puts it in str (exception on failure). If
|
||||
/// PeekToken() had been previously called, it is possible that the stream had
|
||||
/// failed to unget the starting '<' character. In this case ReadToken() returns
|
||||
/// the token string without the leading '<'. You must be prepared to handle
|
||||
/// this case. ExpectToken() handles this internally, and is not affected.
|
||||
void ReadToken(std::istream &is, bool binary, std::string *token);
|
||||
|
||||
/// PeekToken will return the first character of the next token, or -1 if end of
|
||||
/// file. It's the same as Peek(), except if the first character is '<' it will
|
||||
/// skip over it and will return the next character. It will attempt to unget
|
||||
/// the '<' so the stream is where it was before you did PeekToken(), however,
|
||||
/// this is not guaranteed (see ReadToken()).
|
||||
int PeekToken(std::istream &is, bool binary);
|
||||
|
||||
/// ExpectToken tries to read in the given token, and throws an exception
|
||||
/// on failure.
|
||||
void ExpectToken(std::istream &is, bool binary, const char *token);
|
||||
void ExpectToken(std::istream &is, bool binary, const std::string & token);
|
||||
|
||||
/// ExpectPretty attempts to read the text in "token", but only in non-binary
|
||||
/// mode. Throws exception on failure. It expects an exact match except that
|
||||
/// arbitrary whitespace matches arbitrary whitespace.
|
||||
void ExpectPretty(std::istream &is, bool binary, const char *token);
|
||||
void ExpectPretty(std::istream &is, bool binary, const std::string & token);
|
||||
|
||||
/// @} end "addtogroup io_funcs_basic"
|
||||
|
||||
|
||||
/// InitKaldiOutputStream initializes an opened stream for writing by writing an
|
||||
/// optional binary header and modifying the floating-point precision; it will
|
||||
/// typically not be called by users directly.
|
||||
inline void InitKaldiOutputStream(std::ostream &os, bool binary);
|
||||
|
||||
/// InitKaldiInputStream initializes an opened stream for reading by detecting
|
||||
/// the binary header and setting the "binary" value appropriately;
|
||||
/// It will typically not be called by users directly.
|
||||
inline bool InitKaldiInputStream(std::istream &is, bool *binary);
|
||||
|
||||
} // end namespace kaldi.
|
||||
#endif // KALDI_BASE_IO_FUNCS_H_
|
||||
@@ -0,0 +1,41 @@
|
||||
// base/kaldi-common.h
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef KALDI_BASE_KALDI_COMMON_H_
|
||||
#define KALDI_BASE_KALDI_COMMON_H_ 1
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdlib>
|
||||
#include <cstring> // C string stuff like strcpy
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
#include "base/kaldi-utils.h"
|
||||
#include "base/kaldi-error.h"
|
||||
#include "base/kaldi-types.h"
|
||||
#include "base/io-funcs.h"
|
||||
#include "base/kaldi-math.h"
|
||||
#include "base/timer.h"
|
||||
|
||||
#endif // KALDI_BASE_KALDI_COMMON_H_
|
||||
@@ -0,0 +1,82 @@
|
||||
// base/kaldi-error-test.cc
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "base/kaldi-common.h"
|
||||
|
||||
// testing that we get the stack trace.
|
||||
namespace kaldi {
|
||||
|
||||
void MyFunction2() { KALDI_ERR << "Ignore this error"; }
|
||||
|
||||
void MyFunction1() { MyFunction2(); }
|
||||
|
||||
void UnitTestError() {
|
||||
{
|
||||
std::cerr << "Ignore next error:\n";
|
||||
MyFunction1();
|
||||
}
|
||||
}
|
||||
|
||||
void VerifySymbolRange(const std::string &trace, const bool want_found,
|
||||
const std::string &want_symbol) {
|
||||
size_t begin, end;
|
||||
const bool found = internal::LocateSymbolRange(trace, &begin, &end);
|
||||
if (found != want_found) {
|
||||
KALDI_ERR << "Found mismatch, got " << found << " want " << want_found;
|
||||
}
|
||||
if (!found) {
|
||||
return;
|
||||
}
|
||||
const std::string symbol = trace.substr(begin, end - begin);
|
||||
if (symbol != want_symbol) {
|
||||
KALDI_ERR << "Symbol mismatch, got " << symbol << " want " << want_symbol;
|
||||
}
|
||||
}
|
||||
|
||||
void TestLocateSymbolRange() {
|
||||
VerifySymbolRange("", false, "");
|
||||
VerifySymbolRange(
|
||||
R"TRACE(./kaldi-error-test(_ZN5kaldi13UnitTestErrorEv+0xb) [0x804965d])TRACE",
|
||||
true, "_ZN5kaldi13UnitTestErrorEv");
|
||||
// It is ok thread_start is not found because it is a C symbol.
|
||||
VerifySymbolRange(
|
||||
R"TRACE(31 libsystem_pthread.dylib 0x00007fff6fe4e40d thread_start + 13)TRACE",
|
||||
false, "");
|
||||
VerifySymbolRange(
|
||||
R"TRACE(0 server 0x000000010f67614d _ZNK5kaldi13MessageLogger10LogMessageEv + 813)TRACE",
|
||||
true, "_ZNK5kaldi13MessageLogger10LogMessageEv");
|
||||
VerifySymbolRange(
|
||||
R"TRACE(29 libsystem_pthread.dylib 0x00007fff6fe4f2eb _pthread_body + 126)TRACE",
|
||||
true, "_pthread_body");
|
||||
}
|
||||
|
||||
} // namespace kaldi
|
||||
|
||||
int main() {
|
||||
kaldi::TestLocateSymbolRange();
|
||||
|
||||
kaldi::SetProgramName("/foo/bar/kaldi-error-test");
|
||||
try {
|
||||
kaldi::UnitTestError();
|
||||
KALDI_ASSERT(0); // should not happen.
|
||||
exit(1);
|
||||
} catch (kaldi::KaldiFatalError &e) {
|
||||
std::cout << "The error we generated was: '" << e.KaldiMessage() << "'\n";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
// base/kaldi-error.cc
|
||||
|
||||
// Copyright 2019 LAIX (Yi Sun)
|
||||
// Copyright 2019 SmartAction LLC (kkm)
|
||||
// Copyright 2016 Brno University of Technology (author: Karel Vesely)
|
||||
// Copyright 2009-2011 Microsoft Corporation; Lukas Burget; Ondrej Glembek
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifdef HAVE_EXECINFO_H
|
||||
#include <execinfo.h> // To get stack trace in error messages.
|
||||
// If this #include fails there is an error in the Makefile, it does not
|
||||
// support your platform well. Make sure HAVE_EXECINFO_H is undefined,
|
||||
// and the code will compile.
|
||||
#ifdef HAVE_CXXABI_H
|
||||
#include <cxxabi.h> // For name demangling.
|
||||
// Useful to decode the stack trace, but only used if we have execinfo.h
|
||||
#endif // HAVE_CXXABI_H
|
||||
#endif // HAVE_EXECINFO_H
|
||||
|
||||
#include "base/kaldi-common.h"
|
||||
#include "base/kaldi-error.h"
|
||||
|
||||
// KALDI_GIT_HEAD is useless currently in full repo
|
||||
//#if !defined(KALDI_VERSION)
|
||||
//#include "base/version.h"
|
||||
//#endif
|
||||
|
||||
namespace kaldi {
|
||||
|
||||
/***** GLOBAL VARIABLES FOR LOGGING *****/
|
||||
|
||||
int32 g_kaldi_verbose_level = 0;
|
||||
static std::string program_name;
|
||||
static LogHandler log_handler = NULL;
|
||||
|
||||
void SetProgramName(const char *basename) {
|
||||
// Using the 'static std::string' for the program name is mostly harmless,
|
||||
// because (a) Kaldi logging is undefined before main(), and (b) no stdc++
|
||||
// string implementation has been found in the wild that would not be just
|
||||
// an empty string when zero-initialized but not yet constructed.
|
||||
program_name = basename;
|
||||
}
|
||||
|
||||
/***** HELPER FUNCTIONS *****/
|
||||
|
||||
// Trim filename to at most 1 trailing directory long. Given a filename like
|
||||
// "/a/b/c/d/e/f.cc", return "e/f.cc". Support both '/' and '\' as the path
|
||||
// separator.
|
||||
static const char *GetShortFileName(const char *path) {
|
||||
if (path == nullptr)
|
||||
return "";
|
||||
|
||||
const char *prev = path, *last = path;
|
||||
while ((path = std::strpbrk(path, "\\/")) != nullptr) {
|
||||
++path;
|
||||
prev = last;
|
||||
last = path;
|
||||
}
|
||||
return prev;
|
||||
}
|
||||
|
||||
/***** STACK TRACE *****/
|
||||
|
||||
namespace internal {
|
||||
bool LocateSymbolRange(const std::string &trace_name, size_t *begin,
|
||||
size_t *end) {
|
||||
// Find the first '_' with leading ' ' or '('.
|
||||
*begin = std::string::npos;
|
||||
for (size_t i = 1; i < trace_name.size(); i++) {
|
||||
if (trace_name[i] != '_') {
|
||||
continue;
|
||||
}
|
||||
if (trace_name[i - 1] == ' ' || trace_name[i - 1] == '(') {
|
||||
*begin = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (*begin == std::string::npos) {
|
||||
return false;
|
||||
}
|
||||
*end = trace_name.find_first_of(" +", *begin);
|
||||
return *end != std::string::npos;
|
||||
}
|
||||
} // namespace internal
|
||||
|
||||
#ifdef HAVE_EXECINFO_H
|
||||
static std::string Demangle(std::string trace_name) {
|
||||
#ifndef HAVE_CXXABI_H
|
||||
return trace_name;
|
||||
#else // HAVE_CXXABI_H
|
||||
// Try demangle the symbol. We are trying to support the following formats
|
||||
// produced by different platforms:
|
||||
//
|
||||
// Linux:
|
||||
// ./kaldi-error-test(_ZN5kaldi13UnitTestErrorEv+0xb) [0x804965d]
|
||||
//
|
||||
// Mac:
|
||||
// 0 server 0x000000010f67614d _ZNK5kaldi13MessageLogger10LogMessageEv + 813
|
||||
//
|
||||
// We want to extract the name e.g., '_ZN5kaldi13UnitTestErrorEv' and
|
||||
// demangle it info a readable name like kaldi::UnitTextError.
|
||||
size_t begin, end;
|
||||
if (!internal::LocateSymbolRange(trace_name, &begin, &end)) {
|
||||
return trace_name;
|
||||
}
|
||||
std::string symbol = trace_name.substr(begin, end - begin);
|
||||
int status;
|
||||
char *demangled_name = abi::__cxa_demangle(symbol.c_str(), 0, 0, &status);
|
||||
if (status == 0 && demangled_name != nullptr) {
|
||||
symbol = demangled_name;
|
||||
free(demangled_name);
|
||||
}
|
||||
return trace_name.substr(0, begin) + symbol +
|
||||
trace_name.substr(end, std::string::npos);
|
||||
#endif // HAVE_CXXABI_H
|
||||
}
|
||||
#endif // HAVE_EXECINFO_H
|
||||
|
||||
static std::string KaldiGetStackTrace() {
|
||||
std::string ans;
|
||||
#ifdef HAVE_EXECINFO_H
|
||||
const size_t KALDI_MAX_TRACE_SIZE = 50;
|
||||
const size_t KALDI_MAX_TRACE_PRINT = 50; // Must be even.
|
||||
// Buffer for the trace.
|
||||
void *trace[KALDI_MAX_TRACE_SIZE];
|
||||
// Get the trace.
|
||||
size_t size = backtrace(trace, KALDI_MAX_TRACE_SIZE);
|
||||
// Get the trace symbols.
|
||||
char **trace_symbol = backtrace_symbols(trace, size);
|
||||
if (trace_symbol == NULL)
|
||||
return ans;
|
||||
|
||||
// Compose a human-readable backtrace string.
|
||||
ans += "[ Stack-Trace: ]\n";
|
||||
if (size <= KALDI_MAX_TRACE_PRINT) {
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
ans += Demangle(trace_symbol[i]) + "\n";
|
||||
}
|
||||
} else { // Print out first+last (e.g.) 5.
|
||||
for (size_t i = 0; i < KALDI_MAX_TRACE_PRINT / 2; i++) {
|
||||
ans += Demangle(trace_symbol[i]) + "\n";
|
||||
}
|
||||
ans += ".\n.\n.\n";
|
||||
for (size_t i = size - KALDI_MAX_TRACE_PRINT / 2; i < size; i++) {
|
||||
ans += Demangle(trace_symbol[i]) + "\n";
|
||||
}
|
||||
if (size == KALDI_MAX_TRACE_SIZE)
|
||||
ans += ".\n.\n.\n"; // Stack was too long, probably a bug.
|
||||
}
|
||||
|
||||
// We must free the array of pointers allocated by backtrace_symbols(),
|
||||
// but not the strings themselves.
|
||||
free(trace_symbol);
|
||||
#endif // HAVE_EXECINFO_H
|
||||
return ans;
|
||||
}
|
||||
|
||||
/***** KALDI LOGGING *****/
|
||||
|
||||
MessageLogger::MessageLogger(LogMessageEnvelope::Severity severity,
|
||||
const char *func, const char *file, int32 line) {
|
||||
// Obviously, we assume the strings survive the destruction of this object.
|
||||
envelope_.severity = severity;
|
||||
envelope_.func = func;
|
||||
envelope_.file = GetShortFileName(file); // Points inside 'file'.
|
||||
envelope_.line = line;
|
||||
}
|
||||
|
||||
void MessageLogger::LogMessage() const {
|
||||
// Send to the logging handler if provided.
|
||||
if (log_handler != NULL) {
|
||||
log_handler(envelope_, GetMessage().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, use the default Kaldi logging.
|
||||
// Build the log-message header.
|
||||
std::stringstream full_message;
|
||||
if (envelope_.severity > LogMessageEnvelope::kInfo) {
|
||||
full_message << "VLOG[" << envelope_.severity << "] (";
|
||||
} else {
|
||||
switch (envelope_.severity) {
|
||||
case LogMessageEnvelope::kInfo:
|
||||
//full_message << "LOG (\n";
|
||||
break;
|
||||
case LogMessageEnvelope::kWarning:
|
||||
//full_message << "WARNING (\n";
|
||||
break;
|
||||
case LogMessageEnvelope::kAssertFailed:
|
||||
full_message << "ASSERTION_FAILED (\n";
|
||||
break;
|
||||
case LogMessageEnvelope::kError:
|
||||
default: // If not the ERROR, it still an error!
|
||||
full_message << "ERROR (\n";
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Add other info from the envelope and the message text.
|
||||
//full_message << program_name.c_str() << "[" KALDI_VERSION "]" << ':'
|
||||
// << envelope_.func << "():" << envelope_.file << ':'
|
||||
// << envelope_.line << ") " << GetMessage().c_str();
|
||||
|
||||
// Add stack trace for errors and assertion failures, if available.
|
||||
if (envelope_.severity < LogMessageEnvelope::kWarning) {
|
||||
const std::string &stack_trace = KaldiGetStackTrace();
|
||||
if (!stack_trace.empty()) {
|
||||
full_message << "\n\n" << stack_trace;
|
||||
}
|
||||
}
|
||||
|
||||
// Print the complete message to stderr.
|
||||
std::cerr << full_message.str();
|
||||
}
|
||||
|
||||
/***** KALDI ASSERTS *****/
|
||||
|
||||
void KaldiAssertFailure_(const char *func, const char *file, int32 line,
|
||||
const char *cond_str) {
|
||||
MessageLogger::Log() =
|
||||
MessageLogger(LogMessageEnvelope::kAssertFailed, func, file, line)
|
||||
<< "Assertion failed: (" << cond_str << ")";
|
||||
fflush(NULL); // Flush all pending buffers, abort() may not flush stderr.
|
||||
std::abort();
|
||||
}
|
||||
|
||||
/***** THIRD-PARTY LOG-HANDLER *****/
|
||||
|
||||
LogHandler SetLogHandler(LogHandler handler) {
|
||||
LogHandler old_handler = log_handler;
|
||||
log_handler = handler;
|
||||
return old_handler;
|
||||
}
|
||||
|
||||
} // namespace kaldi
|
||||
@@ -0,0 +1,231 @@
|
||||
// base/kaldi-error.h
|
||||
|
||||
// Copyright 2019 LAIX (Yi Sun)
|
||||
// Copyright 2019 SmartAction LLC (kkm)
|
||||
// Copyright 2016 Brno University of Technology (author: Karel Vesely)
|
||||
// Copyright 2009-2011 Microsoft Corporation; Ondrej Glembek; Lukas Burget;
|
||||
// Saarland University
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef KALDI_BASE_KALDI_ERROR_H_
|
||||
#define KALDI_BASE_KALDI_ERROR_H_ 1
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/kaldi-types.h"
|
||||
#include "base/kaldi-utils.h"
|
||||
/* Important that this file does not depend on any other kaldi headers. */
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define __func__ __FUNCTION__
|
||||
#endif
|
||||
|
||||
namespace kaldi {
|
||||
|
||||
/// \addtogroup error_group
|
||||
/// @{
|
||||
|
||||
/***** PROGRAM NAME AND VERBOSITY LEVEL *****/
|
||||
|
||||
/// Called by ParseOptions to set base name (no directory) of the executing
|
||||
/// program. The name is printed in logging code along with every message,
|
||||
/// because in our scripts, we often mix together the stderr of many programs.
|
||||
/// This function is very thread-unsafe.
|
||||
void SetProgramName(const char *basename);
|
||||
|
||||
/// This is set by util/parse-options.{h,cc} if you set --verbose=? option.
|
||||
/// Do not use directly, prefer {Get,Set}VerboseLevel().
|
||||
extern int32 g_kaldi_verbose_level;
|
||||
|
||||
/// Get verbosity level, usually set via command line '--verbose=' switch.
|
||||
inline int32 GetVerboseLevel() { return g_kaldi_verbose_level; }
|
||||
|
||||
/// This should be rarely used, except by programs using Kaldi as library;
|
||||
/// command-line programs set the verbose level automatically from ParseOptions.
|
||||
inline void SetVerboseLevel(int32 i) { g_kaldi_verbose_level = i; }
|
||||
|
||||
/***** KALDI LOGGING *****/
|
||||
|
||||
/// Log message severity and source location info.
|
||||
struct LogMessageEnvelope {
|
||||
/// Message severity. In addition to these levels, positive values (1 to 6)
|
||||
/// specify verbose logging level. Verbose messages are produced only when
|
||||
/// SetVerboseLevel() has been called to set logging level to at least the
|
||||
/// corresponding value.
|
||||
enum Severity {
|
||||
kAssertFailed = -3, //!< Assertion failure. abort() will be called.
|
||||
kError = -2, //!< Fatal error. KaldiFatalError will be thrown.
|
||||
kWarning = -1, //!< Indicates a recoverable but abnormal condition.
|
||||
kInfo = 0, //!< Informational message.
|
||||
};
|
||||
int severity; //!< A Severity value, or positive verbosity level.
|
||||
const char *func; //!< Name of the function invoking the logging.
|
||||
const char *file; //!< Source file name with up to 1 leading directory.
|
||||
int32 line; //<! Line number in the source file.
|
||||
};
|
||||
|
||||
/// Kaldi fatal runtime error exception. This exception is thrown from any use
|
||||
/// of the KALDI_ERR logging macro after the logging function, either set by
|
||||
/// SetLogHandler(), or the Kaldi's internal one, has returned.
|
||||
class KaldiFatalError : public std::runtime_error {
|
||||
public:
|
||||
explicit KaldiFatalError(const std::string &message)
|
||||
: std::runtime_error(message) {}
|
||||
explicit KaldiFatalError(const char *message) : std::runtime_error(message) {}
|
||||
|
||||
/// Returns the exception name, "kaldi::KaldiFatalError".
|
||||
virtual const char *what() const noexcept override {
|
||||
return "kaldi::KaldiFatalError";
|
||||
}
|
||||
|
||||
/// Returns the Kaldi error message logged by KALDI_ERR.
|
||||
const char *KaldiMessage() const { return std::runtime_error::what(); }
|
||||
};
|
||||
|
||||
// Class MessageLogger is the workhorse behind the KALDI_ASSERT, KALDI_ERR,
|
||||
// KALDI_WARN, KALDI_LOG and KALDI_VLOG macros. It formats the message, then
|
||||
// either prints it to stderr or passes to the custom logging handler if
|
||||
// provided. Then, in case of the error, throws a KaldiFatalError exception, or
|
||||
// in case of failed KALDI_ASSERT, calls std::abort().
|
||||
class MessageLogger {
|
||||
public:
|
||||
/// The constructor stores the message's "envelope", a set of data which
|
||||
// identifies the location in source which is sending the message to log.
|
||||
// The pointers to strings are stored internally, and not owned or copied,
|
||||
// so that their storage must outlive this object.
|
||||
MessageLogger(LogMessageEnvelope::Severity severity, const char *func,
|
||||
const char *file, int32 line);
|
||||
|
||||
// The stream insertion operator, used in e.g. 'KALDI_LOG << "Message"'.
|
||||
template <typename T> MessageLogger &operator<<(const T &val) {
|
||||
ss_ << val;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// When assigned a MessageLogger, log its contents.
|
||||
struct Log final {
|
||||
void operator=(const MessageLogger &logger) { logger.LogMessage(); }
|
||||
};
|
||||
|
||||
// When assigned a MessageLogger, log its contents and then throw
|
||||
// a KaldiFatalError.
|
||||
struct LogAndThrow final {
|
||||
[[noreturn]] void operator=(const MessageLogger &logger) {
|
||||
logger.LogMessage();
|
||||
throw KaldiFatalError(logger.GetMessage());
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
std::string GetMessage() const { return ss_.str(); }
|
||||
void LogMessage() const;
|
||||
|
||||
LogMessageEnvelope envelope_;
|
||||
std::ostringstream ss_;
|
||||
};
|
||||
|
||||
// Logging macros.
|
||||
#define KALDI_ERR \
|
||||
::kaldi::MessageLogger::LogAndThrow() = ::kaldi::MessageLogger( \
|
||||
::kaldi::LogMessageEnvelope::kError, __func__, __FILE__, __LINE__)
|
||||
#define KALDI_WARN \
|
||||
::kaldi::MessageLogger::Log() = ::kaldi::MessageLogger( \
|
||||
::kaldi::LogMessageEnvelope::kWarning, __func__, __FILE__, __LINE__)
|
||||
#define KALDI_LOG \
|
||||
::kaldi::MessageLogger::Log() = ::kaldi::MessageLogger( \
|
||||
::kaldi::LogMessageEnvelope::kInfo, __func__, __FILE__, __LINE__)
|
||||
#define KALDI_VLOG(v) \
|
||||
if ((v) <= ::kaldi::GetVerboseLevel()) \
|
||||
::kaldi::MessageLogger::Log() = \
|
||||
::kaldi::MessageLogger((::kaldi::LogMessageEnvelope::Severity)(v), \
|
||||
__func__, __FILE__, __LINE__)
|
||||
|
||||
/***** KALDI ASSERTS *****/
|
||||
|
||||
[[noreturn]] void KaldiAssertFailure_(const char *func, const char *file,
|
||||
int32 line, const char *cond_str);
|
||||
|
||||
// Note on KALDI_ASSERT and KALDI_PARANOID_ASSERT:
|
||||
//
|
||||
// A single block {} around if /else does not work, because it causes
|
||||
// syntax error (unmatched else block) in the following code:
|
||||
//
|
||||
// if (condition)
|
||||
// KALDI_ASSERT(condition2);
|
||||
// else
|
||||
// SomethingElse();
|
||||
//
|
||||
// do {} while(0) -- note there is no semicolon at the end! -- works nicely,
|
||||
// and compilers will be able to optimize the loop away (as the condition
|
||||
// is always false).
|
||||
//
|
||||
// Also see KALDI_COMPILE_TIME_ASSERT, defined in base/kaldi-utils.h, and
|
||||
// KALDI_ASSERT_IS_INTEGER_TYPE and KALDI_ASSERT_IS_FLOATING_TYPE, also defined
|
||||
// there.
|
||||
#ifndef NDEBUG
|
||||
#define KALDI_ASSERT(cond) \
|
||||
do { \
|
||||
if (cond) \
|
||||
(void)0; \
|
||||
else \
|
||||
::kaldi::KaldiAssertFailure_(__func__, __FILE__, __LINE__, #cond); \
|
||||
} while (0)
|
||||
#else
|
||||
#define KALDI_ASSERT(cond) (void)0
|
||||
#endif
|
||||
|
||||
// Some more expensive asserts only checked if this defined.
|
||||
#ifdef KALDI_PARANOID
|
||||
#define KALDI_PARANOID_ASSERT(cond) \
|
||||
do { \
|
||||
if (cond) \
|
||||
(void)0; \
|
||||
else \
|
||||
::kaldi::KaldiAssertFailure_(__func__, __FILE__, __LINE__, #cond); \
|
||||
} while (0)
|
||||
#else
|
||||
#define KALDI_PARANOID_ASSERT(cond) (void)0
|
||||
#endif
|
||||
|
||||
/***** THIRD-PARTY LOG-HANDLER *****/
|
||||
|
||||
/// Type of third-party logging function.
|
||||
typedef void (*LogHandler)(const LogMessageEnvelope &envelope,
|
||||
const char *message);
|
||||
|
||||
/// Set logging handler. If called with a non-NULL function pointer, the
|
||||
/// function pointed by it is called to send messages to a caller-provided log.
|
||||
/// If called with a NULL pointer, restores default Kaldi error logging to
|
||||
/// stderr. This function is obviously not thread safe; the log handler must be.
|
||||
/// Returns a previously set logging handler pointer, or NULL.
|
||||
LogHandler SetLogHandler(LogHandler);
|
||||
|
||||
/// @} end "addtogroup error_group"
|
||||
|
||||
//// Functions within internal is exported for testing only, do not use.
|
||||
//namespace internal {
|
||||
//bool LocateSymbolRange(const std::string &trace_name, size_t *begin,
|
||||
// size_t *end);
|
||||
//} // namespace internal
|
||||
} // namespace kaldi
|
||||
|
||||
#endif // KALDI_BASE_KALDI_ERROR_H_
|
||||
@@ -0,0 +1,334 @@
|
||||
// base/kaldi-math-test.cc
|
||||
//
|
||||
// Copyright 2009-2011 Microsoft Corporation; Yanmin Qian; Jan Silovsky
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
#include "base/kaldi-math.h"
|
||||
#include <limits>
|
||||
#include "base/timer.h"
|
||||
|
||||
namespace kaldi {
|
||||
|
||||
template<class I> void UnitTestGcdLcmTpl() {
|
||||
for (I a = 1; a < 15; a++) { // a is min gcd.
|
||||
I b = (I)(Rand() % 10);
|
||||
I c = (I)(Rand() % 10);
|
||||
if (std::numeric_limits<I>::is_signed) {
|
||||
if (Rand() % 2 == 0) b = -b;
|
||||
if (Rand() % 2 == 0) c = -c;
|
||||
}
|
||||
if (b == 0 && c == 0) continue; // gcd not defined for such numbers.
|
||||
I g = Gcd(b*a, c*a);
|
||||
KALDI_ASSERT(g >= a);
|
||||
KALDI_ASSERT((b*a) % g == 0);
|
||||
KALDI_ASSERT((c*a) % g == 0);
|
||||
|
||||
// test least common multiple
|
||||
if (b <= 0 || c <= 0) continue; // lcm not defined unless both positive.
|
||||
I h = Lcm(b*a, c*a);
|
||||
KALDI_ASSERT(h != 0 && (h % (b*a)) == 0 &&
|
||||
(h % (c*a)) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
void UnitTestRoundUpToNearestPowerOfTwo() {
|
||||
KALDI_ASSERT(RoundUpToNearestPowerOfTwo(1) == 1);
|
||||
KALDI_ASSERT(RoundUpToNearestPowerOfTwo(2) == 2);
|
||||
KALDI_ASSERT(RoundUpToNearestPowerOfTwo(3) == 4);
|
||||
KALDI_ASSERT(RoundUpToNearestPowerOfTwo(4) == 4);
|
||||
KALDI_ASSERT(RoundUpToNearestPowerOfTwo(7) == 8);
|
||||
KALDI_ASSERT(RoundUpToNearestPowerOfTwo(8) == 8);
|
||||
KALDI_ASSERT(RoundUpToNearestPowerOfTwo(255) == 256);
|
||||
KALDI_ASSERT(RoundUpToNearestPowerOfTwo(256) == 256);
|
||||
KALDI_ASSERT(RoundUpToNearestPowerOfTwo(257) == 512);
|
||||
KALDI_ASSERT(RoundUpToNearestPowerOfTwo(1073700000) == 1073741824);
|
||||
}
|
||||
|
||||
void UnitTestDivideRoundingDown() {
|
||||
for (int32 i = 0; i < 100; i++) {
|
||||
int32 a = RandInt(-100, 100);
|
||||
int32 b = 0;
|
||||
while (b == 0)
|
||||
b = RandInt(-100, 100);
|
||||
KALDI_ASSERT(DivideRoundingDown(a, b) ==
|
||||
std::floor(static_cast<double>(a) / static_cast<double>(b)));
|
||||
}
|
||||
}
|
||||
|
||||
void UnitTestGcdLcm() {
|
||||
UnitTestGcdLcmTpl<int>();
|
||||
UnitTestGcdLcmTpl<size_t>();
|
||||
UnitTestGcdLcmTpl<int16>();
|
||||
}
|
||||
|
||||
void UnitTestRand() {
|
||||
// Testing random-number generation.
|
||||
std::cout << "Testing random-number generation. "
|
||||
<< "If there is an error this may not terminate.\n";
|
||||
std::cout << "If this does not terminate, look more closely. "
|
||||
<< "There might be a problem [but might not be]\n";
|
||||
for (int i = 1; i < 10; i++) {
|
||||
{ // test RandUniform.
|
||||
std::cout << "Test RandUniform\n";
|
||||
KALDI_ASSERT(RandUniform() >= 0 && RandUniform() <= 1);
|
||||
float sum = RandUniform()-0.5;
|
||||
for (int j = 0; ; j++) {
|
||||
sum += RandUniform()-0.5;
|
||||
if (std::abs(sum) < 0.5*sqrt(static_cast<double>(j))) break;
|
||||
}
|
||||
}
|
||||
{ // test RandGauss.
|
||||
float sum = RandGauss();
|
||||
for (int j = 0; ; j++) {
|
||||
sum += RandGauss();
|
||||
if (std::abs(sum) < 0.5*sqrt(static_cast<double>(j))) break;
|
||||
}
|
||||
}
|
||||
{ // test RandGauss.
|
||||
float sum = RandGauss();
|
||||
for (int j = 0; ; j++) {
|
||||
float a, b;
|
||||
RandGauss2(&a, &b);
|
||||
if (i % 2 == 0) sum += a;
|
||||
else
|
||||
sum += b;
|
||||
if (std::abs(sum) < 0.5*sqrt(static_cast<double>(j))) break;
|
||||
}
|
||||
}
|
||||
{ // test poisson_Rand().
|
||||
KALDI_ASSERT(RandPoisson(3.0) >= 0);
|
||||
KALDI_ASSERT(RandPoisson(0.0) == 0);
|
||||
std::cout << "Test RandPoisson\n";
|
||||
float lambda = RandUniform() * 3.0; // between 0 and 3.
|
||||
double sum = RandPoisson(lambda) - lambda; // expected value is zero.
|
||||
for (int j = 0; ; j++) {
|
||||
sum += RandPoisson(lambda) - lambda;
|
||||
if (std::abs(sum) < 0.5*sqrt(static_cast<double>(j))) break;
|
||||
}
|
||||
}
|
||||
|
||||
{ // test WithProb().
|
||||
for (int32 i = 0; i < 10; i++) {
|
||||
KALDI_ASSERT((WithProb(0.0) == false) && (WithProb(1.0) == true));
|
||||
}
|
||||
{
|
||||
int32 tot = 0, n = 10000;
|
||||
BaseFloat p = 0.5;
|
||||
for (int32 i = 0; i < n; i++)
|
||||
tot += WithProb(p);
|
||||
KALDI_ASSERT(tot > (n * p * 0.8) && tot < (n * p * 1.2));
|
||||
}
|
||||
{
|
||||
int32 tot = 0, n = 10000;
|
||||
BaseFloat p = 0.25;
|
||||
for (int32 i = 0; i < n; i++)
|
||||
tot += WithProb(p);
|
||||
KALDI_ASSERT(tot > (n * p * 0.8) && tot < (n * p * 1.2));
|
||||
}
|
||||
}
|
||||
{ // test RandInt().
|
||||
KALDI_ASSERT(RandInt(0, 3) >= 0 && RandInt(0, 3) <= 3);
|
||||
|
||||
std::cout << "Test RandInt\n";
|
||||
int minint = Rand() % 200;
|
||||
int maxint = minint + 1 + Rand() % 20;
|
||||
|
||||
float sum = RandInt(minint, maxint) + 0.5*(minint+maxint);
|
||||
for (int j = 0; ; j++) {
|
||||
sum += RandInt(minint, maxint) - 0.5*(minint+maxint);
|
||||
if (std::abs(static_cast<float>(sum)) <
|
||||
0.5*sqrt(static_cast<double>(j))*(maxint-minint)) break;
|
||||
}
|
||||
}
|
||||
{ // test RandPrune in basic way.
|
||||
KALDI_ASSERT(RandPrune(1.1, 1.0) == 1.1);
|
||||
KALDI_ASSERT(RandPrune(0.0, 0.0) == 0.0);
|
||||
KALDI_ASSERT(RandPrune(-1.1, 1.0) == -1.1);
|
||||
KALDI_ASSERT(RandPrune(0.0, 1.0) == 0.0);
|
||||
KALDI_ASSERT(RandPrune(0.5, 1.0) >= 0.0);
|
||||
KALDI_ASSERT(RandPrune(-0.5, 1.0) <= 0.0);
|
||||
BaseFloat f = RandPrune(-0.5, 1.0);
|
||||
KALDI_ASSERT(f == 0.0 || f == -1.0);
|
||||
f = RandPrune(0.5, 1.0);
|
||||
KALDI_ASSERT(f == 0.0 || f == 1.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UnitTestLogAddSub() {
|
||||
for (int i = 0; i < 100; i++) {
|
||||
double f1 = Rand() % 10000, f2 = Rand() % 20;
|
||||
double add1 = Exp(LogAdd(Log(f1), Log(f2)));
|
||||
double add2 = Exp(LogAdd(Log(f2), Log(f1)));
|
||||
double add = f1 + f2, thresh = add*0.00001;
|
||||
KALDI_ASSERT(std::abs(add-add1) < thresh && std::abs(add-add2) < thresh);
|
||||
|
||||
|
||||
try {
|
||||
double f2_check = Exp(LogSub(Log(add), Log(f1))),
|
||||
thresh = (f2*0.01)+0.001;
|
||||
KALDI_ASSERT(std::abs(f2_check-f2) < thresh);
|
||||
} catch(...) {
|
||||
KALDI_ASSERT(f2 == 0); // It will probably crash for f2=0.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UnitTestDefines() { // Yes, we even unit-test the preprocessor statements.
|
||||
KALDI_ASSERT(Exp(kLogZeroFloat) == 0.0);
|
||||
KALDI_ASSERT(Exp(kLogZeroDouble) == 0.0);
|
||||
BaseFloat den = 0.0;
|
||||
KALDI_ASSERT(KALDI_ISNAN(0.0 / den));
|
||||
KALDI_ASSERT(!KALDI_ISINF(0.0 / den));
|
||||
KALDI_ASSERT(!KALDI_ISFINITE(0.0 / den));
|
||||
KALDI_ASSERT(!KALDI_ISNAN(1.0 / den));
|
||||
KALDI_ASSERT(KALDI_ISINF(1.0 / den));
|
||||
KALDI_ASSERT(!KALDI_ISFINITE(1.0 / den));
|
||||
KALDI_ASSERT(KALDI_ISFINITE(0.0));
|
||||
KALDI_ASSERT(!KALDI_ISINF(0.0));
|
||||
KALDI_ASSERT(!KALDI_ISNAN(0.0));
|
||||
|
||||
std::cout << 1.0+DBL_EPSILON;
|
||||
std::cout << 1.0 + 0.5*DBL_EPSILON;
|
||||
KALDI_ASSERT(1.0 + DBL_EPSILON != 1.0 && 1.0 + (0.5*DBL_EPSILON) == 1.0
|
||||
&& "If this test fails, you can probably just comment it out-- "
|
||||
"may mean your CPU exceeds expected floating point precision");
|
||||
KALDI_ASSERT(1.0f + FLT_EPSILON != 1.0f && 1.0f + (0.5f*FLT_EPSILON) == 1.0f
|
||||
&& "If this test fails, you can probably just comment it out-- "
|
||||
"may mean your CPU exceeds expected floating point precision");
|
||||
KALDI_ASSERT(std::abs(sin(M_PI)) < 1.0e-05
|
||||
&& std::abs(cos(M_PI)+1.0) < 1.0e-05);
|
||||
KALDI_ASSERT(std::abs(sin(M_2PI)) < 1.0e-05
|
||||
&& std::abs(cos(M_2PI)-1.0) < 1.0e-05);
|
||||
KALDI_ASSERT(std::abs(sin(Exp(M_LOG_2PI))) < 1.0e-05);
|
||||
KALDI_ASSERT(std::abs(cos(Exp(M_LOG_2PI)) - 1.0) < 1.0e-05);
|
||||
}
|
||||
|
||||
void UnitTestAssertFunc() { // Testing Assert** *functions
|
||||
for (int i = 1; i < 100; i++) {
|
||||
float f1 = Rand() % 10000 + 1, f2 = Rand() % 20 + 1;
|
||||
float tmp1 = f1 * f2;
|
||||
float tmp2 = (1/f1 + 1/f2);
|
||||
float add = f1 + f2;
|
||||
float addeql = tmp1 * tmp2;
|
||||
float thresh = 0.00001;
|
||||
AssertEqual(add, addeql, thresh); // test AssertEqual()
|
||||
}
|
||||
}
|
||||
|
||||
template<class I> void UnitTestFactorizeTpl() {
|
||||
for (int p= 0; p < 100; p++) {
|
||||
I m = Rand() % 100000;
|
||||
if (m >= 1) {
|
||||
std::vector<I> factors;
|
||||
Factorize(m, &factors);
|
||||
I m2 = 1;
|
||||
for (size_t i = 0; i < factors.size(); i++) {
|
||||
m2 *= factors[i];
|
||||
if (i+1 < factors.size())
|
||||
KALDI_ASSERT(factors[i+1] >= factors[i]); // check sorted.
|
||||
}
|
||||
KALDI_ASSERT(m2 == m); // check correctness.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UnitTestFactorize() {
|
||||
UnitTestFactorizeTpl<int>();
|
||||
UnitTestFactorizeTpl<size_t>();
|
||||
UnitTestFactorizeTpl<int16>();
|
||||
}
|
||||
|
||||
void UnitTestApproxEqual() {
|
||||
KALDI_ASSERT(ApproxEqual(1.0, 1.00001));
|
||||
KALDI_ASSERT(ApproxEqual(1.0, 1.00001, 0.001));
|
||||
KALDI_ASSERT(!ApproxEqual(1.0, 1.1));
|
||||
KALDI_ASSERT(!ApproxEqual(1.0, 1.01, 0.001));
|
||||
KALDI_ASSERT(!ApproxEqual(1.0, 0.0));
|
||||
KALDI_ASSERT(ApproxEqual(0.0, 0.0));
|
||||
KALDI_ASSERT(!ApproxEqual(0.0, 0.00001));
|
||||
KALDI_ASSERT(!ApproxEqual(std::numeric_limits<float>::infinity(),
|
||||
-std::numeric_limits<float>::infinity()));
|
||||
KALDI_ASSERT(ApproxEqual(std::numeric_limits<float>::infinity(),
|
||||
std::numeric_limits<float>::infinity()));
|
||||
KALDI_ASSERT(ApproxEqual(-std::numeric_limits<float>::infinity(),
|
||||
-std::numeric_limits<float>::infinity()));
|
||||
KALDI_ASSERT(!ApproxEqual(-std::numeric_limits<float>::infinity(),
|
||||
0));
|
||||
KALDI_ASSERT(!ApproxEqual(-std::numeric_limits<float>::infinity(),
|
||||
1));
|
||||
}
|
||||
|
||||
template<class Real>
|
||||
void UnitTestExpSpeed() {
|
||||
Real sum = 0.0; // compute the sum to avoid optimizing it away.
|
||||
Real time = 0.01; // how long this should last.
|
||||
int block_size = 10;
|
||||
int num_ops = 0;
|
||||
Timer tim;
|
||||
while (tim.Elapsed() < time) {
|
||||
for (int i = 0; i < block_size; i++) {
|
||||
sum += Exp((Real)i);
|
||||
}
|
||||
num_ops += block_size;
|
||||
}
|
||||
KALDI_ASSERT(sum > 0.0); // make it harder for the compiler to optimize Exp
|
||||
// away, as we have a conditional.
|
||||
Real flops = 1.0e-06 * num_ops / tim.Elapsed();
|
||||
KALDI_LOG << "Megaflops doing Exp("
|
||||
<< (sizeof(Real) == 4 ? "float" : "double") << ") is " << flops;
|
||||
}
|
||||
|
||||
|
||||
template<class Real>
|
||||
void UnitTestLogSpeed() {
|
||||
Real sum = 0.0; // compute the sum to avoid optimizing it away.
|
||||
Real time = 0.01; // how long this should last.
|
||||
int block_size = 10;
|
||||
int num_ops = 0;
|
||||
Timer tim;
|
||||
while (tim.Elapsed() < time) {
|
||||
for (int i = 0; i < block_size; i++) {
|
||||
sum += Log(static_cast<float>(i + 1));
|
||||
}
|
||||
num_ops += block_size;
|
||||
}
|
||||
KALDI_ASSERT(sum > 0.0); // make it harder for the compiler to optimize Log
|
||||
// away, as we have a conditional.
|
||||
Real flops = 1.0e-06 * num_ops / tim.Elapsed();
|
||||
KALDI_LOG << "Megaflops doing Log("
|
||||
<< (sizeof(Real) == 4 ? "float" : "double") << ") is " << flops;
|
||||
}
|
||||
|
||||
} // end namespace kaldi.
|
||||
|
||||
int main() {
|
||||
using namespace kaldi;
|
||||
UnitTestApproxEqual();
|
||||
UnitTestGcdLcm();
|
||||
UnitTestFactorize();
|
||||
UnitTestDefines();
|
||||
UnitTestLogAddSub();
|
||||
UnitTestRand();
|
||||
UnitTestAssertFunc();
|
||||
UnitTestRoundUpToNearestPowerOfTwo();
|
||||
UnitTestDivideRoundingDown();
|
||||
UnitTestExpSpeed<float>();
|
||||
UnitTestExpSpeed<double>();
|
||||
UnitTestLogSpeed<float>();
|
||||
UnitTestLogSpeed<double>();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
// base/kaldi-math.cc
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation; Yanmin Qian;
|
||||
// Saarland University; Jan Silovsky
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "base/kaldi-math.h"
|
||||
#ifndef _MSC_VER
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
|
||||
namespace kaldi {
|
||||
// These routines are tested in matrix/matrix-test.cc
|
||||
|
||||
int32 RoundUpToNearestPowerOfTwo(int32 n) {
|
||||
KALDI_ASSERT(n > 0);
|
||||
n--;
|
||||
n |= n >> 1;
|
||||
n |= n >> 2;
|
||||
n |= n >> 4;
|
||||
n |= n >> 8;
|
||||
n |= n >> 16;
|
||||
return n+1;
|
||||
}
|
||||
|
||||
static std::mutex _RandMutex;
|
||||
|
||||
int Rand(struct RandomState* state) {
|
||||
#if !defined(_POSIX_THREAD_SAFE_FUNCTIONS)
|
||||
// On Windows and Cygwin, just call Rand()
|
||||
return rand();
|
||||
#else
|
||||
if (state) {
|
||||
return rand_r(&(state->seed));
|
||||
} else {
|
||||
std::lock_guard<std::mutex> lock(_RandMutex);
|
||||
return rand();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
RandomState::RandomState() {
|
||||
// we initialize it as Rand() + 27437 instead of just Rand(), because on some
|
||||
// systems, e.g. at the very least Mac OSX Yosemite and later, it seems to be
|
||||
// the case that rand_r when initialized with rand() will give you the exact
|
||||
// same sequence of numbers that rand() will give if you keep calling rand()
|
||||
// after that initial call. This can cause problems with repeated sequences.
|
||||
// For example if you initialize two RandomState structs one after the other
|
||||
// without calling rand() in between, they would give you the same sequence
|
||||
// offset by one (if we didn't have the "+ 27437" in the code). 27437 is just
|
||||
// a randomly chosen prime number.
|
||||
seed = unsigned(Rand()) + 27437;
|
||||
}
|
||||
|
||||
bool WithProb(BaseFloat prob, struct RandomState* state) {
|
||||
KALDI_ASSERT(prob >= 0 && prob <= 1.1); // prob should be <= 1.0,
|
||||
// but we allow slightly larger values that could arise from roundoff in
|
||||
// previous calculations.
|
||||
KALDI_COMPILE_TIME_ASSERT(RAND_MAX > 128 * 128);
|
||||
if (prob == 0) return false;
|
||||
else if (prob == 1.0) return true;
|
||||
else if (prob * RAND_MAX < 128.0) {
|
||||
// prob is very small but nonzero, and the "main algorithm"
|
||||
// wouldn't work that well. So: with probability 1/128, we
|
||||
// return WithProb (prob * 128), else return false.
|
||||
if (Rand(state) < RAND_MAX / 128) { // with probability 128...
|
||||
// Note: we know that prob * 128.0 < 1.0, because
|
||||
// we asserted RAND_MAX > 128 * 128.
|
||||
return WithProb(prob * 128.0);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return (Rand(state) < ((RAND_MAX + static_cast<BaseFloat>(1.0)) * prob));
|
||||
}
|
||||
}
|
||||
|
||||
int32 RandInt(int32 min_val, int32 max_val, struct RandomState* state) {
|
||||
// This is not exact.
|
||||
KALDI_ASSERT(max_val >= min_val);
|
||||
if (max_val == min_val) return min_val;
|
||||
|
||||
#ifdef _MSC_VER
|
||||
// RAND_MAX is quite small on Windows -> may need to handle larger numbers.
|
||||
if (RAND_MAX > (max_val-min_val)*8) {
|
||||
// *8 to avoid large inaccuracies in probability, from the modulus...
|
||||
return min_val +
|
||||
((unsigned int)Rand(state) % (unsigned int)(max_val+1-min_val));
|
||||
} else {
|
||||
if ((unsigned int)(RAND_MAX*RAND_MAX) >
|
||||
(unsigned int)((max_val+1-min_val)*8)) {
|
||||
// *8 to avoid inaccuracies in probability, from the modulus...
|
||||
return min_val + ( (unsigned int)( (Rand(state)+RAND_MAX*Rand(state)))
|
||||
% (unsigned int)(max_val+1-min_val));
|
||||
} else {
|
||||
KALDI_ERR << "rand_int failed because we do not support such large "
|
||||
"random numbers. (Extend this function).";
|
||||
}
|
||||
}
|
||||
#else
|
||||
return min_val +
|
||||
(static_cast<int32>(Rand(state)) % static_cast<int32>(max_val+1-min_val));
|
||||
#endif
|
||||
}
|
||||
|
||||
// Returns poisson-distributed random number.
|
||||
// Take care: this takes time proportional
|
||||
// to lambda. Faster algorithms exist but are more complex.
|
||||
int32 RandPoisson(float lambda, struct RandomState* state) {
|
||||
// Knuth's algorithm.
|
||||
KALDI_ASSERT(lambda >= 0);
|
||||
float L = expf(-lambda), p = 1.0;
|
||||
int32 k = 0;
|
||||
do {
|
||||
k++;
|
||||
float u = RandUniform(state);
|
||||
p *= u;
|
||||
} while (p > L);
|
||||
return k-1;
|
||||
}
|
||||
|
||||
void RandGauss2(float *a, float *b, RandomState *state) {
|
||||
KALDI_ASSERT(a);
|
||||
KALDI_ASSERT(b);
|
||||
float u1 = RandUniform(state);
|
||||
float u2 = RandUniform(state);
|
||||
u1 = sqrtf(-2.0f * logf(u1));
|
||||
u2 = 2.0f * M_PI * u2;
|
||||
*a = u1 * cosf(u2);
|
||||
*b = u1 * sinf(u2);
|
||||
}
|
||||
|
||||
void RandGauss2(double *a, double *b, RandomState *state) {
|
||||
KALDI_ASSERT(a);
|
||||
KALDI_ASSERT(b);
|
||||
float a_float, b_float;
|
||||
// Just because we're using doubles doesn't mean we need super-high-quality
|
||||
// random numbers, so we just use the floating-point version internally.
|
||||
RandGauss2(&a_float, &b_float, state);
|
||||
*a = a_float;
|
||||
*b = b_float;
|
||||
}
|
||||
|
||||
|
||||
} // end namespace kaldi
|
||||
@@ -0,0 +1,363 @@
|
||||
// base/kaldi-math.h
|
||||
|
||||
// Copyright 2009-2011 Ondrej Glembek; Microsoft Corporation; Yanmin Qian;
|
||||
// Jan Silovsky; Saarland University
|
||||
//
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef KALDI_BASE_KALDI_MATH_H_
|
||||
#define KALDI_BASE_KALDI_MATH_H_ 1
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <float.h>
|
||||
#endif
|
||||
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
#include "base/kaldi-types.h"
|
||||
#include "base/kaldi-common.h"
|
||||
|
||||
|
||||
#ifndef DBL_EPSILON
|
||||
#define DBL_EPSILON 2.2204460492503131e-16
|
||||
#endif
|
||||
#ifndef FLT_EPSILON
|
||||
#define FLT_EPSILON 1.19209290e-7f
|
||||
#endif
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.1415926535897932384626433832795
|
||||
#endif
|
||||
|
||||
#ifndef M_SQRT2
|
||||
#define M_SQRT2 1.4142135623730950488016887
|
||||
#endif
|
||||
|
||||
#ifndef M_2PI
|
||||
#define M_2PI 6.283185307179586476925286766559005
|
||||
#endif
|
||||
|
||||
#ifndef M_SQRT1_2
|
||||
#define M_SQRT1_2 0.7071067811865475244008443621048490
|
||||
#endif
|
||||
|
||||
#ifndef M_LOG_2PI
|
||||
#define M_LOG_2PI 1.8378770664093454835606594728112
|
||||
#endif
|
||||
|
||||
#ifndef M_LN2
|
||||
#define M_LN2 0.693147180559945309417232121458
|
||||
#endif
|
||||
|
||||
#ifndef M_LN10
|
||||
#define M_LN10 2.302585092994045684017991454684
|
||||
#endif
|
||||
|
||||
|
||||
#define KALDI_ISNAN std::isnan
|
||||
#define KALDI_ISINF std::isinf
|
||||
#define KALDI_ISFINITE(x) std::isfinite(x)
|
||||
|
||||
#if !defined(KALDI_SQR)
|
||||
# define KALDI_SQR(x) ((x) * (x))
|
||||
#endif
|
||||
|
||||
namespace kaldi {
|
||||
|
||||
#if !defined(_MSC_VER) || (_MSC_VER >= 1900)
|
||||
inline double Exp(double x) { return exp(x); }
|
||||
#ifndef KALDI_NO_EXPF
|
||||
inline float Exp(float x) { return expf(x); }
|
||||
#else
|
||||
inline float Exp(float x) { return exp(static_cast<double>(x)); }
|
||||
#endif // KALDI_NO_EXPF
|
||||
#else
|
||||
inline double Exp(double x) { return exp(x); }
|
||||
#if !defined(__INTEL_COMPILER) && _MSC_VER == 1800 && defined(_M_X64)
|
||||
// Microsoft CL v18.0 buggy 64-bit implementation of
|
||||
// expf() incorrectly returns -inf for exp(-inf).
|
||||
inline float Exp(float x) { return exp(static_cast<double>(x)); }
|
||||
#else
|
||||
inline float Exp(float x) { return expf(x); }
|
||||
#endif // !defined(__INTEL_COMPILER) && _MSC_VER == 1800 && defined(_M_X64)
|
||||
#endif // !defined(_MSC_VER) || (_MSC_VER >= 1900)
|
||||
|
||||
inline double Log(double x) { return log(x); }
|
||||
inline float Log(float x) { return logf(x); }
|
||||
|
||||
#if !defined(_MSC_VER) || (_MSC_VER >= 1700)
|
||||
inline double Log1p(double x) { return log1p(x); }
|
||||
inline float Log1p(float x) { return log1pf(x); }
|
||||
#else
|
||||
inline double Log1p(double x) {
|
||||
const double cutoff = 1.0e-08;
|
||||
if (x < cutoff)
|
||||
return x - 0.5 * x * x;
|
||||
else
|
||||
return Log(1.0 + x);
|
||||
}
|
||||
|
||||
inline float Log1p(float x) {
|
||||
const float cutoff = 1.0e-07;
|
||||
if (x < cutoff)
|
||||
return x - 0.5 * x * x;
|
||||
else
|
||||
return Log(1.0 + x);
|
||||
}
|
||||
#endif
|
||||
|
||||
static const double kMinLogDiffDouble = Log(DBL_EPSILON); // negative!
|
||||
static const float kMinLogDiffFloat = Log(FLT_EPSILON); // negative!
|
||||
|
||||
// -infinity
|
||||
const float kLogZeroFloat = -std::numeric_limits<float>::infinity();
|
||||
const double kLogZeroDouble = -std::numeric_limits<double>::infinity();
|
||||
const BaseFloat kLogZeroBaseFloat = -std::numeric_limits<BaseFloat>::infinity();
|
||||
|
||||
// Returns a random integer between 0 and RAND_MAX, inclusive
|
||||
int Rand(struct RandomState* state = NULL);
|
||||
|
||||
// State for thread-safe random number generator
|
||||
struct RandomState {
|
||||
RandomState();
|
||||
unsigned seed;
|
||||
};
|
||||
|
||||
// Returns a random integer between first and last inclusive.
|
||||
int32 RandInt(int32 first, int32 last, struct RandomState* state = NULL);
|
||||
|
||||
// Returns true with probability "prob",
|
||||
bool WithProb(BaseFloat prob, struct RandomState* state = NULL);
|
||||
// with 0 <= prob <= 1 [we check this].
|
||||
// Internally calls Rand(). This function is carefully implemented so
|
||||
// that it should work even if prob is very small.
|
||||
|
||||
/// Returns a random number strictly between 0 and 1.
|
||||
inline float RandUniform(struct RandomState* state = NULL) {
|
||||
return static_cast<float>((Rand(state) + 1.0) / (RAND_MAX+2.0));
|
||||
}
|
||||
|
||||
inline float RandGauss(struct RandomState* state = NULL) {
|
||||
return static_cast<float>(sqrtf (-2 * Log(RandUniform(state)))
|
||||
* cosf(2*M_PI*RandUniform(state)));
|
||||
}
|
||||
|
||||
// Returns poisson-distributed random number. Uses Knuth's algorithm.
|
||||
// Take care: this takes time proportional
|
||||
// to lambda. Faster algorithms exist but are more complex.
|
||||
int32 RandPoisson(float lambda, struct RandomState* state = NULL);
|
||||
|
||||
// Returns a pair of gaussian random numbers. Uses Box-Muller transform
|
||||
void RandGauss2(float *a, float *b, RandomState *state = NULL);
|
||||
void RandGauss2(double *a, double *b, RandomState *state = NULL);
|
||||
|
||||
// Also see Vector<float,double>::RandCategorical().
|
||||
|
||||
// This is a randomized pruning mechanism that preserves expectations,
|
||||
// that we typically use to prune posteriors.
|
||||
template<class Float>
|
||||
inline Float RandPrune(Float post, BaseFloat prune_thresh,
|
||||
struct RandomState* state = NULL) {
|
||||
KALDI_ASSERT(prune_thresh >= 0.0);
|
||||
if (post == 0.0 || std::abs(post) >= prune_thresh)
|
||||
return post;
|
||||
return (post >= 0 ? 1.0 : -1.0) *
|
||||
(RandUniform(state) <= fabs(post)/prune_thresh ? prune_thresh : 0.0);
|
||||
}
|
||||
|
||||
// returns log(exp(x) + exp(y)).
|
||||
inline double LogAdd(double x, double y) {
|
||||
double diff;
|
||||
|
||||
if (x < y) {
|
||||
diff = x - y;
|
||||
x = y;
|
||||
} else {
|
||||
diff = y - x;
|
||||
}
|
||||
// diff is negative. x is now the larger one.
|
||||
|
||||
if (diff >= kMinLogDiffDouble) {
|
||||
double res;
|
||||
res = x + Log1p(Exp(diff));
|
||||
return res;
|
||||
} else {
|
||||
return x; // return the larger one.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// returns log(exp(x) + exp(y)).
|
||||
inline float LogAdd(float x, float y) {
|
||||
float diff;
|
||||
|
||||
if (x < y) {
|
||||
diff = x - y;
|
||||
x = y;
|
||||
} else {
|
||||
diff = y - x;
|
||||
}
|
||||
// diff is negative. x is now the larger one.
|
||||
|
||||
if (diff >= kMinLogDiffFloat) {
|
||||
float res;
|
||||
res = x + Log1p(Exp(diff));
|
||||
return res;
|
||||
} else {
|
||||
return x; // return the larger one.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// returns log(exp(x) - exp(y)).
|
||||
inline double LogSub(double x, double y) {
|
||||
if (y >= x) { // Throws exception if y>=x.
|
||||
if (y == x)
|
||||
return kLogZeroDouble;
|
||||
else
|
||||
KALDI_ERR << "Cannot subtract a larger from a smaller number.";
|
||||
}
|
||||
|
||||
double diff = y - x; // Will be negative.
|
||||
double res = x + Log(1.0 - Exp(diff));
|
||||
|
||||
// res might be NAN if diff ~0.0, and 1.0-exp(diff) == 0 to machine precision
|
||||
if (KALDI_ISNAN(res))
|
||||
return kLogZeroDouble;
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
// returns log(exp(x) - exp(y)).
|
||||
inline float LogSub(float x, float y) {
|
||||
if (y >= x) { // Throws exception if y>=x.
|
||||
if (y == x)
|
||||
return kLogZeroDouble;
|
||||
else
|
||||
KALDI_ERR << "Cannot subtract a larger from a smaller number.";
|
||||
}
|
||||
|
||||
float diff = y - x; // Will be negative.
|
||||
float res = x + Log(1.0f - Exp(diff));
|
||||
|
||||
// res might be NAN if diff ~0.0, and 1.0-exp(diff) == 0 to machine precision
|
||||
if (KALDI_ISNAN(res))
|
||||
return kLogZeroFloat;
|
||||
return res;
|
||||
}
|
||||
|
||||
/// return abs(a - b) <= relative_tolerance * (abs(a)+abs(b)).
|
||||
static inline bool ApproxEqual(float a, float b,
|
||||
float relative_tolerance = 0.001) {
|
||||
// a==b handles infinities.
|
||||
if (a == b) return true;
|
||||
float diff = std::abs(a-b);
|
||||
if (diff == std::numeric_limits<float>::infinity()
|
||||
|| diff != diff) return false; // diff is +inf or nan.
|
||||
return (diff <= relative_tolerance*(std::abs(a)+std::abs(b)));
|
||||
}
|
||||
|
||||
/// assert abs(a - b) <= relative_tolerance * (abs(a)+abs(b))
|
||||
static inline void AssertEqual(float a, float b,
|
||||
float relative_tolerance = 0.001) {
|
||||
// a==b handles infinities.
|
||||
KALDI_ASSERT(ApproxEqual(a, b, relative_tolerance));
|
||||
}
|
||||
|
||||
|
||||
// RoundUpToNearestPowerOfTwo does the obvious thing. It crashes if n <= 0.
|
||||
int32 RoundUpToNearestPowerOfTwo(int32 n);
|
||||
|
||||
/// Returns a / b, rounding towards negative infinity in all cases.
|
||||
static inline int32 DivideRoundingDown(int32 a, int32 b) {
|
||||
KALDI_ASSERT(b != 0);
|
||||
if (a * b >= 0)
|
||||
return a / b;
|
||||
else if (a < 0)
|
||||
return (a - b + 1) / b;
|
||||
else
|
||||
return (a - b - 1) / b;
|
||||
}
|
||||
|
||||
template<class I> I Gcd(I m, I n) {
|
||||
if (m == 0 || n == 0) {
|
||||
if (m == 0 && n == 0) { // gcd not defined, as all integers are divisors.
|
||||
KALDI_ERR << "Undefined GCD since m = 0, n = 0.";
|
||||
}
|
||||
return (m == 0 ? (n > 0 ? n : -n) : ( m > 0 ? m : -m));
|
||||
// return absolute value of whichever is nonzero
|
||||
}
|
||||
// could use compile-time assertion
|
||||
// but involves messing with complex template stuff.
|
||||
KALDI_ASSERT(std::numeric_limits<I>::is_integer);
|
||||
while (1) {
|
||||
m %= n;
|
||||
if (m == 0) return (n > 0 ? n : -n);
|
||||
n %= m;
|
||||
if (n == 0) return (m > 0 ? m : -m);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the least common multiple of two integers. Will
|
||||
/// crash unless the inputs are positive.
|
||||
template<class I> I Lcm(I m, I n) {
|
||||
KALDI_ASSERT(m > 0 && n > 0);
|
||||
I gcd = Gcd(m, n);
|
||||
return gcd * (m/gcd) * (n/gcd);
|
||||
}
|
||||
|
||||
|
||||
template<class I> void Factorize(I m, std::vector<I> *factors) {
|
||||
// Splits a number into its prime factors, in sorted order from
|
||||
// least to greatest, with duplication. A very inefficient
|
||||
// algorithm, which is mainly intended for use in the
|
||||
// mixed-radix FFT computation (where we assume most factors
|
||||
// are small).
|
||||
KALDI_ASSERT(factors != NULL);
|
||||
KALDI_ASSERT(m >= 1); // Doesn't work for zero or negative numbers.
|
||||
factors->clear();
|
||||
I small_factors[10] = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29 };
|
||||
|
||||
// First try small factors.
|
||||
for (I i = 0; i < 10; i++) {
|
||||
if (m == 1) return; // We're done.
|
||||
while (m % small_factors[i] == 0) {
|
||||
m /= small_factors[i];
|
||||
factors->push_back(small_factors[i]);
|
||||
}
|
||||
}
|
||||
// Next try all odd numbers starting from 31.
|
||||
for (I j = 31;; j += 2) {
|
||||
if (m == 1) return;
|
||||
while (m % j == 0) {
|
||||
m /= j;
|
||||
factors->push_back(j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline double Hypot(double x, double y) { return hypot(x, y); }
|
||||
inline float Hypot(float x, float y) { return hypotf(x, y); }
|
||||
|
||||
|
||||
|
||||
|
||||
} // namespace kaldi
|
||||
|
||||
|
||||
#endif // KALDI_BASE_KALDI_MATH_H_
|
||||
@@ -0,0 +1,76 @@
|
||||
// base/kaldi-types.h
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation; Saarland University;
|
||||
// Jan Silovsky; Yanmin Qian
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef KALDI_BASE_KALDI_TYPES_H_
|
||||
#define KALDI_BASE_KALDI_TYPES_H_ 1
|
||||
|
||||
namespace kaldi {
|
||||
// TYPEDEFS ..................................................................
|
||||
#if (KALDI_DOUBLEPRECISION != 0)
|
||||
typedef double BaseFloat;
|
||||
#else
|
||||
typedef float BaseFloat;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <basetsd.h>
|
||||
// Don't define ssize_t as macro - let fst/types.h handle it
|
||||
// #define ssize_t SSIZE_T // This causes conflicts with fst/types.h
|
||||
#endif
|
||||
|
||||
// we can do this a different way if some platform
|
||||
// we find in the future lacks stdint.h
|
||||
#include <stdint.h>
|
||||
|
||||
// for discussion on what to do if you need compile kaldi
|
||||
// without OpenFST, see the bottom of this this file
|
||||
#include <fst/types.h>
|
||||
|
||||
namespace kaldi {
|
||||
using ::int16;
|
||||
using ::int32;
|
||||
using ::int64;
|
||||
using ::uint16;
|
||||
using ::uint32;
|
||||
using ::uint64;
|
||||
typedef float float32;
|
||||
typedef double double64;
|
||||
} // end namespace kaldi
|
||||
|
||||
// In a theoretical case you decide compile Kaldi without the OpenFST
|
||||
// comment the previous namespace statement and uncomment the following
|
||||
/*
|
||||
namespace kaldi {
|
||||
typedef int8_t int8;
|
||||
typedef int16_t int16;
|
||||
typedef int32_t int32;
|
||||
typedef int64_t int64;
|
||||
|
||||
typedef uint8_t uint8;
|
||||
typedef uint16_t uint16;
|
||||
typedef uint32_t uint32;
|
||||
typedef uint64_t uint64;
|
||||
typedef float float32;
|
||||
typedef double double64;
|
||||
} // end namespace kaldi
|
||||
*/
|
||||
|
||||
#endif // KALDI_BASE_KALDI_TYPES_H_
|
||||
@@ -0,0 +1,46 @@
|
||||
// base/kaldi-utils.cc
|
||||
// Copyright 2009-2011 Karel Vesely; Yanmin Qian; Microsoft Corporation
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "base/kaldi-utils.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <thread>
|
||||
|
||||
|
||||
namespace kaldi {
|
||||
|
||||
std::string CharToString(const char &c) {
|
||||
char buf[20];
|
||||
if (std::isprint(c))
|
||||
std::snprintf(buf, sizeof(buf), "\'%c\'", c);
|
||||
else
|
||||
std::snprintf(buf, sizeof(buf), "[character %d]", static_cast<int>(c));
|
||||
return buf;
|
||||
}
|
||||
|
||||
void Sleep(double sec) {
|
||||
// duration_cast<> rounds down, add 0.5 to compensate.
|
||||
auto dur_nanos = std::chrono::duration<double, std::nano>(sec * 1E9 + 0.5);
|
||||
auto dur_syshires = std::chrono::duration_cast<
|
||||
typename std::chrono::high_resolution_clock::duration>(dur_nanos);
|
||||
std::this_thread::sleep_for(dur_syshires);
|
||||
}
|
||||
|
||||
} // end namespace kaldi
|
||||
@@ -0,0 +1,162 @@
|
||||
// base/kaldi-utils.h
|
||||
|
||||
// Copyright 2009-2011 Ondrej Glembek; Microsoft Corporation;
|
||||
// Saarland University; Karel Vesely; Yanmin Qian
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef KALDI_BASE_KALDI_UTILS_H_
|
||||
#define KALDI_BASE_KALDI_UTILS_H_ 1
|
||||
|
||||
#if _MSC_VER
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN 1
|
||||
#endif
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX 1
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <stdio.h>
|
||||
#define unlink _unlink
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <limits>
|
||||
#include <string>
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning(disable: 4244 4056 4305 4800 4267 4996 4756 4661)
|
||||
#if _MSC_VER < 1400
|
||||
#define __restrict__
|
||||
#else
|
||||
#define __restrict__ __restrict
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
# define KALDI_MEMALIGN(align, size, pp_orig) \
|
||||
(*(pp_orig) = _aligned_malloc(size, align))
|
||||
# define KALDI_MEMALIGN_FREE(x) _aligned_free(x)
|
||||
#elif defined(__CYGWIN__)
|
||||
# define KALDI_MEMALIGN(align, size, pp_orig) \
|
||||
(*(pp_orig) = aligned_alloc(align, size))
|
||||
# define KALDI_MEMALIGN_FREE(x) free(x)
|
||||
#else
|
||||
# define KALDI_MEMALIGN(align, size, pp_orig) \
|
||||
(!posix_memalign(pp_orig, align, size) ? *(pp_orig) : NULL)
|
||||
# define KALDI_MEMALIGN_FREE(x) free(x)
|
||||
#endif
|
||||
|
||||
#ifdef __ICC
|
||||
#pragma warning(disable: 383) // ICPC remark we don't want.
|
||||
#pragma warning(disable: 810) // ICPC remark we don't want.
|
||||
#pragma warning(disable: 981) // ICPC remark we don't want.
|
||||
#pragma warning(disable: 1418) // ICPC remark we don't want.
|
||||
#pragma warning(disable: 444) // ICPC remark we don't want.
|
||||
#pragma warning(disable: 869) // ICPC remark we don't want.
|
||||
#pragma warning(disable: 1287) // ICPC remark we don't want.
|
||||
#pragma warning(disable: 279) // ICPC remark we don't want.
|
||||
#pragma warning(disable: 981) // ICPC remark we don't want.
|
||||
#endif
|
||||
|
||||
|
||||
namespace kaldi {
|
||||
|
||||
|
||||
// CharToString prints the character in a human-readable form, for debugging.
|
||||
std::string CharToString(const char &c);
|
||||
|
||||
|
||||
inline int MachineIsLittleEndian() {
|
||||
int check = 1;
|
||||
return (*reinterpret_cast<char*>(&check) != 0);
|
||||
}
|
||||
|
||||
// This function kaldi::Sleep() provides a portable way
|
||||
// to sleep for a possibly fractional
|
||||
// number of seconds. On Windows it's only accurate to microseconds.
|
||||
void Sleep(double seconds);
|
||||
}
|
||||
|
||||
#define KALDI_SWAP8(a) do { \
|
||||
int t = (reinterpret_cast<char*>(&a))[0];\
|
||||
(reinterpret_cast<char*>(&a))[0]=(reinterpret_cast<char*>(&a))[7];\
|
||||
(reinterpret_cast<char*>(&a))[7]=t;\
|
||||
t = (reinterpret_cast<char*>(&a))[1];\
|
||||
(reinterpret_cast<char*>(&a))[1]=(reinterpret_cast<char*>(&a))[6];\
|
||||
(reinterpret_cast<char*>(&a))[6]=t;\
|
||||
t = (reinterpret_cast<char*>(&a))[2];\
|
||||
(reinterpret_cast<char*>(&a))[2]=(reinterpret_cast<char*>(&a))[5];\
|
||||
(reinterpret_cast<char*>(&a))[5]=t;\
|
||||
t = (reinterpret_cast<char*>(&a))[3];\
|
||||
(reinterpret_cast<char*>(&a))[3]=(reinterpret_cast<char*>(&a))[4];\
|
||||
(reinterpret_cast<char*>(&a))[4]=t;} while (0)
|
||||
#define KALDI_SWAP4(a) do { \
|
||||
int t = (reinterpret_cast<char*>(&a))[0];\
|
||||
(reinterpret_cast<char*>(&a))[0]=(reinterpret_cast<char*>(&a))[3];\
|
||||
(reinterpret_cast<char*>(&a))[3]=t;\
|
||||
t = (reinterpret_cast<char*>(&a))[1];\
|
||||
(reinterpret_cast<char*>(&a))[1]=(reinterpret_cast<char*>(&a))[2];\
|
||||
(reinterpret_cast<char*>(&a))[2]=t;} while (0)
|
||||
#define KALDI_SWAP2(a) do { \
|
||||
int t = (reinterpret_cast<char*>(&a))[0];\
|
||||
(reinterpret_cast<char*>(&a))[0]=(reinterpret_cast<char*>(&a))[1];\
|
||||
(reinterpret_cast<char*>(&a))[1]=t;} while (0)
|
||||
|
||||
|
||||
///\brief Declare deleted copy constructor and copy assignment operator=().
|
||||
///
|
||||
/// Use this macro in the \e public part of a class declaration in a header
|
||||
/// file, next to its constructors, so that uncopyability of the type is
|
||||
/// clearly readable. Place a semicolon after it.
|
||||
///\param type The exact enclosing class name.
|
||||
#define KALDI_DISALLOW_COPY_AND_ASSIGN(type) \
|
||||
type(const type&) = delete; \
|
||||
type& operator=(const type&) = delete
|
||||
|
||||
#if __cplusplus >= 201703L
|
||||
#define KALDI_COMPILE_TIME_ASSERT static_assert
|
||||
#else
|
||||
#define KALDI_COMPILE_TIME_ASSERT(b) static_assert((b), #b)
|
||||
#endif
|
||||
|
||||
#define KALDI_ASSERT_IS_INTEGER_TYPE(I) \
|
||||
KALDI_COMPILE_TIME_ASSERT(std::numeric_limits<I>::is_specialized \
|
||||
&& std::numeric_limits<I>::is_integer)
|
||||
|
||||
#define KALDI_ASSERT_IS_FLOATING_TYPE(F) \
|
||||
KALDI_COMPILE_TIME_ASSERT(std::numeric_limits<F>::is_specialized \
|
||||
&& !std::numeric_limits<F>::is_integer)
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#define KALDI_STRCASECMP _stricmp
|
||||
#elif defined(__CYGWIN__)
|
||||
#include <strings.h>
|
||||
#define KALDI_STRCASECMP strcasecmp
|
||||
#else
|
||||
#define KALDI_STRCASECMP strcasecmp
|
||||
#endif
|
||||
#ifdef _MSC_VER
|
||||
# define KALDI_STRTOLL(cur_cstr, end_cstr) _strtoi64(cur_cstr, end_cstr, 10);
|
||||
#else
|
||||
# define KALDI_STRTOLL(cur_cstr, end_cstr) strtoll(cur_cstr, end_cstr, 10);
|
||||
#endif
|
||||
|
||||
#endif // KALDI_BASE_KALDI_UTILS_H_
|
||||
@@ -0,0 +1,45 @@
|
||||
// base/timer-test.cc
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation
|
||||
// 2014 Johns Hopkins University (author: Daniel Povey)
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "base/timer.h"
|
||||
#include "base/kaldi-common.h"
|
||||
#include "base/kaldi-utils.h"
|
||||
|
||||
|
||||
namespace kaldi {
|
||||
|
||||
void TimerTest() {
|
||||
float time_secs = 0.025 * (rand() % 10);
|
||||
std::cout << "target is " << time_secs << "\n";
|
||||
Timer timer;
|
||||
Sleep(time_secs);
|
||||
BaseFloat f = timer.Elapsed();
|
||||
std::cout << "time is " << f << std::endl;
|
||||
if (fabs(time_secs - f) > 0.05)
|
||||
KALDI_ERR << "Timer fail: waited " << f << " seconds instead of "
|
||||
<< time_secs << " secs.";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int main() {
|
||||
for (int i = 0; i < 4; i++)
|
||||
kaldi::TimerTest();
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// base/timer.cc
|
||||
|
||||
// Copyright 2018 Johns Hopkins University (author: Daniel Povey)
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "base/timer.h"
|
||||
#include "base/kaldi-error.h"
|
||||
#include <algorithm>
|
||||
#include <iomanip>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace kaldi {
|
||||
|
||||
class ProfileStats {
|
||||
public:
|
||||
void AccStats(const char *function_name, double elapsed) {
|
||||
std::unordered_map<const char*, ProfileStatsEntry>::iterator
|
||||
iter = map_.find(function_name);
|
||||
if (iter == map_.end()) {
|
||||
map_[function_name] = ProfileStatsEntry(function_name);
|
||||
map_[function_name].total_time = elapsed;
|
||||
} else {
|
||||
iter->second.total_time += elapsed;
|
||||
}
|
||||
}
|
||||
~ProfileStats() {
|
||||
// This map makes sure we agglomerate the time if there were any duplicate
|
||||
// addresses of strings.
|
||||
std::unordered_map<std::string, double> total_time;
|
||||
for (auto iter = map_.begin(); iter != map_.end(); iter++)
|
||||
total_time[iter->second.name] += iter->second.total_time;
|
||||
|
||||
ReverseSecondComparator comp;
|
||||
std::vector<std::pair<std::string, double> > pairs(total_time.begin(),
|
||||
total_time.end());
|
||||
std::sort(pairs.begin(), pairs.end(), comp);
|
||||
for (size_t i = 0; i < pairs.size(); i++) {
|
||||
KALDI_LOG << "Time taken in " << pairs[i].first << " is "
|
||||
<< std::fixed << std::setprecision(2) << pairs[i].second << "s.";
|
||||
}
|
||||
}
|
||||
private:
|
||||
|
||||
struct ProfileStatsEntry {
|
||||
std::string name;
|
||||
double total_time;
|
||||
ProfileStatsEntry() { }
|
||||
ProfileStatsEntry(const char *name): name(name) { }
|
||||
};
|
||||
|
||||
struct ReverseSecondComparator {
|
||||
bool operator () (const std::pair<std::string, double> &a,
|
||||
const std::pair<std::string, double> &b) {
|
||||
return a.second > b.second;
|
||||
}
|
||||
};
|
||||
|
||||
// Note: this map is keyed on the address of the string, there is no proper
|
||||
// hash function. The assumption is that the strings are compile-time
|
||||
// constants.
|
||||
std::unordered_map<const char*, ProfileStatsEntry> map_;
|
||||
};
|
||||
|
||||
ProfileStats g_profile_stats;
|
||||
|
||||
Profiler::~Profiler() {
|
||||
g_profile_stats.AccStats(name_, tim_.Elapsed());
|
||||
}
|
||||
|
||||
} // namespace kaldi
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
// base/timer.h
|
||||
|
||||
// Copyright 2009-2011 Ondrej Glembek; Microsoft Corporation
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
#ifndef KALDI_BASE_TIMER_H_
|
||||
#define KALDI_BASE_TIMER_H_
|
||||
|
||||
#include "base/kaldi-utils.h"
|
||||
#include "base/kaldi-error.h"
|
||||
|
||||
|
||||
#if defined(_MSC_VER) || defined(MINGW)
|
||||
|
||||
namespace kaldi {
|
||||
class Timer {
|
||||
public:
|
||||
Timer() { Reset(); }
|
||||
|
||||
// You can initialize with bool to control whether or not you want the time to
|
||||
// be set when the object is created.
|
||||
explicit Timer(bool set_timer) { if (set_timer) Reset(); }
|
||||
|
||||
void Reset() {
|
||||
QueryPerformanceCounter(&time_start_);
|
||||
}
|
||||
double Elapsed() const {
|
||||
LARGE_INTEGER time_end;
|
||||
LARGE_INTEGER freq;
|
||||
QueryPerformanceCounter(&time_end);
|
||||
|
||||
if (QueryPerformanceFrequency(&freq) == 0) {
|
||||
// Hardware does not support this.
|
||||
return 0.0;
|
||||
}
|
||||
return (static_cast<double>(time_end.QuadPart) -
|
||||
static_cast<double>(time_start_.QuadPart)) /
|
||||
(static_cast<double>(freq.QuadPart));
|
||||
}
|
||||
private:
|
||||
LARGE_INTEGER time_start_;
|
||||
};
|
||||
|
||||
|
||||
#else
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace kaldi {
|
||||
class Timer {
|
||||
public:
|
||||
Timer() { Reset(); }
|
||||
|
||||
// You can initialize with bool to control whether or not you want the time to
|
||||
// be set when the object is created.
|
||||
explicit Timer(bool set_timer) { if (set_timer) Reset(); }
|
||||
|
||||
void Reset() { gettimeofday(&this->time_start_, &time_zone_); }
|
||||
|
||||
/// Returns time in seconds.
|
||||
double Elapsed() const {
|
||||
struct timeval time_end;
|
||||
struct timezone time_zone;
|
||||
gettimeofday(&time_end, &time_zone);
|
||||
double t1, t2;
|
||||
t1 = static_cast<double>(time_start_.tv_sec) +
|
||||
static_cast<double>(time_start_.tv_usec)/(1000*1000);
|
||||
t2 = static_cast<double>(time_end.tv_sec) +
|
||||
static_cast<double>(time_end.tv_usec)/(1000*1000);
|
||||
return t2-t1;
|
||||
}
|
||||
|
||||
private:
|
||||
struct timeval time_start_;
|
||||
struct timezone time_zone_;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
class Profiler {
|
||||
public:
|
||||
// Caution: the 'const char' should always be a string constant; for speed,
|
||||
// internally the profiling code uses the address of it as a lookup key.
|
||||
Profiler(const char *function_name): name_(function_name) { }
|
||||
~Profiler();
|
||||
private:
|
||||
Timer tim_;
|
||||
const char *name_;
|
||||
};
|
||||
|
||||
// To add timing info for a function, you just put
|
||||
// KALDI_PROFILE;
|
||||
// at the beginning of the function. Caution: this doesn't
|
||||
// include the class name.
|
||||
#define KALDI_PROFILE Profiler _profiler(__func__)
|
||||
|
||||
|
||||
|
||||
} // namespace kaldi
|
||||
|
||||
|
||||
#endif // KALDI_BASE_TIMER_H_
|
||||
@@ -0,0 +1,20 @@
|
||||
all:
|
||||
|
||||
EXTRA_CXXFLAGS = -Wno-sign-compare
|
||||
include ../kaldi.mk
|
||||
|
||||
TESTFILES =
|
||||
|
||||
OBJFILES = training-graph-compiler.o lattice-simple-decoder.o lattice-faster-decoder.o \
|
||||
lattice-faster-online-decoder.o simple-decoder.o faster-decoder.o \
|
||||
decoder-wrappers.o grammar-fst.o decodable-matrix.o \
|
||||
lattice-incremental-decoder.o lattice-incremental-online-decoder.o
|
||||
|
||||
LIBNAME = kaldi-decoder
|
||||
|
||||
ADDLIBS = ../lat/kaldi-lat.a ../fstext/kaldi-fstext.a ../hmm/kaldi-hmm.a \
|
||||
../transform/kaldi-transform.a ../gmm/kaldi-gmm.a \
|
||||
../tree/kaldi-tree.a ../util/kaldi-util.a ../matrix/kaldi-matrix.a \
|
||||
../base/kaldi-base.a
|
||||
|
||||
include ../makefiles/default_rules.mk
|
||||
@@ -0,0 +1,502 @@
|
||||
// decoder/biglm-faster-decoder.h
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation, Gilles Boulianne
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef KALDI_DECODER_BIGLM_FASTER_DECODER_H_
|
||||
#define KALDI_DECODER_BIGLM_FASTER_DECODER_H_
|
||||
|
||||
#include "util/stl-utils.h"
|
||||
#include "util/hash-list.h"
|
||||
#include "fst/fstlib.h"
|
||||
#include "itf/decodable-itf.h"
|
||||
#include "lat/kaldi-lattice.h" // for CompactLatticeArc
|
||||
#include "decoder/faster-decoder.h" // for options class
|
||||
#include "fstext/deterministic-fst.h"
|
||||
|
||||
namespace kaldi {
|
||||
|
||||
struct BiglmFasterDecoderOptions: public FasterDecoderOptions {
|
||||
BiglmFasterDecoderOptions() {
|
||||
min_active = 200;
|
||||
}
|
||||
};
|
||||
|
||||
/** This is as FasterDecoder, but does online composition between
|
||||
HCLG and the "difference language model", which is a deterministic
|
||||
FST that represents the difference between the language model you want
|
||||
and the language model you compiled HCLG with. The class
|
||||
DeterministicOnDemandFst follows through the epsilons in G for you
|
||||
(assuming G is a standard backoff language model) and makes it look
|
||||
like a determinized FST. Actually, in practice,
|
||||
DeterministicOnDemandFst operates in a mode where it composes two
|
||||
G's together; one has negated likelihoods and works by removing the
|
||||
LM probabilities that you made HCLG with, and one is the language model
|
||||
you want to use.
|
||||
*/
|
||||
class BiglmFasterDecoder {
|
||||
public:
|
||||
typedef fst::StdArc Arc;
|
||||
typedef Arc::Label Label;
|
||||
typedef Arc::StateId StateId;
|
||||
// A PairId will be constructed as: (StateId in fst) + (StateId in lm_diff_fst) << 32;
|
||||
typedef uint64 PairId;
|
||||
typedef Arc::Weight Weight;
|
||||
|
||||
// This constructor is the same as for FasterDecoder, except the second
|
||||
// argument (lm_diff_fst) is new; it's an FST (actually, a
|
||||
// DeterministicOnDemandFst) that represents the difference in LM scores
|
||||
// between the LM we want and the LM the decoding-graph "fst" was built with.
|
||||
// See e.g. gmm-decode-biglm-faster.cc for an example of how this is called.
|
||||
// Basically, we are using fst o lm_diff_fst (where o is composition)
|
||||
// as the decoding graph. Instead of having everything indexed by the state in
|
||||
// "fst", we now index by the pair of states in (fst, lm_diff_fst).
|
||||
// Whenever we cross a word, we need to propagate the state within
|
||||
// lm_diff_fst.
|
||||
BiglmFasterDecoder(const fst::Fst<fst::StdArc> &fst,
|
||||
const BiglmFasterDecoderOptions &opts,
|
||||
fst::DeterministicOnDemandFst<fst::StdArc> *lm_diff_fst):
|
||||
fst_(fst), lm_diff_fst_(lm_diff_fst), opts_(opts), warned_noarc_(false) {
|
||||
KALDI_ASSERT(opts_.hash_ratio >= 1.0); // less doesn't make much sense.
|
||||
KALDI_ASSERT(opts_.max_active > 1);
|
||||
KALDI_ASSERT(fst.Start() != fst::kNoStateId &&
|
||||
lm_diff_fst->Start() != fst::kNoStateId);
|
||||
toks_.SetSize(1000); // just so on the first frame we do something reasonable.
|
||||
}
|
||||
|
||||
void SetOptions(const BiglmFasterDecoderOptions &opts) { opts_ = opts; }
|
||||
|
||||
~BiglmFasterDecoder() {
|
||||
ClearToks(toks_.Clear());
|
||||
}
|
||||
|
||||
void Decode(DecodableInterface *decodable) {
|
||||
// clean up from last time:
|
||||
ClearToks(toks_.Clear());
|
||||
PairId start_pair = ConstructPair(fst_.Start(), lm_diff_fst_->Start());
|
||||
Arc dummy_arc(0, 0, Weight::One(), fst_.Start()); // actually, the last element of
|
||||
// the Arcs (fst_.Start(), here) is never needed.
|
||||
toks_.Insert(start_pair, new Token(dummy_arc, NULL));
|
||||
ProcessNonemitting(std::numeric_limits<float>::max());
|
||||
for (int32 frame = 0; !decodable->IsLastFrame(frame-1); frame++) {
|
||||
BaseFloat weight_cutoff = ProcessEmitting(decodable, frame);
|
||||
ProcessNonemitting(weight_cutoff);
|
||||
}
|
||||
}
|
||||
|
||||
bool ReachedFinal() {
|
||||
for (const Elem *e = toks_.GetList(); e != NULL; e = e->tail) {
|
||||
PairId state_pair = e->key;
|
||||
StateId state = PairToState(state_pair),
|
||||
lm_state = PairToLmState(state_pair);
|
||||
Weight this_weight =
|
||||
Times(e->val->weight_,
|
||||
Times(fst_.Final(state), lm_diff_fst_->Final(lm_state)));
|
||||
if (this_weight != Weight::Zero())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GetBestPath(fst::MutableFst<LatticeArc> *fst_out,
|
||||
bool use_final_probs = true) {
|
||||
// GetBestPath gets the decoding output. If "use_final_probs" is true
|
||||
// AND we reached a final state, it limits itself to final states;
|
||||
// otherwise it gets the most likely token not taking into
|
||||
// account final-probs. fst_out will be empty (Start() == kNoStateId) if
|
||||
// nothing was available. It returns true if it got output (thus, fst_out
|
||||
// will be nonempty).
|
||||
fst_out->DeleteStates();
|
||||
Token *best_tok = NULL;
|
||||
Weight best_final = Weight::Zero(); // set only if is_final == true. The
|
||||
// final-prob corresponding to the best final token (i.e. the one with best
|
||||
// weight best_weight, below).
|
||||
bool is_final = ReachedFinal();
|
||||
if (!is_final) {
|
||||
for (const Elem *e = toks_.GetList(); e != NULL; e = e->tail)
|
||||
if (best_tok == NULL || *best_tok < *(e->val) )
|
||||
best_tok = e->val;
|
||||
} else {
|
||||
Weight best_weight = Weight::Zero();
|
||||
for (const Elem *e = toks_.GetList(); e != NULL; e = e->tail) {
|
||||
Weight fst_final = fst_.Final(PairToState(e->key)),
|
||||
lm_final = lm_diff_fst_->Final(PairToLmState(e->key)),
|
||||
final = Times(fst_final, lm_final);
|
||||
Weight this_weight = Times(e->val->weight_, final);
|
||||
if (this_weight != Weight::Zero() &&
|
||||
this_weight.Value() < best_weight.Value()) {
|
||||
best_weight = this_weight;
|
||||
best_final = final;
|
||||
best_tok = e->val;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (best_tok == NULL) return false; // No output.
|
||||
|
||||
std::vector<LatticeArc> arcs_reverse; // arcs in reverse order.
|
||||
|
||||
for (Token *tok = best_tok; tok != NULL; tok = tok->prev_) {
|
||||
BaseFloat tot_cost = tok->weight_.Value() -
|
||||
(tok->prev_ ? tok->prev_->weight_.Value() : 0.0),
|
||||
graph_cost = tok->arc_.weight.Value(),
|
||||
ac_cost = tot_cost - graph_cost;
|
||||
LatticeArc l_arc(tok->arc_.ilabel,
|
||||
tok->arc_.olabel,
|
||||
LatticeWeight(graph_cost, ac_cost),
|
||||
tok->arc_.nextstate);
|
||||
arcs_reverse.push_back(l_arc);
|
||||
}
|
||||
KALDI_ASSERT(arcs_reverse.back().nextstate == fst_.Start());
|
||||
arcs_reverse.pop_back(); // that was a "fake" token... gives no info.
|
||||
|
||||
StateId cur_state = fst_out->AddState();
|
||||
fst_out->SetStart(cur_state);
|
||||
for (ssize_t i = static_cast<ssize_t>(arcs_reverse.size())-1; i >= 0; i--) {
|
||||
LatticeArc arc = arcs_reverse[i];
|
||||
arc.nextstate = fst_out->AddState();
|
||||
fst_out->AddArc(cur_state, arc);
|
||||
cur_state = arc.nextstate;
|
||||
}
|
||||
if (is_final && use_final_probs) {
|
||||
fst_out->SetFinal(cur_state, LatticeWeight(best_final.Value(), 0.0));
|
||||
} else {
|
||||
fst_out->SetFinal(cur_state, LatticeWeight::One());
|
||||
}
|
||||
RemoveEpsLocal(fst_out);
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
inline PairId ConstructPair(StateId fst_state, StateId lm_state) {
|
||||
return static_cast<PairId>(fst_state) + (static_cast<PairId>(lm_state) << 32);
|
||||
}
|
||||
|
||||
static inline StateId PairToState(PairId state_pair) {
|
||||
return static_cast<StateId>(static_cast<uint32>(state_pair));
|
||||
}
|
||||
static inline StateId PairToLmState(PairId state_pair) {
|
||||
return static_cast<StateId>(static_cast<uint32>(state_pair >> 32));
|
||||
}
|
||||
|
||||
class Token {
|
||||
public:
|
||||
Arc arc_; // contains only the graph part of the cost,
|
||||
// including the part in "fst" (== HCLG) plus lm_diff_fst.
|
||||
// We can work out the acoustic part from difference between
|
||||
// "weight_" and prev->weight_.
|
||||
Token *prev_;
|
||||
int32 ref_count_;
|
||||
Weight weight_; // weight up to current point.
|
||||
inline Token(const Arc &arc, Weight &ac_weight, Token *prev):
|
||||
arc_(arc), prev_(prev), ref_count_(1) {
|
||||
if (prev) {
|
||||
prev->ref_count_++;
|
||||
weight_ = Times(Times(prev->weight_, arc.weight), ac_weight);
|
||||
} else {
|
||||
weight_ = Times(arc.weight, ac_weight);
|
||||
}
|
||||
}
|
||||
inline Token(const Arc &arc, Token *prev):
|
||||
arc_(arc), prev_(prev), ref_count_(1) {
|
||||
if (prev) {
|
||||
prev->ref_count_++;
|
||||
weight_ = Times(prev->weight_, arc.weight);
|
||||
} else {
|
||||
weight_ = arc.weight;
|
||||
}
|
||||
}
|
||||
inline bool operator < (const Token &other) {
|
||||
return weight_.Value() > other.weight_.Value();
|
||||
// This makes sense for log + tropical semiring.
|
||||
}
|
||||
|
||||
inline ~Token() {
|
||||
KALDI_ASSERT(ref_count_ == 1);
|
||||
if (prev_ != NULL) TokenDelete(prev_);
|
||||
}
|
||||
inline static void TokenDelete(Token *tok) {
|
||||
if (tok->ref_count_ == 1) {
|
||||
delete tok;
|
||||
} else {
|
||||
tok->ref_count_--;
|
||||
}
|
||||
}
|
||||
};
|
||||
typedef HashList<PairId, Token*>::Elem Elem;
|
||||
|
||||
|
||||
/// Gets the weight cutoff. Also counts the active tokens.
|
||||
BaseFloat GetCutoff(Elem *list_head, size_t *tok_count,
|
||||
BaseFloat *adaptive_beam, Elem **best_elem) {
|
||||
BaseFloat best_weight = 1.0e+10; // positive == high cost == bad.
|
||||
size_t count = 0;
|
||||
if (opts_.max_active == std::numeric_limits<int32>::max() &&
|
||||
opts_.min_active == 0) {
|
||||
for (Elem *e = list_head; e != NULL; e = e->tail, count++) {
|
||||
BaseFloat w = static_cast<BaseFloat>(e->val->weight_.Value());
|
||||
if (w < best_weight) {
|
||||
best_weight = w;
|
||||
if (best_elem) *best_elem = e;
|
||||
}
|
||||
}
|
||||
if (tok_count != NULL) *tok_count = count;
|
||||
if (adaptive_beam != NULL) *adaptive_beam = opts_.beam;
|
||||
return best_weight + opts_.beam;
|
||||
} else {
|
||||
tmp_array_.clear();
|
||||
for (Elem *e = list_head; e != NULL; e = e->tail, count++) {
|
||||
BaseFloat w = e->val->weight_.Value();
|
||||
tmp_array_.push_back(w);
|
||||
if (w < best_weight) {
|
||||
best_weight = w;
|
||||
if (best_elem) *best_elem = e;
|
||||
}
|
||||
}
|
||||
if (tok_count != NULL) *tok_count = count;
|
||||
|
||||
BaseFloat beam_cutoff = best_weight + opts_.beam,
|
||||
min_active_cutoff = std::numeric_limits<BaseFloat>::infinity(),
|
||||
max_active_cutoff = std::numeric_limits<BaseFloat>::infinity();
|
||||
|
||||
if (tmp_array_.size() > static_cast<size_t>(opts_.max_active)) {
|
||||
std::nth_element(tmp_array_.begin(),
|
||||
tmp_array_.begin() + opts_.max_active,
|
||||
tmp_array_.end());
|
||||
max_active_cutoff = tmp_array_[opts_.max_active];
|
||||
}
|
||||
if (tmp_array_.size() > static_cast<size_t>(opts_.min_active)) {
|
||||
if (opts_.min_active == 0) min_active_cutoff = best_weight;
|
||||
else {
|
||||
std::nth_element(tmp_array_.begin(),
|
||||
tmp_array_.begin() + opts_.min_active,
|
||||
tmp_array_.size() > static_cast<size_t>(opts_.max_active) ?
|
||||
tmp_array_.begin() + opts_.max_active :
|
||||
tmp_array_.end());
|
||||
min_active_cutoff = tmp_array_[opts_.min_active];
|
||||
}
|
||||
}
|
||||
|
||||
if (max_active_cutoff < beam_cutoff) { // max_active is tighter than beam.
|
||||
if (adaptive_beam)
|
||||
*adaptive_beam = max_active_cutoff - best_weight + opts_.beam_delta;
|
||||
return max_active_cutoff;
|
||||
} else if (min_active_cutoff > beam_cutoff) { // min_active is looser than beam.
|
||||
if (adaptive_beam)
|
||||
*adaptive_beam = min_active_cutoff - best_weight + opts_.beam_delta;
|
||||
return min_active_cutoff;
|
||||
} else {
|
||||
*adaptive_beam = opts_.beam;
|
||||
return beam_cutoff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PossiblyResizeHash(size_t num_toks) {
|
||||
size_t new_sz = static_cast<size_t>(static_cast<BaseFloat>(num_toks)
|
||||
* opts_.hash_ratio);
|
||||
if (new_sz > toks_.Size()) {
|
||||
toks_.SetSize(new_sz);
|
||||
}
|
||||
}
|
||||
|
||||
inline StateId PropagateLm(StateId lm_state,
|
||||
Arc *arc) { // returns new LM state.
|
||||
if (arc->olabel == 0) {
|
||||
return lm_state; // no change in LM state if no word crossed.
|
||||
} else { // Propagate in the LM-diff FST.
|
||||
Arc lm_arc;
|
||||
bool ans = lm_diff_fst_->GetArc(lm_state, arc->olabel, &lm_arc);
|
||||
if (!ans) { // this case is unexpected for statistical LMs.
|
||||
if (!warned_noarc_) {
|
||||
warned_noarc_ = true;
|
||||
KALDI_WARN << "No arc available in LM (unlikely to be correct "
|
||||
"if a statistical language model); will not warn again";
|
||||
}
|
||||
arc->weight = Weight::Zero();
|
||||
return lm_state; // doesn't really matter what we return here; will
|
||||
// be pruned.
|
||||
} else {
|
||||
arc->weight = Times(arc->weight, lm_arc.weight);
|
||||
arc->olabel = lm_arc.olabel; // probably will be the same.
|
||||
return lm_arc.nextstate; // return the new LM state.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ProcessEmitting returns the likelihood cutoff used.
|
||||
BaseFloat ProcessEmitting(DecodableInterface *decodable, int frame) {
|
||||
Elem *last_toks = toks_.Clear();
|
||||
size_t tok_cnt;
|
||||
BaseFloat adaptive_beam;
|
||||
Elem *best_elem = NULL;
|
||||
BaseFloat weight_cutoff = GetCutoff(last_toks, &tok_cnt,
|
||||
&adaptive_beam, &best_elem);
|
||||
PossiblyResizeHash(tok_cnt); // This makes sure the hash is always big enough.
|
||||
|
||||
// This is the cutoff we use after adding in the log-likes (i.e.
|
||||
// for the next frame). This is a bound on the cutoff we will use
|
||||
// on the next frame.
|
||||
BaseFloat next_weight_cutoff = 1.0e+10;
|
||||
|
||||
// First process the best token to get a hopefully
|
||||
// reasonably tight bound on the next cutoff.
|
||||
if (best_elem) {
|
||||
PairId state_pair = best_elem->key;
|
||||
StateId state = PairToState(state_pair),
|
||||
lm_state = PairToLmState(state_pair);
|
||||
Token *tok = best_elem->val;
|
||||
for (fst::ArcIterator<fst::Fst<Arc> > aiter(fst_, state);
|
||||
!aiter.Done();
|
||||
aiter.Next()) {
|
||||
Arc arc = aiter.Value();
|
||||
if (arc.ilabel != 0) { // we'd propagate..
|
||||
PropagateLm(lm_state, &arc); // may affect "arc.weight".
|
||||
// We don't need the return value (the new LM state).
|
||||
BaseFloat ac_cost = - decodable->LogLikelihood(frame, arc.ilabel),
|
||||
new_weight = arc.weight.Value() + tok->weight_.Value() + ac_cost;
|
||||
if (new_weight + adaptive_beam < next_weight_cutoff)
|
||||
next_weight_cutoff = new_weight + adaptive_beam;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the tokens are now owned here, in last_toks, and the hash is empty.
|
||||
// 'owned' is a complex thing here; the point is we need to call toks_.Delete(e)
|
||||
// on each elem 'e' to let toks_ know we're done with them.
|
||||
for (Elem *e = last_toks, *e_tail; e != NULL; e = e_tail) { // loop this way
|
||||
// because we delete "e" as we go.
|
||||
PairId state_pair = e->key;
|
||||
StateId state = PairToState(state_pair),
|
||||
lm_state = PairToLmState(state_pair);
|
||||
Token *tok = e->val;
|
||||
if (tok->weight_.Value() < weight_cutoff) { // not pruned.
|
||||
KALDI_ASSERT(state == tok->arc_.nextstate);
|
||||
for (fst::ArcIterator<fst::Fst<Arc> > aiter(fst_, state);
|
||||
!aiter.Done();
|
||||
aiter.Next()) {
|
||||
Arc arc = aiter.Value();
|
||||
if (arc.ilabel != 0) { // propagate.
|
||||
StateId next_lm_state = PropagateLm(lm_state, &arc);
|
||||
Weight ac_weight(-decodable->LogLikelihood(frame, arc.ilabel));
|
||||
BaseFloat new_weight = arc.weight.Value() + tok->weight_.Value()
|
||||
+ ac_weight.Value();
|
||||
if (new_weight < next_weight_cutoff) { // not pruned..
|
||||
PairId next_pair = ConstructPair(arc.nextstate, next_lm_state);
|
||||
Token *new_tok = new Token(arc, ac_weight, tok);
|
||||
Elem *e_found = toks_.Insert(next_pair, new_tok);
|
||||
if (new_weight + adaptive_beam < next_weight_cutoff)
|
||||
next_weight_cutoff = new_weight + adaptive_beam;
|
||||
if (e_found->val != new_tok) {
|
||||
if (*(e_found->val) < *new_tok) {
|
||||
Token::TokenDelete(e_found->val);
|
||||
e_found->val = new_tok;
|
||||
} else {
|
||||
Token::TokenDelete(new_tok);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
e_tail = e->tail;
|
||||
Token::TokenDelete(e->val);
|
||||
toks_.Delete(e);
|
||||
}
|
||||
return next_weight_cutoff;
|
||||
}
|
||||
|
||||
// TODO: first time we go through this, could avoid using the queue.
|
||||
void ProcessNonemitting(BaseFloat cutoff) {
|
||||
// Processes nonemitting arcs for one frame.
|
||||
KALDI_ASSERT(queue_.empty());
|
||||
for (const Elem *e = toks_.GetList(); e != NULL; e = e->tail)
|
||||
queue_.push_back(e);
|
||||
while (!queue_.empty()) {
|
||||
const Elem *e = queue_.back();
|
||||
queue_.pop_back();
|
||||
PairId state_pair = e->key;
|
||||
Token *tok = e->val; // would segfault if state not
|
||||
// in toks_ but this can't happen.
|
||||
if (tok->weight_.Value() > cutoff) { // Don't bother processing successors.
|
||||
continue;
|
||||
}
|
||||
KALDI_ASSERT(tok != NULL);
|
||||
StateId state = PairToState(state_pair),
|
||||
lm_state = PairToLmState(state_pair);
|
||||
for (fst::ArcIterator<fst::Fst<Arc> > aiter(fst_, state);
|
||||
!aiter.Done();
|
||||
aiter.Next()) {
|
||||
const Arc &arc_ref = aiter.Value();
|
||||
if (arc_ref.ilabel == 0) { // propagate nonemitting only...
|
||||
Arc arc(arc_ref);
|
||||
StateId next_lm_state = PropagateLm(lm_state, &arc);
|
||||
PairId next_pair = ConstructPair(arc.nextstate, next_lm_state);
|
||||
Token *new_tok = new Token(arc, tok);
|
||||
if (new_tok->weight_.Value() > cutoff) { // prune
|
||||
Token::TokenDelete(new_tok);
|
||||
} else {
|
||||
Elem *e_found = toks_.Insert(next_pair, new_tok);
|
||||
if (e_found->val == new_tok) {
|
||||
queue_.push_back(e_found);
|
||||
} else {
|
||||
if ( *(e_found->val) < *new_tok ) {
|
||||
Token::TokenDelete(e_found->val);
|
||||
e_found->val = new_tok;
|
||||
queue_.push_back(e_found);
|
||||
} else {
|
||||
Token::TokenDelete(new_tok);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// HashList defined in ../util/hash-list.h. It actually allows us to maintain
|
||||
// more than one list (e.g. for current and previous frames), but only one of
|
||||
// them at a time can be indexed by PairId.
|
||||
HashList<PairId, Token*> toks_;
|
||||
const fst::Fst<fst::StdArc> &fst_;
|
||||
fst::DeterministicOnDemandFst<fst::StdArc> *lm_diff_fst_;
|
||||
BiglmFasterDecoderOptions opts_;
|
||||
bool warned_noarc_;
|
||||
std::vector<const Elem* > queue_; // temp variable used in ProcessNonemitting,
|
||||
std::vector<BaseFloat> tmp_array_; // used in GetCutoff.
|
||||
// make it class member to avoid internal new/delete.
|
||||
|
||||
// It might seem unclear why we call ClearToks(toks_.Clear()).
|
||||
// There are two separate cleanup tasks we need to do at when we start a new file.
|
||||
// one is to delete the Token objects in the list; the other is to delete
|
||||
// the Elem objects. toks_.Clear() just clears them from the hash and gives ownership
|
||||
// to the caller, who then has to call toks_.Delete(e) for each one. It was designed
|
||||
// this way for convenience in propagating tokens from one frame to the next.
|
||||
void ClearToks(Elem *list) {
|
||||
for (Elem *e = list, *e_tail; e != NULL; e = e_tail) {
|
||||
Token::TokenDelete(e->val);
|
||||
e_tail = e->tail;
|
||||
toks_.Delete(e);
|
||||
}
|
||||
}
|
||||
KALDI_DISALLOW_COPY_AND_ASSIGN(BiglmFasterDecoder);
|
||||
};
|
||||
|
||||
|
||||
} // end namespace kaldi.
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,69 @@
|
||||
// decoder/decodable-mapped.h
|
||||
|
||||
// Copyright 2009-2011 Saarland University; Microsoft Corporation;
|
||||
// Lukas Burget
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef KALDI_DECODER_DECODABLE_MAPPED_H_
|
||||
#define KALDI_DECODER_DECODABLE_MAPPED_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "base/kaldi-common.h"
|
||||
#include "itf/decodable-itf.h"
|
||||
|
||||
namespace kaldi {
|
||||
|
||||
// The DecodableMapped object is initialized by a normal decodable object,
|
||||
// and a vector that maps indices. The "pdf index" into this decodable object
|
||||
// is the index into the vector, and the value it finds there is used
|
||||
// to index into the base decodable object.
|
||||
|
||||
class DecodableMapped: public DecodableInterface {
|
||||
public:
|
||||
DecodableMapped(const std::vector<int32> &index_map, DecodableInterface *d):
|
||||
index_map_(index_map), decodable_(d) { }
|
||||
|
||||
// Note, frames are numbered from zero. But state_index is numbered
|
||||
// from one (this routine is called by FSTs).
|
||||
virtual BaseFloat LogLikelihood(int32 frame, int32 state_index) {
|
||||
KALDI_ASSERT(static_cast<size_t>(state_index) < index_map_.size());
|
||||
return decodable_->LogLikelihood(frame, index_map_[state_index]);
|
||||
}
|
||||
|
||||
// note: indices are assumed to be numbered from one, so
|
||||
// NumIndices() will be the same as the largest index.
|
||||
virtual int32 NumIndices() const { return static_cast<int32>(index_map_.size()) - 1; }
|
||||
|
||||
virtual bool IsLastFrame(int32 frame) const {
|
||||
// We require all the decodables have the same #frames. We don't check this though.
|
||||
return decodable_->IsLastFrame(frame);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<int32> index_map_;
|
||||
DecodableInterface *decodable_;
|
||||
|
||||
KALDI_DISALLOW_COPY_AND_ASSIGN(DecodableMapped);
|
||||
};
|
||||
|
||||
|
||||
|
||||
} // namespace kaldi
|
||||
|
||||
#endif // KALDI_DECODER_DECODABLE_MAPPED_H_
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
// decoder/decodable-matrix.cc
|
||||
|
||||
// Copyright 2018 Johns Hopkins University (author: Daniel Povey)
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "decoder/decodable-matrix.h"
|
||||
|
||||
namespace kaldi {
|
||||
|
||||
DecodableMatrixMapped::DecodableMatrixMapped(
|
||||
const TransitionInformation &tm,
|
||||
const MatrixBase<BaseFloat> &likes,
|
||||
int32 frame_offset):
|
||||
trans_model_(tm),
|
||||
tid_to_pdf_(trans_model_.TransitionIdToPdfArray()),
|
||||
likes_(&likes), likes_to_delete_(NULL),
|
||||
frame_offset_(frame_offset) {
|
||||
stride_ = likes.Stride();
|
||||
raw_data_ = likes.Data() - (stride_ * frame_offset);
|
||||
|
||||
if (likes.NumCols() != tm.NumPdfs())
|
||||
KALDI_ERR << "Mismatch, matrix has "
|
||||
<< likes.NumCols() << " cols but transition-model has "
|
||||
<< tm.NumPdfs() << " pdf-ids.";
|
||||
}
|
||||
|
||||
DecodableMatrixMapped::DecodableMatrixMapped(
|
||||
const TransitionInformation &tm, const Matrix<BaseFloat> *likes,
|
||||
int32 frame_offset):
|
||||
trans_model_(tm),
|
||||
tid_to_pdf_(trans_model_.TransitionIdToPdfArray()),
|
||||
likes_(likes), likes_to_delete_(likes),
|
||||
frame_offset_(frame_offset) {
|
||||
stride_ = likes->Stride();
|
||||
raw_data_ = likes->Data() - (stride_ * frame_offset_);
|
||||
if (likes->NumCols() != tm.NumPdfs())
|
||||
KALDI_ERR << "Mismatch, matrix has "
|
||||
<< likes->NumCols() << " cols but transition-model has "
|
||||
<< tm.NumPdfs() << " pdf-ids.";
|
||||
}
|
||||
|
||||
|
||||
BaseFloat DecodableMatrixMapped::LogLikelihood(int32 frame, int32 tid) {
|
||||
KALDI_PARANOID_ASSERT(tid >= 1 && tid < tid_to_pdf_.size());
|
||||
int32 pdf_id = tid_to_pdf_[tid];
|
||||
#ifdef KALDI_PARANOID
|
||||
return (*likes_)(frame - frame_offset_, pdf_id);
|
||||
#else
|
||||
return raw_data_[frame * stride_ + pdf_id];
|
||||
#endif
|
||||
}
|
||||
|
||||
int32 DecodableMatrixMapped::NumFramesReady() const {
|
||||
return frame_offset_ + likes_->NumRows();
|
||||
}
|
||||
|
||||
bool DecodableMatrixMapped::IsLastFrame(int32 frame) const {
|
||||
KALDI_ASSERT(frame < NumFramesReady());
|
||||
return (frame == NumFramesReady() - 1);
|
||||
}
|
||||
|
||||
// Indices are one-based! This is for compatibility with OpenFst.
|
||||
int32 DecodableMatrixMapped::NumIndices() const {
|
||||
return trans_model_.NumTransitionIds();
|
||||
}
|
||||
|
||||
DecodableMatrixMapped::~DecodableMatrixMapped() {
|
||||
delete likes_to_delete_;
|
||||
}
|
||||
|
||||
|
||||
void DecodableMatrixMappedOffset::AcceptLoglikes(
|
||||
Matrix<BaseFloat> *loglikes, int32 frames_to_discard) {
|
||||
if (loglikes->NumRows() == 0) return;
|
||||
KALDI_ASSERT(loglikes->NumCols() == trans_model_.NumPdfs());
|
||||
KALDI_ASSERT(frames_to_discard <= loglikes_.NumRows() &&
|
||||
frames_to_discard >= 0);
|
||||
if (frames_to_discard == loglikes_.NumRows()) {
|
||||
loglikes_.Swap(loglikes);
|
||||
loglikes->Resize(0, 0);
|
||||
} else {
|
||||
int32 old_rows_kept = loglikes_.NumRows() - frames_to_discard,
|
||||
new_num_rows = old_rows_kept + loglikes->NumRows();
|
||||
Matrix<BaseFloat> new_loglikes(new_num_rows, loglikes->NumCols());
|
||||
new_loglikes.RowRange(0, old_rows_kept).CopyFromMat(
|
||||
loglikes_.RowRange(frames_to_discard, old_rows_kept));
|
||||
new_loglikes.RowRange(old_rows_kept, loglikes->NumRows()).CopyFromMat(
|
||||
*loglikes);
|
||||
loglikes_.Swap(&new_loglikes);
|
||||
}
|
||||
frame_offset_ += frames_to_discard;
|
||||
stride_ = loglikes_.Stride();
|
||||
raw_data_ = loglikes_.Data() - (frame_offset_ * stride_);
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // end namespace kaldi.
|
||||
@@ -0,0 +1,253 @@
|
||||
// decoder/decodable-matrix.h
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation
|
||||
// 2013 Johns Hopkins University (author: Daniel Povey)
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef KALDI_DECODER_DECODABLE_MATRIX_H_
|
||||
#define KALDI_DECODER_DECODABLE_MATRIX_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "base/kaldi-common.h"
|
||||
#include "itf/decodable-itf.h"
|
||||
#include "itf/transition-information.h"
|
||||
#include "matrix/kaldi-matrix.h"
|
||||
|
||||
namespace kaldi {
|
||||
|
||||
|
||||
class DecodableMatrixScaledMapped: public DecodableInterface {
|
||||
public:
|
||||
// This constructor creates an object that will not delete "likes" when done.
|
||||
DecodableMatrixScaledMapped(const TransitionInformation &tm,
|
||||
const Matrix<BaseFloat> &likes,
|
||||
BaseFloat scale): trans_model_(tm), likes_(&likes),
|
||||
tid_to_pdf_(trans_model_.TransitionIdToPdfArray()),
|
||||
scale_(scale), delete_likes_(false) {
|
||||
if (likes.NumCols() != tm.NumPdfs())
|
||||
KALDI_ERR << "DecodableMatrixScaledMapped: mismatch, matrix has "
|
||||
<< likes.NumCols() << " cols but transition-model has "
|
||||
<< tm.NumPdfs() << " pdf-ids.";
|
||||
}
|
||||
|
||||
// This constructor creates an object that will delete "likes"
|
||||
// when done.
|
||||
DecodableMatrixScaledMapped(const TransitionInformation &tm,
|
||||
BaseFloat scale,
|
||||
const Matrix<BaseFloat> *likes):
|
||||
trans_model_(tm), likes_(likes),
|
||||
tid_to_pdf_(trans_model_.TransitionIdToPdfArray()),
|
||||
scale_(scale), delete_likes_(true) {
|
||||
if (likes->NumCols() != tm.NumPdfs())
|
||||
KALDI_ERR << "DecodableMatrixScaledMapped: mismatch, matrix has "
|
||||
<< likes->NumCols() << " cols but transition-model has "
|
||||
<< tm.NumPdfs() << " pdf-ids.";
|
||||
}
|
||||
|
||||
virtual int32 NumFramesReady() const { return likes_->NumRows(); }
|
||||
|
||||
virtual bool IsLastFrame(int32 frame) const {
|
||||
KALDI_ASSERT(frame < NumFramesReady());
|
||||
return (frame == NumFramesReady() - 1);
|
||||
}
|
||||
|
||||
// Note, frames are numbered from zero.
|
||||
virtual BaseFloat LogLikelihood(int32 frame, int32 tid) {
|
||||
KALDI_PARANOID_ASSERT(tid >= 1 && tid < tid_to_pdf_.size());
|
||||
return scale_ * (*likes_)(frame, tid_to_pdf_[tid]);
|
||||
}
|
||||
|
||||
// Indices are one-based! This is for compatibility with OpenFst.
|
||||
virtual int32 NumIndices() const { return trans_model_.NumTransitionIds(); }
|
||||
|
||||
virtual ~DecodableMatrixScaledMapped() {
|
||||
if (delete_likes_) delete likes_;
|
||||
}
|
||||
private:
|
||||
const TransitionInformation &trans_model_; // for tid to pdf mapping
|
||||
const Matrix<BaseFloat> *likes_;
|
||||
const std::vector<int32> &tid_to_pdf_;
|
||||
BaseFloat scale_;
|
||||
bool delete_likes_;
|
||||
KALDI_DISALLOW_COPY_AND_ASSIGN(DecodableMatrixScaledMapped);
|
||||
};
|
||||
|
||||
/**
|
||||
This is like DecodableMatrixScaledMapped, but it doesn't support an acoustic
|
||||
scale, and it does support a frame offset, whereby you can state that the
|
||||
first row of 'likes' is actually the n'th row of the matrix of available
|
||||
log-likelihoods. It's useful if the neural net output comes in chunks for
|
||||
different frame ranges.
|
||||
|
||||
Note: DecodableMatrixMappedOffset solves the same problem in a slightly
|
||||
different way, where you use the same decodable object. This one, unlike
|
||||
DecodableMatrixMappedOffset, is compatible with when the loglikes are in a
|
||||
SubMatrix.
|
||||
*/
|
||||
class DecodableMatrixMapped: public DecodableInterface {
|
||||
public:
|
||||
// This constructor creates an object that will not delete "likes" when done.
|
||||
// the frame_offset is the frame the row 0 of 'likes' corresponds to, would be
|
||||
// greater than one if this is not the first chunk of likelihoods.
|
||||
DecodableMatrixMapped(const TransitionInformation &tm,
|
||||
const MatrixBase<BaseFloat> &likes,
|
||||
int32 frame_offset = 0);
|
||||
|
||||
// This constructor creates an object that will delete "likes"
|
||||
// when done.
|
||||
DecodableMatrixMapped(const TransitionInformation &tm,
|
||||
const Matrix<BaseFloat> *likes,
|
||||
int32 frame_offset = 0);
|
||||
|
||||
virtual int32 NumFramesReady() const;
|
||||
|
||||
virtual bool IsLastFrame(int32 frame) const;
|
||||
|
||||
virtual BaseFloat LogLikelihood(int32 frame, int32 tid);
|
||||
|
||||
// Note: these indices are 1-based.
|
||||
virtual int32 NumIndices() const;
|
||||
|
||||
virtual ~DecodableMatrixMapped();
|
||||
|
||||
private:
|
||||
const TransitionInformation &trans_model_; // for tid to pdf mapping
|
||||
const std::vector<int32>& tid_to_pdf_;
|
||||
const MatrixBase<BaseFloat> *likes_;
|
||||
const Matrix<BaseFloat> *likes_to_delete_;
|
||||
int32 frame_offset_;
|
||||
|
||||
// raw_data_ and stride_ are a kind of fast look-aside for 'likes_', to be
|
||||
// used when KALDI_PARANOID is false.
|
||||
const BaseFloat *raw_data_;
|
||||
int32 stride_;
|
||||
|
||||
KALDI_DISALLOW_COPY_AND_ASSIGN(DecodableMatrixMapped);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
This decodable class returns log-likes stored in a matrix; it supports
|
||||
repeatedly writing to the matrix and setting a time-offset representing the
|
||||
frame-index of the first row of the matrix. It's intended for use in
|
||||
multi-threaded decoding; mutex and semaphores are not included. External
|
||||
code will call SetLoglikes() each time more log-likelihods are available.
|
||||
If you try to access a log-likelihood that's no longer available because
|
||||
the frame index is less than the current offset, it is of course an error.
|
||||
|
||||
See also DecodableMatrixMapped, which supports the same type of thing but
|
||||
with a different interface where you are expected to re-construct the
|
||||
object each time you want to decode.
|
||||
*/
|
||||
class DecodableMatrixMappedOffset: public DecodableInterface {
|
||||
public:
|
||||
DecodableMatrixMappedOffset(const TransitionInformation &tm):
|
||||
trans_model_(tm), tid_to_pdf_(trans_model_.TransitionIdToPdfArray()),
|
||||
frame_offset_(0), input_is_finished_(false) { }
|
||||
|
||||
// this is not part of the generic Decodable interface.
|
||||
int32 FirstAvailableFrame() const { return frame_offset_; }
|
||||
|
||||
// Logically, this function appends 'loglikes' (interpreted as newly available
|
||||
// frames) to the log-likelihoods stored in the class.
|
||||
//
|
||||
// This function is destructive of the input "loglikes" because it may
|
||||
// under some circumstances do a shallow copy using Swap(). This function
|
||||
// appends loglikes to any existing likelihoods you've previously supplied.
|
||||
void AcceptLoglikes(Matrix<BaseFloat> *loglikes,
|
||||
int32 frames_to_discard);
|
||||
|
||||
void InputIsFinished() { input_is_finished_ = true; }
|
||||
|
||||
virtual int32 NumFramesReady() const {
|
||||
return loglikes_.NumRows() + frame_offset_;
|
||||
}
|
||||
|
||||
virtual bool IsLastFrame(int32 frame) const {
|
||||
KALDI_ASSERT(frame < NumFramesReady());
|
||||
return (frame == NumFramesReady() - 1 && input_is_finished_);
|
||||
}
|
||||
|
||||
virtual BaseFloat LogLikelihood(int32 frame, int32 tid) {
|
||||
KALDI_PARANOID_ASSERT(tid >= 1 && tid < tid_to_pdf_.size());
|
||||
int32 pdf_id = tid_to_pdf_[tid];
|
||||
#ifdef KALDI_PARANOID
|
||||
return loglikes_(frame - frame_offset_, pdf_id);
|
||||
#else
|
||||
// This does no checking, so will be faster.
|
||||
return raw_data_[frame * stride_ + pdf_id];
|
||||
#endif
|
||||
}
|
||||
|
||||
virtual int32 NumIndices() const { return trans_model_.NumTransitionIds(); }
|
||||
|
||||
// nothing special to do in destructor.
|
||||
virtual ~DecodableMatrixMappedOffset() { }
|
||||
private:
|
||||
const TransitionInformation &trans_model_; // for tid to pdf mapping
|
||||
const std::vector<int32>& tid_to_pdf_;
|
||||
Matrix<BaseFloat> loglikes_;
|
||||
int32 frame_offset_;
|
||||
bool input_is_finished_;
|
||||
|
||||
// 'raw_data_' and 'stride_' are intended as a fast look-aside which is an
|
||||
// alternative to accessing data_. raw_data_ is a faked version of
|
||||
// data_->Data() as if it started from frame zero rather than frame_offset_.
|
||||
// This simplifies the code of LogLikelihood(), in cases where KALDI_PARANOID
|
||||
// is not defined.
|
||||
BaseFloat *raw_data_;
|
||||
int32 stride_;
|
||||
|
||||
KALDI_DISALLOW_COPY_AND_ASSIGN(DecodableMatrixMappedOffset);
|
||||
};
|
||||
|
||||
|
||||
class DecodableMatrixScaled: public DecodableInterface {
|
||||
public:
|
||||
DecodableMatrixScaled(const Matrix<BaseFloat> &likes,
|
||||
BaseFloat scale):
|
||||
likes_(likes), scale_(scale) { }
|
||||
|
||||
virtual int32 NumFramesReady() const { return likes_.NumRows(); }
|
||||
|
||||
virtual bool IsLastFrame(int32 frame) const {
|
||||
KALDI_ASSERT(frame < NumFramesReady());
|
||||
return (frame == NumFramesReady() - 1);
|
||||
}
|
||||
|
||||
// Note, frames are numbered from zero.
|
||||
virtual BaseFloat LogLikelihood(int32 frame, int32 index) {
|
||||
if (index > likes_.NumCols() || index <= 0 ||
|
||||
frame < 0 || frame >= likes_.NumRows())
|
||||
KALDI_ERR << "Invalid (frame, index - 1) = ("
|
||||
<< frame << ", " << index - 1 << ") for matrix of size "
|
||||
<< likes_.NumRows() << " x " << likes_.NumCols();
|
||||
return scale_ * likes_(frame, index - 1);
|
||||
}
|
||||
|
||||
// Indices are one-based! This is for compatibility with OpenFst.
|
||||
virtual int32 NumIndices() const { return likes_.NumCols(); }
|
||||
|
||||
private:
|
||||
const Matrix<BaseFloat> &likes_;
|
||||
BaseFloat scale_;
|
||||
KALDI_DISALLOW_COPY_AND_ASSIGN(DecodableMatrixScaled);
|
||||
};
|
||||
} // namespace kaldi
|
||||
|
||||
#endif // KALDI_DECODER_DECODABLE_MATRIX_H_
|
||||
@@ -0,0 +1,109 @@
|
||||
// decoder/decodable-sum.h
|
||||
|
||||
// Copyright 2009-2011 Saarland University; Microsoft Corporation;
|
||||
// Lukas Burget, Pawel Swietojanski
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef KALDI_DECODER_DECODABLE_SUM_H_
|
||||
#define KALDI_DECODER_DECODABLE_SUM_H_
|
||||
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
|
||||
#include "base/kaldi-common.h"
|
||||
#include "itf/decodable-itf.h"
|
||||
|
||||
namespace kaldi {
|
||||
|
||||
// The DecodableSum object is a very simple object that just sums
|
||||
// scores over a number of Decodable objects. They must all have
|
||||
// the same dimensions.
|
||||
|
||||
class DecodableSum: public DecodableInterface {
|
||||
public:
|
||||
// Does not take ownership of pointers! They are just
|
||||
// pointers because they are non-const.
|
||||
DecodableSum(DecodableInterface *d1, BaseFloat w1,
|
||||
DecodableInterface *d2, BaseFloat w2) {
|
||||
decodables_.push_back(std::make_pair(d1, w1));
|
||||
decodables_.push_back(std::make_pair(d2, w2));
|
||||
CheckSizes();
|
||||
}
|
||||
|
||||
// Does not take ownership of pointers!
|
||||
DecodableSum(
|
||||
const std::vector<std::pair<DecodableInterface*, BaseFloat> > &decodables) :
|
||||
decodables_(decodables) { CheckSizes(); }
|
||||
|
||||
void CheckSizes() const {
|
||||
KALDI_ASSERT(decodables_.size() >= 1
|
||||
&& decodables_[0].first != NULL);
|
||||
for (size_t i = 1; i < decodables_.size(); i++)
|
||||
KALDI_ASSERT(decodables_[i].first != NULL &&
|
||||
decodables_[i].first->NumIndices() ==
|
||||
decodables_[0].first->NumIndices());
|
||||
}
|
||||
|
||||
// Note, frames are numbered from zero. But state_index is numbered
|
||||
// from one (this routine is called by FSTs).
|
||||
virtual BaseFloat LogLikelihood(int32 frame, int32 state_index) {
|
||||
BaseFloat sum = 0.0;
|
||||
// int32 i=1;
|
||||
for (std::vector<std::pair<DecodableInterface*, BaseFloat> >::iterator iter = decodables_.begin();
|
||||
iter != decodables_.end();
|
||||
++iter) {
|
||||
sum += iter->first->LogLikelihood(frame, state_index) * iter->second;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
virtual int32 NumIndices() const { return decodables_[0].first->NumIndices(); }
|
||||
|
||||
virtual bool IsLastFrame(int32 frame) const {
|
||||
// We require all the decodables have the same #frames. We don't check this though.
|
||||
return decodables_[0].first->IsLastFrame(frame);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::pair<DecodableInterface*, BaseFloat> > decodables_;
|
||||
KALDI_DISALLOW_COPY_AND_ASSIGN(DecodableSum);
|
||||
};
|
||||
|
||||
class DecodableSumScaled : public DecodableSum {
|
||||
public:
|
||||
DecodableSumScaled(DecodableInterface *d1, BaseFloat w1,
|
||||
DecodableInterface *d2, BaseFloat w2,
|
||||
BaseFloat scale)
|
||||
: DecodableSum(d1, w1, d2, w2), scale_(scale) {}
|
||||
|
||||
DecodableSumScaled(const std::vector<std::pair<DecodableInterface*, BaseFloat> > &decodables,
|
||||
BaseFloat scale)
|
||||
: DecodableSum(decodables), scale_(scale) {}
|
||||
|
||||
virtual BaseFloat LogLikelihood(int32 frame, int32 state_index) {
|
||||
return scale_ * DecodableSum::LogLikelihood(frame, state_index);
|
||||
}
|
||||
|
||||
private:
|
||||
BaseFloat scale_;
|
||||
KALDI_DISALLOW_COPY_AND_ASSIGN(DecodableSumScaled);
|
||||
};
|
||||
|
||||
} // namespace kaldi
|
||||
|
||||
#endif // KALDI_DECODER_DECODABLE_SUM_H_
|
||||
|
||||
@@ -0,0 +1,665 @@
|
||||
// decoder/decoder-wrappers.cc
|
||||
|
||||
// Copyright 2014 Johns Hopkins University (author: Daniel Povey)
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "decoder/decoder-wrappers.h"
|
||||
#include "decoder/faster-decoder.h"
|
||||
#include "decoder/lattice-faster-decoder.h"
|
||||
#include "decoder/grammar-fst.h"
|
||||
#include "lat/lattice-functions.h"
|
||||
|
||||
namespace kaldi {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
DecodeUtteranceLatticeFasterClass::DecodeUtteranceLatticeFasterClass(
|
||||
LatticeFasterDecoder *decoder,
|
||||
DecodableInterface *decodable,
|
||||
const TransitionInformation &trans_model,
|
||||
const fst::SymbolTable *word_syms,
|
||||
const std::string &utt,
|
||||
BaseFloat acoustic_scale,
|
||||
bool determinize,
|
||||
bool allow_partial,
|
||||
Int32VectorWriter *alignments_writer,
|
||||
Int32VectorWriter *words_writer,
|
||||
CompactLatticeWriter *compact_lattice_writer,
|
||||
LatticeWriter *lattice_writer,
|
||||
double *like_sum, // on success, adds likelihood to this.
|
||||
int64 *frame_sum, // on success, adds #frames to this.
|
||||
int32 *num_done, // on success (including partial decode), increments this.
|
||||
int32 *num_err, // on failure, increments this.
|
||||
int32 *num_partial): // If partial decode (final-state not reached), increments this.
|
||||
decoder_(decoder), decodable_(decodable), trans_model_(&trans_model),
|
||||
word_syms_(word_syms), utt_(utt), acoustic_scale_(acoustic_scale),
|
||||
determinize_(determinize), allow_partial_(allow_partial),
|
||||
alignments_writer_(alignments_writer),
|
||||
words_writer_(words_writer),
|
||||
compact_lattice_writer_(compact_lattice_writer),
|
||||
lattice_writer_(lattice_writer),
|
||||
like_sum_(like_sum), frame_sum_(frame_sum),
|
||||
num_done_(num_done), num_err_(num_err),
|
||||
num_partial_(num_partial),
|
||||
computed_(false), success_(false), partial_(false),
|
||||
clat_(NULL), lat_(NULL) { }
|
||||
|
||||
|
||||
void DecodeUtteranceLatticeFasterClass::operator () () {
|
||||
// Decoding and lattice determinization happens here.
|
||||
computed_ = true; // Just means this function was called-- a check on the
|
||||
// calling code.
|
||||
success_ = true;
|
||||
using fst::VectorFst;
|
||||
if (!decoder_->Decode(decodable_)) {
|
||||
KALDI_WARN << "Failed to decode utterance with id " << utt_;
|
||||
success_ = false;
|
||||
}
|
||||
if (!decoder_->ReachedFinal()) {
|
||||
if (allow_partial_) {
|
||||
KALDI_WARN << "Outputting partial output for utterance " << utt_
|
||||
<< " since no final-state reached\n";
|
||||
partial_ = true;
|
||||
} else {
|
||||
KALDI_WARN << "Not producing output for utterance " << utt_
|
||||
<< " since no final-state reached and "
|
||||
<< "--allow-partial=false.\n";
|
||||
success_ = false;
|
||||
}
|
||||
}
|
||||
if (!success_) return;
|
||||
|
||||
// Get lattice, and do determinization if requested.
|
||||
lat_ = new Lattice;
|
||||
decoder_->GetRawLattice(lat_);
|
||||
if (lat_->NumStates() == 0)
|
||||
KALDI_ERR << "Unexpected problem getting lattice for utterance " << utt_;
|
||||
fst::Connect(lat_);
|
||||
if (determinize_) {
|
||||
clat_ = new CompactLattice;
|
||||
if (!DeterminizeLatticePhonePrunedWrapper(
|
||||
*trans_model_,
|
||||
lat_,
|
||||
decoder_->GetOptions().lattice_beam,
|
||||
clat_,
|
||||
decoder_->GetOptions().det_opts))
|
||||
KALDI_WARN << "Determinization finished earlier than the beam for "
|
||||
<< "utterance " << utt_;
|
||||
delete lat_;
|
||||
lat_ = NULL;
|
||||
// We'll write the lattice without acoustic scaling.
|
||||
if (acoustic_scale_ != 0.0)
|
||||
fst::ScaleLattice(fst::AcousticLatticeScale(1.0 / acoustic_scale_), clat_);
|
||||
} else {
|
||||
// We'll write the lattice without acoustic scaling.
|
||||
if (acoustic_scale_ != 0.0)
|
||||
fst::ScaleLattice(fst::AcousticLatticeScale(1.0 / acoustic_scale_), lat_);
|
||||
}
|
||||
}
|
||||
|
||||
DecodeUtteranceLatticeFasterClass::~DecodeUtteranceLatticeFasterClass() {
|
||||
if (!computed_)
|
||||
KALDI_ERR << "Destructor called without operator (), error in calling code.";
|
||||
|
||||
if (!success_) {
|
||||
if (num_err_ != NULL) (*num_err_)++;
|
||||
} else { // successful decode.
|
||||
// Getting the one-best output is lightweight enough that we can do it in
|
||||
// the destructor (easier than adding more variables to the class, and
|
||||
// will rarely slow down the main thread.)
|
||||
double likelihood;
|
||||
LatticeWeight weight;
|
||||
int32 num_frames;
|
||||
{ // First do some stuff with word-level traceback...
|
||||
// This is basically for diagnostics.
|
||||
fst::VectorFst<LatticeArc> decoded;
|
||||
decoder_->GetBestPath(&decoded);
|
||||
if (decoded.NumStates() == 0) {
|
||||
// Shouldn't really reach this point as already checked success.
|
||||
KALDI_ERR << "Failed to get traceback for utterance " << utt_;
|
||||
}
|
||||
std::vector<int32> alignment;
|
||||
std::vector<int32> words;
|
||||
GetLinearSymbolSequence(decoded, &alignment, &words, &weight);
|
||||
num_frames = alignment.size();
|
||||
if (words_writer_->IsOpen())
|
||||
words_writer_->Write(utt_, words);
|
||||
if (alignments_writer_->IsOpen())
|
||||
alignments_writer_->Write(utt_, alignment);
|
||||
if (word_syms_ != NULL) {
|
||||
std::cerr << utt_ << ' ';
|
||||
for (size_t i = 0; i < words.size(); i++) {
|
||||
std::string s = word_syms_->Find(words[i]);
|
||||
if (s == "")
|
||||
KALDI_ERR << "Word-id " << words[i] << " not in symbol table.";
|
||||
std::cerr << s << ' ';
|
||||
}
|
||||
std::cerr << '\n';
|
||||
}
|
||||
likelihood = -(weight.Value1() + weight.Value2());
|
||||
}
|
||||
|
||||
// Ouptut the lattices.
|
||||
if (determinize_) { // CompactLattice output.
|
||||
KALDI_ASSERT(compact_lattice_writer_ != NULL && clat_ != NULL);
|
||||
if (clat_->NumStates() == 0) {
|
||||
KALDI_WARN << "Empty lattice for utterance " << utt_;
|
||||
} else {
|
||||
compact_lattice_writer_->Write(utt_, *clat_);
|
||||
}
|
||||
delete clat_;
|
||||
clat_ = NULL;
|
||||
} else {
|
||||
KALDI_ASSERT(lattice_writer_ != NULL && lat_ != NULL);
|
||||
if (lat_->NumStates() == 0) {
|
||||
KALDI_WARN << "Empty lattice for utterance " << utt_;
|
||||
} else {
|
||||
lattice_writer_->Write(utt_, *lat_);
|
||||
}
|
||||
delete lat_;
|
||||
lat_ = NULL;
|
||||
}
|
||||
|
||||
// Print out logging information.
|
||||
KALDI_LOG << "Log-like per frame for utterance " << utt_ << " is "
|
||||
<< (likelihood / num_frames) << " over "
|
||||
<< num_frames << " frames.";
|
||||
KALDI_VLOG(2) << "Cost for utterance " << utt_ << " is "
|
||||
<< weight.Value1() << " + " << weight.Value2();
|
||||
|
||||
// Now output the various diagnostic variables.
|
||||
if (like_sum_ != NULL) *like_sum_ += likelihood;
|
||||
if (frame_sum_ != NULL) *frame_sum_ += num_frames;
|
||||
if (num_done_ != NULL) (*num_done_)++;
|
||||
if (partial_ && num_partial_ != NULL) (*num_partial_)++;
|
||||
}
|
||||
// We were given ownership of these two objects that were passed in in
|
||||
// the initializer.
|
||||
delete decoder_;
|
||||
delete decodable_;
|
||||
}
|
||||
|
||||
template <typename FST>
|
||||
bool DecodeUtteranceLatticeIncremental(
|
||||
LatticeIncrementalDecoderTpl<FST> &decoder, // not const but is really an input.
|
||||
DecodableInterface &decodable, // not const but is really an input.
|
||||
const TransitionInformation &trans_model,
|
||||
const fst::SymbolTable *word_syms,
|
||||
std::string utt,
|
||||
double acoustic_scale,
|
||||
bool determinize,
|
||||
bool allow_partial,
|
||||
Int32VectorWriter *alignment_writer,
|
||||
Int32VectorWriter *words_writer,
|
||||
CompactLatticeWriter *compact_lattice_writer,
|
||||
LatticeWriter *lattice_writer,
|
||||
double *like_ptr) { // puts utterance's like in like_ptr on success.
|
||||
using fst::VectorFst;
|
||||
if (!decoder.Decode(&decodable)) {
|
||||
KALDI_WARN << "Failed to decode utterance with id " << utt;
|
||||
return false;
|
||||
}
|
||||
if (!decoder.ReachedFinal()) {
|
||||
if (allow_partial) {
|
||||
KALDI_WARN << "Outputting partial output for utterance " << utt
|
||||
<< " since no final-state reached\n";
|
||||
} else {
|
||||
KALDI_WARN << "Not producing output for utterance " << utt
|
||||
<< " since no final-state reached and "
|
||||
<< "--allow-partial=false.\n";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Get lattice
|
||||
CompactLattice clat = decoder.GetLattice(decoder.NumFramesDecoded(), true);
|
||||
if (clat.NumStates() == 0)
|
||||
KALDI_ERR << "Unexpected problem getting lattice for utterance " << utt;
|
||||
|
||||
double likelihood;
|
||||
LatticeWeight weight;
|
||||
int32 num_frames;
|
||||
{ // First do some stuff with word-level traceback...
|
||||
CompactLattice decoded_clat;
|
||||
CompactLatticeShortestPath(clat, &decoded_clat);
|
||||
Lattice decoded;
|
||||
fst::ConvertLattice(decoded_clat, &decoded);
|
||||
|
||||
if (decoded.Start() == fst::kNoStateId)
|
||||
// Shouldn't really reach this point as already checked success.
|
||||
KALDI_ERR << "Failed to get traceback for utterance " << utt;
|
||||
|
||||
std::vector<int32> alignment;
|
||||
std::vector<int32> words;
|
||||
GetLinearSymbolSequence(decoded, &alignment, &words, &weight);
|
||||
num_frames = alignment.size();
|
||||
KALDI_ASSERT(num_frames == decoder.NumFramesDecoded());
|
||||
if (words_writer->IsOpen())
|
||||
words_writer->Write(utt, words);
|
||||
if (alignment_writer->IsOpen())
|
||||
alignment_writer->Write(utt, alignment);
|
||||
if (word_syms != NULL) {
|
||||
std::cerr << utt << ' ';
|
||||
for (size_t i = 0; i < words.size(); i++) {
|
||||
std::string s = word_syms->Find(words[i]);
|
||||
if (s == "")
|
||||
KALDI_ERR << "Word-id " << words[i] << " not in symbol table.";
|
||||
std::cerr << s << ' ';
|
||||
}
|
||||
std::cerr << '\n';
|
||||
}
|
||||
likelihood = -(weight.Value1() + weight.Value2());
|
||||
}
|
||||
|
||||
// We'll write the lattice without acoustic scaling.
|
||||
if (acoustic_scale != 0.0)
|
||||
fst::ScaleLattice(fst::AcousticLatticeScale(1.0 / acoustic_scale), &clat);
|
||||
Connect(&clat);
|
||||
compact_lattice_writer->Write(utt, clat);
|
||||
KALDI_LOG << "Log-like per frame for utterance " << utt << " is "
|
||||
<< (likelihood / num_frames) << " over "
|
||||
<< num_frames << " frames.";
|
||||
KALDI_VLOG(2) << "Cost for utterance " << utt << " is "
|
||||
<< weight.Value1() << " + " << weight.Value2();
|
||||
*like_ptr = likelihood;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Takes care of output. Returns true on success.
|
||||
template <typename FST>
|
||||
bool DecodeUtteranceLatticeFaster(
|
||||
LatticeFasterDecoderTpl<FST> &decoder, // not const but is really an input.
|
||||
DecodableInterface &decodable, // not const but is really an input.
|
||||
const TransitionInformation &trans_model,
|
||||
const fst::SymbolTable *word_syms,
|
||||
std::string utt,
|
||||
double acoustic_scale,
|
||||
bool determinize,
|
||||
bool allow_partial,
|
||||
Int32VectorWriter *alignment_writer,
|
||||
Int32VectorWriter *words_writer,
|
||||
CompactLatticeWriter *compact_lattice_writer,
|
||||
LatticeWriter *lattice_writer,
|
||||
double *like_ptr) { // puts utterance's like in like_ptr on success.
|
||||
using fst::VectorFst;
|
||||
|
||||
if (!decoder.Decode(&decodable)) {
|
||||
KALDI_WARN << "Failed to decode utterance with id " << utt;
|
||||
return false;
|
||||
}
|
||||
if (!decoder.ReachedFinal()) {
|
||||
if (allow_partial) {
|
||||
KALDI_WARN << "Outputting partial output for utterance " << utt
|
||||
<< " since no final-state reached\n";
|
||||
} else {
|
||||
KALDI_WARN << "Not producing output for utterance " << utt
|
||||
<< " since no final-state reached and "
|
||||
<< "--allow-partial=false.\n";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
double likelihood;
|
||||
LatticeWeight weight;
|
||||
int32 num_frames;
|
||||
{ // First do some stuff with word-level traceback...
|
||||
VectorFst<LatticeArc> decoded;
|
||||
if (!decoder.GetBestPath(&decoded))
|
||||
// Shouldn't really reach this point as already checked success.
|
||||
KALDI_ERR << "Failed to get traceback for utterance " << utt;
|
||||
|
||||
std::vector<int32> alignment;
|
||||
std::vector<int32> words;
|
||||
GetLinearSymbolSequence(decoded, &alignment, &words, &weight);
|
||||
num_frames = alignment.size();
|
||||
if (words_writer->IsOpen())
|
||||
words_writer->Write(utt, words);
|
||||
if (alignment_writer->IsOpen())
|
||||
alignment_writer->Write(utt, alignment);
|
||||
if (word_syms != NULL) {
|
||||
std::cerr << utt << ' ';
|
||||
for (size_t i = 0; i < words.size(); i++) {
|
||||
std::string s = word_syms->Find(words[i]);
|
||||
if (s == "")
|
||||
KALDI_ERR << "Word-id " << words[i] << " not in symbol table.";
|
||||
std::cerr << s << ' ';
|
||||
}
|
||||
std::cerr << '\n';
|
||||
}
|
||||
likelihood = -(weight.Value1() + weight.Value2());
|
||||
}
|
||||
|
||||
// Get lattice, and do determinization if requested.
|
||||
Lattice lat;
|
||||
decoder.GetRawLattice(&lat);
|
||||
if (lat.NumStates() == 0)
|
||||
KALDI_ERR << "Unexpected problem getting lattice for utterance " << utt;
|
||||
fst::Connect(&lat);
|
||||
if (determinize) {
|
||||
CompactLattice clat;
|
||||
if (!DeterminizeLatticePhonePrunedWrapper(
|
||||
trans_model,
|
||||
&lat,
|
||||
decoder.GetOptions().lattice_beam,
|
||||
&clat,
|
||||
decoder.GetOptions().det_opts))
|
||||
KALDI_WARN << "Determinization finished earlier than the beam for "
|
||||
<< "utterance " << utt;
|
||||
// We'll write the lattice without acoustic scaling.
|
||||
if (acoustic_scale != 0.0)
|
||||
fst::ScaleLattice(fst::AcousticLatticeScale(1.0 / acoustic_scale), &clat);
|
||||
compact_lattice_writer->Write(utt, clat);
|
||||
} else {
|
||||
// We'll write the lattice without acoustic scaling.
|
||||
if (acoustic_scale != 0.0)
|
||||
fst::ScaleLattice(fst::AcousticLatticeScale(1.0 / acoustic_scale), &lat);
|
||||
lattice_writer->Write(utt, lat);
|
||||
}
|
||||
KALDI_LOG << "Log-like per frame for utterance " << utt << " is "
|
||||
<< (likelihood / num_frames) << " over "
|
||||
<< num_frames << " frames.";
|
||||
KALDI_VLOG(2) << "Cost for utterance " << utt << " is "
|
||||
<< weight.Value1() << " + " << weight.Value2();
|
||||
*like_ptr = likelihood;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Instantiate the template above for the two required FST types.
|
||||
template bool DecodeUtteranceLatticeIncremental(
|
||||
LatticeIncrementalDecoderTpl<fst::Fst<fst::StdArc> > &decoder,
|
||||
DecodableInterface &decodable,
|
||||
const TransitionInformation &trans_model,
|
||||
const fst::SymbolTable *word_syms,
|
||||
std::string utt,
|
||||
double acoustic_scale,
|
||||
bool determinize,
|
||||
bool allow_partial,
|
||||
Int32VectorWriter *alignment_writer,
|
||||
Int32VectorWriter *words_writer,
|
||||
CompactLatticeWriter *compact_lattice_writer,
|
||||
LatticeWriter *lattice_writer,
|
||||
double *like_ptr);
|
||||
|
||||
template bool DecodeUtteranceLatticeIncremental(
|
||||
LatticeIncrementalDecoderTpl<fst::ConstGrammarFst > &decoder,
|
||||
DecodableInterface &decodable,
|
||||
const TransitionInformation &trans_model,
|
||||
const fst::SymbolTable *word_syms,
|
||||
std::string utt,
|
||||
double acoustic_scale,
|
||||
bool determinize,
|
||||
bool allow_partial,
|
||||
Int32VectorWriter *alignment_writer,
|
||||
Int32VectorWriter *words_writer,
|
||||
CompactLatticeWriter *compact_lattice_writer,
|
||||
LatticeWriter *lattice_writer,
|
||||
double *like_ptr);
|
||||
|
||||
template bool DecodeUtteranceLatticeFaster(
|
||||
LatticeFasterDecoderTpl<fst::Fst<fst::StdArc> > &decoder,
|
||||
DecodableInterface &decodable,
|
||||
const TransitionInformation &trans_model,
|
||||
const fst::SymbolTable *word_syms,
|
||||
std::string utt,
|
||||
double acoustic_scale,
|
||||
bool determinize,
|
||||
bool allow_partial,
|
||||
Int32VectorWriter *alignment_writer,
|
||||
Int32VectorWriter *words_writer,
|
||||
CompactLatticeWriter *compact_lattice_writer,
|
||||
LatticeWriter *lattice_writer,
|
||||
double *like_ptr);
|
||||
|
||||
template bool DecodeUtteranceLatticeFaster(
|
||||
LatticeFasterDecoderTpl<fst::ConstGrammarFst > &decoder,
|
||||
DecodableInterface &decodable,
|
||||
const TransitionInformation &trans_model,
|
||||
const fst::SymbolTable *word_syms,
|
||||
std::string utt,
|
||||
double acoustic_scale,
|
||||
bool determinize,
|
||||
bool allow_partial,
|
||||
Int32VectorWriter *alignment_writer,
|
||||
Int32VectorWriter *words_writer,
|
||||
CompactLatticeWriter *compact_lattice_writer,
|
||||
LatticeWriter *lattice_writer,
|
||||
double *like_ptr);
|
||||
|
||||
|
||||
// Takes care of output. Returns true on success.
|
||||
bool DecodeUtteranceLatticeSimple(
|
||||
LatticeSimpleDecoder &decoder, // not const but is really an input.
|
||||
DecodableInterface &decodable, // not const but is really an input.
|
||||
const TransitionInformation &trans_model,
|
||||
const fst::SymbolTable *word_syms,
|
||||
std::string utt,
|
||||
double acoustic_scale,
|
||||
bool determinize,
|
||||
bool allow_partial,
|
||||
Int32VectorWriter *alignment_writer,
|
||||
Int32VectorWriter *words_writer,
|
||||
CompactLatticeWriter *compact_lattice_writer,
|
||||
LatticeWriter *lattice_writer,
|
||||
double *like_ptr) { // puts utterance's like in like_ptr on success.
|
||||
using fst::VectorFst;
|
||||
|
||||
if (!decoder.Decode(&decodable)) {
|
||||
KALDI_WARN << "Failed to decode utterance with id " << utt;
|
||||
return false;
|
||||
}
|
||||
if (!decoder.ReachedFinal()) {
|
||||
if (allow_partial) {
|
||||
KALDI_WARN << "Outputting partial output for utterance " << utt
|
||||
<< " since no final-state reached\n";
|
||||
} else {
|
||||
KALDI_WARN << "Not producing output for utterance " << utt
|
||||
<< " since no final-state reached and "
|
||||
<< "--allow-partial=false.\n";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
double likelihood;
|
||||
LatticeWeight weight = LatticeWeight::Zero();
|
||||
int32 num_frames;
|
||||
{ // First do some stuff with word-level traceback...
|
||||
VectorFst<LatticeArc> decoded;
|
||||
if (!decoder.GetBestPath(&decoded))
|
||||
// Shouldn't really reach this point as already checked success.
|
||||
KALDI_ERR << "Failed to get traceback for utterance " << utt;
|
||||
|
||||
std::vector<int32> alignment;
|
||||
std::vector<int32> words;
|
||||
GetLinearSymbolSequence(decoded, &alignment, &words, &weight);
|
||||
num_frames = alignment.size();
|
||||
if (words_writer->IsOpen())
|
||||
words_writer->Write(utt, words);
|
||||
if (alignment_writer->IsOpen())
|
||||
alignment_writer->Write(utt, alignment);
|
||||
if (word_syms != NULL) {
|
||||
std::cerr << utt << ' ';
|
||||
for (size_t i = 0; i < words.size(); i++) {
|
||||
std::string s = word_syms->Find(words[i]);
|
||||
if (s == "")
|
||||
KALDI_ERR << "Word-id " << words[i] << " not in symbol table.";
|
||||
std::cerr << s << ' ';
|
||||
}
|
||||
std::cerr << '\n';
|
||||
}
|
||||
likelihood = -(weight.Value1() + weight.Value2());
|
||||
}
|
||||
|
||||
// Get lattice, and do determinization if requested.
|
||||
Lattice lat;
|
||||
if (!decoder.GetRawLattice(&lat))
|
||||
KALDI_ERR << "Unexpected problem getting lattice for utterance " << utt;
|
||||
fst::Connect(&lat);
|
||||
if (determinize) {
|
||||
CompactLattice clat;
|
||||
if (!DeterminizeLatticePhonePrunedWrapper(
|
||||
trans_model,
|
||||
&lat,
|
||||
decoder.GetOptions().lattice_beam,
|
||||
&clat,
|
||||
decoder.GetOptions().det_opts))
|
||||
KALDI_WARN << "Determinization finished earlier than the beam for "
|
||||
<< "utterance " << utt;
|
||||
// We'll write the lattice without acoustic scaling.
|
||||
if (acoustic_scale != 0.0)
|
||||
fst::ScaleLattice(fst::AcousticLatticeScale(1.0 / acoustic_scale), &clat);
|
||||
compact_lattice_writer->Write(utt, clat);
|
||||
} else {
|
||||
// We'll write the lattice without acoustic scaling.
|
||||
if (acoustic_scale != 0.0)
|
||||
fst::ScaleLattice(fst::AcousticLatticeScale(1.0 / acoustic_scale), &lat);
|
||||
lattice_writer->Write(utt, lat);
|
||||
}
|
||||
KALDI_LOG << "Log-like per frame for utterance " << utt << " is "
|
||||
<< (likelihood / num_frames) << " over "
|
||||
<< num_frames << " frames.";
|
||||
KALDI_VLOG(2) << "Cost for utterance " << utt << " is "
|
||||
<< weight.Value1() << " + " << weight.Value2();
|
||||
*like_ptr = likelihood;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// see comment in header.
|
||||
void ModifyGraphForCarefulAlignment(
|
||||
fst::VectorFst<fst::StdArc> *fst) {
|
||||
typedef fst::StdArc Arc;
|
||||
typedef Arc::StateId StateId;
|
||||
typedef Arc::Label Label;
|
||||
typedef Arc::Weight Weight;
|
||||
StateId num_states = fst->NumStates();
|
||||
if (num_states == 0) {
|
||||
KALDI_WARN << "Empty FST input.";
|
||||
return;
|
||||
}
|
||||
Weight zero = Weight::Zero();
|
||||
// fst_rhs will be the right hand side of the Concat operation.
|
||||
fst::VectorFst<fst::StdArc> fst_rhs(*fst);
|
||||
// first remove the final-probs from fst_rhs.
|
||||
for (StateId state = 0; state < num_states; state++)
|
||||
fst_rhs.SetFinal(state, zero);
|
||||
StateId pre_initial = fst_rhs.AddState();
|
||||
Arc to_initial(0, 0, Weight::One(), fst_rhs.Start());
|
||||
fst_rhs.AddArc(pre_initial, to_initial);
|
||||
fst_rhs.SetStart(pre_initial);
|
||||
// make the pre_initial state final with probability one;
|
||||
// this is equivalent to keeping the final-probs of the first
|
||||
// FST when we do concat (otherwise they would get deleted).
|
||||
fst_rhs.SetFinal(pre_initial, Weight::One());
|
||||
fst::VectorFst<fst::StdArc> fst_concat;
|
||||
fst::Concat(fst, fst_rhs);
|
||||
}
|
||||
|
||||
|
||||
void AlignUtteranceWrapper(
|
||||
const AlignConfig &config,
|
||||
const std::string &utt,
|
||||
BaseFloat acoustic_scale, // affects scores written to scores_writer, if
|
||||
// present
|
||||
fst::VectorFst<fst::StdArc> *fst, // non-const in case config.careful ==
|
||||
// true.
|
||||
DecodableInterface *decodable, // not const but is really an input.
|
||||
Int32VectorWriter *alignment_writer,
|
||||
BaseFloatWriter *scores_writer,
|
||||
int32 *num_done,
|
||||
int32 *num_error,
|
||||
int32 *num_retried,
|
||||
double *tot_like,
|
||||
int64 *frame_count,
|
||||
BaseFloatVectorWriter *per_frame_acwt_writer) {
|
||||
|
||||
if ((config.retry_beam != 0 && config.retry_beam <= config.beam) ||
|
||||
config.beam <= 0.0) {
|
||||
KALDI_ERR << "Beams do not make sense: beam " << config.beam
|
||||
<< ", retry-beam " << config.retry_beam;
|
||||
}
|
||||
|
||||
if (fst->Start() == fst::kNoStateId) {
|
||||
KALDI_WARN << "Empty decoding graph for " << utt;
|
||||
if (num_error != NULL) (*num_error)++;
|
||||
return;
|
||||
}
|
||||
|
||||
if (config.careful)
|
||||
ModifyGraphForCarefulAlignment(fst);
|
||||
|
||||
FasterDecoderOptions decode_opts;
|
||||
decode_opts.beam = config.beam;
|
||||
|
||||
FasterDecoder decoder(*fst, decode_opts);
|
||||
decoder.Decode(decodable);
|
||||
|
||||
bool ans = decoder.ReachedFinal(); // consider only final states.
|
||||
|
||||
if (!ans && config.retry_beam != 0.0) {
|
||||
if (num_retried != NULL) (*num_retried)++;
|
||||
KALDI_WARN << "Retrying utterance " << utt << " with beam "
|
||||
<< config.retry_beam;
|
||||
decode_opts.beam = config.retry_beam;
|
||||
decoder.SetOptions(decode_opts);
|
||||
decoder.Decode(decodable);
|
||||
ans = decoder.ReachedFinal();
|
||||
}
|
||||
|
||||
if (!ans) { // Still did not reach final state.
|
||||
KALDI_WARN << "Did not successfully decode file " << utt << ", len = "
|
||||
<< decodable->NumFramesReady();
|
||||
if (num_error != NULL) (*num_error)++;
|
||||
return;
|
||||
}
|
||||
|
||||
fst::VectorFst<LatticeArc> decoded; // linear FST.
|
||||
decoder.GetBestPath(&decoded);
|
||||
if (decoded.NumStates() == 0) {
|
||||
KALDI_WARN << "Error getting best path from decoder (likely a bug)";
|
||||
if (num_error != NULL) (*num_error)++;
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<int32> alignment;
|
||||
std::vector<int32> words;
|
||||
LatticeWeight weight;
|
||||
|
||||
GetLinearSymbolSequence(decoded, &alignment, &words, &weight);
|
||||
BaseFloat like = -(weight.Value1()+weight.Value2()) / acoustic_scale;
|
||||
|
||||
if (num_done != NULL) (*num_done)++;
|
||||
if (tot_like != NULL) (*tot_like) += like;
|
||||
if (frame_count != NULL) (*frame_count) += decodable->NumFramesReady();
|
||||
|
||||
if (alignment_writer != NULL && alignment_writer->IsOpen())
|
||||
alignment_writer->Write(utt, alignment);
|
||||
|
||||
if (scores_writer != NULL && scores_writer->IsOpen())
|
||||
scores_writer->Write(utt, -(weight.Value1()+weight.Value2()));
|
||||
|
||||
Vector<BaseFloat> per_frame_loglikes;
|
||||
if (per_frame_acwt_writer != NULL && per_frame_acwt_writer->IsOpen()) {
|
||||
GetPerFrameAcousticCosts(decoded, &per_frame_loglikes);
|
||||
per_frame_loglikes.Scale(-1 / acoustic_scale);
|
||||
per_frame_acwt_writer->Write(utt, per_frame_loglikes);
|
||||
}
|
||||
}
|
||||
|
||||
} // end namespace kaldi.
|
||||
@@ -0,0 +1,221 @@
|
||||
// decoder/decoder-wrappers.h
|
||||
|
||||
// Copyright 2014 Johns Hopkins University (author: Daniel Povey)
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef KALDI_DECODER_DECODER_WRAPPERS_H_
|
||||
#define KALDI_DECODER_DECODER_WRAPPERS_H_
|
||||
|
||||
#include "itf/options-itf.h"
|
||||
#include "decoder/lattice-faster-decoder.h"
|
||||
#include "decoder/lattice-incremental-decoder.h"
|
||||
#include "decoder/lattice-simple-decoder.h"
|
||||
|
||||
// This header contains declarations from various convenience functions that are called
|
||||
// from binary-level programs such as gmm-decode-faster.cc, gmm-align-compiled.cc, and
|
||||
// so on.
|
||||
|
||||
namespace kaldi {
|
||||
|
||||
|
||||
struct AlignConfig {
|
||||
BaseFloat beam;
|
||||
BaseFloat retry_beam;
|
||||
bool careful;
|
||||
|
||||
AlignConfig(): beam(200.0), retry_beam(0.0), careful(false) { }
|
||||
|
||||
void Register(OptionsItf *opts) {
|
||||
opts->Register("beam", &beam, "Decoding beam used in alignment");
|
||||
opts->Register("retry-beam", &retry_beam,
|
||||
"Decoding beam for second try at alignment");
|
||||
opts->Register("careful", &careful,
|
||||
"If true, do 'careful' alignment, which is better at detecting "
|
||||
"alignment failure (involves loop to start of decoding graph).");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/// AlignUtteranceWapper is a wrapper for alignment code used in training, that
|
||||
/// is called from many different binaries, e.g. gmm-align, gmm-align-compiled,
|
||||
/// sgmm-align, etc. The writers for alignments and words will only be written
|
||||
/// to if they are open. The num_done, num_error, num_retried, tot_like and
|
||||
/// frame_count pointers will (if non-NULL) be incremented or added to, not set,
|
||||
/// by this function.
|
||||
void AlignUtteranceWrapper(
|
||||
const AlignConfig &config,
|
||||
const std::string &utt,
|
||||
BaseFloat acoustic_scale, // affects scores written to scores_writer, if
|
||||
// present
|
||||
fst::VectorFst<fst::StdArc> *fst, // non-const in case config.careful ==
|
||||
// true, we add loop.
|
||||
DecodableInterface *decodable, // not const but is really an input.
|
||||
Int32VectorWriter *alignment_writer,
|
||||
BaseFloatWriter *scores_writer,
|
||||
int32 *num_done,
|
||||
int32 *num_error,
|
||||
int32 *num_retried,
|
||||
double *tot_like,
|
||||
int64 *frame_count,
|
||||
BaseFloatVectorWriter *per_frame_acwt_writer = NULL);
|
||||
|
||||
|
||||
|
||||
/// This function modifies the decoding graph for what we call "careful
|
||||
/// alignment". The problem we are trying to solve is that if the decoding eats
|
||||
/// up the words in the graph too fast, it can get stuck at the end, and produce
|
||||
/// what looks like a valid alignment even though there was really a failure.
|
||||
/// So what we want to do is to introduce, after the final-states of the graph,
|
||||
/// a "blind alley" with no final-probs reachable, where the decoding can go to
|
||||
/// get lost. Our basic idea is to append the decoding-graph to itself using
|
||||
/// the fst Concat operation; but in order that there should be final-probs at the end of
|
||||
/// the first but not the second FST, we modify the right-hand argument to the
|
||||
/// Concat operation so that it has none of the original final-probs, and add
|
||||
/// a "pre-initial" state that is final.
|
||||
void ModifyGraphForCarefulAlignment(
|
||||
fst::VectorFst<fst::StdArc> *fst);
|
||||
|
||||
/// TODO
|
||||
template <typename FST>
|
||||
bool DecodeUtteranceLatticeIncremental(
|
||||
LatticeIncrementalDecoderTpl<FST> &decoder, // not const but is really an input.
|
||||
DecodableInterface &decodable, // not const but is really an input.
|
||||
const TransitionInformation &trans_model,
|
||||
const fst::SymbolTable *word_syms,
|
||||
std::string utt,
|
||||
double acoustic_scale,
|
||||
bool determinize,
|
||||
bool allow_partial,
|
||||
Int32VectorWriter *alignments_writer,
|
||||
Int32VectorWriter *words_writer,
|
||||
CompactLatticeWriter *compact_lattice_writer,
|
||||
LatticeWriter *lattice_writer,
|
||||
double *like_ptr); // puts utterance's likelihood in like_ptr on success.
|
||||
|
||||
|
||||
/// This function DecodeUtteranceLatticeFaster is used in several decoders, and
|
||||
/// we have moved it here. Note: this is really "binary-level" code as it
|
||||
/// involves table readers and writers; we've just put it here as there is no
|
||||
/// other obvious place to put it. If determinize == false, it writes to
|
||||
/// lattice_writer, else to compact_lattice_writer. The writers for
|
||||
/// alignments and words will only be written to if they are open.
|
||||
///
|
||||
/// Caution: this will only link correctly if FST is either fst::Fst<fst::StdArc>,
|
||||
/// or fst::GrammarFst, as the template function is defined in the .cc file and
|
||||
/// only instantiated for those two types.
|
||||
template <typename FST>
|
||||
bool DecodeUtteranceLatticeFaster(
|
||||
LatticeFasterDecoderTpl<FST> &decoder, // not const but is really an input.
|
||||
DecodableInterface &decodable, // not const but is really an input.
|
||||
const TransitionInformation &trans_model,
|
||||
const fst::SymbolTable *word_syms,
|
||||
std::string utt,
|
||||
double acoustic_scale,
|
||||
bool determinize,
|
||||
bool allow_partial,
|
||||
Int32VectorWriter *alignments_writer,
|
||||
Int32VectorWriter *words_writer,
|
||||
CompactLatticeWriter *compact_lattice_writer,
|
||||
LatticeWriter *lattice_writer,
|
||||
double *like_ptr); // puts utterance's likelihood in like_ptr on success.
|
||||
|
||||
|
||||
/// This class basically does the same job as the function
|
||||
/// DecodeUtteranceLatticeFaster, but in a way that allows us
|
||||
/// to build a multi-threaded command line program more easily.
|
||||
/// The main computation takes place in operator (), and the output
|
||||
/// happens in the destructor.
|
||||
class DecodeUtteranceLatticeFasterClass {
|
||||
public:
|
||||
// Initializer sets various variables.
|
||||
// NOTE: we "take ownership" of "decoder" and "decodable". These
|
||||
// are deleted by the destructor. On error, "num_err" is incremented.
|
||||
DecodeUtteranceLatticeFasterClass(
|
||||
LatticeFasterDecoder *decoder,
|
||||
DecodableInterface *decodable,
|
||||
const TransitionInformation &trans_model,
|
||||
const fst::SymbolTable *word_syms,
|
||||
const std::string &utt,
|
||||
BaseFloat acoustic_scale,
|
||||
bool determinize,
|
||||
bool allow_partial,
|
||||
Int32VectorWriter *alignments_writer,
|
||||
Int32VectorWriter *words_writer,
|
||||
CompactLatticeWriter *compact_lattice_writer,
|
||||
LatticeWriter *lattice_writer,
|
||||
double *like_sum, // on success, adds likelihood to this.
|
||||
int64 *frame_sum, // on success, adds #frames to this.
|
||||
int32 *num_done, // on success (including partial decode), increments this.
|
||||
int32 *num_err, // on failure, increments this.
|
||||
int32 *num_partial); // If partial decode (final-state not reached), increments this.
|
||||
void operator () (); // The decoding happens here.
|
||||
~DecodeUtteranceLatticeFasterClass(); // Output happens here.
|
||||
private:
|
||||
// The following variables correspond to inputs:
|
||||
LatticeFasterDecoder *decoder_;
|
||||
DecodableInterface *decodable_;
|
||||
const TransitionInformation *trans_model_;
|
||||
const fst::SymbolTable *word_syms_;
|
||||
std::string utt_;
|
||||
BaseFloat acoustic_scale_;
|
||||
bool determinize_;
|
||||
bool allow_partial_;
|
||||
Int32VectorWriter *alignments_writer_;
|
||||
Int32VectorWriter *words_writer_;
|
||||
CompactLatticeWriter *compact_lattice_writer_;
|
||||
LatticeWriter *lattice_writer_;
|
||||
double *like_sum_;
|
||||
int64 *frame_sum_;
|
||||
int32 *num_done_;
|
||||
int32 *num_err_;
|
||||
int32 *num_partial_;
|
||||
|
||||
// The following variables are stored by the computation.
|
||||
bool computed_; // operator () was called.
|
||||
bool success_; // decoding succeeded (possibly partial)
|
||||
bool partial_; // decoding was partial.
|
||||
CompactLattice *clat_; // Stored output, if determinize_ == true.
|
||||
Lattice *lat_; // Stored output, if determinize_ == false.
|
||||
};
|
||||
|
||||
// This function DecodeUtteranceLatticeSimple is used in several decoders, and
|
||||
// we have moved it here. Note: this is really "binary-level" code as it
|
||||
// involves table readers and writers; we've just put it here as there is no
|
||||
// other obvious place to put it. If determinize == false, it writes to
|
||||
// lattice_writer, else to compact_lattice_writer. The writers for
|
||||
// alignments and words will only be written to if they are open.
|
||||
bool DecodeUtteranceLatticeSimple(
|
||||
LatticeSimpleDecoder &decoder, // not const but is really an input.
|
||||
DecodableInterface &decodable, // not const but is really an input.
|
||||
const TransitionInformation &trans_model,
|
||||
const fst::SymbolTable *word_syms,
|
||||
std::string utt,
|
||||
double acoustic_scale,
|
||||
bool determinize,
|
||||
bool allow_partial,
|
||||
Int32VectorWriter *alignments_writer,
|
||||
Int32VectorWriter *words_writer,
|
||||
CompactLatticeWriter *compact_lattice_writer,
|
||||
LatticeWriter *lattice_writer,
|
||||
double *like_ptr); // puts utterance's likelihood in like_ptr on success.
|
||||
|
||||
|
||||
|
||||
} // end namespace kaldi.
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,351 @@
|
||||
// decoder/faster-decoder.cc
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation
|
||||
// 2012-2013 Johns Hopkins University (author: Daniel Povey)
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "decoder/faster-decoder.h"
|
||||
|
||||
namespace kaldi {
|
||||
|
||||
|
||||
FasterDecoder::FasterDecoder(const fst::Fst<fst::StdArc> &fst,
|
||||
const FasterDecoderOptions &opts):
|
||||
fst_(fst), config_(opts), num_frames_decoded_(-1) {
|
||||
KALDI_ASSERT(config_.hash_ratio >= 1.0); // less doesn't make much sense.
|
||||
KALDI_ASSERT(config_.max_active > 1);
|
||||
KALDI_ASSERT(config_.min_active >= 0 && config_.min_active < config_.max_active);
|
||||
toks_.SetSize(1000); // just so on the first frame we do something reasonable.
|
||||
}
|
||||
|
||||
|
||||
void FasterDecoder::InitDecoding() {
|
||||
// clean up from last time:
|
||||
ClearToks(toks_.Clear());
|
||||
StateId start_state = fst_.Start();
|
||||
KALDI_ASSERT(start_state != fst::kNoStateId);
|
||||
Arc dummy_arc(0, 0, Weight::One(), start_state);
|
||||
toks_.Insert(start_state, new Token(dummy_arc, NULL));
|
||||
ProcessNonemitting(std::numeric_limits<float>::max());
|
||||
num_frames_decoded_ = 0;
|
||||
}
|
||||
|
||||
|
||||
void FasterDecoder::Decode(DecodableInterface *decodable) {
|
||||
InitDecoding();
|
||||
AdvanceDecoding(decodable);
|
||||
}
|
||||
|
||||
void FasterDecoder::AdvanceDecoding(DecodableInterface *decodable,
|
||||
int32 max_num_frames) {
|
||||
KALDI_ASSERT(num_frames_decoded_ >= 0 &&
|
||||
"You must call InitDecoding() before AdvanceDecoding()");
|
||||
int32 num_frames_ready = decodable->NumFramesReady();
|
||||
// num_frames_ready must be >= num_frames_decoded, or else
|
||||
// the number of frames ready must have decreased (which doesn't
|
||||
// make sense) or the decodable object changed between calls
|
||||
// (which isn't allowed).
|
||||
KALDI_ASSERT(num_frames_ready >= num_frames_decoded_);
|
||||
int32 target_frames_decoded = num_frames_ready;
|
||||
if (max_num_frames >= 0)
|
||||
target_frames_decoded = std::min(target_frames_decoded,
|
||||
num_frames_decoded_ + max_num_frames);
|
||||
while (num_frames_decoded_ < target_frames_decoded) {
|
||||
// note: ProcessEmitting() increments num_frames_decoded_
|
||||
double weight_cutoff = ProcessEmitting(decodable);
|
||||
ProcessNonemitting(weight_cutoff);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool FasterDecoder::ReachedFinal() const {
|
||||
for (const Elem *e = toks_.GetList(); e != NULL; e = e->tail) {
|
||||
if (e->val->cost_ != std::numeric_limits<double>::infinity() &&
|
||||
fst_.Final(e->key) != Weight::Zero())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FasterDecoder::GetBestPath(fst::MutableFst<LatticeArc> *fst_out,
|
||||
bool use_final_probs) {
|
||||
// GetBestPath gets the decoding output. If "use_final_probs" is true
|
||||
// AND we reached a final state, it limits itself to final states;
|
||||
// otherwise it gets the most likely token not taking into
|
||||
// account final-probs. fst_out will be empty (Start() == kNoStateId) if
|
||||
// nothing was available. It returns true if it got output (thus, fst_out
|
||||
// will be nonempty).
|
||||
fst_out->DeleteStates();
|
||||
Token *best_tok = NULL;
|
||||
bool is_final = ReachedFinal();
|
||||
if (!is_final) {
|
||||
for (const Elem *e = toks_.GetList(); e != NULL; e = e->tail)
|
||||
if (best_tok == NULL || *best_tok < *(e->val) )
|
||||
best_tok = e->val;
|
||||
} else {
|
||||
double infinity = std::numeric_limits<double>::infinity(),
|
||||
best_cost = infinity;
|
||||
for (const Elem *e = toks_.GetList(); e != NULL; e = e->tail) {
|
||||
double this_cost = e->val->cost_ + fst_.Final(e->key).Value();
|
||||
if (this_cost < best_cost && this_cost != infinity) {
|
||||
best_cost = this_cost;
|
||||
best_tok = e->val;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (best_tok == NULL) return false; // No output.
|
||||
|
||||
std::vector<LatticeArc> arcs_reverse; // arcs in reverse order.
|
||||
|
||||
for (Token *tok = best_tok; tok != NULL; tok = tok->prev_) {
|
||||
BaseFloat tot_cost = tok->cost_ -
|
||||
(tok->prev_ ? tok->prev_->cost_ : 0.0),
|
||||
graph_cost = tok->arc_.weight.Value(),
|
||||
ac_cost = tot_cost - graph_cost;
|
||||
LatticeArc l_arc(tok->arc_.ilabel,
|
||||
tok->arc_.olabel,
|
||||
LatticeWeight(graph_cost, ac_cost),
|
||||
tok->arc_.nextstate);
|
||||
arcs_reverse.push_back(l_arc);
|
||||
}
|
||||
KALDI_ASSERT(arcs_reverse.back().nextstate == fst_.Start());
|
||||
arcs_reverse.pop_back(); // that was a "fake" token... gives no info.
|
||||
|
||||
StateId cur_state = fst_out->AddState();
|
||||
fst_out->SetStart(cur_state);
|
||||
for (ssize_t i = static_cast<ssize_t>(arcs_reverse.size())-1; i >= 0; i--) {
|
||||
LatticeArc arc = arcs_reverse[i];
|
||||
arc.nextstate = fst_out->AddState();
|
||||
fst_out->AddArc(cur_state, arc);
|
||||
cur_state = arc.nextstate;
|
||||
}
|
||||
if (is_final && use_final_probs) {
|
||||
Weight final_weight = fst_.Final(best_tok->arc_.nextstate);
|
||||
fst_out->SetFinal(cur_state, LatticeWeight(final_weight.Value(), 0.0));
|
||||
} else {
|
||||
fst_out->SetFinal(cur_state, LatticeWeight::One());
|
||||
}
|
||||
RemoveEpsLocal(fst_out);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Gets the weight cutoff. Also counts the active tokens.
|
||||
double FasterDecoder::GetCutoff(Elem *list_head, size_t *tok_count,
|
||||
BaseFloat *adaptive_beam, Elem **best_elem) {
|
||||
double best_cost = std::numeric_limits<double>::infinity();
|
||||
size_t count = 0;
|
||||
if (config_.max_active == std::numeric_limits<int32>::max() &&
|
||||
config_.min_active == 0) {
|
||||
for (Elem *e = list_head; e != NULL; e = e->tail, count++) {
|
||||
double w = e->val->cost_;
|
||||
if (w < best_cost) {
|
||||
best_cost = w;
|
||||
if (best_elem) *best_elem = e;
|
||||
}
|
||||
}
|
||||
if (tok_count != NULL) *tok_count = count;
|
||||
if (adaptive_beam != NULL) *adaptive_beam = config_.beam;
|
||||
return best_cost + config_.beam;
|
||||
} else {
|
||||
tmp_array_.clear();
|
||||
for (Elem *e = list_head; e != NULL; e = e->tail, count++) {
|
||||
double w = e->val->cost_;
|
||||
tmp_array_.push_back(w);
|
||||
if (w < best_cost) {
|
||||
best_cost = w;
|
||||
if (best_elem) *best_elem = e;
|
||||
}
|
||||
}
|
||||
if (tok_count != NULL) *tok_count = count;
|
||||
double beam_cutoff = best_cost + config_.beam,
|
||||
min_active_cutoff = std::numeric_limits<double>::infinity(),
|
||||
max_active_cutoff = std::numeric_limits<double>::infinity();
|
||||
|
||||
if (tmp_array_.size() > static_cast<size_t>(config_.max_active)) {
|
||||
std::nth_element(tmp_array_.begin(),
|
||||
tmp_array_.begin() + config_.max_active,
|
||||
tmp_array_.end());
|
||||
max_active_cutoff = tmp_array_[config_.max_active];
|
||||
}
|
||||
if (max_active_cutoff < beam_cutoff) { // max_active is tighter than beam.
|
||||
if (adaptive_beam)
|
||||
*adaptive_beam = max_active_cutoff - best_cost + config_.beam_delta;
|
||||
return max_active_cutoff;
|
||||
}
|
||||
if (tmp_array_.size() > static_cast<size_t>(config_.min_active)) {
|
||||
if (config_.min_active == 0) min_active_cutoff = best_cost;
|
||||
else {
|
||||
std::nth_element(tmp_array_.begin(),
|
||||
tmp_array_.begin() + config_.min_active,
|
||||
tmp_array_.size() > static_cast<size_t>(config_.max_active) ?
|
||||
tmp_array_.begin() + config_.max_active :
|
||||
tmp_array_.end());
|
||||
min_active_cutoff = tmp_array_[config_.min_active];
|
||||
}
|
||||
}
|
||||
if (min_active_cutoff > beam_cutoff) { // min_active is looser than beam.
|
||||
if (adaptive_beam)
|
||||
*adaptive_beam = min_active_cutoff - best_cost + config_.beam_delta;
|
||||
return min_active_cutoff;
|
||||
} else {
|
||||
*adaptive_beam = config_.beam;
|
||||
return beam_cutoff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FasterDecoder::PossiblyResizeHash(size_t num_toks) {
|
||||
size_t new_sz = static_cast<size_t>(static_cast<BaseFloat>(num_toks)
|
||||
* config_.hash_ratio);
|
||||
if (new_sz > toks_.Size()) {
|
||||
toks_.SetSize(new_sz);
|
||||
}
|
||||
}
|
||||
|
||||
// ProcessEmitting returns the likelihood cutoff used.
|
||||
double FasterDecoder::ProcessEmitting(DecodableInterface *decodable) {
|
||||
int32 frame = num_frames_decoded_;
|
||||
Elem *last_toks = toks_.Clear();
|
||||
size_t tok_cnt;
|
||||
BaseFloat adaptive_beam;
|
||||
Elem *best_elem = NULL;
|
||||
double weight_cutoff = GetCutoff(last_toks, &tok_cnt,
|
||||
&adaptive_beam, &best_elem);
|
||||
KALDI_VLOG(3) << tok_cnt << " tokens active.";
|
||||
PossiblyResizeHash(tok_cnt); // This makes sure the hash is always big enough.
|
||||
|
||||
// This is the cutoff we use after adding in the log-likes (i.e.
|
||||
// for the next frame). This is a bound on the cutoff we will use
|
||||
// on the next frame.
|
||||
double next_weight_cutoff = std::numeric_limits<double>::infinity();
|
||||
|
||||
// First process the best token to get a hopefully
|
||||
// reasonably tight bound on the next cutoff.
|
||||
if (best_elem) {
|
||||
StateId state = best_elem->key;
|
||||
Token *tok = best_elem->val;
|
||||
for (fst::ArcIterator<fst::Fst<Arc> > aiter(fst_, state);
|
||||
!aiter.Done();
|
||||
aiter.Next()) {
|
||||
const Arc &arc = aiter.Value();
|
||||
if (arc.ilabel != 0) { // we'd propagate..
|
||||
BaseFloat ac_cost = - decodable->LogLikelihood(frame, arc.ilabel);
|
||||
double new_weight = arc.weight.Value() + tok->cost_ + ac_cost;
|
||||
if (new_weight + adaptive_beam < next_weight_cutoff)
|
||||
next_weight_cutoff = new_weight + adaptive_beam;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// int32 n = 0, np = 0;
|
||||
|
||||
// the tokens are now owned here, in last_toks, and the hash is empty.
|
||||
// 'owned' is a complex thing here; the point is we need to call TokenDelete
|
||||
// on each elem 'e' to let toks_ know we're done with them.
|
||||
for (Elem *e = last_toks, *e_tail; e != NULL; e = e_tail) { // loop this way
|
||||
// n++;
|
||||
// because we delete "e" as we go.
|
||||
StateId state = e->key;
|
||||
Token *tok = e->val;
|
||||
if (tok->cost_ < weight_cutoff) { // not pruned.
|
||||
// np++;
|
||||
KALDI_ASSERT(state == tok->arc_.nextstate);
|
||||
for (fst::ArcIterator<fst::Fst<Arc> > aiter(fst_, state);
|
||||
!aiter.Done();
|
||||
aiter.Next()) {
|
||||
Arc arc = aiter.Value();
|
||||
if (arc.ilabel != 0) { // propagate..
|
||||
BaseFloat ac_cost = - decodable->LogLikelihood(frame, arc.ilabel);
|
||||
double new_weight = arc.weight.Value() + tok->cost_ + ac_cost;
|
||||
if (new_weight < next_weight_cutoff) { // not pruned..
|
||||
Token *new_tok = new Token(arc, ac_cost, tok);
|
||||
Elem *e_found = toks_.Insert(arc.nextstate, new_tok);
|
||||
if (new_weight + adaptive_beam < next_weight_cutoff)
|
||||
next_weight_cutoff = new_weight + adaptive_beam;
|
||||
if (e_found->val != new_tok) {
|
||||
if (*(e_found->val) < *new_tok) {
|
||||
Token::TokenDelete(e_found->val);
|
||||
e_found->val = new_tok;
|
||||
} else {
|
||||
Token::TokenDelete(new_tok);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
e_tail = e->tail;
|
||||
Token::TokenDelete(e->val);
|
||||
toks_.Delete(e);
|
||||
}
|
||||
num_frames_decoded_++;
|
||||
return next_weight_cutoff;
|
||||
}
|
||||
|
||||
// TODO: first time we go through this, could avoid using the queue.
|
||||
void FasterDecoder::ProcessNonemitting(double cutoff) {
|
||||
// Processes nonemitting arcs for one frame.
|
||||
KALDI_ASSERT(queue_.empty());
|
||||
for (const Elem *e = toks_.GetList(); e != NULL; e = e->tail)
|
||||
queue_.push_back(e);
|
||||
while (!queue_.empty()) {
|
||||
const Elem* e = queue_.back();
|
||||
queue_.pop_back();
|
||||
StateId state = e->key;
|
||||
Token *tok = e->val; // would segfault if state not
|
||||
// in toks_ but this can't happen.
|
||||
if (tok->cost_ > cutoff) { // Don't bother processing successors.
|
||||
continue;
|
||||
}
|
||||
KALDI_ASSERT(tok != NULL && state == tok->arc_.nextstate);
|
||||
for (fst::ArcIterator<fst::Fst<Arc> > aiter(fst_, state);
|
||||
!aiter.Done();
|
||||
aiter.Next()) {
|
||||
const Arc &arc = aiter.Value();
|
||||
if (arc.ilabel == 0) { // propagate nonemitting only...
|
||||
Token *new_tok = new Token(arc, tok);
|
||||
if (new_tok->cost_ > cutoff) { // prune
|
||||
Token::TokenDelete(new_tok);
|
||||
} else {
|
||||
Elem *e_found = toks_.Insert(arc.nextstate, new_tok);
|
||||
if (e_found->val == new_tok) {
|
||||
queue_.push_back(e_found);
|
||||
} else {
|
||||
if (*(e_found->val) < *new_tok) {
|
||||
Token::TokenDelete(e_found->val);
|
||||
e_found->val = new_tok;
|
||||
queue_.push_back(e_found);
|
||||
} else {
|
||||
Token::TokenDelete(new_tok);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FasterDecoder::ClearToks(Elem *list) {
|
||||
for (Elem *e = list, *e_tail; e != NULL; e = e_tail) {
|
||||
Token::TokenDelete(e->val);
|
||||
e_tail = e->tail;
|
||||
toks_.Delete(e);
|
||||
}
|
||||
}
|
||||
|
||||
} // end namespace kaldi.
|
||||
@@ -0,0 +1,195 @@
|
||||
// decoder/faster-decoder.h
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation
|
||||
// 2013 Johns Hopkins University (author: Daniel Povey)
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef KALDI_DECODER_FASTER_DECODER_H_
|
||||
#define KALDI_DECODER_FASTER_DECODER_H_
|
||||
|
||||
#include "util/stl-utils.h"
|
||||
#include "itf/options-itf.h"
|
||||
#include "util/hash-list.h"
|
||||
#include "fst/fstlib.h"
|
||||
#include "itf/decodable-itf.h"
|
||||
#include "lat/kaldi-lattice.h" // for CompactLatticeArc
|
||||
|
||||
namespace kaldi {
|
||||
|
||||
struct FasterDecoderOptions {
|
||||
BaseFloat beam;
|
||||
int32 max_active;
|
||||
int32 min_active;
|
||||
BaseFloat beam_delta;
|
||||
BaseFloat hash_ratio;
|
||||
FasterDecoderOptions(): beam(16.0),
|
||||
max_active(std::numeric_limits<int32>::max()),
|
||||
min_active(20), // This decoder mostly used for
|
||||
// alignment, use small default.
|
||||
beam_delta(0.5),
|
||||
hash_ratio(2.0) { }
|
||||
void Register(OptionsItf *opts, bool full) { /// if "full", use obscure
|
||||
/// options too.
|
||||
/// Depends on program.
|
||||
opts->Register("beam", &beam, "Decoding beam. Larger->slower, more accurate.");
|
||||
opts->Register("max-active", &max_active, "Decoder max active states. Larger->slower; "
|
||||
"more accurate");
|
||||
opts->Register("min-active", &min_active,
|
||||
"Decoder min active states (don't prune if #active less than this).");
|
||||
if (full) {
|
||||
opts->Register("beam-delta", &beam_delta,
|
||||
"Increment used in decoder [obscure setting]");
|
||||
opts->Register("hash-ratio", &hash_ratio,
|
||||
"Setting used in decoder to control hash behavior");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class FasterDecoder {
|
||||
public:
|
||||
typedef fst::StdArc Arc;
|
||||
typedef Arc::Label Label;
|
||||
typedef Arc::StateId StateId;
|
||||
typedef Arc::Weight Weight;
|
||||
|
||||
FasterDecoder(const fst::Fst<fst::StdArc> &fst,
|
||||
const FasterDecoderOptions &config);
|
||||
|
||||
void SetOptions(const FasterDecoderOptions &config) { config_ = config; }
|
||||
|
||||
~FasterDecoder() { ClearToks(toks_.Clear()); }
|
||||
|
||||
void Decode(DecodableInterface *decodable);
|
||||
|
||||
/// Returns true if a final state was active on the last frame.
|
||||
bool ReachedFinal() const;
|
||||
|
||||
/// GetBestPath gets the decoding traceback. If "use_final_probs" is true
|
||||
/// AND we reached a final state, it limits itself to final states;
|
||||
/// otherwise it gets the most likely token not taking into account
|
||||
/// final-probs. Returns true if the output best path was not the empty
|
||||
/// FST (will only return false in unusual circumstances where
|
||||
/// no tokens survived).
|
||||
bool GetBestPath(fst::MutableFst<LatticeArc> *fst_out,
|
||||
bool use_final_probs = true);
|
||||
|
||||
/// As a new alternative to Decode(), you can call InitDecoding
|
||||
/// and then (possibly multiple times) AdvanceDecoding().
|
||||
void InitDecoding();
|
||||
|
||||
|
||||
/// This will decode until there are no more frames ready in the decodable
|
||||
/// object, but if max_num_frames is >= 0 it will decode no more than
|
||||
/// that many frames.
|
||||
void AdvanceDecoding(DecodableInterface *decodable,
|
||||
int32 max_num_frames = -1);
|
||||
|
||||
/// Returns the number of frames already decoded.
|
||||
int32 NumFramesDecoded() const { return num_frames_decoded_; }
|
||||
|
||||
protected:
|
||||
|
||||
class Token {
|
||||
public:
|
||||
Arc arc_; // contains only the graph part of the cost;
|
||||
// we can work out the acoustic part from difference between
|
||||
// "cost_" and prev->cost_.
|
||||
Token *prev_;
|
||||
int32 ref_count_;
|
||||
// if you are looking for weight_ here, it was removed and now we just have
|
||||
// cost_, which corresponds to ConvertToCost(weight_).
|
||||
double cost_;
|
||||
inline Token(const Arc &arc, BaseFloat ac_cost, Token *prev):
|
||||
arc_(arc), prev_(prev), ref_count_(1) {
|
||||
if (prev) {
|
||||
prev->ref_count_++;
|
||||
cost_ = prev->cost_ + arc.weight.Value() + ac_cost;
|
||||
} else {
|
||||
cost_ = arc.weight.Value() + ac_cost;
|
||||
}
|
||||
}
|
||||
inline Token(const Arc &arc, Token *prev):
|
||||
arc_(arc), prev_(prev), ref_count_(1) {
|
||||
if (prev) {
|
||||
prev->ref_count_++;
|
||||
cost_ = prev->cost_ + arc.weight.Value();
|
||||
} else {
|
||||
cost_ = arc.weight.Value();
|
||||
}
|
||||
}
|
||||
inline bool operator < (const Token &other) {
|
||||
return cost_ > other.cost_;
|
||||
}
|
||||
|
||||
inline static void TokenDelete(Token *tok) {
|
||||
while (--tok->ref_count_ == 0) {
|
||||
Token *prev = tok->prev_;
|
||||
delete tok;
|
||||
if (prev == NULL) return;
|
||||
else tok = prev;
|
||||
}
|
||||
#ifdef KALDI_PARANOID
|
||||
KALDI_ASSERT(tok->ref_count_ > 0);
|
||||
#endif
|
||||
}
|
||||
};
|
||||
typedef HashList<StateId, Token*>::Elem Elem;
|
||||
|
||||
|
||||
/// Gets the weight cutoff. Also counts the active tokens.
|
||||
double GetCutoff(Elem *list_head, size_t *tok_count,
|
||||
BaseFloat *adaptive_beam, Elem **best_elem);
|
||||
|
||||
void PossiblyResizeHash(size_t num_toks);
|
||||
|
||||
// ProcessEmitting returns the likelihood cutoff used.
|
||||
// It decodes the frame num_frames_decoded_ of the decodable object
|
||||
// and then increments num_frames_decoded_
|
||||
double ProcessEmitting(DecodableInterface *decodable);
|
||||
|
||||
// TODO: first time we go through this, could avoid using the queue.
|
||||
void ProcessNonemitting(double cutoff);
|
||||
|
||||
// HashList defined in ../util/hash-list.h. It actually allows us to maintain
|
||||
// more than one list (e.g. for current and previous frames), but only one of
|
||||
// them at a time can be indexed by StateId.
|
||||
HashList<StateId, Token*> toks_;
|
||||
const fst::Fst<fst::StdArc> &fst_;
|
||||
FasterDecoderOptions config_;
|
||||
std::vector<const Elem* > queue_; // temp variable used in ProcessNonemitting,
|
||||
std::vector<BaseFloat> tmp_array_; // used in GetCutoff.
|
||||
// make it class member to avoid internal new/delete.
|
||||
|
||||
// Keep track of the number of frames decoded in the current file.
|
||||
int32 num_frames_decoded_;
|
||||
|
||||
// It might seem unclear why we call ClearToks(toks_.Clear()).
|
||||
// There are two separate cleanup tasks we need to do at when we start a new file.
|
||||
// one is to delete the Token objects in the list; the other is to delete
|
||||
// the Elem objects. toks_.Clear() just clears them from the hash and gives ownership
|
||||
// to the caller, who then has to call toks_.Delete(e) for each one. It was designed
|
||||
// this way for convenience in propagating tokens from one frame to the next.
|
||||
void ClearToks(Elem *list);
|
||||
|
||||
KALDI_DISALLOW_COPY_AND_ASSIGN(FasterDecoder);
|
||||
};
|
||||
|
||||
|
||||
} // end namespace kaldi.
|
||||
|
||||
|
||||
#endif
|
||||
+886
@@ -0,0 +1,886 @@
|
||||
// decoder/lattice-biglm-faster-decoder.h
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation, Mirko Hannemann,
|
||||
// Gilles Boulianne
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef KALDI_DECODER_LATTICE_BIGLM_FASTER_DECODER_H_
|
||||
#define KALDI_DECODER_LATTICE_BIGLM_FASTER_DECODER_H_
|
||||
|
||||
|
||||
#include "util/stl-utils.h"
|
||||
#include "util/hash-list.h"
|
||||
#include "fst/fstlib.h"
|
||||
#include "itf/decodable-itf.h"
|
||||
#include "fstext/fstext-lib.h"
|
||||
#include "lat/kaldi-lattice.h"
|
||||
#include "decoder/lattice-faster-decoder.h" // for options.
|
||||
|
||||
|
||||
namespace kaldi {
|
||||
|
||||
// The options are the same as for lattice-faster-decoder.h for now.
|
||||
typedef LatticeFasterDecoderConfig LatticeBiglmFasterDecoderConfig;
|
||||
|
||||
/** This is as LatticeFasterDecoder, but does online composition between
|
||||
HCLG and the "difference language model", which is a deterministic
|
||||
FST that represents the difference between the language model you want
|
||||
and the language model you compiled HCLG with. The class
|
||||
DeterministicOnDemandFst follows through the epsilons in G for you
|
||||
(assuming G is a standard backoff language model) and makes it look
|
||||
like a determinized FST.
|
||||
*/
|
||||
|
||||
class LatticeBiglmFasterDecoder {
|
||||
public:
|
||||
typedef fst::StdArc Arc;
|
||||
typedef Arc::Label Label;
|
||||
typedef Arc::StateId StateId;
|
||||
// A PairId will be constructed as: (StateId in fst) + (StateId in lm_diff_fst) << 32;
|
||||
typedef uint64 PairId;
|
||||
typedef Arc::Weight Weight;
|
||||
// instantiate this class once for each thing you have to decode.
|
||||
LatticeBiglmFasterDecoder(
|
||||
const fst::Fst<fst::StdArc> &fst,
|
||||
const LatticeBiglmFasterDecoderConfig &config,
|
||||
fst::DeterministicOnDemandFst<fst::StdArc> *lm_diff_fst):
|
||||
fst_(fst), lm_diff_fst_(lm_diff_fst), config_(config),
|
||||
warned_noarc_(false), num_toks_(0) {
|
||||
config.Check();
|
||||
KALDI_ASSERT(fst.Start() != fst::kNoStateId &&
|
||||
lm_diff_fst->Start() != fst::kNoStateId);
|
||||
toks_.SetSize(1000); // just so on the first frame we do something reasonable.
|
||||
}
|
||||
void SetOptions(const LatticeBiglmFasterDecoderConfig &config) { config_ = config; }
|
||||
LatticeBiglmFasterDecoderConfig GetOptions() { return config_; }
|
||||
~LatticeBiglmFasterDecoder() {
|
||||
DeleteElems(toks_.Clear());
|
||||
ClearActiveTokens();
|
||||
}
|
||||
|
||||
// Returns true if any kind of traceback is available (not necessarily from
|
||||
// a final state).
|
||||
bool Decode(DecodableInterface *decodable) {
|
||||
// clean up from last time:
|
||||
DeleteElems(toks_.Clear());
|
||||
ClearActiveTokens();
|
||||
warned_ = false;
|
||||
final_active_ = false;
|
||||
final_costs_.clear();
|
||||
num_toks_ = 0;
|
||||
PairId start_pair = ConstructPair(fst_.Start(), lm_diff_fst_->Start());
|
||||
active_toks_.resize(1);
|
||||
Token *start_tok = new Token(0.0, 0.0, NULL, NULL);
|
||||
active_toks_[0].toks = start_tok;
|
||||
toks_.Insert(start_pair, start_tok);
|
||||
num_toks_++;
|
||||
ProcessNonemitting(0);
|
||||
|
||||
// We use 1-based indexing for frames in this decoder (if you view it in
|
||||
// terms of features), but note that the decodable object uses zero-based
|
||||
// numbering, which we have to correct for when we call it.
|
||||
for (int32 frame = 1; !decodable->IsLastFrame(frame-2); frame++) {
|
||||
active_toks_.resize(frame+1); // new column
|
||||
|
||||
ProcessEmitting(decodable, frame);
|
||||
|
||||
ProcessNonemitting(frame);
|
||||
|
||||
if (decodable->IsLastFrame(frame-1))
|
||||
PruneActiveTokensFinal(frame);
|
||||
else if (frame % config_.prune_interval == 0)
|
||||
PruneActiveTokens(frame, config_.lattice_beam * 0.1); // use larger delta.
|
||||
}
|
||||
// Returns true if we have any kind of traceback available (not necessarily
|
||||
// to the end state; query ReachedFinal() for that).
|
||||
return !final_costs_.empty();
|
||||
}
|
||||
|
||||
/// says whether a final-state was active on the last frame. If it was not, the
|
||||
/// lattice (or traceback) will end with states that are not final-states.
|
||||
bool ReachedFinal() const { return final_active_; }
|
||||
|
||||
|
||||
// Outputs an FST corresponding to the single best path
|
||||
// through the lattice.
|
||||
bool GetBestPath(fst::MutableFst<LatticeArc> *ofst,
|
||||
bool use_final_probs = true) const {
|
||||
fst::VectorFst<LatticeArc> fst;
|
||||
if (!GetRawLattice(&fst, use_final_probs)) return false;
|
||||
// std::cout << "Raw lattice is:\n";
|
||||
// fst::FstPrinter<LatticeArc> fstprinter(fst, NULL, NULL, NULL, false, true);
|
||||
// fstprinter.Print(&std::cout, "standard output");
|
||||
ShortestPath(fst, ofst);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Outputs an FST corresponding to the raw, state-level
|
||||
// tracebacks.
|
||||
bool GetRawLattice(fst::MutableFst<LatticeArc> *ofst,
|
||||
bool use_final_probs = true) const {
|
||||
typedef LatticeArc Arc;
|
||||
typedef Arc::StateId StateId;
|
||||
// A PairId will be constructed as: (StateId in fst) + (StateId in lm_diff_fst) << 32;
|
||||
typedef uint64 PairId;
|
||||
typedef Arc::Weight Weight;
|
||||
typedef Arc::Label Label;
|
||||
ofst->DeleteStates();
|
||||
// num-frames plus one (since frames are one-based, and we have
|
||||
// an extra frame for the start-state).
|
||||
int32 num_frames = active_toks_.size() - 1;
|
||||
KALDI_ASSERT(num_frames > 0);
|
||||
unordered_map<Token*, StateId> tok_map(num_toks_/2 + 3); // bucket count
|
||||
// First create all states.
|
||||
for (int32 f = 0; f <= num_frames; f++) {
|
||||
if (active_toks_[f].toks == NULL) {
|
||||
KALDI_WARN << "GetRawLattice: no tokens active on frame " << f
|
||||
<< ": not producing lattice.\n";
|
||||
return false;
|
||||
}
|
||||
for (Token *tok = active_toks_[f].toks; tok != NULL; tok = tok->next)
|
||||
tok_map[tok] = ofst->AddState();
|
||||
// The next statement sets the start state of the output FST.
|
||||
// Because we always add new states to the head of the list
|
||||
// active_toks_[f].toks, and the start state was the first one
|
||||
// added, it will be the last one added to ofst.
|
||||
if (f == 0 && ofst->NumStates() > 0)
|
||||
ofst->SetStart(ofst->NumStates()-1);
|
||||
}
|
||||
KALDI_VLOG(3) << "init:" << num_toks_/2 + 3 << " buckets:"
|
||||
<< tok_map.bucket_count() << " load:" << tok_map.load_factor()
|
||||
<< " max:" << tok_map.max_load_factor();
|
||||
// Now create all arcs.
|
||||
StateId cur_state = 0; // we rely on the fact that we numbered these
|
||||
// consecutively (AddState() returns the numbers in order..)
|
||||
for (int32 f = 0; f <= num_frames; f++) {
|
||||
for (Token *tok = active_toks_[f].toks; tok != NULL; tok = tok->next,
|
||||
cur_state++) {
|
||||
for (ForwardLink *l = tok->links;
|
||||
l != NULL;
|
||||
l = l->next) {
|
||||
unordered_map<Token*, StateId>::const_iterator iter =
|
||||
tok_map.find(l->next_tok);
|
||||
StateId nextstate = iter->second;
|
||||
KALDI_ASSERT(iter != tok_map.end());
|
||||
Arc arc(l->ilabel, l->olabel,
|
||||
Weight(l->graph_cost, l->acoustic_cost),
|
||||
nextstate);
|
||||
ofst->AddArc(cur_state, arc);
|
||||
}
|
||||
if (f == num_frames) {
|
||||
if (use_final_probs && !final_costs_.empty()) {
|
||||
std::map<Token*, BaseFloat>::const_iterator iter =
|
||||
final_costs_.find(tok);
|
||||
if (iter != final_costs_.end())
|
||||
ofst->SetFinal(cur_state, LatticeWeight(iter->second, 0));
|
||||
} else {
|
||||
ofst->SetFinal(cur_state, LatticeWeight::One());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
KALDI_ASSERT(cur_state == ofst->NumStates());
|
||||
return (cur_state != 0);
|
||||
}
|
||||
|
||||
// This function is now deprecated, since now we do determinization from
|
||||
// outside the LatticeBiglmFasterDecoder class.
|
||||
// Outputs an FST corresponding to the lattice-determinized
|
||||
// lattice (one path per word sequence).
|
||||
bool GetLattice(fst::MutableFst<CompactLatticeArc> *ofst,
|
||||
bool use_final_probs = true) const {
|
||||
Lattice raw_fst;
|
||||
if (!GetRawLattice(&raw_fst, use_final_probs)) return false;
|
||||
Invert(&raw_fst); // make it so word labels are on the input.
|
||||
if (!TopSort(&raw_fst)) // topological sort makes lattice-determinization more efficient
|
||||
KALDI_WARN << "Topological sorting of state-level lattice failed "
|
||||
"(probably your lexicon has empty words or your LM has epsilon cycles; this "
|
||||
" is a bad idea.)";
|
||||
// (in phase where we get backward-costs).
|
||||
fst::ILabelCompare<LatticeArc> ilabel_comp;
|
||||
ArcSort(&raw_fst, ilabel_comp); // sort on ilabel; makes
|
||||
// lattice-determinization more efficient.
|
||||
|
||||
fst::DeterminizeLatticePrunedOptions lat_opts;
|
||||
lat_opts.max_mem = config_.det_opts.max_mem;
|
||||
|
||||
DeterminizeLatticePruned(raw_fst, config_.lattice_beam, ofst, lat_opts);
|
||||
raw_fst.DeleteStates(); // Free memory-- raw_fst no longer needed.
|
||||
Connect(ofst); // Remove unreachable states... there might be
|
||||
// a small number of these, in some cases.
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
inline PairId ConstructPair(StateId fst_state, StateId lm_state) {
|
||||
return static_cast<PairId>(fst_state) + (static_cast<PairId>(lm_state) << 32);
|
||||
}
|
||||
|
||||
static inline StateId PairToState(PairId state_pair) {
|
||||
return static_cast<StateId>(static_cast<uint32>(state_pair));
|
||||
}
|
||||
static inline StateId PairToLmState(PairId state_pair) {
|
||||
return static_cast<StateId>(static_cast<uint32>(state_pair >> 32));
|
||||
}
|
||||
|
||||
struct Token;
|
||||
// ForwardLinks are the links from a token to a token on the next frame.
|
||||
// or sometimes on the current frame (for input-epsilon links).
|
||||
struct ForwardLink {
|
||||
Token *next_tok; // the next token [or NULL if represents final-state]
|
||||
Label ilabel; // ilabel on link.
|
||||
Label olabel; // olabel on link.
|
||||
BaseFloat graph_cost; // graph cost of traversing link (contains LM, etc.)
|
||||
BaseFloat acoustic_cost; // acoustic cost (pre-scaled) of traversing link
|
||||
ForwardLink *next; // next in singly-linked list of forward links from a
|
||||
// token.
|
||||
inline ForwardLink(Token *next_tok, Label ilabel, Label olabel,
|
||||
BaseFloat graph_cost, BaseFloat acoustic_cost,
|
||||
ForwardLink *next):
|
||||
next_tok(next_tok), ilabel(ilabel), olabel(olabel),
|
||||
graph_cost(graph_cost), acoustic_cost(acoustic_cost),
|
||||
next(next) { }
|
||||
};
|
||||
|
||||
// Token is what's resident in a particular state at a particular time.
|
||||
// In this decoder a Token actually contains *forward* links.
|
||||
// When first created, a Token just has the (total) cost. We add forward
|
||||
// links to it when we process the next frame.
|
||||
struct Token {
|
||||
BaseFloat tot_cost; // would equal weight.Value()... cost up to this point.
|
||||
BaseFloat extra_cost; // >= 0. After calling PruneForwardLinks, this equals
|
||||
// the minimum difference between the cost of the best path, and the cost of
|
||||
// this is on, and the cost of the absolute best path, under the assumption
|
||||
// that any of the currently active states at the decoding front may
|
||||
// eventually succeed (e.g. if you were to take the currently active states
|
||||
// one by one and compute this difference, and then take the minimum).
|
||||
|
||||
ForwardLink *links; // Head of singly linked list of ForwardLinks
|
||||
|
||||
Token *next; // Next in list of tokens for this frame.
|
||||
|
||||
inline Token(BaseFloat tot_cost, BaseFloat extra_cost, ForwardLink *links,
|
||||
Token *next): tot_cost(tot_cost), extra_cost(extra_cost),
|
||||
links(links), next(next) { }
|
||||
inline void DeleteForwardLinks() {
|
||||
ForwardLink *l = links, *m;
|
||||
while (l != NULL) {
|
||||
m = l->next;
|
||||
delete l;
|
||||
l = m;
|
||||
}
|
||||
links = NULL;
|
||||
}
|
||||
};
|
||||
|
||||
// head and tail of per-frame list of Tokens (list is in topological order),
|
||||
// and something saying whether we ever pruned it using PruneForwardLinks.
|
||||
struct TokenList {
|
||||
Token *toks;
|
||||
bool must_prune_forward_links;
|
||||
bool must_prune_tokens;
|
||||
TokenList(): toks(NULL), must_prune_forward_links(true),
|
||||
must_prune_tokens(true) { }
|
||||
};
|
||||
|
||||
typedef HashList<PairId, Token*>::Elem Elem;
|
||||
|
||||
void PossiblyResizeHash(size_t num_toks) {
|
||||
size_t new_sz = static_cast<size_t>(static_cast<BaseFloat>(num_toks)
|
||||
* config_.hash_ratio);
|
||||
if (new_sz > toks_.Size()) {
|
||||
toks_.SetSize(new_sz);
|
||||
}
|
||||
}
|
||||
|
||||
// FindOrAddToken either locates a token in hash of toks_,
|
||||
// or if necessary inserts a new, empty token (i.e. with no forward links)
|
||||
// for the current frame. [note: it's inserted if necessary into hash toks_
|
||||
// and also into the singly linked list of tokens active on this frame
|
||||
// (whose head is at active_toks_[frame]).
|
||||
inline Elem *FindOrAddToken(PairId state_pair, int32 frame,
|
||||
BaseFloat tot_cost, bool emitting, bool *changed) {
|
||||
// Returns the Token pointer. Sets "changed" (if non-NULL) to true
|
||||
// if the token was newly created or the cost changed.
|
||||
KALDI_ASSERT(frame < active_toks_.size());
|
||||
Token *&toks = active_toks_[frame].toks;
|
||||
Elem *e_found = toks_.Insert(state_pair, NULL);
|
||||
if (e_found->val == NULL) { // no such token presently.
|
||||
const BaseFloat extra_cost = 0.0;
|
||||
// tokens on the currently final frame have zero extra_cost
|
||||
// as any of them could end up
|
||||
// on the winning path.
|
||||
Token *new_tok = new Token (tot_cost, extra_cost, NULL, toks);
|
||||
// NULL: no forward links yet
|
||||
toks = new_tok;
|
||||
num_toks_++;
|
||||
e_found->val = new_tok;
|
||||
if (changed) *changed = true;
|
||||
return e_found;
|
||||
} else {
|
||||
Token *tok = e_found->val; // There is an existing Token for this state.
|
||||
if (tok->tot_cost > tot_cost) { // replace old token
|
||||
tok->tot_cost = tot_cost;
|
||||
// we don't allocate a new token, the old stays linked in active_toks_
|
||||
// we only replace the tot_cost
|
||||
// in the current frame, there are no forward links (and no extra_cost)
|
||||
// only in ProcessNonemitting we have to delete forward links
|
||||
// in case we visit a state for the second time
|
||||
// those forward links, that lead to this replaced token before:
|
||||
// they remain and will hopefully be pruned later (PruneForwardLinks...)
|
||||
if (changed) *changed = true;
|
||||
} else {
|
||||
if (changed) *changed = false;
|
||||
}
|
||||
return e_found;
|
||||
}
|
||||
}
|
||||
|
||||
// prunes outgoing links for all tokens in active_toks_[frame]
|
||||
// it's called by PruneActiveTokens
|
||||
// all links, that have link_extra_cost > lattice_beam are pruned
|
||||
void PruneForwardLinks(int32 frame, bool *extra_costs_changed,
|
||||
bool *links_pruned,
|
||||
BaseFloat delta) {
|
||||
// delta is the amount by which the extra_costs must change
|
||||
// If delta is larger, we'll tend to go back less far
|
||||
// toward the beginning of the file.
|
||||
// extra_costs_changed is set to true if extra_cost was changed for any token
|
||||
// links_pruned is set to true if any link in any token was pruned
|
||||
|
||||
*extra_costs_changed = false;
|
||||
*links_pruned = false;
|
||||
KALDI_ASSERT(frame >= 0 && frame < active_toks_.size());
|
||||
if (active_toks_[frame].toks == NULL ) { // empty list; should not happen.
|
||||
if (!warned_) {
|
||||
KALDI_WARN << "No tokens alive [doing pruning].. warning first "
|
||||
"time only for each utterance\n";
|
||||
warned_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
// We have to iterate until there is no more change, because the links
|
||||
// are not guaranteed to be in topological order.
|
||||
bool changed = true; // difference new minus old extra cost >= delta ?
|
||||
while (changed) {
|
||||
changed = false;
|
||||
for (Token *tok = active_toks_[frame].toks; tok != NULL; tok = tok->next) {
|
||||
ForwardLink *link, *prev_link=NULL;
|
||||
// will recompute tok_extra_cost for tok.
|
||||
BaseFloat tok_extra_cost = std::numeric_limits<BaseFloat>::infinity();
|
||||
// tok_extra_cost is the best (min) of link_extra_cost of outgoing links
|
||||
for (link = tok->links; link != NULL; ) {
|
||||
// See if we need to excise this link...
|
||||
Token *next_tok = link->next_tok;
|
||||
BaseFloat link_extra_cost = next_tok->extra_cost +
|
||||
((tok->tot_cost + link->acoustic_cost + link->graph_cost)
|
||||
- next_tok->tot_cost); // difference in brackets is >= 0
|
||||
// link_exta_cost is the difference in score between the best paths
|
||||
// through link source state and through link destination state
|
||||
KALDI_ASSERT(link_extra_cost == link_extra_cost); // check for NaN
|
||||
if (link_extra_cost > config_.lattice_beam) { // excise link
|
||||
ForwardLink *next_link = link->next;
|
||||
if (prev_link != NULL) prev_link->next = next_link;
|
||||
else tok->links = next_link;
|
||||
delete link;
|
||||
link = next_link; // advance link but leave prev_link the same.
|
||||
*links_pruned = true;
|
||||
} else { // keep the link and update the tok_extra_cost if needed.
|
||||
if (link_extra_cost < 0.0) { // this is just a precaution.
|
||||
if (link_extra_cost < -0.01)
|
||||
KALDI_WARN << "Negative extra_cost: " << link_extra_cost;
|
||||
link_extra_cost = 0.0;
|
||||
}
|
||||
if (link_extra_cost < tok_extra_cost)
|
||||
tok_extra_cost = link_extra_cost;
|
||||
prev_link = link; // move to next link
|
||||
link = link->next;
|
||||
}
|
||||
} // for all outgoing links
|
||||
if (fabs(tok_extra_cost - tok->extra_cost) > delta)
|
||||
changed = true; // difference new minus old is bigger than delta
|
||||
tok->extra_cost = tok_extra_cost;
|
||||
// will be +infinity or <= lattice_beam_.
|
||||
// infinity indicates, that no forward link survived pruning
|
||||
} // for all Token on active_toks_[frame]
|
||||
if (changed) *extra_costs_changed = true;
|
||||
|
||||
// Note: it's theoretically possible that aggressive compiler
|
||||
// optimizations could cause an infinite loop here for small delta and
|
||||
// high-dynamic-range scores.
|
||||
} // while changed
|
||||
}
|
||||
|
||||
// PruneForwardLinksFinal is a version of PruneForwardLinks that we call
|
||||
// on the final frame. If there are final tokens active, it uses
|
||||
// the final-probs for pruning, otherwise it treats all tokens as final.
|
||||
void PruneForwardLinksFinal(int32 frame) {
|
||||
KALDI_ASSERT(static_cast<size_t>(frame+1) == active_toks_.size());
|
||||
if (active_toks_[frame].toks == NULL ) // empty list; should not happen.
|
||||
KALDI_WARN << "No tokens alive at end of file\n";
|
||||
|
||||
// First go through, working out the best token (do it in parallel
|
||||
// including final-probs and not including final-probs; we'll take
|
||||
// the one with final-probs if it's valid).
|
||||
const BaseFloat infinity = std::numeric_limits<BaseFloat>::infinity();
|
||||
BaseFloat best_cost_final = infinity,
|
||||
best_cost_nofinal = infinity;
|
||||
unordered_map<Token*, BaseFloat> tok_to_final_cost;
|
||||
Elem *cur_toks = toks_.Clear(); // swapping prev_toks_ / cur_toks_
|
||||
for (Elem *e = cur_toks, *e_tail; e != NULL; e = e_tail) {
|
||||
PairId state_pair = e->key;
|
||||
StateId state = PairToState(state_pair),
|
||||
lm_state = PairToLmState(state_pair);
|
||||
Token *tok = e->val;
|
||||
BaseFloat final_cost = fst_.Final(state).Value() +
|
||||
lm_diff_fst_->Final(lm_state).Value();
|
||||
tok_to_final_cost[tok] = final_cost;
|
||||
best_cost_final = std::min(best_cost_final, tok->tot_cost + final_cost);
|
||||
best_cost_nofinal = std::min(best_cost_nofinal, tok->tot_cost);
|
||||
e_tail = e->tail;
|
||||
toks_.Delete(e);
|
||||
}
|
||||
final_active_ = (best_cost_final != infinity);
|
||||
|
||||
// Now go through tokens on this frame, pruning forward links... may have
|
||||
// to iterate a few times until there is no more change, because the list is
|
||||
// not in topological order.
|
||||
|
||||
bool changed = true;
|
||||
BaseFloat delta = 1.0e-05;
|
||||
while (changed) {
|
||||
changed = false;
|
||||
for (Token *tok = active_toks_[frame].toks; tok != NULL; tok = tok->next) {
|
||||
ForwardLink *link, *prev_link=NULL;
|
||||
// will recompute tok_extra_cost. It has a term in it that corresponds
|
||||
// to the "final-prob", so instead of initializing tok_extra_cost to infinity
|
||||
// below we set it to the difference between the (score+final_prob) of this token,
|
||||
// and the best such (score+final_prob).
|
||||
BaseFloat tok_extra_cost;
|
||||
if (final_active_) {
|
||||
BaseFloat final_cost = tok_to_final_cost[tok];
|
||||
tok_extra_cost = (tok->tot_cost + final_cost) - best_cost_final;
|
||||
} else
|
||||
tok_extra_cost = tok->tot_cost - best_cost_nofinal;
|
||||
|
||||
for (link = tok->links; link != NULL; ) {
|
||||
// See if we need to excise this link...
|
||||
Token *next_tok = link->next_tok;
|
||||
BaseFloat link_extra_cost = next_tok->extra_cost +
|
||||
((tok->tot_cost + link->acoustic_cost + link->graph_cost)
|
||||
- next_tok->tot_cost);
|
||||
if (link_extra_cost > config_.lattice_beam) { // excise link
|
||||
ForwardLink *next_link = link->next;
|
||||
if (prev_link != NULL) prev_link->next = next_link;
|
||||
else tok->links = next_link;
|
||||
delete link;
|
||||
link = next_link; // advance link but leave prev_link the same.
|
||||
} else { // keep the link and update the tok_extra_cost if needed.
|
||||
if (link_extra_cost < 0.0) { // this is just a precaution.
|
||||
if (link_extra_cost < -0.01)
|
||||
KALDI_WARN << "Negative extra_cost: " << link_extra_cost;
|
||||
link_extra_cost = 0.0;
|
||||
}
|
||||
if (link_extra_cost < tok_extra_cost)
|
||||
tok_extra_cost = link_extra_cost;
|
||||
prev_link = link;
|
||||
link = link->next;
|
||||
}
|
||||
}
|
||||
// prune away tokens worse than lattice_beam above best path. This step
|
||||
// was not necessary in the non-final case because then, this case
|
||||
// showed up as having no forward links. Here, the tok_extra_cost has
|
||||
// an extra component relating to the final-prob.
|
||||
if (tok_extra_cost > config_.lattice_beam)
|
||||
tok_extra_cost = infinity;
|
||||
// to be pruned in PruneTokensForFrame
|
||||
|
||||
if (!ApproxEqual(tok->extra_cost, tok_extra_cost, delta))
|
||||
changed = true;
|
||||
tok->extra_cost = tok_extra_cost; // will be +infinity or <= lattice_beam_.
|
||||
}
|
||||
} // while changed
|
||||
|
||||
// Now put surviving Tokens in the final_costs_ hash, which is a class
|
||||
// member (unlike tok_to_final_costs).
|
||||
for (Token *tok = active_toks_[frame].toks; tok != NULL; tok = tok->next) {
|
||||
if (tok->extra_cost != infinity) {
|
||||
// If the token was not pruned away,
|
||||
if (final_active_) {
|
||||
BaseFloat final_cost = tok_to_final_cost[tok];
|
||||
if (final_cost != infinity)
|
||||
final_costs_[tok] = final_cost;
|
||||
} else {
|
||||
final_costs_[tok] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Prune away any tokens on this frame that have no forward links.
|
||||
// [we don't do this in PruneForwardLinks because it would give us
|
||||
// a problem with dangling pointers].
|
||||
// It's called by PruneActiveTokens if any forward links have been pruned
|
||||
void PruneTokensForFrame(int32 frame) {
|
||||
KALDI_ASSERT(frame >= 0 && frame < active_toks_.size());
|
||||
Token *&toks = active_toks_[frame].toks;
|
||||
if (toks == NULL)
|
||||
KALDI_WARN << "No tokens alive [doing pruning]\n";
|
||||
Token *tok, *next_tok, *prev_tok = NULL;
|
||||
for (tok = toks; tok != NULL; tok = next_tok) {
|
||||
next_tok = tok->next;
|
||||
if (tok->extra_cost == std::numeric_limits<BaseFloat>::infinity()) {
|
||||
// token is unreachable from end of graph; (no forward links survived)
|
||||
// excise tok from list and delete tok.
|
||||
if (prev_tok != NULL) prev_tok->next = tok->next;
|
||||
else toks = tok->next;
|
||||
delete tok;
|
||||
num_toks_--;
|
||||
} else { // fetch next Token
|
||||
prev_tok = tok;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Go backwards through still-alive tokens, pruning them. note: cur_frame is
|
||||
// where hash toks_ are (so we do not want to mess with it because these tokens
|
||||
// don't yet have forward pointers), but we do all previous frames, unless we
|
||||
// know that we can safely ignore them because the frame after them was unchanged.
|
||||
// delta controls when it considers a cost to have changed enough to continue
|
||||
// going backward and propagating the change.
|
||||
// for a larger delta, we will recurse less far back
|
||||
void PruneActiveTokens(int32 cur_frame, BaseFloat delta) {
|
||||
int32 num_toks_begin = num_toks_;
|
||||
for (int32 frame = cur_frame-1; frame >= 0; frame--) {
|
||||
// Reason why we need to prune forward links in this situation:
|
||||
// (1) we have never pruned them (new TokenList)
|
||||
// (2) we have not yet pruned the forward links to the next frame,
|
||||
// after any of those tokens have changed their extra_cost.
|
||||
if (active_toks_[frame].must_prune_forward_links) {
|
||||
bool extra_costs_changed = false, links_pruned = false;
|
||||
PruneForwardLinks(frame, &extra_costs_changed, &links_pruned, delta);
|
||||
if (extra_costs_changed && frame > 0) // any token has changed extra_cost
|
||||
active_toks_[frame-1].must_prune_forward_links = true;
|
||||
if (links_pruned) // any link was pruned
|
||||
active_toks_[frame].must_prune_tokens = true;
|
||||
active_toks_[frame].must_prune_forward_links = false; // job done
|
||||
}
|
||||
if (frame+1 < cur_frame && // except for last frame (no forward links)
|
||||
active_toks_[frame+1].must_prune_tokens) {
|
||||
PruneTokensForFrame(frame+1);
|
||||
active_toks_[frame+1].must_prune_tokens = false;
|
||||
}
|
||||
}
|
||||
KALDI_VLOG(3) << "PruneActiveTokens: pruned tokens from " << num_toks_begin
|
||||
<< " to " << num_toks_;
|
||||
}
|
||||
|
||||
// Version of PruneActiveTokens that we call on the final frame.
|
||||
// Takes into account the final-prob of tokens.
|
||||
void PruneActiveTokensFinal(int32 cur_frame) {
|
||||
// returns true if there were final states active
|
||||
// else returns false and treats all states as final while doing the pruning
|
||||
// (this can be useful if you want partial lattice output,
|
||||
// although it can be dangerous, depending what you want the lattices for).
|
||||
// final_active_ and final_probs_ (a hash) are set internally
|
||||
// by PruneForwardLinksFinal
|
||||
int32 num_toks_begin = num_toks_;
|
||||
PruneForwardLinksFinal(cur_frame); // prune final frame (with final-probs)
|
||||
// sets final_active_ and final_probs_
|
||||
for (int32 frame = cur_frame-1; frame >= 0; frame--) {
|
||||
bool b1, b2; // values not used.
|
||||
BaseFloat dontcare = 0.0; // delta of zero means we must always update
|
||||
PruneForwardLinks(frame, &b1, &b2, dontcare);
|
||||
PruneTokensForFrame(frame+1);
|
||||
}
|
||||
PruneTokensForFrame(0);
|
||||
KALDI_VLOG(3) << "PruneActiveTokensFinal: pruned tokens from " << num_toks_begin
|
||||
<< " to " << num_toks_;
|
||||
}
|
||||
|
||||
/// Gets the weight cutoff. Also counts the active tokens.
|
||||
BaseFloat GetCutoff(Elem *list_head, size_t *tok_count,
|
||||
BaseFloat *adaptive_beam, Elem **best_elem) {
|
||||
BaseFloat best_weight = std::numeric_limits<BaseFloat>::infinity();
|
||||
// positive == high cost == bad.
|
||||
size_t count = 0;
|
||||
if (config_.max_active == std::numeric_limits<int32>::max()) {
|
||||
for (Elem *e = list_head; e != NULL; e = e->tail, count++) {
|
||||
BaseFloat w = static_cast<BaseFloat>(e->val->tot_cost);
|
||||
if (w < best_weight) {
|
||||
best_weight = w;
|
||||
if (best_elem) *best_elem = e;
|
||||
}
|
||||
}
|
||||
if (tok_count != NULL) *tok_count = count;
|
||||
if (adaptive_beam != NULL) *adaptive_beam = config_.beam;
|
||||
return best_weight + config_.beam;
|
||||
} else {
|
||||
tmp_array_.clear();
|
||||
for (Elem *e = list_head; e != NULL; e = e->tail, count++) {
|
||||
BaseFloat w = e->val->tot_cost;
|
||||
tmp_array_.push_back(w);
|
||||
if (w < best_weight) {
|
||||
best_weight = w;
|
||||
if (best_elem) *best_elem = e;
|
||||
}
|
||||
}
|
||||
if (tok_count != NULL) *tok_count = count;
|
||||
if (tmp_array_.size() <= static_cast<size_t>(config_.max_active)) {
|
||||
if (adaptive_beam) *adaptive_beam = config_.beam;
|
||||
return best_weight + config_.beam;
|
||||
} else {
|
||||
// the lowest elements (lowest costs, highest likes)
|
||||
// will be put in the left part of tmp_array.
|
||||
std::nth_element(tmp_array_.begin(),
|
||||
tmp_array_.begin()+config_.max_active,
|
||||
tmp_array_.end());
|
||||
// return the tighter of the two beams.
|
||||
BaseFloat ans = std::min(best_weight + config_.beam,
|
||||
*(tmp_array_.begin()+config_.max_active));
|
||||
if (adaptive_beam)
|
||||
*adaptive_beam = std::min(config_.beam,
|
||||
ans - best_weight + config_.beam_delta);
|
||||
return ans;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline StateId PropagateLm(StateId lm_state,
|
||||
Arc *arc) { // returns new LM state.
|
||||
if (arc->olabel == 0) {
|
||||
return lm_state; // no change in LM state if no word crossed.
|
||||
} else { // Propagate in the LM-diff FST.
|
||||
Arc lm_arc;
|
||||
bool ans = lm_diff_fst_->GetArc(lm_state, arc->olabel, &lm_arc);
|
||||
if (!ans) { // this case is unexpected for statistical LMs.
|
||||
if (!warned_noarc_) {
|
||||
warned_noarc_ = true;
|
||||
KALDI_WARN << "No arc available in LM (unlikely to be correct "
|
||||
"if a statistical language model); will not warn again";
|
||||
}
|
||||
arc->weight = Weight::Zero();
|
||||
return lm_state; // doesn't really matter what we return here; will
|
||||
// be pruned.
|
||||
} else {
|
||||
arc->weight = Times(arc->weight, lm_arc.weight);
|
||||
arc->olabel = lm_arc.olabel; // probably will be the same.
|
||||
return lm_arc.nextstate; // return the new LM state.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProcessEmitting(DecodableInterface *decodable, int32 frame) {
|
||||
// Processes emitting arcs for one frame. Propagates from prev_toks_ to cur_toks_.
|
||||
Elem *last_toks = toks_.Clear(); // swapping prev_toks_ / cur_toks_
|
||||
Elem *best_elem = NULL;
|
||||
BaseFloat adaptive_beam;
|
||||
size_t tok_cnt;
|
||||
BaseFloat cur_cutoff = GetCutoff(last_toks, &tok_cnt, &adaptive_beam, &best_elem);
|
||||
PossiblyResizeHash(tok_cnt); // This makes sure the hash is always big enough.
|
||||
|
||||
BaseFloat next_cutoff = std::numeric_limits<BaseFloat>::infinity();
|
||||
// pruning "online" before having seen all tokens
|
||||
|
||||
// First process the best token to get a hopefully
|
||||
// reasonably tight bound on the next cutoff.
|
||||
if (best_elem) {
|
||||
PairId state_pair = best_elem->key;
|
||||
StateId state = PairToState(state_pair), // state in "fst"
|
||||
lm_state = PairToLmState(state_pair);
|
||||
Token *tok = best_elem->val;
|
||||
for (fst::ArcIterator<fst::Fst<Arc> > aiter(fst_, state);
|
||||
!aiter.Done();
|
||||
aiter.Next()) {
|
||||
Arc arc = aiter.Value();
|
||||
if (arc.ilabel != 0) { // propagate..
|
||||
PropagateLm(lm_state, &arc); // may affect "arc.weight".
|
||||
// We don't need the return value (the new LM state).
|
||||
arc.weight = Times(arc.weight,
|
||||
Weight(-decodable->LogLikelihood(frame-1, arc.ilabel)));
|
||||
BaseFloat new_weight = arc.weight.Value() + tok->tot_cost;
|
||||
if (new_weight + adaptive_beam < next_cutoff)
|
||||
next_cutoff = new_weight + adaptive_beam;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the tokens are now owned here, in last_toks, and the hash is empty.
|
||||
// 'owned' is a complex thing here; the point is we need to call DeleteElem
|
||||
// on each elem 'e' to let toks_ know we're done with them.
|
||||
for (Elem *e = last_toks, *e_tail; e != NULL; e = e_tail) {
|
||||
// loop this way because we delete "e" as we go.
|
||||
PairId state_pair = e->key;
|
||||
StateId state = PairToState(state_pair),
|
||||
lm_state = PairToLmState(state_pair);
|
||||
Token *tok = e->val;
|
||||
if (tok->tot_cost <= cur_cutoff) {
|
||||
for (fst::ArcIterator<fst::Fst<Arc> > aiter(fst_, state);
|
||||
!aiter.Done();
|
||||
aiter.Next()) {
|
||||
const Arc &arc_ref = aiter.Value();
|
||||
if (arc_ref.ilabel != 0) { // propagate..
|
||||
Arc arc(arc_ref);
|
||||
StateId next_lm_state = PropagateLm(lm_state, &arc);
|
||||
BaseFloat ac_cost = -decodable->LogLikelihood(frame-1, arc.ilabel),
|
||||
graph_cost = arc.weight.Value(),
|
||||
cur_cost = tok->tot_cost,
|
||||
tot_cost = cur_cost + ac_cost + graph_cost;
|
||||
if (tot_cost >= next_cutoff) continue;
|
||||
else if (tot_cost + adaptive_beam < next_cutoff)
|
||||
next_cutoff = tot_cost + adaptive_beam; // prune by best current token
|
||||
PairId next_pair = ConstructPair(arc.nextstate, next_lm_state);
|
||||
Elem *e_next = FindOrAddToken(next_pair, frame, tot_cost, true, NULL);
|
||||
// true: emitting, NULL: no change indicator needed
|
||||
|
||||
// Add ForwardLink from tok to next_tok (put on head of list tok->links)
|
||||
tok->links = new ForwardLink(e_next->val, arc.ilabel, arc.olabel,
|
||||
graph_cost, ac_cost, tok->links);
|
||||
}
|
||||
} // for all arcs
|
||||
}
|
||||
e_tail = e->tail;
|
||||
toks_.Delete(e); // delete Elem
|
||||
}
|
||||
}
|
||||
|
||||
void ProcessNonemitting(int32 frame) {
|
||||
// note: "frame" is the same as emitting states just processed.
|
||||
|
||||
// Processes nonemitting arcs for one frame. Propagates within toks_.
|
||||
// Note-- this queue structure is is not very optimal as
|
||||
// it may cause us to process states unnecessarily (e.g. more than once),
|
||||
// but in the baseline code, turning this vector into a set to fix this
|
||||
// problem did not improve overall speed.
|
||||
|
||||
KALDI_ASSERT(queue_.empty());
|
||||
BaseFloat best_cost = std::numeric_limits<BaseFloat>::infinity();
|
||||
for (const Elem *e = toks_.GetList(); e != NULL; e = e->tail) {
|
||||
queue_.push_back(e);
|
||||
// for pruning with current best token
|
||||
best_cost = std::min(best_cost, static_cast<BaseFloat>(e->val->tot_cost));
|
||||
}
|
||||
if (queue_.empty()) {
|
||||
if (!warned_) {
|
||||
KALDI_ERR << "Error in ProcessNonemitting: no surviving tokens: frame is "
|
||||
<< frame;
|
||||
warned_ = true;
|
||||
}
|
||||
}
|
||||
BaseFloat cutoff = best_cost + config_.beam;
|
||||
|
||||
while (!queue_.empty()) {
|
||||
const Elem *e = queue_.back();
|
||||
queue_.pop_back();
|
||||
|
||||
PairId state_pair = e->key;
|
||||
Token *tok = e->val; // would segfault if state not in
|
||||
// toks_ but this can't happen.
|
||||
BaseFloat cur_cost = tok->tot_cost;
|
||||
if (cur_cost >= cutoff) // Don't bother processing successors.
|
||||
continue;
|
||||
StateId state = PairToState(state_pair),
|
||||
lm_state = PairToLmState(state_pair);
|
||||
// If "tok" has any existing forward links, delete them,
|
||||
// because we're about to regenerate them. This is a kind
|
||||
// of non-optimality (remember, this is the simple decoder),
|
||||
// but since most states are emitting it's not a huge issue.
|
||||
tok->DeleteForwardLinks(); // necessary when re-visiting
|
||||
tok->links = NULL;
|
||||
for (fst::ArcIterator<fst::Fst<Arc> > aiter(fst_, state);
|
||||
!aiter.Done();
|
||||
aiter.Next()) {
|
||||
const Arc &arc_ref = aiter.Value();
|
||||
if (arc_ref.ilabel == 0) { // propagate nonemitting only...
|
||||
Arc arc(arc_ref);
|
||||
StateId next_lm_state = PropagateLm(lm_state, &arc);
|
||||
BaseFloat graph_cost = arc.weight.Value(),
|
||||
tot_cost = cur_cost + graph_cost;
|
||||
if (tot_cost < cutoff) {
|
||||
bool changed;
|
||||
PairId next_pair = ConstructPair(arc.nextstate, next_lm_state);
|
||||
Elem *e_new = FindOrAddToken(next_pair, frame, tot_cost,
|
||||
false, &changed); // false: non-emit
|
||||
|
||||
tok->links = new ForwardLink(e_new->val, 0, arc.olabel,
|
||||
graph_cost, 0, tok->links);
|
||||
|
||||
// "changed" tells us whether the new token has a different
|
||||
// cost from before, or is new [if so, add into queue].
|
||||
if (changed) queue_.push_back(e_new);
|
||||
}
|
||||
}
|
||||
} // for all arcs
|
||||
} // while queue not empty
|
||||
}
|
||||
|
||||
|
||||
// HashList defined in ../util/hash-list.h. It actually allows us to maintain
|
||||
// more than one list (e.g. for current and previous frames), but only one of
|
||||
// them at a time can be indexed by StateId.
|
||||
HashList<PairId, Token*> toks_;
|
||||
std::vector<TokenList> active_toks_; // Lists of tokens, indexed by
|
||||
// frame (members of TokenList are toks, must_prune_forward_links,
|
||||
// must_prune_tokens).
|
||||
std::vector<const Elem* > queue_; // temp variable used in ProcessNonemitting,
|
||||
std::vector<BaseFloat> tmp_array_; // used in GetCutoff.
|
||||
// make it class member to avoid internal new/delete.
|
||||
const fst::Fst<fst::StdArc> &fst_;
|
||||
fst::DeterministicOnDemandFst<fst::StdArc> *lm_diff_fst_;
|
||||
LatticeBiglmFasterDecoderConfig config_;
|
||||
bool warned_noarc_;
|
||||
int32 num_toks_; // current total #toks allocated...
|
||||
bool warned_;
|
||||
bool final_active_; // use this to say whether we found active final tokens
|
||||
// on the last frame.
|
||||
std::map<Token*, BaseFloat> final_costs_; // A cache of final-costs
|
||||
// of tokens on the last frame-- it's just convenient to store it this way.
|
||||
|
||||
// It might seem unclear why we call DeleteElems(toks_.Clear()).
|
||||
// There are two separate cleanup tasks we need to do at when we start a new file.
|
||||
// one is to delete the Token objects in the list; the other is to delete
|
||||
// the Elem objects. toks_.Clear() just clears them from the hash and gives ownership
|
||||
// to the caller, who then has to call toks_.Delete(e) for each one. It was designed
|
||||
// this way for convenience in propagating tokens from one frame to the next.
|
||||
void DeleteElems(Elem *list) {
|
||||
for (Elem *e = list, *e_tail; e != NULL; e = e_tail) {
|
||||
e_tail = e->tail;
|
||||
toks_.Delete(e);
|
||||
}
|
||||
toks_.Clear();
|
||||
}
|
||||
|
||||
void ClearActiveTokens() { // a cleanup routine, at utt end/begin
|
||||
for (size_t i = 0; i < active_toks_.size(); i++) {
|
||||
// Delete all tokens alive on this frame, and any forward
|
||||
// links they may have.
|
||||
for (Token *tok = active_toks_[i].toks; tok != NULL; ) {
|
||||
tok->DeleteForwardLinks();
|
||||
Token *next_tok = tok->next;
|
||||
delete tok;
|
||||
num_toks_--;
|
||||
tok = next_tok;
|
||||
}
|
||||
}
|
||||
active_toks_.clear();
|
||||
KALDI_ASSERT(num_toks_ == 0);
|
||||
}
|
||||
};
|
||||
|
||||
} // end namespace kaldi.
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,597 @@
|
||||
// decoder/lattice-faster-decoder.h
|
||||
|
||||
// Copyright 2009-2013 Microsoft Corporation; Mirko Hannemann;
|
||||
// 2013-2014 Johns Hopkins University (Author: Daniel Povey)
|
||||
// 2014 Guoguo Chen
|
||||
// 2018 Zhehuai Chen
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef KALDI_DECODER_LATTICE_FASTER_DECODER_H_
|
||||
#define KALDI_DECODER_LATTICE_FASTER_DECODER_H_
|
||||
|
||||
//#include "decoder/grammar-fst.h"
|
||||
#include "fst/fstlib.h"
|
||||
#include "fst/memory.h"
|
||||
#include "fstext/fstext-lib.h"
|
||||
#include "itf/decodable-itf.h"
|
||||
#include "lat/determinize-lattice-pruned.h"
|
||||
#include "lat/kaldi-lattice.h"
|
||||
#include "util/hash-list.h"
|
||||
#include "util/stl-utils.h"
|
||||
#include "bias-lm.h"
|
||||
|
||||
namespace kaldi {
|
||||
|
||||
struct LatticeFasterDecoderConfig {
|
||||
BaseFloat beam;
|
||||
int32 max_active;
|
||||
int32 min_active;
|
||||
BaseFloat lattice_beam;
|
||||
int32 prune_interval;
|
||||
bool determinize_lattice; // not inspected by this class... used in
|
||||
// command-line program.
|
||||
BaseFloat beam_delta;
|
||||
BaseFloat hash_ratio;
|
||||
// Note: we don't make prune_scale configurable on the command line, it's not
|
||||
// a very important parameter. It affects the algorithm that prunes the
|
||||
// tokens as we go.
|
||||
BaseFloat prune_scale;
|
||||
|
||||
// Number of elements in the block for Token and ForwardLink memory
|
||||
// pool allocation.
|
||||
int32 memory_pool_tokens_block_size;
|
||||
int32 memory_pool_links_block_size;
|
||||
|
||||
// Most of the options inside det_opts are not actually queried by the
|
||||
// LatticeFasterDecoder class itself, but by the code that calls it, for
|
||||
// example in the function DecodeUtteranceLatticeFaster.
|
||||
fst::DeterminizeLatticePhonePrunedOptions det_opts;
|
||||
|
||||
LatticeFasterDecoderConfig(float glob_beam, float lat_beam)
|
||||
: beam(glob_beam),
|
||||
max_active(std::numeric_limits<int32>::max()),
|
||||
min_active(200),
|
||||
lattice_beam(lat_beam),
|
||||
prune_interval(25),
|
||||
determinize_lattice(true),
|
||||
beam_delta(0.5),
|
||||
hash_ratio(2.0),
|
||||
prune_scale(0.1),
|
||||
memory_pool_tokens_block_size(1 << 8),
|
||||
memory_pool_links_block_size(1 << 8) {}
|
||||
|
||||
LatticeFasterDecoderConfig()
|
||||
: beam(3.0),
|
||||
max_active(std::numeric_limits<int32>::max()),
|
||||
min_active(200),
|
||||
lattice_beam(3.0),
|
||||
prune_interval(25),
|
||||
determinize_lattice(true),
|
||||
beam_delta(0.5),
|
||||
hash_ratio(2.0),
|
||||
prune_scale(0.1),
|
||||
memory_pool_tokens_block_size(1 << 8),
|
||||
memory_pool_links_block_size(1 << 8) {}
|
||||
void Register(OptionsItf *opts) {
|
||||
det_opts.Register(opts);
|
||||
opts->Register("beam", &beam, "Decoding beam. Larger->slower, more accurate.");
|
||||
opts->Register("max-active", &max_active, "Decoder max active states. Larger->slower; "
|
||||
"more accurate");
|
||||
opts->Register("min-active", &min_active, "Decoder minimum #active states.");
|
||||
opts->Register("lattice-beam", &lattice_beam, "Lattice generation beam. Larger->slower, "
|
||||
"and deeper lattices");
|
||||
opts->Register("prune-interval", &prune_interval, "Interval (in frames) at "
|
||||
"which to prune tokens");
|
||||
opts->Register("determinize-lattice", &determinize_lattice, "If true, "
|
||||
"determinize the lattice (lattice-determinization, keeping only "
|
||||
"best pdf-sequence for each word-sequence).");
|
||||
opts->Register("beam-delta", &beam_delta, "Increment used in decoding-- this "
|
||||
"parameter is obscure and relates to a speedup in the way the "
|
||||
"max-active constraint is applied. Larger is more accurate.");
|
||||
opts->Register("hash-ratio", &hash_ratio, "Setting used in decoder to "
|
||||
"control hash behavior");
|
||||
opts->Register("memory-pool-tokens-block-size", &memory_pool_tokens_block_size,
|
||||
"Memory pool block size suggestion for storing tokens (in elements). "
|
||||
"Smaller uses less memory but increases cache misses.");
|
||||
opts->Register("memory-pool-links-block-size", &memory_pool_links_block_size,
|
||||
"Memory pool block size suggestion for storing links (in elements). "
|
||||
"Smaller uses less memory but increases cache misses.");
|
||||
}
|
||||
void Check() const {
|
||||
KALDI_ASSERT(beam > 0.0 && max_active > 1 && lattice_beam > 0.0
|
||||
&& min_active <= max_active
|
||||
&& prune_interval > 0 && beam_delta > 0.0 && hash_ratio >= 1.0
|
||||
&& prune_scale > 0.0 && prune_scale < 1.0);
|
||||
}
|
||||
};
|
||||
|
||||
namespace decoder {
|
||||
// We will template the decoder on the token type as well as the FST type; this
|
||||
// is a mechanism so that we can use the same underlying decoder code for
|
||||
// versions of the decoder that support quickly getting the best path
|
||||
// (LatticeFasterOnlineDecoder, see lattice-faster-online-decoder.h) and also
|
||||
// those that do not (LatticeFasterDecoder).
|
||||
|
||||
|
||||
// ForwardLinks are the links from a token to a token on the next frame.
|
||||
// or sometimes on the current frame (for input-epsilon links).
|
||||
template <typename Token>
|
||||
struct ForwardLink {
|
||||
using Label = fst::StdArc::Label;
|
||||
|
||||
Token *next_tok; // the next token [or NULL if represents final-state]
|
||||
Label ilabel; // ilabel on arc
|
||||
Label olabel; // olabel on arc
|
||||
BaseFloat graph_cost; // graph cost of traversing arc (contains LM, etc.)
|
||||
BaseFloat acoustic_cost; // acoustic cost (pre-scaled) of traversing arc
|
||||
ForwardLink *next; // next in singly-linked list of forward arcs (arcs
|
||||
// in the state-level lattice) from a token.
|
||||
inline ForwardLink(Token *next_tok, Label ilabel, Label olabel,
|
||||
BaseFloat graph_cost, BaseFloat acoustic_cost,
|
||||
ForwardLink *next):
|
||||
next_tok(next_tok), ilabel(ilabel), olabel(olabel),
|
||||
graph_cost(graph_cost), acoustic_cost(acoustic_cost),
|
||||
next(next) { }
|
||||
};
|
||||
|
||||
|
||||
struct StdToken {
|
||||
using ForwardLinkT = ForwardLink<StdToken>;
|
||||
using Token = StdToken;
|
||||
|
||||
// Standard token type for LatticeFasterDecoder. Each active HCLG
|
||||
// (decoding-graph) state on each frame has one token.
|
||||
|
||||
// tot_cost is the total (LM + acoustic) cost from the beginning of the
|
||||
// utterance up to this point. (but see cost_offset_, which is subtracted
|
||||
// to keep it in a good numerical range).
|
||||
BaseFloat tot_cost;
|
||||
|
||||
// exta_cost is >= 0. After calling PruneForwardLinks, this equals the
|
||||
// minimum difference between the cost of the best path that this link is a
|
||||
// part of, and the cost of the absolute best path, under the assumption that
|
||||
// any of the currently active states at the decoding front may eventually
|
||||
// succeed (e.g. if you were to take the currently active states one by one
|
||||
// and compute this difference, and then take the minimum).
|
||||
BaseFloat extra_cost;
|
||||
// 'links' is the head of singly-linked list of ForwardLinks, which is what we
|
||||
// use for lattice generation.
|
||||
ForwardLinkT *links;
|
||||
|
||||
//'next' is the next in the singly-linked list of tokens for this frame.
|
||||
Token *next;
|
||||
|
||||
// bias_lm_state is used to record the state of tokens in the bias lm network
|
||||
LatticeArc::StateId bias_lm_state;
|
||||
|
||||
// This function does nothing and should be optimized out; it's needed
|
||||
// so we can share the regular LatticeFasterDecoderTpl code and the code
|
||||
// for LatticeFasterOnlineDecoder that supports fast traceback.
|
||||
inline void SetBackpointer (Token *backpointer) { }
|
||||
|
||||
// This constructor just ignores the 'backpointer' argument. That argument is
|
||||
// needed so that we can use the same decoder code for LatticeFasterDecoderTpl
|
||||
// and LatticeFasterOnlineDecoderTpl (which needs backpointers to support a
|
||||
// fast way to obtain the best path).
|
||||
inline StdToken(BaseFloat tot_cost, BaseFloat extra_cost, ForwardLinkT *links,
|
||||
Token *next, Token *backpointer):
|
||||
tot_cost(tot_cost), extra_cost(extra_cost), links(links), next(next), bias_lm_state(0) { }
|
||||
|
||||
inline void GetLabelSeq(Token *tok, std::vector<int> &phn_id) {}
|
||||
};
|
||||
|
||||
struct BackpointerToken {
|
||||
using ForwardLinkT = ForwardLink<BackpointerToken>;
|
||||
using Token = BackpointerToken;
|
||||
|
||||
// BackpointerToken is like Token but also
|
||||
// Standard token type for LatticeFasterDecoder. Each active HCLG
|
||||
// (decoding-graph) state on each frame has one token.
|
||||
|
||||
// tot_cost is the total (LM + acoustic) cost from the beginning of the
|
||||
// utterance up to this point. (but see cost_offset_, which is subtracted
|
||||
// to keep it in a good numerical range).
|
||||
BaseFloat tot_cost;
|
||||
|
||||
// exta_cost is >= 0. After calling PruneForwardLinks, this equals
|
||||
// the minimum difference between the cost of the best path, and the cost of
|
||||
// this is on, and the cost of the absolute best path, under the assumption
|
||||
// that any of the currently active states at the decoding front may
|
||||
// eventually succeed (e.g. if you were to take the currently active states
|
||||
// one by one and compute this difference, and then take the minimum).
|
||||
BaseFloat extra_cost;
|
||||
|
||||
// 'links' is the head of singly-linked list of ForwardLinks, which is what we
|
||||
// use for lattice generation.
|
||||
ForwardLinkT *links;
|
||||
|
||||
//'next' is the next in the singly-linked list of tokens for this frame.
|
||||
BackpointerToken *next;
|
||||
|
||||
// Best preceding BackpointerToken (could be a on this frame, connected to
|
||||
// this via an epsilon transition, or on a previous frame). This is only
|
||||
// required for an efficient GetBestPath function in
|
||||
// LatticeFasterOnlineDecoderTpl; it plays no part in the lattice generation
|
||||
// (the "links" list is what stores the forward links, for that).
|
||||
Token *backpointer;
|
||||
|
||||
// bias_lm_state is used to record the state of tokens in the bias lm network
|
||||
LatticeArc::StateId bias_lm_state;
|
||||
|
||||
inline void SetBackpointer (Token *backpointer) {
|
||||
this->backpointer = backpointer;
|
||||
}
|
||||
|
||||
inline BackpointerToken(BaseFloat tot_cost, BaseFloat extra_cost, ForwardLinkT *links,
|
||||
Token *next, Token *backpointer):
|
||||
tot_cost(tot_cost), extra_cost(extra_cost), links(links), next(next),
|
||||
backpointer(backpointer), bias_lm_state(0) { }
|
||||
|
||||
inline void GetLabelSeq(Token *token, std::vector<int> &phn_id) {
|
||||
ForwardLinkT* link;
|
||||
Token *tok = token;
|
||||
while (tok && tok->backpointer) {
|
||||
for (link = tok->backpointer->links; link != NULL; link = link->next) {
|
||||
if (link->next_tok == tok) {
|
||||
phn_id.push_back(link->ilabel - 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
tok = tok->backpointer;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace decoder
|
||||
|
||||
|
||||
/** This is the "normal" lattice-generating decoder.
|
||||
See \ref lattices_generation \ref decoders_faster and \ref decoders_simple
|
||||
for more information.
|
||||
|
||||
The decoder is templated on the FST type and the token type. The token type
|
||||
will normally be StdToken, but also may be BackpointerToken which is to support
|
||||
quick lookup of the current best path (see lattice-faster-online-decoder.h)
|
||||
|
||||
The FST you invoke this decoder which is expected to equal
|
||||
Fst::Fst<fst::StdArc>, a.k.a. StdFst, or GrammarFst. If you invoke it with
|
||||
FST == StdFst and it notices that the actual FST type is
|
||||
fst::VectorFst<fst::StdArc> or fst::ConstFst<fst::StdArc>, the decoder object
|
||||
will internally cast itself to one that is templated on those more specific
|
||||
types; this is an optimization for speed.
|
||||
*/
|
||||
template <typename FST, typename Token = decoder::StdToken>
|
||||
class LatticeFasterDecoderTpl {
|
||||
public:
|
||||
using Arc = typename FST::Arc;
|
||||
using Label = typename Arc::Label;
|
||||
using StateId = typename Arc::StateId;
|
||||
using Weight = typename Arc::Weight;
|
||||
using ForwardLinkT = decoder::ForwardLink<Token>;
|
||||
|
||||
// Instantiate this class once for each thing you have to decode.
|
||||
// This version of the constructor does not take ownership of
|
||||
// 'fst'.
|
||||
LatticeFasterDecoderTpl(const FST &fst,
|
||||
const LatticeFasterDecoderConfig &config);
|
||||
|
||||
// This version of the constructor takes ownership of the fst, and will delete
|
||||
// it when this object is destroyed.
|
||||
LatticeFasterDecoderTpl(const LatticeFasterDecoderConfig &config,
|
||||
FST *fst);
|
||||
//LatticeFasterDecoderTpl() { }
|
||||
|
||||
void SetOptions(const LatticeFasterDecoderConfig &config) {
|
||||
config_ = config;
|
||||
}
|
||||
|
||||
const LatticeFasterDecoderConfig &GetOptions() const {
|
||||
return config_;
|
||||
}
|
||||
|
||||
~LatticeFasterDecoderTpl();
|
||||
|
||||
/// Decodes until there are no more frames left in the "decodable" object..
|
||||
/// note, this may block waiting for input if the "decodable" object blocks.
|
||||
/// Returns true if any kind of traceback is available (not necessarily from a
|
||||
/// final state).
|
||||
bool Decode(DecodableInterface *decodable);
|
||||
|
||||
|
||||
/// says whether a final-state was active on the last frame. If it was not, the
|
||||
/// lattice (or traceback) will end with states that are not final-states.
|
||||
bool ReachedFinal() const {
|
||||
return FinalRelativeCost() != std::numeric_limits<BaseFloat>::infinity();
|
||||
}
|
||||
|
||||
/// Outputs an FST corresponding to the single best path through the lattice.
|
||||
/// Returns true if result is nonempty (using the return status is deprecated,
|
||||
/// it will become void). If "use_final_probs" is true AND we reached the
|
||||
/// final-state of the graph then it will include those as final-probs, else
|
||||
/// it will treat all final-probs as one. Note: this just calls GetRawLattice()
|
||||
/// and figures out the shortest path.
|
||||
bool GetBestPath(Lattice *ofst,
|
||||
bool use_final_probs = true) const;
|
||||
|
||||
/// Outputs an FST corresponding to the raw, state-level
|
||||
/// tracebacks. Returns true if result is nonempty.
|
||||
/// If "use_final_probs" is true AND we reached the final-state
|
||||
/// of the graph then it will include those as final-probs, else
|
||||
/// it will treat all final-probs as one.
|
||||
/// The raw lattice will be topologically sorted.
|
||||
///
|
||||
/// See also GetRawLatticePruned in lattice-faster-online-decoder.h,
|
||||
/// which also supports a pruning beam, in case for some reason
|
||||
/// you want it pruned tighter than the regular lattice beam.
|
||||
/// We could put that here in future needed.
|
||||
bool GetRawLattice(Lattice *ofst, bool use_final_probs = true) const;
|
||||
|
||||
|
||||
|
||||
/// [Deprecated, users should now use GetRawLattice and determinize it
|
||||
/// themselves, e.g. using DeterminizeLatticePhonePrunedWrapper].
|
||||
/// Outputs an FST corresponding to the lattice-determinized
|
||||
/// lattice (one path per word sequence). Returns true if result is nonempty.
|
||||
/// If "use_final_probs" is true AND we reached the final-state of the graph
|
||||
/// then it will include those as final-probs, else it will treat all
|
||||
/// final-probs as one.
|
||||
bool GetLattice(CompactLattice *ofst,
|
||||
bool use_final_probs = true) const;
|
||||
|
||||
/// InitDecoding initializes the decoding, and should only be used if you
|
||||
/// intend to call AdvanceDecoding(). If you call Decode(), you don't need to
|
||||
/// call this. You can also call InitDecoding if you have already decoded an
|
||||
/// utterance and want to start with a new utterance.
|
||||
void InitDecoding();
|
||||
|
||||
/// This will decode until there are no more frames ready in the decodable
|
||||
/// object. You can keep calling it each time more frames become available.
|
||||
/// If max_num_frames is specified, it specifies the maximum number of frames
|
||||
/// the function will decode before returning.
|
||||
void AdvanceDecoding(DecodableInterface *decodable,
|
||||
int32 max_num_frames = -1);
|
||||
|
||||
/// This function may be optionally called after AdvanceDecoding(), when you
|
||||
/// do not plan to decode any further. It does an extra pruning step that
|
||||
/// will help to prune the lattices output by GetLattice and (particularly)
|
||||
/// GetRawLattice more completely, particularly toward the end of the
|
||||
/// utterance. If you call this, you cannot call AdvanceDecoding again (it
|
||||
/// will fail), and you cannot call GetLattice() and related functions with
|
||||
/// use_final_probs = false. Used to be called PruneActiveTokensFinal().
|
||||
void FinalizeDecoding();
|
||||
|
||||
/// FinalRelativeCost() serves the same purpose as ReachedFinal(), but gives
|
||||
/// more information. It returns the difference between the best (final-cost
|
||||
/// plus cost) of any token on the final frame, and the best cost of any token
|
||||
/// on the final frame. If it is infinity it means no final-states were
|
||||
/// present on the final frame. It will usually be nonnegative. If it not
|
||||
/// too positive (e.g. < 5 is my first guess, but this is not tested) you can
|
||||
/// take it as a good indication that we reached the final-state with
|
||||
/// reasonable likelihood.
|
||||
BaseFloat FinalRelativeCost() const;
|
||||
|
||||
|
||||
// Returns the number of frames decoded so far. The value returned changes
|
||||
// whenever we call ProcessEmitting().
|
||||
inline int32 NumFramesDecoded() const { return active_toks_.size() - 1; }
|
||||
|
||||
std::string GetTokResult(Token *tok);
|
||||
|
||||
void SetBiasLm(std::shared_ptr<funasr::BiasLm> &bias_lm) {
|
||||
bias_lm_ = bias_lm;
|
||||
}
|
||||
|
||||
void ClearBiasLm() {
|
||||
bias_lm_.reset();
|
||||
}
|
||||
|
||||
protected:
|
||||
// we make things protected instead of private, as code in
|
||||
// LatticeFasterOnlineDecoderTpl, which inherits from this, also uses the
|
||||
// internals.
|
||||
|
||||
// Deletes the elements of the singly linked list tok->links.
|
||||
void DeleteForwardLinks(Token *tok);
|
||||
|
||||
// head of per-frame list of Tokens (list is in topological order),
|
||||
// and something saying whether we ever pruned it using PruneForwardLinks.
|
||||
struct TokenList {
|
||||
Token *toks;
|
||||
bool must_prune_forward_links;
|
||||
bool must_prune_tokens;
|
||||
TokenList(): toks(NULL), must_prune_forward_links(true),
|
||||
must_prune_tokens(true) { }
|
||||
};
|
||||
|
||||
using Elem = typename HashList<StateId, Token*>::Elem;
|
||||
// Equivalent to:
|
||||
// struct Elem {
|
||||
// StateId key;
|
||||
// Token *val;
|
||||
// Elem *tail;
|
||||
// };
|
||||
|
||||
void PossiblyResizeHash(size_t num_toks);
|
||||
|
||||
// FindOrAddToken either locates a token in hash of toks_, or if necessary
|
||||
// inserts a new, empty token (i.e. with no forward links) for the current
|
||||
// frame. [note: it's inserted if necessary into hash toks_ and also into the
|
||||
// singly linked list of tokens active on this frame (whose head is at
|
||||
// active_toks_[frame]). The frame_plus_one argument is the acoustic frame
|
||||
// index plus one, which is used to index into the active_toks_ array.
|
||||
// Returns the Token pointer. Sets "changed" (if non-NULL) to true if the
|
||||
// token was newly created or the cost changed.
|
||||
// If Token == StdToken, the 'backpointer' argument has no purpose (and will
|
||||
// hopefully be optimized out).
|
||||
inline Elem *FindOrAddToken(StateId state, int32 frame_plus_one,
|
||||
BaseFloat tot_cost, Token *backpointer,
|
||||
bool *changed, StateId bias_lm_state = 0);
|
||||
|
||||
// prunes outgoing links for all tokens in active_toks_[frame]
|
||||
// it's called by PruneActiveTokens
|
||||
// all links, that have link_extra_cost > lattice_beam are pruned
|
||||
// delta is the amount by which the extra_costs must change
|
||||
// before we set *extra_costs_changed = true.
|
||||
// If delta is larger, we'll tend to go back less far
|
||||
// toward the beginning of the file.
|
||||
// extra_costs_changed is set to true if extra_cost was changed for any token
|
||||
// links_pruned is set to true if any link in any token was pruned
|
||||
void PruneForwardLinks(int32 frame_plus_one, bool *extra_costs_changed,
|
||||
bool *links_pruned,
|
||||
BaseFloat delta);
|
||||
|
||||
// This function computes the final-costs for tokens active on the final
|
||||
// frame. It outputs to final-costs, if non-NULL, a map from the Token*
|
||||
// pointer to the final-prob of the corresponding state, for all Tokens
|
||||
// that correspond to states that have final-probs. This map will be
|
||||
// empty if there were no final-probs. It outputs to
|
||||
// final_relative_cost, if non-NULL, the difference between the best
|
||||
// forward-cost including the final-prob cost, and the best forward-cost
|
||||
// without including the final-prob cost (this will usually be positive), or
|
||||
// infinity if there were no final-probs. [c.f. FinalRelativeCost(), which
|
||||
// outputs this quanitity]. It outputs to final_best_cost, if
|
||||
// non-NULL, the lowest for any token t active on the final frame, of
|
||||
// forward-cost[t] + final-cost[t], where final-cost[t] is the final-cost in
|
||||
// the graph of the state corresponding to token t, or the best of
|
||||
// forward-cost[t] if there were no final-probs active on the final frame.
|
||||
// You cannot call this after FinalizeDecoding() has been called; in that
|
||||
// case you should get the answer from class-member variables.
|
||||
void ComputeFinalCosts(unordered_map<Token*, BaseFloat> *final_costs,
|
||||
BaseFloat *final_relative_cost,
|
||||
BaseFloat *final_best_cost) const;
|
||||
|
||||
// PruneForwardLinksFinal is a version of PruneForwardLinks that we call
|
||||
// on the final frame. If there are final tokens active, it uses
|
||||
// the final-probs for pruning, otherwise it treats all tokens as final.
|
||||
void PruneForwardLinksFinal();
|
||||
|
||||
// Prune away any tokens on this frame that have no forward links.
|
||||
// [we don't do this in PruneForwardLinks because it would give us
|
||||
// a problem with dangling pointers].
|
||||
// It's called by PruneActiveTokens if any forward links have been pruned
|
||||
void PruneTokensForFrame(int32 frame_plus_one);
|
||||
|
||||
|
||||
// Go backwards through still-alive tokens, pruning them if the
|
||||
// forward+backward cost is more than lat_beam away from the best path. It's
|
||||
// possible to prove that this is "correct" in the sense that we won't lose
|
||||
// anything outside of lat_beam, regardless of what happens in the future.
|
||||
// delta controls when it considers a cost to have changed enough to continue
|
||||
// going backward and propagating the change. larger delta -> will recurse
|
||||
// less far.
|
||||
void PruneActiveTokens(BaseFloat delta);
|
||||
|
||||
/// Gets the weight cutoff. Also counts the active tokens.
|
||||
BaseFloat GetCutoff(Elem *list_head, size_t *tok_count,
|
||||
BaseFloat *adaptive_beam, Elem **best_elem);
|
||||
|
||||
/// Processes emitting arcs for one frame. Propagates from prev_toks_ to
|
||||
/// cur_toks_. Returns the cost cutoff for subsequent ProcessNonemitting() to
|
||||
/// use.
|
||||
BaseFloat ProcessEmitting(DecodableInterface *decodable);
|
||||
|
||||
/// Processes nonemitting (epsilon) arcs for one frame. Called after
|
||||
/// ProcessEmitting() on each frame. The cost cutoff is computed by the
|
||||
/// preceding ProcessEmitting().
|
||||
void ProcessNonemitting(BaseFloat cost_cutoff);
|
||||
|
||||
// HashList defined in ../util/hash-list.h. It actually allows us to maintain
|
||||
// more than one list (e.g. for current and previous frames), but only one of
|
||||
// them at a time can be indexed by StateId. It is indexed by frame-index
|
||||
// plus one, where the frame-index is zero-based, as used in decodable object.
|
||||
// That is, the emitting probs of frame t are accounted for in tokens at
|
||||
// toks_[t+1]. The zeroth frame is for nonemitting transition at the start of
|
||||
// the graph.
|
||||
HashList<StateId, Token*> toks_;
|
||||
|
||||
std::vector<TokenList> active_toks_; // Lists of tokens, indexed by
|
||||
// frame (members of TokenList are toks, must_prune_forward_links,
|
||||
// must_prune_tokens).
|
||||
std::vector<const Elem* > queue_; // temp variable used in ProcessNonemitting,
|
||||
std::vector<BaseFloat> tmp_array_; // used in GetCutoff.
|
||||
|
||||
// fst_ is a pointer to the FST we are decoding from.
|
||||
const FST *fst_;
|
||||
// delete_fst_ is true if the pointer fst_ needs to be deleted when this
|
||||
// object is destroyed.
|
||||
bool delete_fst_;
|
||||
|
||||
std::vector<BaseFloat> cost_offsets_; // This contains, for each
|
||||
// frame, an offset that was added to the acoustic log-likelihoods on that
|
||||
// frame in order to keep everything in a nice dynamic range i.e. close to
|
||||
// zero, to reduce roundoff errors.
|
||||
LatticeFasterDecoderConfig config_;
|
||||
int32 num_toks_; // current total #toks allocated...
|
||||
bool warned_;
|
||||
|
||||
/// decoding_finalized_ is true if someone called FinalizeDecoding(). [note,
|
||||
/// calling this is optional]. If true, it's forbidden to decode more. Also,
|
||||
/// if this is set, then the output of ComputeFinalCosts() is in the next
|
||||
/// three variables. The reason we need to do this is that after
|
||||
/// FinalizeDecoding() calls PruneTokensForFrame() for the final frame, some
|
||||
/// of the tokens on the last frame are freed, so we free the list from toks_
|
||||
/// to avoid having dangling pointers hanging around.
|
||||
bool decoding_finalized_;
|
||||
/// For the meaning of the next 3 variables, see the comment for
|
||||
/// decoding_finalized_ above., and ComputeFinalCosts().
|
||||
unordered_map<Token*, BaseFloat> final_costs_;
|
||||
BaseFloat final_relative_cost_;
|
||||
BaseFloat final_best_cost_;
|
||||
|
||||
// Memory pools for storing tokens and forward links.
|
||||
// We use it to decrease the work put on allocator and to move some of data
|
||||
// together. Too small block sizes will result in more work to allocator but
|
||||
// bigger ones increase the memory usage.
|
||||
fst::MemoryPool<Token> token_pool_;
|
||||
fst::MemoryPool<ForwardLinkT> forward_link_pool_;
|
||||
|
||||
// There are various cleanup tasks... the toks_ structure contains
|
||||
// singly linked lists of Token pointers, where Elem is the list type.
|
||||
// It also indexes them in a hash, indexed by state (this hash is only
|
||||
// maintained for the most recent frame). toks_.Clear()
|
||||
// deletes them from the hash and returns the list of Elems. The
|
||||
// function DeleteElems calls toks_.Delete(elem) for each elem in
|
||||
// the list, which returns ownership of the Elem to the toks_ structure
|
||||
// for reuse, but does not delete the Token pointer. The Token pointers
|
||||
// are reference-counted and are ultimately deleted in PruneTokensForFrame,
|
||||
// but are also linked together on each frame by their own linked-list,
|
||||
// using the "next" pointer. We delete them manually.
|
||||
void DeleteElems(Elem *list);
|
||||
|
||||
// This function takes a singly linked list of tokens for a single frame, and
|
||||
// outputs a list of them in topological order (it will crash if no such order
|
||||
// can be found, which will typically be due to decoding graphs with epsilon
|
||||
// cycles, which are not allowed). Note: the output list may contain NULLs,
|
||||
// which the caller should pass over; it just happens to be more efficient for
|
||||
// the algorithm to output a list that contains NULLs.
|
||||
static void TopSortTokens(Token *tok_list,
|
||||
std::vector<Token*> *topsorted_list);
|
||||
|
||||
void ClearActiveTokens();
|
||||
|
||||
KALDI_DISALLOW_COPY_AND_ASSIGN(LatticeFasterDecoderTpl);
|
||||
|
||||
std::shared_ptr<funasr::BiasLm> bias_lm_ = nullptr;
|
||||
};
|
||||
|
||||
typedef LatticeFasterDecoderTpl<fst::StdFst, decoder::StdToken> LatticeFasterDecoder;
|
||||
|
||||
|
||||
|
||||
} // end namespace kaldi.
|
||||
|
||||
#endif
|
||||
+285
@@ -0,0 +1,285 @@
|
||||
// decoder/lattice-faster-online-decoder.cc
|
||||
|
||||
// Copyright 2009-2012 Microsoft Corporation Mirko Hannemann
|
||||
// 2013-2014 Johns Hopkins University (Author: Daniel Povey)
|
||||
// 2014 Guoguo Chen
|
||||
// 2014 IMSL, PKU-HKUST (author: Wei Shi)
|
||||
// 2018 Zhehuai Chen
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// see note at the top of lattice-faster-decoder.cc, about how to maintain this
|
||||
// file in sync with lattice-faster-decoder.cc
|
||||
|
||||
#include "decoder/lattice-faster-online-decoder.h"
|
||||
#include "lat/lattice-functions.h"
|
||||
|
||||
namespace kaldi {
|
||||
|
||||
template <typename FST>
|
||||
bool LatticeFasterOnlineDecoderTpl<FST>::TestGetBestPath(
|
||||
bool use_final_probs) const {
|
||||
Lattice lat1;
|
||||
{
|
||||
Lattice raw_lat;
|
||||
this->GetRawLattice(&raw_lat, use_final_probs);
|
||||
ShortestPath(raw_lat, &lat1);
|
||||
}
|
||||
Lattice lat2;
|
||||
GetBestPath(&lat2, use_final_probs);
|
||||
BaseFloat delta = 0.1;
|
||||
int32 num_paths = 1;
|
||||
if (!fst::RandEquivalent(lat1, lat2, num_paths, delta, rand())) {
|
||||
KALDI_WARN << "Best-path test failed";
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Outputs an FST corresponding to the single best path through the lattice.
|
||||
template <typename FST>
|
||||
bool LatticeFasterOnlineDecoderTpl<FST>::GetBestPath(Lattice *olat,
|
||||
bool use_final_probs) const {
|
||||
olat->DeleteStates();
|
||||
BaseFloat final_graph_cost;
|
||||
BestPathIterator iter = BestPathEnd(use_final_probs, &final_graph_cost);
|
||||
if (iter.Done())
|
||||
return false; // would have printed warning.
|
||||
StateId state = olat->AddState();
|
||||
olat->SetFinal(state, LatticeWeight(final_graph_cost, 0.0));
|
||||
while (!iter.Done()) {
|
||||
LatticeArc arc;
|
||||
iter = TraceBackBestPath(iter, &arc);
|
||||
arc.nextstate = state;
|
||||
StateId new_state = olat->AddState();
|
||||
olat->AddArc(new_state, arc);
|
||||
state = new_state;
|
||||
}
|
||||
olat->SetStart(state);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename FST>
|
||||
typename LatticeFasterOnlineDecoderTpl<FST>::BestPathIterator LatticeFasterOnlineDecoderTpl<FST>::BestPathEnd(
|
||||
bool use_final_probs,
|
||||
BaseFloat *final_cost_out) const {
|
||||
if (this->decoding_finalized_ && !use_final_probs)
|
||||
KALDI_ERR << "You cannot call FinalizeDecoding() and then call "
|
||||
<< "BestPathEnd() with use_final_probs == false";
|
||||
KALDI_ASSERT(this->NumFramesDecoded() > 0 &&
|
||||
"You cannot call BestPathEnd if no frames were decoded.");
|
||||
|
||||
unordered_map<Token*, BaseFloat> final_costs_local;
|
||||
|
||||
const unordered_map<Token*, BaseFloat> &final_costs =
|
||||
(this->decoding_finalized_ ? this->final_costs_ :final_costs_local);
|
||||
if (!this->decoding_finalized_ && use_final_probs)
|
||||
this->ComputeFinalCosts(&final_costs_local, NULL, NULL);
|
||||
|
||||
// Singly linked list of tokens on last frame (access list through "next"
|
||||
// pointer).
|
||||
BaseFloat best_cost = std::numeric_limits<BaseFloat>::infinity();
|
||||
BaseFloat best_final_cost = 0;
|
||||
Token *best_tok = NULL;
|
||||
for (Token *tok = this->active_toks_.back().toks;
|
||||
tok != NULL; tok = tok->next) {
|
||||
BaseFloat cost = tok->tot_cost, final_cost = 0.0;
|
||||
if (use_final_probs && !final_costs.empty()) {
|
||||
// if we are instructed to use final-probs, and any final tokens were
|
||||
// active on final frame, include the final-prob in the cost of the token.
|
||||
typename unordered_map<Token*, BaseFloat>::const_iterator
|
||||
iter = final_costs.find(tok);
|
||||
if (iter != final_costs.end()) {
|
||||
final_cost = iter->second;
|
||||
cost += final_cost;
|
||||
} else {
|
||||
cost = std::numeric_limits<BaseFloat>::infinity();
|
||||
}
|
||||
}
|
||||
if (cost < best_cost) {
|
||||
best_cost = cost;
|
||||
best_tok = tok;
|
||||
best_final_cost = final_cost;
|
||||
}
|
||||
}
|
||||
if (best_tok == NULL) { // this should not happen, and is likely a code error or
|
||||
// caused by infinities in likelihoods, but I'm not making
|
||||
// it a fatal error for now.
|
||||
KALDI_WARN << "No final token found.";
|
||||
}
|
||||
if (final_cost_out)
|
||||
*final_cost_out = best_final_cost;
|
||||
return BestPathIterator(best_tok, this->NumFramesDecoded() - 1);
|
||||
}
|
||||
|
||||
|
||||
template <typename FST>
|
||||
typename LatticeFasterOnlineDecoderTpl<FST>::BestPathIterator LatticeFasterOnlineDecoderTpl<FST>::TraceBackBestPath(
|
||||
BestPathIterator iter, LatticeArc *oarc) const {
|
||||
KALDI_ASSERT(!iter.Done() && oarc != NULL);
|
||||
Token *tok = static_cast<Token*>(iter.tok);
|
||||
int32 cur_t = iter.frame, step_t = 0;
|
||||
if (tok->backpointer != NULL) {
|
||||
// retrieve the correct forward link(with the best link cost)
|
||||
BaseFloat best_cost = std::numeric_limits<BaseFloat>::infinity();
|
||||
ForwardLinkT *link;
|
||||
for (link = tok->backpointer->links;
|
||||
link != NULL; link = link->next) {
|
||||
if (link->next_tok == tok) { // this is a link to "tok"
|
||||
BaseFloat graph_cost = link->graph_cost,
|
||||
acoustic_cost = link->acoustic_cost;
|
||||
BaseFloat cost = graph_cost + acoustic_cost;
|
||||
if (cost < best_cost) {
|
||||
oarc->ilabel = link->ilabel;
|
||||
oarc->olabel = link->olabel;
|
||||
if (link->ilabel != 0) {
|
||||
KALDI_ASSERT(static_cast<size_t>(cur_t) < this->cost_offsets_.size());
|
||||
acoustic_cost -= this->cost_offsets_[cur_t];
|
||||
step_t = -1;
|
||||
} else {
|
||||
step_t = 0;
|
||||
}
|
||||
oarc->weight = LatticeWeight(graph_cost, acoustic_cost);
|
||||
best_cost = cost;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (link == NULL &&
|
||||
best_cost == std::numeric_limits<BaseFloat>::infinity()) { // Did not find correct link.
|
||||
KALDI_ERR << "Error tracing best-path back (likely "
|
||||
<< "bug in token-pruning algorithm)";
|
||||
}
|
||||
} else {
|
||||
oarc->ilabel = 0;
|
||||
oarc->olabel = 0;
|
||||
oarc->weight = LatticeWeight::One(); // zero costs.
|
||||
}
|
||||
return BestPathIterator(tok->backpointer, cur_t + step_t);
|
||||
}
|
||||
|
||||
template <typename FST>
|
||||
bool LatticeFasterOnlineDecoderTpl<FST>::GetRawLatticePruned(
|
||||
Lattice *ofst,
|
||||
bool use_final_probs,
|
||||
BaseFloat beam) const {
|
||||
typedef LatticeArc Arc;
|
||||
typedef Arc::StateId StateId;
|
||||
typedef Arc::Weight Weight;
|
||||
typedef Arc::Label Label;
|
||||
|
||||
// Note: you can't use the old interface (Decode()) if you want to
|
||||
// get the lattice with use_final_probs = false. You'd have to do
|
||||
// InitDecoding() and then AdvanceDecoding().
|
||||
if (this->decoding_finalized_ && !use_final_probs)
|
||||
KALDI_ERR << "You cannot call FinalizeDecoding() and then call "
|
||||
<< "GetRawLattice() with use_final_probs == false";
|
||||
|
||||
unordered_map<Token*, BaseFloat> final_costs_local;
|
||||
|
||||
const unordered_map<Token*, BaseFloat> &final_costs =
|
||||
(this->decoding_finalized_ ? this->final_costs_ : final_costs_local);
|
||||
if (!this->decoding_finalized_ && use_final_probs)
|
||||
this->ComputeFinalCosts(&final_costs_local, NULL, NULL);
|
||||
|
||||
ofst->DeleteStates();
|
||||
// num-frames plus one (since frames are one-based, and we have
|
||||
// an extra frame for the start-state).
|
||||
int32 num_frames = this->active_toks_.size() - 1;
|
||||
KALDI_ASSERT(num_frames > 0);
|
||||
for (int32 f = 0; f <= num_frames; f++) {
|
||||
if (this->active_toks_[f].toks == NULL) {
|
||||
KALDI_WARN << "No tokens active on frame " << f
|
||||
<< ": not producing lattice.\n";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
unordered_map<Token*, StateId> tok_map;
|
||||
std::queue<std::pair<Token*, int32> > tok_queue;
|
||||
// First initialize the queue and states. Put the initial state on the queue;
|
||||
// this is the last token in the list active_toks_[0].toks.
|
||||
for (Token *tok = this->active_toks_[0].toks;
|
||||
tok != NULL; tok = tok->next) {
|
||||
if (tok->next == NULL) {
|
||||
tok_map[tok] = ofst->AddState();
|
||||
ofst->SetStart(tok_map[tok]);
|
||||
std::pair<Token*, int32> tok_pair(tok, 0); // #frame = 0
|
||||
tok_queue.push(tok_pair);
|
||||
}
|
||||
}
|
||||
|
||||
// Next create states for "good" tokens
|
||||
while (!tok_queue.empty()) {
|
||||
std::pair<Token*, int32> cur_tok_pair = tok_queue.front();
|
||||
tok_queue.pop();
|
||||
Token *cur_tok = cur_tok_pair.first;
|
||||
int32 cur_frame = cur_tok_pair.second;
|
||||
KALDI_ASSERT(cur_frame >= 0 &&
|
||||
cur_frame <= this->cost_offsets_.size());
|
||||
|
||||
typename unordered_map<Token*, StateId>::const_iterator iter =
|
||||
tok_map.find(cur_tok);
|
||||
KALDI_ASSERT(iter != tok_map.end());
|
||||
StateId cur_state = iter->second;
|
||||
|
||||
for (ForwardLinkT *l = cur_tok->links;
|
||||
l != NULL;
|
||||
l = l->next) {
|
||||
Token *next_tok = l->next_tok;
|
||||
if (next_tok->extra_cost < beam) {
|
||||
// so both the current and the next token are good; create the arc
|
||||
int32 next_frame = l->ilabel == 0 ? cur_frame : cur_frame + 1;
|
||||
StateId nextstate;
|
||||
if (tok_map.find(next_tok) == tok_map.end()) {
|
||||
nextstate = tok_map[next_tok] = ofst->AddState();
|
||||
tok_queue.push(std::pair<Token*, int32>(next_tok, next_frame));
|
||||
} else {
|
||||
nextstate = tok_map[next_tok];
|
||||
}
|
||||
BaseFloat cost_offset = (l->ilabel != 0 ?
|
||||
this->cost_offsets_[cur_frame] : 0);
|
||||
Arc arc(l->ilabel, l->olabel,
|
||||
Weight(l->graph_cost, l->acoustic_cost - cost_offset),
|
||||
nextstate);
|
||||
ofst->AddArc(cur_state, arc);
|
||||
}
|
||||
}
|
||||
if (cur_frame == num_frames) {
|
||||
if (use_final_probs && !final_costs.empty()) {
|
||||
typename unordered_map<Token*, BaseFloat>::const_iterator iter =
|
||||
final_costs.find(cur_tok);
|
||||
if (iter != final_costs.end())
|
||||
ofst->SetFinal(cur_state, LatticeWeight(iter->second, 0));
|
||||
} else {
|
||||
ofst->SetFinal(cur_state, LatticeWeight::One());
|
||||
}
|
||||
}
|
||||
}
|
||||
return (ofst->NumStates() != 0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Instantiate the template for the FST types that we'll need.
|
||||
template class LatticeFasterOnlineDecoderTpl<fst::Fst<fst::StdArc> >;
|
||||
template class LatticeFasterOnlineDecoderTpl<fst::VectorFst<fst::StdArc> >;
|
||||
template class LatticeFasterOnlineDecoderTpl<fst::ConstFst<fst::StdArc> >;
|
||||
//template class LatticeFasterOnlineDecoderTpl<fst::ConstGrammarFst >;
|
||||
//template class LatticeFasterOnlineDecoderTpl<fst::VectorGrammarFst >;
|
||||
|
||||
|
||||
} // end namespace kaldi.
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
// decoder/lattice-faster-online-decoder.h
|
||||
|
||||
// Copyright 2009-2013 Microsoft Corporation; Mirko Hannemann;
|
||||
// 2013-2014 Johns Hopkins University (Author: Daniel Povey)
|
||||
// 2014 Guoguo Chen
|
||||
// 2018 Zhehuai Chen
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// see note at the top of lattice-faster-decoder.h, about how to maintain this
|
||||
// file in sync with lattice-faster-decoder.h
|
||||
|
||||
|
||||
#ifndef KALDI_DECODER_LATTICE_FASTER_ONLINE_DECODER_H_
|
||||
#define KALDI_DECODER_LATTICE_FASTER_ONLINE_DECODER_H_
|
||||
|
||||
#include "util/stl-utils.h"
|
||||
#include "util/hash-list.h"
|
||||
#include "fst/fstlib.h"
|
||||
#include "itf/decodable-itf.h"
|
||||
#include "fstext/fstext-lib.h"
|
||||
#include "lat/determinize-lattice-pruned.h"
|
||||
#include "lat/kaldi-lattice.h"
|
||||
#include "decoder/lattice-faster-decoder.h"
|
||||
|
||||
namespace kaldi {
|
||||
|
||||
|
||||
|
||||
/** LatticeFasterOnlineDecoderTpl is as LatticeFasterDecoderTpl but also
|
||||
supports an efficient way to get the best path (see the function
|
||||
BestPathEnd()), which is useful in endpointing and in situations where you
|
||||
might want to frequently access the best path.
|
||||
|
||||
This is only templated on the FST type, since the Token type is required to
|
||||
be BackpointerToken. Actually it only makes sense to instantiate
|
||||
LatticeFasterDecoderTpl with Token == BackpointerToken if you do so indirectly via
|
||||
this child class.
|
||||
*/
|
||||
template <typename FST>
|
||||
class LatticeFasterOnlineDecoderTpl:
|
||||
public LatticeFasterDecoderTpl<FST, decoder::BackpointerToken> {
|
||||
public:
|
||||
using Arc = typename FST::Arc;
|
||||
using Label = typename Arc::Label;
|
||||
using StateId = typename Arc::StateId;
|
||||
using Weight = typename Arc::Weight;
|
||||
using Token = decoder::BackpointerToken;
|
||||
using ForwardLinkT = decoder::ForwardLink<Token>;
|
||||
|
||||
// Instantiate this class once for each thing you have to decode.
|
||||
// This version of the constructor does not take ownership of
|
||||
// 'fst'.
|
||||
LatticeFasterOnlineDecoderTpl(const FST &fst,
|
||||
const LatticeFasterDecoderConfig &config):
|
||||
LatticeFasterDecoderTpl<FST, Token>(fst, config) { }
|
||||
|
||||
// This version of the initializer takes ownership of 'fst', and will delete
|
||||
// it when this object is destroyed.
|
||||
LatticeFasterOnlineDecoderTpl(const LatticeFasterDecoderConfig &config, FST *fst):
|
||||
LatticeFasterDecoderTpl<FST, Token>(config, fst) { }
|
||||
//LatticeFasterOnlineDecoderTpl() {}
|
||||
|
||||
struct BestPathIterator {
|
||||
void *tok;
|
||||
int32 frame;
|
||||
// note, "frame" is the frame-index of the frame you'll get the
|
||||
// transition-id for next time, if you call TraceBackBestPath on this
|
||||
// iterator (assuming it's not an epsilon transition). Note that this
|
||||
// is one less than you might reasonably expect, e.g. it's -1 for
|
||||
// the nonemitting transitions before the first frame.
|
||||
BestPathIterator(void *t, int32 f): tok(t), frame(f) { }
|
||||
bool Done() const { return tok == NULL; }
|
||||
};
|
||||
|
||||
|
||||
/// Outputs an FST corresponding to the single best path through the lattice.
|
||||
/// This is quite efficient because it doesn't get the entire raw lattice and find
|
||||
/// the best path through it; instead, it uses the BestPathEnd and BestPathIterator
|
||||
/// so it basically traces it back through the lattice.
|
||||
/// Returns true if result is nonempty (using the return status is deprecated,
|
||||
/// it will become void). If "use_final_probs" is true AND we reached the
|
||||
/// final-state of the graph then it will include those as final-probs, else
|
||||
/// it will treat all final-probs as one.
|
||||
bool GetBestPath(Lattice *ofst,
|
||||
bool use_final_probs = true) const;
|
||||
|
||||
|
||||
/// This function does a self-test of GetBestPath(). Returns true on
|
||||
/// success; returns false and prints a warning on failure.
|
||||
bool TestGetBestPath(bool use_final_probs = true) const;
|
||||
|
||||
|
||||
/// This function returns an iterator that can be used to trace back
|
||||
/// the best path. If use_final_probs == true and at least one final state
|
||||
/// survived till the end, it will use the final-probs in working out the best
|
||||
/// final Token, and will output the final cost to *final_cost (if non-NULL),
|
||||
/// else it will use only the forward likelihood, and will put zero in
|
||||
/// *final_cost (if non-NULL).
|
||||
/// Requires that NumFramesDecoded() > 0.
|
||||
BestPathIterator BestPathEnd(bool use_final_probs,
|
||||
BaseFloat *final_cost = NULL) const;
|
||||
|
||||
|
||||
/// This function can be used in conjunction with BestPathEnd() to trace back
|
||||
/// the best path one link at a time (e.g. this can be useful in endpoint
|
||||
/// detection). By "link" we mean a link in the graph; not all links cross
|
||||
/// frame boundaries, but each time you see a nonzero ilabel you can interpret
|
||||
/// that as a frame. The return value is the updated iterator. It outputs
|
||||
/// the ilabel and olabel, and the (graph and acoustic) weight to the "arc" pointer,
|
||||
/// while leaving its "nextstate" variable unchanged.
|
||||
BestPathIterator TraceBackBestPath(
|
||||
BestPathIterator iter, LatticeArc *arc) const;
|
||||
|
||||
|
||||
/// Behaves the same as GetRawLattice but only processes tokens whose
|
||||
/// extra_cost is smaller than the best-cost plus the specified beam.
|
||||
/// It is only worthwhile to call this function if beam is less than
|
||||
/// the lattice_beam specified in the config; otherwise, it would
|
||||
/// return essentially the same thing as GetRawLattice, but more slowly.
|
||||
bool GetRawLatticePruned(Lattice *ofst,
|
||||
bool use_final_probs,
|
||||
BaseFloat beam) const;
|
||||
|
||||
KALDI_DISALLOW_COPY_AND_ASSIGN(LatticeFasterOnlineDecoderTpl);
|
||||
};
|
||||
|
||||
typedef LatticeFasterOnlineDecoderTpl<fst::StdFst> LatticeFasterOnlineDecoder;
|
||||
|
||||
|
||||
} // end namespace kaldi.
|
||||
|
||||
#endif
|
||||
+1730
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,734 @@
|
||||
// decoder/lattice-incremental-decoder.h
|
||||
|
||||
// Copyright 2019 Zhehuai Chen, Daniel Povey
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef KALDI_DECODER_LATTICE_INCREMENTAL_DECODER_H_
|
||||
#define KALDI_DECODER_LATTICE_INCREMENTAL_DECODER_H_
|
||||
|
||||
#include "util/stl-utils.h"
|
||||
#include "util/hash-list.h"
|
||||
#include "fst/fstlib.h"
|
||||
#include "itf/decodable-itf.h"
|
||||
#include "fstext/fstext-lib.h"
|
||||
#include "lat/determinize-lattice-pruned.h"
|
||||
#include "lat/kaldi-lattice.h"
|
||||
#include "decoder/grammar-fst.h"
|
||||
#include "decoder/lattice-faster-decoder.h"
|
||||
|
||||
namespace kaldi {
|
||||
/**
|
||||
The normal decoder, lattice-faster-decoder.h, sometimes has an issue when
|
||||
doing real-time applications with long utterances, that each time you get the
|
||||
lattice the lattice determinization can take a considerable amount of time;
|
||||
this introduces latency. This version of the decoder spreads the work of
|
||||
lattice determinization out throughout the decoding process.
|
||||
|
||||
NOTE:
|
||||
|
||||
Please see https://www.danielpovey.com/files/ *TBD* .pdf for a technical
|
||||
explanation of what is going on here.
|
||||
|
||||
GLOSSARY OF TERMS:
|
||||
chunk: We do the determinization on chunks of frames; these
|
||||
may coincide with the chunks on which the user calls
|
||||
AdvanceDecoding(). The basic idea is to extract chunks
|
||||
of the raw lattice and determinize them individually, but
|
||||
it gets much more complicated than that. The chunks
|
||||
should normally be at least as long as a word (let's say,
|
||||
at least 20 frames), or the overhead of this algorithm
|
||||
might become excessive and affect RTF.
|
||||
|
||||
raw lattice chunk: A chunk of raw (i.e. undeterminized) lattice
|
||||
that we will determinize. In the paper this corresponds
|
||||
to the FST B that is described in Section 5.2.
|
||||
|
||||
token_label, state_label / token-label, state-label:
|
||||
|
||||
In the paper these are both referred to as `state labels` (these are
|
||||
special, large integer id's that refer to states in the undeterminized
|
||||
lattice and in the the determinized lattice); but we use two separate
|
||||
terms here, for more clarity, when referring to the undeterminized
|
||||
vs. determinized lattice.
|
||||
|
||||
token_label conceptually refers to states in the
|
||||
raw lattice, but we don't materialize the entire
|
||||
raw lattice as a physical FST and and these tokens
|
||||
are actually tokens (template type Token) held by
|
||||
the decoder
|
||||
|
||||
state_label when used in this code refers specifically
|
||||
to labels that identify states in the determinized
|
||||
lattice (i.e. state indexes in lat_).
|
||||
|
||||
token-final state
|
||||
A state in a raw lattice or in a determinized chunk that has an arc
|
||||
entering it that has a `token-label` on it (as defined above).
|
||||
These states will have nonzero final-probs.
|
||||
|
||||
redeterminized-non-splice-state, aka ns_redet:
|
||||
A redeterminized state which is not also a splice state;
|
||||
refer to the paper for explanation. In the already-determinized
|
||||
part this means a redeterminized state which is not final.
|
||||
|
||||
canonical appended lattice: This is the appended compact lattice
|
||||
that we conceptually have (i.e. what we described in the paper).
|
||||
The difference from the "actual appended lattice" stored
|
||||
in LatticeIncrementalDeterminizer::clat_ is that the
|
||||
actual appended lattice has all its final-arcs replaced with
|
||||
final-probs, and we keep the real final-arcs "on the side" in a
|
||||
separate data structure. The final-probs in clat_ aren't
|
||||
necessarily related to the costs on the final-arcs; instead
|
||||
they can have arbitrary values passed in by the user (e.g.
|
||||
if we want to include final-probs). This means that the
|
||||
clat_ can be returned without modification to the user who wants
|
||||
a partially determinized result.
|
||||
|
||||
final-arc: An arc in the canonical appended CompactLattice which
|
||||
goes to a final-state. These arcs will have `state-labels` as
|
||||
their labels.
|
||||
|
||||
*/
|
||||
struct LatticeIncrementalDecoderConfig {
|
||||
// All the configuration values until det_opts are the same as in
|
||||
// LatticeFasterDecoder. For clarity we repeat them rather than inheriting.
|
||||
BaseFloat beam;
|
||||
int32 max_active;
|
||||
int32 min_active;
|
||||
BaseFloat lattice_beam;
|
||||
int32 prune_interval;
|
||||
BaseFloat beam_delta; // has nothing to do with beam_ratio
|
||||
BaseFloat hash_ratio;
|
||||
BaseFloat prune_scale; // Note: we don't make this configurable on the command line,
|
||||
// it's not a very important parameter. It affects the
|
||||
// algorithm that prunes the tokens as we go.
|
||||
// Most of the options inside det_opts are not actually queried by the
|
||||
// LatticeIncrementalDecoder class itself, but by the code that calls it, for
|
||||
// example in the function DecodeUtteranceLatticeIncremental.
|
||||
fst::DeterminizeLatticePhonePrunedOptions det_opts;
|
||||
|
||||
// The configuration values from this point on are specific to the
|
||||
// incremental determinization. See where they are registered for
|
||||
// explanation.
|
||||
// Caution: these are only inspected in UpdateLatticeDeterminization().
|
||||
// If you call
|
||||
int32 determinize_max_delay;
|
||||
int32 determinize_min_chunk_size;
|
||||
int32 determinize_max_active;
|
||||
|
||||
|
||||
LatticeIncrementalDecoderConfig()
|
||||
: beam(16.0),
|
||||
max_active(std::numeric_limits<int32>::max()),
|
||||
min_active(200),
|
||||
lattice_beam(10.0),
|
||||
prune_interval(25),
|
||||
beam_delta(0.5),
|
||||
hash_ratio(2.0),
|
||||
prune_scale(0.01),
|
||||
determinize_max_delay(60),
|
||||
determinize_min_chunk_size(20),
|
||||
determinize_max_active(200) {
|
||||
det_opts.minimize = false;
|
||||
}
|
||||
void Register(OptionsItf *opts) {
|
||||
det_opts.Register(opts);
|
||||
opts->Register("beam", &beam, "Decoding beam. Larger->slower, more accurate.");
|
||||
opts->Register("max-active", &max_active,
|
||||
"Decoder max active states. Larger->slower; "
|
||||
"more accurate");
|
||||
opts->Register("min-active", &min_active, "Decoder minimum #active states.");
|
||||
opts->Register("lattice-beam", &lattice_beam,
|
||||
"Lattice generation beam. Larger->slower, "
|
||||
"and deeper lattices");
|
||||
opts->Register("prune-interval", &prune_interval,
|
||||
"Interval (in frames) at "
|
||||
"which to prune tokens");
|
||||
opts->Register("beam-delta", &beam_delta,
|
||||
"Increment used in decoding-- this "
|
||||
"parameter is obscure and relates to a speedup in the way the "
|
||||
"max-active constraint is applied. Larger is more accurate.");
|
||||
opts->Register("hash-ratio", &hash_ratio,
|
||||
"Setting used in decoder to "
|
||||
"control hash behavior");
|
||||
opts->Register("determinize-max-delay", &determinize_max_delay,
|
||||
"Maximum frames of delay between decoding a frame and "
|
||||
"determinizing it");
|
||||
opts->Register("determinize-min-chunk-size", &determinize_min_chunk_size,
|
||||
"Minimum chunk size used in determinization");
|
||||
opts->Register("determinize-max-active", &determinize_max_active,
|
||||
"Maximum number of active tokens to update determinization");
|
||||
|
||||
}
|
||||
void Check() const {
|
||||
if (!(beam > 0.0 && max_active > 1 && lattice_beam > 0.0 &&
|
||||
min_active <= max_active && prune_interval > 0 &&
|
||||
beam_delta > 0.0 && hash_ratio >= 1.0 &&
|
||||
prune_scale > 0.0 && prune_scale < 1.0 &&
|
||||
determinize_max_delay > determinize_min_chunk_size &&
|
||||
determinize_min_chunk_size > 0 &&
|
||||
determinize_max_active >= 0))
|
||||
KALDI_ERR << "Invalid options given to decoder";
|
||||
/* Minimization of the chunks is not compatible withour algorithm (or at
|
||||
least, would require additional complexity to implement.) */
|
||||
if (det_opts.minimize || !det_opts.word_determinize)
|
||||
KALDI_ERR << "Invalid determinization options given to decoder.";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
This class is used inside LatticeIncrementalDecoderTpl; it handles
|
||||
some of the details of incremental determinization.
|
||||
https://www.danielpovey.com/files/ *TBD*.pdf for the paper.
|
||||
|
||||
*/
|
||||
class LatticeIncrementalDeterminizer {
|
||||
public:
|
||||
using Label = typename LatticeArc::Label; /* Actualy the same labels appear
|
||||
in both lattice and compact
|
||||
lattice, so we don't use the
|
||||
specific type all the time but
|
||||
just say 'Label' */
|
||||
LatticeIncrementalDeterminizer(
|
||||
const TransitionInformation &trans_model,
|
||||
const LatticeIncrementalDecoderConfig &config):
|
||||
trans_model_(trans_model), config_(config) { }
|
||||
|
||||
// Resets the lattice determinization data for new utterance
|
||||
void Init();
|
||||
|
||||
// Returns the current determinized lattice.
|
||||
const CompactLattice &GetDeterminizedLattice() const { return clat_; }
|
||||
|
||||
/**
|
||||
Starts the process of creating a raw lattice chunk. (Search the glossary
|
||||
for "raw lattice chunk"). This just sets up the initial states and
|
||||
redeterminized-states in the chunk. Relates to sec. 5.2 in the paper,
|
||||
specifically the initial-state i and the redeterminized-states.
|
||||
|
||||
After calling this, the caller would add the remaining arcs and states
|
||||
to `olat` and then call AcceptRawLatticeChunk() with the result.
|
||||
|
||||
@param [out] olat The lattice to be (partially) created
|
||||
|
||||
@param [out] token_label2state This function outputs to here
|
||||
a map from `token-label` to the state we created for
|
||||
it in *olat. See glossary for `token-label`.
|
||||
The keys actually correspond to the .nextstate fields
|
||||
in the arcs in final_arcs_; values are states in `olat`.
|
||||
See the last bullet point before Sec. 5.3 in the paper.
|
||||
*/
|
||||
void InitializeRawLatticeChunk(
|
||||
Lattice *olat,
|
||||
unordered_map<Label, LatticeArc::StateId> *token_label2state);
|
||||
|
||||
/**
|
||||
This function accepts the raw FST (state-level lattice) corresponding to a
|
||||
single chunk of the lattice, determinizes it and appends it to this->clat_.
|
||||
Unless this was the
|
||||
|
||||
Note: final-probs in `raw_fst` are treated specially: they are used to
|
||||
guide the pruned determinization, but when you call GetLattice() it will be
|
||||
-- except for pruning effects-- as if all nonzero final-probs in `raw_fst`
|
||||
were: One() if final_costs == NULL; else the value present in `final_costs`.
|
||||
|
||||
@param [in] raw_fst (Consumed destructively). The input
|
||||
raw (state-level) lattice. Would correspond to the
|
||||
FST A in the paper if first_frame == 0, and B
|
||||
otherwise.
|
||||
|
||||
@return returns false if determinization finished earlier than the beam
|
||||
or the determinized lattice was empty; true otherwise.
|
||||
|
||||
NOTE: if this is not the final chunk, you will probably want to call
|
||||
SetFinalCosts() directly after calling this.
|
||||
*/
|
||||
bool AcceptRawLatticeChunk(Lattice *raw_fst);
|
||||
|
||||
/*
|
||||
Sets final-probs in `clat_`. Must only be called if the final chunk
|
||||
has not been processed. (The final chunk is whenever GetLattice() is
|
||||
called with finalize == true).
|
||||
|
||||
The reason this is a separate function from AcceptRawLatticeChunk() is that
|
||||
there may be situations where a user wants to get the latice with
|
||||
final-probs in it, after previously getting it without final-probs; or
|
||||
vice versa. By final-probs, we mean the Final() probabilities in the
|
||||
HCLG (decoding graph; this->fst_).
|
||||
|
||||
@param [in] token_label2final_cost A map from the token-label
|
||||
corresponding to Tokens active on the final frame of the
|
||||
lattice in the object, to the final-cost we want to use for
|
||||
those tokens. If NULL, it means all Tokens should be treated
|
||||
as final with probability One(). If non-NULL, and a particular
|
||||
token-label is not a key of this map, it means that Token
|
||||
corresponded to a state that was not final in HCLG; and
|
||||
such tokens will be treated as non-final. However,
|
||||
if this would result in no states in the lattice being final,
|
||||
we will treat all Tokens as final with probability One(),
|
||||
a warning will be printed (this should not happen.)
|
||||
*/
|
||||
void SetFinalCosts(const unordered_map<Label, BaseFloat> *token_label2final_cost = NULL);
|
||||
|
||||
const CompactLattice &GetLattice() { return clat_; }
|
||||
|
||||
// kStateLabelOffset is what we add to state-ids in clat_ to produce labels
|
||||
// to identify them in the raw lattice chunk
|
||||
// kTokenLabelOffset is where we start allocating labels corresponding to Tokens
|
||||
// (these correspond with raw lattice states);
|
||||
enum { kStateLabelOffset = (int)1e8, kTokenLabelOffset = (int)2e8, kMaxTokenLabel = (int)3e8 };
|
||||
|
||||
private:
|
||||
|
||||
// [called from AcceptRawLatticeChunk()]
|
||||
// Gets the final costs from token-final states in the raw lattice (see
|
||||
// glossary for definition). These final costs will be subtracted after
|
||||
// determinization; in the normal case they are `temporaries` used to guide
|
||||
// pruning. NOTE: the index of the array is not the FST state that is final,
|
||||
// but the label on arcs entering it (these will be `token-labels`). Each
|
||||
// token-final state will have the same label on all arcs entering it.
|
||||
//
|
||||
// `old_final_costs` is assumed to be empty at entry.
|
||||
void GetRawLatticeFinalCosts(const Lattice &raw_fst,
|
||||
std::unordered_map<Label, BaseFloat> *old_final_costs);
|
||||
|
||||
// Sets up non_final_redet_states_. See documentation for that variable.
|
||||
void GetNonFinalRedetStates();
|
||||
|
||||
/** [called from AcceptRawLatticeChunk()] Processes arcs that leave the
|
||||
start-state of `chunk_clat` (if this is not the first chunk); does nothing
|
||||
if this is the first chunk. This includes using the `state-labels` to
|
||||
work out which states in clat_ these states correspond to, and writing
|
||||
that mapping to `state_map`.
|
||||
|
||||
Also modifies forward_costs_, because it has to do a kind of reweighting
|
||||
of the clat states that are the values it puts in `state_map`, to take
|
||||
account of the probabilities on the arcs from the start state of
|
||||
chunk_clat to the states corresponding to those redeterminized-states
|
||||
(i.e. the states in clat corresponding to the values it puts in
|
||||
`*state_map`). It also modifies arcs_in_, mostly because there
|
||||
are rare cases when we end up `merging` sets of those redeterminized-states,
|
||||
because the determinization process mapped them to a single state,
|
||||
and that means we need to reroute the arcs into members of that
|
||||
set into one single member (which will appear as a value in
|
||||
`*state_map`).
|
||||
|
||||
@param [in] chunk_clat The determinized chunk of lattice we are
|
||||
processing
|
||||
@param [out] state_map Mapping from states in chunk_clat to
|
||||
the state in clat_ they correspond to.
|
||||
@return Returns true if this is the first chunk.
|
||||
*/
|
||||
bool ProcessArcsFromChunkStartState(
|
||||
const CompactLattice &chunk_clat,
|
||||
std::unordered_map<CompactLattice::StateId, CompactLattice::StateId> *state_map);
|
||||
|
||||
/**
|
||||
This function, called from AcceptRawLatticeChunk(), transfers arcs from
|
||||
`chunk_clat` to clat_. For those arcs that have `token-labels` on them,
|
||||
they don't get written to clat_ but instead are stored in the arcs_ array.
|
||||
|
||||
@param [in] chunk_clat The determinized lattice for the chunk
|
||||
we are processing; this is the source of the arcs
|
||||
we are moving.
|
||||
@param [in] is_first_chunk True if this is the first chunk in the
|
||||
utterance; it's needed because if it is, we
|
||||
will also transfer arcs from the start state of
|
||||
chunk_clat.
|
||||
@param [in] state_map Map from state-ids in chunk_clat to state-ids
|
||||
in clat_.
|
||||
@param [in] chunk_state_to_token Map from `token-final states`
|
||||
(see glossary) in chunk_clat, to the token-label
|
||||
on arcs entering those states.
|
||||
@param [in] old_final_costs Map from token-label to the
|
||||
final-costs that were on the corresponding
|
||||
token-final states in the undeterminized lattice;
|
||||
these final-costs need to be removed when
|
||||
we record the weights in final_arcs_, because
|
||||
they were just temporary.
|
||||
*/
|
||||
void TransferArcsToClat(
|
||||
const CompactLattice &chunk_clat,
|
||||
bool is_first_chunk,
|
||||
const std::unordered_map<CompactLattice::StateId, CompactLattice::StateId> &state_map,
|
||||
const std::unordered_map<CompactLattice::StateId, Label> &chunk_state_to_token,
|
||||
const std::unordered_map<Label, BaseFloat> &old_final_costs);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Adds one arc to `clat_`. It's like clat_.AddArc(state, arc), except
|
||||
it also modifies arcs_in_ and forward_costs_.
|
||||
*/
|
||||
void AddArcToClat(CompactLattice::StateId state,
|
||||
const CompactLatticeArc &arc);
|
||||
CompactLattice::StateId AddStateToClat();
|
||||
|
||||
|
||||
// Identifies token-final states in `chunk_clat`; see glossary above for
|
||||
// definition of `token-final`. This function outputs a map from such states
|
||||
// in chunk_clat, to the `token-label` on arcs entering them. (It is not
|
||||
// possible that the same state would have multiple arcs entering it with
|
||||
// different token-labels, or some arcs entering with one token-label and some
|
||||
// another, or be both initial and have such arcs; this is true due to how we
|
||||
// construct the raw lattice.)
|
||||
void IdentifyTokenFinalStates(
|
||||
const CompactLattice &chunk_clat,
|
||||
std::unordered_map<CompactLattice::StateId, CompactLatticeArc::Label> *token_map) const;
|
||||
|
||||
// trans_model_ is needed by DeterminizeLatticePhonePrunedWrapper() which this
|
||||
// class calls.
|
||||
const TransitionInformation &trans_model_;
|
||||
// config_ is needed by DeterminizeLatticePhonePrunedWrapper() which this
|
||||
// class calls.
|
||||
const LatticeIncrementalDecoderConfig &config_;
|
||||
|
||||
|
||||
// Contains the set of redeterminized-states which are not final in the
|
||||
// canonical appended lattice. Since the final ones don't physically appear
|
||||
// in clat_, this means the set of redeterminized-states which are physically
|
||||
// in clat_. In code terms, this means set of .first elements in final_arcs,
|
||||
// plus whatever other states in clat_ are reachable from such states.
|
||||
std::unordered_set<CompactLattice::StateId> non_final_redet_states_;
|
||||
|
||||
|
||||
// clat_ is the appended lattice (containing all chunks processed so
|
||||
// far), except its `final-arcs` (i.e. arcs which in the canonical
|
||||
// lattice would go to final-states) are not present (they are stored
|
||||
// separately in final_arcs_) and states which in the canonical lattice
|
||||
// should have final-arcs leaving them will instead have a final-prob.
|
||||
CompactLattice clat_;
|
||||
|
||||
|
||||
// arcs_in_ is indexed by (state-id in clat_), and is a list of
|
||||
// arcs that come into this state, in the form (prev-state,
|
||||
// arc-index). CAUTION: not all these input-arc records will always
|
||||
// be valid (some may be out-of-date, and may refer to an out-of-range
|
||||
// arc or an arc that does not point to this state). But all
|
||||
// input arcs will always be listed.
|
||||
std::vector<std::vector<std::pair<CompactLattice::StateId, int32> > > arcs_in_;
|
||||
|
||||
// final_arcs_ contains arcs which would appear in the canonical appended
|
||||
// lattice but for implementation reasons are not physically present in clat_.
|
||||
// These are arcs to final states in the canonical appended lattice. The
|
||||
// .first elements are the source states in clat_ (these will all be elements
|
||||
// of non_final_redet_states_); the .nextstate elements of the arcs does not
|
||||
// contain a physical state, but contain state-labels allocated by
|
||||
// AllocateNewStateLabel().
|
||||
std::vector<CompactLatticeArc> final_arcs_;
|
||||
|
||||
// forward_costs_, indexed by the state-id in clat_, stores the alpha
|
||||
// (forward) costs, i.e. the minimum cost from the start state to each state
|
||||
// in clat_. This is relevant for pruned determinization. The BaseFloat can
|
||||
// be thought of as the sum of a Value1() + Value2() in a LatticeWeight.
|
||||
std::vector<BaseFloat> forward_costs_;
|
||||
|
||||
// temporary used in a function, kept here to avoid excessive reallocation.
|
||||
std::unordered_set<int32> temp_;
|
||||
|
||||
KALDI_DISALLOW_COPY_AND_ASSIGN(LatticeIncrementalDeterminizer);
|
||||
};
|
||||
|
||||
|
||||
/** This is an extention to the "normal" lattice-generating decoder.
|
||||
See \ref lattices_generation \ref decoders_faster and \ref decoders_simple
|
||||
for more information.
|
||||
|
||||
The main difference is the incremental determinization which will be
|
||||
discussed in the function GetLattice(). This means that the work of determinizatin
|
||||
isn't done all at once at the end of the file, but incrementally while decoding.
|
||||
See the comment at the top of this file for more explanation.
|
||||
|
||||
The decoder is templated on the FST type and the token type. The token type
|
||||
will normally be StdToken, but also may be BackpointerToken which is to support
|
||||
quick lookup of the current best path (see lattice-faster-online-decoder.h)
|
||||
|
||||
The FST you invoke this decoder with is expected to be of type
|
||||
Fst::Fst<fst::StdArc>, a.k.a. StdFst, or GrammarFst. If you invoke it with
|
||||
FST == StdFst and it notices that the actual FST type is
|
||||
fst::VectorFst<fst::StdArc> or fst::ConstFst<fst::StdArc>, the decoder object
|
||||
will internally cast itself to one that is templated on those more specific
|
||||
types; this is an optimization for speed.
|
||||
*/
|
||||
template <typename FST, typename Token = decoder::StdToken>
|
||||
class LatticeIncrementalDecoderTpl {
|
||||
public:
|
||||
using Arc = typename FST::Arc;
|
||||
using Label = typename Arc::Label;
|
||||
using StateId = typename Arc::StateId;
|
||||
using Weight = typename Arc::Weight;
|
||||
using ForwardLinkT = decoder::ForwardLink<Token>;
|
||||
|
||||
// Instantiate this class once for each thing you have to decode.
|
||||
// This version of the constructor does not take ownership of
|
||||
// 'fst'.
|
||||
LatticeIncrementalDecoderTpl(const FST &fst, const TransitionInformation &trans_model,
|
||||
const LatticeIncrementalDecoderConfig &config);
|
||||
|
||||
// This version of the constructor takes ownership of the fst, and will delete
|
||||
// it when this object is destroyed.
|
||||
LatticeIncrementalDecoderTpl(const LatticeIncrementalDecoderConfig &config,
|
||||
FST *fst, const TransitionInformation &trans_model);
|
||||
|
||||
void SetOptions(const LatticeIncrementalDecoderConfig &config) { config_ = config; }
|
||||
|
||||
const LatticeIncrementalDecoderConfig &GetOptions() const { return config_; }
|
||||
|
||||
~LatticeIncrementalDecoderTpl();
|
||||
|
||||
/**
|
||||
CAUTION: it's unlikely that you will ever want to call this function. In a
|
||||
scenario where you have the entire file and just want to decode it, there
|
||||
is no point using this decoder.
|
||||
|
||||
An example of how to do decoding together with incremental
|
||||
determinization. It decodes until there are no more frames left in the
|
||||
"decodable" object.
|
||||
|
||||
In this example, config_.determinize_max_delay, config_.determinize_min_chunk_size
|
||||
and config_.determinize_max_active are used to determine the time to
|
||||
call GetLattice().
|
||||
|
||||
Users will probably want to use appropriate combinations of
|
||||
AdvanceDecoding() and GetLattice() to build their application; this just
|
||||
gives you some idea how.
|
||||
|
||||
The function returns true if any kind of traceback is available (not
|
||||
necessarily from a final state).
|
||||
*/
|
||||
bool Decode(DecodableInterface *decodable);
|
||||
|
||||
/// says whether a final-state was active on the last frame. If it was not,
|
||||
/// the lattice (or traceback) will end with states that are not final-states.
|
||||
bool ReachedFinal() const {
|
||||
return FinalRelativeCost() != std::numeric_limits<BaseFloat>::infinity();
|
||||
}
|
||||
|
||||
/**
|
||||
This decoder has no GetBestPath() function.
|
||||
If you need that functionality you should probably use lattice-incremental-online-decoder.h,
|
||||
which makes it very efficient to obtain the best path. */
|
||||
|
||||
/**
|
||||
This GetLattice() function returns the lattice containing
|
||||
`num_frames_to_decode` frames; this will be all frames decoded so
|
||||
far, if you let num_frames_to_decode == NumFramesDecoded(),
|
||||
but it will generally be better to make it a few frames less than
|
||||
that to avoid the lattice having too many active states at
|
||||
the end.
|
||||
|
||||
@param [in] num_frames_to_include The number of frames that you want
|
||||
to be included in the lattice. Must be >=
|
||||
NumFramesInLattice() and <= NumFramesDecoded().
|
||||
|
||||
@param [in] use_final_probs True if you want the final-probs
|
||||
of HCLG to be included in the output lattice. Must not
|
||||
be set to true if num_frames_to_include !=
|
||||
NumFramesDecoded(). Must be set to true if you have
|
||||
previously called FinalizeDecoding().
|
||||
|
||||
(If no state was final on frame `num_frames_to_include`, the
|
||||
final-probs won't be included regardless of
|
||||
`use_final_probs`; you can test whether this
|
||||
was the case by calling ReachedFinal().
|
||||
|
||||
@return clat The CompactLattice representing what has been decoded
|
||||
up until `num_frames_to_include` (e.g., LatticeStateTimes()
|
||||
on this lattice would return `num_frames_to_include`).
|
||||
|
||||
See also UpdateLatticeDeterminizaton(). Caution: this const ref
|
||||
is only valid until the next time you call AdvanceDecoding() or
|
||||
GetLattice().
|
||||
|
||||
CAUTION: the lattice may contain disconnnected states; you should
|
||||
call Connect() on the output before writing it out.
|
||||
*/
|
||||
const CompactLattice &GetLattice(int32 num_frames_to_include,
|
||||
bool use_final_probs = false);
|
||||
|
||||
/*
|
||||
Returns the number of frames in the currently-determinized part of the
|
||||
lattice which will be a number in [0, NumFramesDecoded()]. It will
|
||||
be the largest number that GetLattice() was called with, but note
|
||||
that GetLattice() may be called from UpdateLatticeDeterminization().
|
||||
|
||||
Made available in case the user wants to give that same number to
|
||||
GetLattice().
|
||||
*/
|
||||
int NumFramesInLattice() const { return num_frames_in_lattice_; }
|
||||
|
||||
/**
|
||||
InitDecoding initializes the decoding, and should only be used if you
|
||||
intend to call AdvanceDecoding(). If you call Decode(), you don't need to
|
||||
call this. You can also call InitDecoding if you have already decoded an
|
||||
utterance and want to start with a new utterance.
|
||||
*/
|
||||
void InitDecoding();
|
||||
|
||||
/**
|
||||
This will decode until there are no more frames ready in the decodable
|
||||
object. You can keep calling it each time more frames become available
|
||||
(this is the normal pattern in a real-time/online decoding scenario).
|
||||
If max_num_frames is specified, it specifies the maximum number of frames
|
||||
the function will decode before returning.
|
||||
*/
|
||||
void AdvanceDecoding(DecodableInterface *decodable, int32 max_num_frames = -1);
|
||||
|
||||
|
||||
/** FinalRelativeCost() serves the same purpose as ReachedFinal(), but gives
|
||||
more information. It returns the difference between the best (final-cost
|
||||
plus cost) of any token on the final frame, and the best cost of any token
|
||||
on the final frame. If it is infinity it means no final-states were
|
||||
present on the final frame. It will usually be nonnegative. If it not
|
||||
too positive (e.g. < 5 is my first guess, but this is not tested) you can
|
||||
take it as a good indication that we reached the final-state with
|
||||
reasonable likelihood. */
|
||||
BaseFloat FinalRelativeCost() const;
|
||||
|
||||
/** Returns the number of frames decoded so far. */
|
||||
inline int32 NumFramesDecoded() const { return active_toks_.size() - 1; }
|
||||
|
||||
/**
|
||||
Finalizes the decoding, doing an extra pruning step on the last frame
|
||||
that uses the final-probs. May be called only once.
|
||||
*/
|
||||
void FinalizeDecoding();
|
||||
|
||||
protected:
|
||||
/* Some protected things are needed in LatticeIncrementalOnlineDecoderTpl. */
|
||||
|
||||
/** NOTE: for parts the internal implementation that are shared with LatticeFasterDecoer,
|
||||
we have removed the comments.*/
|
||||
inline static void DeleteForwardLinks(Token *tok);
|
||||
struct TokenList {
|
||||
Token *toks;
|
||||
bool must_prune_forward_links;
|
||||
bool must_prune_tokens;
|
||||
int32 num_toks; /* Note: you can only trust `num_toks` if must_prune_tokens
|
||||
* == false, because it is only set in
|
||||
* PruneTokensForFrame(). */
|
||||
TokenList()
|
||||
: toks(NULL), must_prune_forward_links(true), must_prune_tokens(true),
|
||||
num_toks(-1) {}
|
||||
};
|
||||
using Elem = typename HashList<StateId, Token *>::Elem;
|
||||
void PossiblyResizeHash(size_t num_toks);
|
||||
inline Token *FindOrAddToken(StateId state, int32 frame_plus_one,
|
||||
BaseFloat tot_cost, Token *backpointer, bool *changed);
|
||||
void PruneForwardLinks(int32 frame_plus_one, bool *extra_costs_changed,
|
||||
bool *links_pruned, BaseFloat delta);
|
||||
void ComputeFinalCosts(unordered_map<Token *, BaseFloat> *final_costs,
|
||||
BaseFloat *final_relative_cost,
|
||||
BaseFloat *final_best_cost) const;
|
||||
void PruneForwardLinksFinal();
|
||||
void PruneTokensForFrame(int32 frame_plus_one);
|
||||
void PruneActiveTokens(BaseFloat delta);
|
||||
BaseFloat GetCutoff(Elem *list_head, size_t *tok_count, BaseFloat *adaptive_beam,
|
||||
Elem **best_elem);
|
||||
BaseFloat ProcessEmitting(DecodableInterface *decodable);
|
||||
void ProcessNonemitting(BaseFloat cost_cutoff);
|
||||
|
||||
HashList<StateId, Token *> toks_;
|
||||
std::vector<TokenList> active_toks_; // indexed by frame.
|
||||
std::vector<StateId> queue_; // temp variable used in ProcessNonemitting,
|
||||
std::vector<BaseFloat> tmp_array_; // used in GetCutoff.
|
||||
const FST *fst_;
|
||||
bool delete_fst_;
|
||||
std::vector<BaseFloat> cost_offsets_;
|
||||
int32 num_toks_;
|
||||
bool warned_;
|
||||
bool decoding_finalized_;
|
||||
|
||||
unordered_map<Token *, BaseFloat> final_costs_;
|
||||
BaseFloat final_relative_cost_;
|
||||
BaseFloat final_best_cost_;
|
||||
|
||||
/***********************
|
||||
Variables below this point relate to the incremental
|
||||
determinization.
|
||||
*********************/
|
||||
LatticeIncrementalDecoderConfig config_;
|
||||
/** Much of the the incremental determinization algorithm is encapsulated in
|
||||
the determinize_ object. */
|
||||
LatticeIncrementalDeterminizer determinizer_;
|
||||
|
||||
|
||||
/* Just a temporary used in a function; stored here to avoid reallocation. */
|
||||
unordered_map<Token*, StateId> temp_token_map_;
|
||||
|
||||
/** num_frames_in_lattice_ is the highest `num_frames_to_include_` argument
|
||||
for any prior call to GetLattice(). */
|
||||
int32 num_frames_in_lattice_;
|
||||
|
||||
// A map from Token to its token_label. Will contain an entry for
|
||||
// each Token in active_toks_[num_frames_in_lattice_].
|
||||
unordered_map<Token*, Label> token2label_map_;
|
||||
|
||||
// A temporary used in a function, kept here to avoid reallocation.
|
||||
unordered_map<Token*, Label> token2label_map_temp_;
|
||||
|
||||
// we allocate a unique id for each Token
|
||||
Label next_token_label_;
|
||||
|
||||
inline Label AllocateNewTokenLabel() { return next_token_label_++; }
|
||||
|
||||
|
||||
// There are various cleanup tasks... the the toks_ structure contains
|
||||
// singly linked lists of Token pointers, where Elem is the list type.
|
||||
// It also indexes them in a hash, indexed by state (this hash is only
|
||||
// maintained for the most recent frame). toks_.Clear()
|
||||
// deletes them from the hash and returns the list of Elems. The
|
||||
// function DeleteElems calls toks_.Delete(elem) for each elem in
|
||||
// the list, which returns ownership of the Elem to the toks_ structure
|
||||
// for reuse, but does not delete the Token pointer. The Token pointers
|
||||
// are reference-counted and are ultimately deleted in PruneTokensForFrame,
|
||||
// but are also linked together on each frame by their own linked-list,
|
||||
// using the "next" pointer. We delete them manually.
|
||||
void DeleteElems(Elem *list);
|
||||
|
||||
void ClearActiveTokens();
|
||||
|
||||
|
||||
// Returns the number of active tokens on frame `frame`. Can be used as part
|
||||
// of a heuristic to decide which frame to determinize until, if you are not
|
||||
// at the end of an utterance.
|
||||
int32 GetNumToksForFrame(int32 frame);
|
||||
|
||||
/**
|
||||
UpdateLatticeDeterminization() ensures the work of determinization is kept
|
||||
up to date so that when you do need the lattice you can get it fast. It
|
||||
uses the configuration values `determinize_max_delay`, `determinize_min_chunk_size`
|
||||
and `determinize_max_active`, to decide whether and when to call
|
||||
GetLattice(). You can safely call this as often as you want (e.g. after
|
||||
each time you call AdvanceDecoding(); it won't do subtantially more work if
|
||||
it is called frequently.
|
||||
*/
|
||||
void UpdateLatticeDeterminization();
|
||||
|
||||
|
||||
KALDI_DISALLOW_COPY_AND_ASSIGN(LatticeIncrementalDecoderTpl);
|
||||
};
|
||||
|
||||
typedef LatticeIncrementalDecoderTpl<fst::StdFst, decoder::StdToken>
|
||||
LatticeIncrementalDecoder;
|
||||
|
||||
|
||||
} // end namespace kaldi.
|
||||
|
||||
#endif
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
// decoder/lattice-incremental-online-decoder.cc
|
||||
|
||||
// Copyright 2019 Zhehuai Chen
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// see note at the top of lattice-faster-decoder.cc, about how to maintain this
|
||||
// file in sync with lattice-faster-decoder.cc
|
||||
|
||||
#include "decoder/lattice-incremental-decoder.h"
|
||||
#include "decoder/lattice-incremental-online-decoder.h"
|
||||
#include "lat/lattice-functions.h"
|
||||
#include "base/timer.h"
|
||||
|
||||
namespace kaldi {
|
||||
|
||||
// Outputs an FST corresponding to the single best path through the lattice.
|
||||
template <typename FST>
|
||||
bool LatticeIncrementalOnlineDecoderTpl<FST>::GetBestPath(Lattice *olat,
|
||||
bool use_final_probs) const {
|
||||
olat->DeleteStates();
|
||||
BaseFloat final_graph_cost;
|
||||
BestPathIterator iter = BestPathEnd(use_final_probs, &final_graph_cost);
|
||||
if (iter.Done())
|
||||
return false; // would have printed warning.
|
||||
StateId state = olat->AddState();
|
||||
olat->SetFinal(state, LatticeWeight(final_graph_cost, 0.0));
|
||||
while (!iter.Done()) {
|
||||
LatticeArc arc;
|
||||
iter = TraceBackBestPath(iter, &arc);
|
||||
arc.nextstate = state;
|
||||
StateId new_state = olat->AddState();
|
||||
olat->AddArc(new_state, arc);
|
||||
state = new_state;
|
||||
}
|
||||
olat->SetStart(state);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename FST>
|
||||
typename LatticeIncrementalOnlineDecoderTpl<FST>::BestPathIterator LatticeIncrementalOnlineDecoderTpl<FST>::BestPathEnd(
|
||||
bool use_final_probs,
|
||||
BaseFloat *final_cost_out) const {
|
||||
if (this->decoding_finalized_ && !use_final_probs)
|
||||
KALDI_ERR << "You cannot call FinalizeDecoding() and then call "
|
||||
<< "BestPathEnd() with use_final_probs == false";
|
||||
KALDI_ASSERT(this->NumFramesDecoded() > 0 &&
|
||||
"You cannot call BestPathEnd if no frames were decoded.");
|
||||
|
||||
unordered_map<Token*, BaseFloat> final_costs_local;
|
||||
|
||||
const unordered_map<Token*, BaseFloat> &final_costs =
|
||||
(this->decoding_finalized_ ? this->final_costs_ :final_costs_local);
|
||||
if (!this->decoding_finalized_ && use_final_probs)
|
||||
this->ComputeFinalCosts(&final_costs_local, NULL, NULL);
|
||||
|
||||
// Singly linked list of tokens on last frame (access list through "next"
|
||||
// pointer).
|
||||
BaseFloat best_cost = std::numeric_limits<BaseFloat>::infinity();
|
||||
BaseFloat best_final_cost = 0;
|
||||
Token *best_tok = NULL;
|
||||
for (Token *tok = this->active_toks_.back().toks;
|
||||
tok != NULL; tok = tok->next) {
|
||||
BaseFloat cost = tok->tot_cost, final_cost = 0.0;
|
||||
if (use_final_probs && !final_costs.empty()) {
|
||||
// if we are instructed to use final-probs, and any final tokens were
|
||||
// active on final frame, include the final-prob in the cost of the token.
|
||||
typename unordered_map<Token*, BaseFloat>::const_iterator
|
||||
iter = final_costs.find(tok);
|
||||
if (iter != final_costs.end()) {
|
||||
final_cost = iter->second;
|
||||
cost += final_cost;
|
||||
} else {
|
||||
cost = std::numeric_limits<BaseFloat>::infinity();
|
||||
}
|
||||
}
|
||||
if (cost < best_cost) {
|
||||
best_cost = cost;
|
||||
best_tok = tok;
|
||||
best_final_cost = final_cost;
|
||||
}
|
||||
}
|
||||
if (best_tok == NULL) { // this should not happen, and is likely a code error or
|
||||
// caused by infinities in likelihoods, but I'm not making
|
||||
// it a fatal error for now.
|
||||
KALDI_WARN << "No final token found.";
|
||||
}
|
||||
if (final_cost_out != NULL)
|
||||
*final_cost_out = best_final_cost;
|
||||
return BestPathIterator(best_tok, this->NumFramesDecoded() - 1);
|
||||
}
|
||||
|
||||
|
||||
template <typename FST>
|
||||
typename LatticeIncrementalOnlineDecoderTpl<FST>::BestPathIterator LatticeIncrementalOnlineDecoderTpl<FST>::TraceBackBestPath(
|
||||
BestPathIterator iter, LatticeArc *oarc) const {
|
||||
KALDI_ASSERT(!iter.Done() && oarc != NULL);
|
||||
Token *tok = static_cast<Token*>(iter.tok);
|
||||
int32 cur_t = iter.frame, step_t = 0;
|
||||
if (tok->backpointer != NULL) {
|
||||
// retrieve the correct forward link(with the best link cost)
|
||||
BaseFloat best_cost = std::numeric_limits<BaseFloat>::infinity();
|
||||
ForwardLinkT *link;
|
||||
for (link = tok->backpointer->links;
|
||||
link != NULL; link = link->next) {
|
||||
if (link->next_tok == tok) { // this is the a to "tok"
|
||||
BaseFloat graph_cost = link->graph_cost,
|
||||
acoustic_cost = link->acoustic_cost;
|
||||
BaseFloat cost = graph_cost + acoustic_cost;
|
||||
if (cost < best_cost) {
|
||||
oarc->ilabel = link->ilabel;
|
||||
oarc->olabel = link->olabel;
|
||||
if (link->ilabel != 0) {
|
||||
KALDI_ASSERT(static_cast<size_t>(cur_t) < this->cost_offsets_.size());
|
||||
acoustic_cost -= this->cost_offsets_[cur_t];
|
||||
step_t = -1;
|
||||
} else {
|
||||
step_t = 0;
|
||||
}
|
||||
oarc->weight = LatticeWeight(graph_cost, acoustic_cost);
|
||||
best_cost = cost;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (link == NULL &&
|
||||
best_cost == std::numeric_limits<BaseFloat>::infinity()) { // Did not find correct link.
|
||||
KALDI_ERR << "Error tracing best-path back (likely "
|
||||
<< "bug in token-pruning algorithm)";
|
||||
}
|
||||
} else {
|
||||
oarc->ilabel = 0;
|
||||
oarc->olabel = 0;
|
||||
oarc->weight = LatticeWeight::One(); // zero costs.
|
||||
}
|
||||
return BestPathIterator(tok->backpointer, cur_t + step_t);
|
||||
}
|
||||
|
||||
// Instantiate the template for the FST types that we'll need.
|
||||
template class LatticeIncrementalOnlineDecoderTpl<fst::Fst<fst::StdArc> >;
|
||||
template class LatticeIncrementalOnlineDecoderTpl<fst::VectorFst<fst::StdArc> >;
|
||||
template class LatticeIncrementalOnlineDecoderTpl<fst::ConstFst<fst::StdArc> >;
|
||||
template class LatticeIncrementalOnlineDecoderTpl<fst::ConstGrammarFst >;
|
||||
template class LatticeIncrementalOnlineDecoderTpl<fst::VectorGrammarFst >;
|
||||
|
||||
} // end namespace kaldi.
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
// decoder/lattice-incremental-online-decoder.h
|
||||
|
||||
// Copyright 2019 Zhehuai Chen
|
||||
//
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// see note at the top of lattice-faster-decoder.h, about how to maintain this
|
||||
// file in sync with lattice-faster-decoder.h
|
||||
|
||||
|
||||
#ifndef KALDI_DECODER_LATTICE_INCREMENTAL_ONLINE_DECODER_H_
|
||||
#define KALDI_DECODER_LATTICE_INCREMENTAL_ONLINE_DECODER_H_
|
||||
|
||||
#include "util/stl-utils.h"
|
||||
#include "util/hash-list.h"
|
||||
#include "fst/fstlib.h"
|
||||
#include "itf/decodable-itf.h"
|
||||
#include "fstext/fstext-lib.h"
|
||||
#include "lat/determinize-lattice-pruned.h"
|
||||
#include "lat/kaldi-lattice.h"
|
||||
#include "decoder/lattice-incremental-decoder.h"
|
||||
|
||||
namespace kaldi {
|
||||
|
||||
|
||||
|
||||
/** LatticeIncrementalOnlineDecoderTpl is as LatticeIncrementalDecoderTpl but also
|
||||
supports an efficient way to get the best path (see the function
|
||||
BestPathEnd()), which is useful in endpointing and in situations where you
|
||||
might want to frequently access the best path.
|
||||
|
||||
This is only templated on the FST type, since the Token type is required to
|
||||
be BackpointerToken. Actually it only makes sense to instantiate
|
||||
LatticeIncrementalDecoderTpl with Token == BackpointerToken if you do so indirectly via
|
||||
this child class.
|
||||
*/
|
||||
template <typename FST>
|
||||
class LatticeIncrementalOnlineDecoderTpl:
|
||||
public LatticeIncrementalDecoderTpl<FST, decoder::BackpointerToken> {
|
||||
public:
|
||||
using Arc = typename FST::Arc;
|
||||
using Label = typename Arc::Label;
|
||||
using StateId = typename Arc::StateId;
|
||||
using Weight = typename Arc::Weight;
|
||||
using Token = decoder::BackpointerToken;
|
||||
using ForwardLinkT = decoder::ForwardLink<Token>;
|
||||
|
||||
// Instantiate this class once for each thing you have to decode.
|
||||
// This version of the constructor does not take ownership of
|
||||
// 'fst'.
|
||||
LatticeIncrementalOnlineDecoderTpl(const FST &fst,
|
||||
const TransitionInformation &trans_model,
|
||||
const LatticeIncrementalDecoderConfig &config):
|
||||
LatticeIncrementalDecoderTpl<FST, Token>(fst, trans_model, config) { }
|
||||
|
||||
// This version of the initializer takes ownership of 'fst', and will delete
|
||||
// it when this object is destroyed.
|
||||
LatticeIncrementalOnlineDecoderTpl(const LatticeIncrementalDecoderConfig &config,
|
||||
FST *fst,
|
||||
const TransitionInformation &trans_model):
|
||||
LatticeIncrementalDecoderTpl<FST, Token>(config, fst, trans_model) { }
|
||||
|
||||
|
||||
struct BestPathIterator {
|
||||
void *tok;
|
||||
int32 frame;
|
||||
// note, "frame" is the frame-index of the frame you'll get the
|
||||
// transition-id for next time, if you call TraceBackBestPath on this
|
||||
// iterator (assuming it's not an epsilon transition). Note that this
|
||||
// is one less than you might reasonably expect, e.g. it's -1 for
|
||||
// the nonemitting transitions before the first frame.
|
||||
BestPathIterator(void *t, int32 f): tok(t), frame(f) { }
|
||||
bool Done() { return tok == NULL; }
|
||||
};
|
||||
|
||||
|
||||
/// Outputs an FST corresponding to the single best path through the lattice.
|
||||
/// This is quite efficient because it doesn't get the entire raw lattice and find
|
||||
/// the best path through it; instead, it uses the BestPathEnd and BestPathIterator
|
||||
/// so it basically traces it back through the lattice.
|
||||
/// Returns true if result is nonempty (using the return status is deprecated,
|
||||
/// it will become void). If "use_final_probs" is true AND we reached the
|
||||
/// final-state of the graph then it will include those as final-probs, else
|
||||
/// it will treat all final-probs as one.
|
||||
bool GetBestPath(Lattice *ofst,
|
||||
bool use_final_probs = true) const;
|
||||
|
||||
|
||||
|
||||
/// This function returns an iterator that can be used to trace back
|
||||
/// the best path. If use_final_probs == true and at least one final state
|
||||
/// survived till the end, it will use the final-probs in working out the best
|
||||
/// final Token, and will output the final cost to *final_cost (if non-NULL),
|
||||
/// else it will use only the forward likelihood, and will put zero in
|
||||
/// *final_cost (if non-NULL).
|
||||
/// Requires that NumFramesDecoded() > 0.
|
||||
BestPathIterator BestPathEnd(bool use_final_probs,
|
||||
BaseFloat *final_cost = NULL) const;
|
||||
|
||||
|
||||
/// This function can be used in conjunction with BestPathEnd() to trace back
|
||||
/// the best path one link at a time (e.g. this can be useful in endpoint
|
||||
/// detection). By "link" we mean a link in the graph; not all links cross
|
||||
/// frame boundaries, but each time you see a nonzero ilabel you can interpret
|
||||
/// that as a frame. The return value is the updated iterator. It outputs
|
||||
/// the ilabel and olabel, and the (graph and acoustic) weight to the "arc" pointer,
|
||||
/// while leaving its "nextstate" variable unchanged.
|
||||
BestPathIterator TraceBackBestPath(
|
||||
BestPathIterator iter, LatticeArc *arc) const;
|
||||
|
||||
KALDI_DISALLOW_COPY_AND_ASSIGN(LatticeIncrementalOnlineDecoderTpl);
|
||||
};
|
||||
|
||||
typedef LatticeIncrementalOnlineDecoderTpl<fst::StdFst> LatticeIncrementalOnlineDecoder;
|
||||
|
||||
|
||||
} // end namespace kaldi.
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,666 @@
|
||||
// decoder/lattice-simple-decoder.cc
|
||||
|
||||
// Copyright 2009-2012 Microsoft Corporation
|
||||
// 2013-2014 Johns Hopkins University (Author: Daniel Povey)
|
||||
// 2014 Guoguo Chen
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "decoder/lattice-simple-decoder.h"
|
||||
|
||||
namespace kaldi {
|
||||
|
||||
|
||||
void LatticeSimpleDecoder::InitDecoding() {
|
||||
// clean up from last time:
|
||||
cur_toks_.clear();
|
||||
prev_toks_.clear();
|
||||
ClearActiveTokens();
|
||||
warned_ = false;
|
||||
decoding_finalized_ = false;
|
||||
final_costs_.clear();
|
||||
num_toks_ = 0;
|
||||
StateId start_state = fst_.Start();
|
||||
KALDI_ASSERT(start_state != fst::kNoStateId);
|
||||
active_toks_.resize(1);
|
||||
Token *start_tok = new Token(0.0, 0.0, NULL, NULL);
|
||||
active_toks_[0].toks = start_tok;
|
||||
cur_toks_[start_state] = start_tok;
|
||||
num_toks_++;
|
||||
ProcessNonemitting();
|
||||
}
|
||||
|
||||
bool LatticeSimpleDecoder::Decode(DecodableInterface *decodable) {
|
||||
InitDecoding();
|
||||
|
||||
while (!decodable->IsLastFrame(NumFramesDecoded() - 1)) {
|
||||
if (NumFramesDecoded() % config_.prune_interval == 0)
|
||||
PruneActiveTokens(config_.lattice_beam * config_.prune_scale);
|
||||
ProcessEmitting(decodable);
|
||||
// Important to call PruneCurrentTokens before ProcessNonemitting, or we
|
||||
// would get dangling forward pointers. Anyway, ProcessNonemitting uses the
|
||||
// beam.
|
||||
PruneCurrentTokens(config_.beam, &cur_toks_);
|
||||
ProcessNonemitting();
|
||||
}
|
||||
FinalizeDecoding();
|
||||
|
||||
// Returns true if we have any kind of traceback available (not necessarily
|
||||
// to the end state; query ReachedFinal() for that).
|
||||
return !final_costs_.empty();
|
||||
}
|
||||
|
||||
// Outputs an FST corresponding to the single best path
|
||||
// through the lattice.
|
||||
bool LatticeSimpleDecoder::GetBestPath(Lattice *ofst,
|
||||
bool use_final_probs) const {
|
||||
fst::VectorFst<LatticeArc> fst;
|
||||
GetRawLattice(&fst, use_final_probs);
|
||||
ShortestPath(fst, ofst);
|
||||
return (ofst->NumStates() > 0);
|
||||
}
|
||||
|
||||
// Outputs an FST corresponding to the raw, state-level
|
||||
// tracebacks.
|
||||
bool LatticeSimpleDecoder::GetRawLattice(Lattice *ofst,
|
||||
bool use_final_probs) const {
|
||||
typedef LatticeArc Arc;
|
||||
typedef Arc::StateId StateId;
|
||||
typedef Arc::Weight Weight;
|
||||
typedef Arc::Label Label;
|
||||
|
||||
// Note: you can't use the old interface (Decode()) if you want to
|
||||
// get the lattice with use_final_probs = false. You'd have to do
|
||||
// InitDecoding() and then AdvanceDecoding().
|
||||
if (decoding_finalized_ && !use_final_probs)
|
||||
KALDI_ERR << "You cannot call FinalizeDecoding() and then call "
|
||||
<< "GetRawLattice() with use_final_probs == false";
|
||||
|
||||
unordered_map<Token*, BaseFloat> final_costs_local;
|
||||
|
||||
const unordered_map<Token*, BaseFloat> &final_costs =
|
||||
(decoding_finalized_ ? final_costs_ : final_costs_local);
|
||||
|
||||
if (!decoding_finalized_ && use_final_probs)
|
||||
ComputeFinalCosts(&final_costs_local, NULL, NULL);
|
||||
|
||||
ofst->DeleteStates();
|
||||
int32 num_frames = NumFramesDecoded();
|
||||
KALDI_ASSERT(num_frames > 0);
|
||||
const int32 bucket_count = num_toks_/2 + 3;
|
||||
unordered_map<Token*, StateId> tok_map(bucket_count);
|
||||
// First create all states.
|
||||
for (int32 f = 0; f <= num_frames; f++) {
|
||||
if (active_toks_[f].toks == NULL) {
|
||||
KALDI_WARN << "GetRawLattice: no tokens active on frame " << f
|
||||
<< ": not producing lattice.\n";
|
||||
return false;
|
||||
}
|
||||
for (Token *tok = active_toks_[f].toks; tok != NULL; tok = tok->next)
|
||||
tok_map[tok] = ofst->AddState();
|
||||
// The next statement sets the start state of the output FST.
|
||||
// Because we always add new states to the head of the list
|
||||
// active_toks_[f].toks, and the start state was the first one
|
||||
// added, it will be the last one added to ofst.
|
||||
if (f == 0 && ofst->NumStates() > 0)
|
||||
ofst->SetStart(ofst->NumStates()-1);
|
||||
}
|
||||
StateId cur_state = 0; // we rely on the fact that we numbered these
|
||||
// consecutively (AddState() returns the numbers in order..)
|
||||
for (int32 f = 0; f <= num_frames; f++) {
|
||||
for (Token *tok = active_toks_[f].toks; tok != NULL; tok = tok->next,
|
||||
cur_state++) {
|
||||
for (ForwardLink *l = tok->links;
|
||||
l != NULL;
|
||||
l = l->next) {
|
||||
unordered_map<Token*, StateId>::const_iterator iter =
|
||||
tok_map.find(l->next_tok);
|
||||
StateId nextstate = iter->second;
|
||||
KALDI_ASSERT(iter != tok_map.end());
|
||||
Arc arc(l->ilabel, l->olabel,
|
||||
Weight(l->graph_cost, l->acoustic_cost),
|
||||
nextstate);
|
||||
ofst->AddArc(cur_state, arc);
|
||||
}
|
||||
if (f == num_frames) {
|
||||
if (use_final_probs && !final_costs.empty()) {
|
||||
unordered_map<Token*, BaseFloat>::const_iterator iter =
|
||||
final_costs.find(tok);
|
||||
if (iter != final_costs.end())
|
||||
ofst->SetFinal(cur_state, LatticeWeight(iter->second, 0));
|
||||
} else {
|
||||
ofst->SetFinal(cur_state, LatticeWeight::One());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
KALDI_ASSERT(cur_state == ofst->NumStates());
|
||||
return (cur_state != 0);
|
||||
}
|
||||
|
||||
// This function is now deprecated, since now we do determinization from outside
|
||||
// the LatticeSimpleDecoder class.
|
||||
// Outputs an FST corresponding to the lattice-determinized
|
||||
// lattice (one path per word sequence).
|
||||
bool LatticeSimpleDecoder::GetLattice(
|
||||
CompactLattice *ofst,
|
||||
bool use_final_probs) const {
|
||||
Lattice raw_fst;
|
||||
GetRawLattice(&raw_fst, use_final_probs);
|
||||
Invert(&raw_fst); // make it so word labels are on the input.
|
||||
if (!TopSort(&raw_fst)) // topological sort makes lattice-determinization more efficient
|
||||
KALDI_WARN << "Topological sorting of state-level lattice failed "
|
||||
"(probably your lexicon has empty words or your LM has epsilon cycles; this "
|
||||
" is a bad idea.)";
|
||||
// (in phase where we get backward-costs).
|
||||
fst::ILabelCompare<LatticeArc> ilabel_comp;
|
||||
ArcSort(&raw_fst, ilabel_comp); // sort on ilabel; makes
|
||||
// lattice-determinization more efficient.
|
||||
|
||||
fst::DeterminizeLatticePrunedOptions lat_opts;
|
||||
lat_opts.max_mem = config_.det_opts.max_mem;
|
||||
|
||||
DeterminizeLatticePruned(raw_fst, config_.lattice_beam, ofst, lat_opts);
|
||||
raw_fst.DeleteStates(); // Free memory-- raw_fst no longer needed.
|
||||
Connect(ofst); // Remove unreachable states... there might be
|
||||
// a small number of these, in some cases.
|
||||
// Note: if something went wrong and the raw lattice was empty,
|
||||
// we should still get to this point in the code without warnings or failures.
|
||||
return (ofst->NumStates() != 0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// FindOrAddToken either locates a token in cur_toks_, or if necessary inserts a new,
|
||||
// empty token (i.e. with no forward links) for the current frame. [note: it's
|
||||
// inserted if necessary into cur_toks_ and also into the singly linked list
|
||||
// of tokens active on this frame (whose head is at active_toks_[frame]).
|
||||
//
|
||||
// Returns the Token pointer. Sets "changed" (if non-NULL) to true
|
||||
// if the token was newly created or the cost changed.
|
||||
inline LatticeSimpleDecoder::Token *LatticeSimpleDecoder::FindOrAddToken(
|
||||
StateId state, int32 frame, BaseFloat tot_cost,
|
||||
bool emitting, bool *changed) {
|
||||
KALDI_ASSERT(frame < active_toks_.size());
|
||||
Token *&toks = active_toks_[frame].toks;
|
||||
|
||||
unordered_map<StateId, Token*>::iterator find_iter = cur_toks_.find(state);
|
||||
if (find_iter == cur_toks_.end()) { // no such token presently.
|
||||
// Create one.
|
||||
const BaseFloat extra_cost = 0.0;
|
||||
// tokens on the currently final frame have zero extra_cost
|
||||
// as any of them could end up
|
||||
// on the winning path.
|
||||
Token *new_tok = new Token (tot_cost, extra_cost, NULL, toks);
|
||||
toks = new_tok;
|
||||
num_toks_++;
|
||||
cur_toks_[state] = new_tok;
|
||||
if (changed) *changed = true;
|
||||
return new_tok;
|
||||
} else {
|
||||
Token *tok = find_iter->second; // There is an existing Token for this state.
|
||||
if (tok->tot_cost > tot_cost) {
|
||||
tok->tot_cost = tot_cost;
|
||||
if (changed) *changed = true;
|
||||
} else {
|
||||
if (changed) *changed = false;
|
||||
}
|
||||
return tok;
|
||||
}
|
||||
}
|
||||
|
||||
// delta is the amount by which the extra_costs must
|
||||
// change before it sets "extra_costs_changed" to true. If delta is larger,
|
||||
// we'll tend to go back less far toward the beginning of the file.
|
||||
void LatticeSimpleDecoder::PruneForwardLinks(
|
||||
int32 frame, bool *extra_costs_changed,
|
||||
bool *links_pruned, BaseFloat delta) {
|
||||
// We have to iterate until there is no more change, because the links
|
||||
// are not guaranteed to be in topological order.
|
||||
|
||||
*extra_costs_changed = false;
|
||||
*links_pruned = false;
|
||||
KALDI_ASSERT(frame >= 0 && frame < active_toks_.size());
|
||||
if (active_toks_[frame].toks == NULL ) { // empty list; this should
|
||||
// not happen.
|
||||
if (!warned_) {
|
||||
KALDI_WARN << "No tokens alive [doing pruning].. warning first "
|
||||
"time only for each utterance\n";
|
||||
warned_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool changed = true;
|
||||
while (changed) {
|
||||
changed = false;
|
||||
for (Token *tok = active_toks_[frame].toks; tok != NULL; tok = tok->next) {
|
||||
ForwardLink *link, *prev_link = NULL;
|
||||
// will recompute tok_extra_cost.
|
||||
BaseFloat tok_extra_cost = std::numeric_limits<BaseFloat>::infinity();
|
||||
for (link = tok->links; link != NULL; ) {
|
||||
// See if we need to excise this link...
|
||||
Token *next_tok = link->next_tok;
|
||||
BaseFloat link_extra_cost = next_tok->extra_cost +
|
||||
((tok->tot_cost + link->acoustic_cost + link->graph_cost)
|
||||
- next_tok->tot_cost);
|
||||
KALDI_ASSERT(link_extra_cost == link_extra_cost); // check for NaN
|
||||
if (link_extra_cost > config_.lattice_beam) { // excise link
|
||||
ForwardLink *next_link = link->next;
|
||||
if (prev_link != NULL) prev_link->next = next_link;
|
||||
else tok->links = next_link;
|
||||
delete link;
|
||||
link = next_link; // advance link but leave prev_link the same.
|
||||
*links_pruned = true;
|
||||
} else { // keep the link and update the tok_extra_cost if needed.
|
||||
if (link_extra_cost < 0.0) { // this is just a precaution.
|
||||
if (link_extra_cost < -0.01)
|
||||
KALDI_WARN << "Negative extra_cost: " << link_extra_cost;
|
||||
link_extra_cost = 0.0;
|
||||
}
|
||||
if (link_extra_cost < tok_extra_cost)
|
||||
tok_extra_cost = link_extra_cost;
|
||||
prev_link = link;
|
||||
link = link->next;
|
||||
}
|
||||
}
|
||||
if (fabs(tok_extra_cost - tok->extra_cost) > delta)
|
||||
changed = true;
|
||||
tok->extra_cost = tok_extra_cost; // will be +infinity or <= lattice_beam_.
|
||||
}
|
||||
if (changed) *extra_costs_changed = true;
|
||||
|
||||
// Note: it's theoretically possible that aggressive compiler
|
||||
// optimizations could cause an infinite loop here for small delta and
|
||||
// high-dynamic-range scores.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void LatticeSimpleDecoder::ComputeFinalCosts(
|
||||
unordered_map<Token*, BaseFloat> *final_costs,
|
||||
BaseFloat *final_relative_cost,
|
||||
BaseFloat *final_best_cost) const {
|
||||
KALDI_ASSERT(!decoding_finalized_);
|
||||
if (final_costs != NULL)
|
||||
final_costs->clear();
|
||||
|
||||
BaseFloat infinity = std::numeric_limits<BaseFloat>::infinity();
|
||||
BaseFloat best_cost = infinity,
|
||||
best_cost_with_final = infinity;
|
||||
|
||||
for (unordered_map<StateId, Token*>::const_iterator iter = cur_toks_.begin();
|
||||
iter != cur_toks_.end(); ++iter) {
|
||||
StateId state = iter->first;
|
||||
Token *tok = iter->second;
|
||||
BaseFloat final_cost = fst_.Final(state).Value();
|
||||
BaseFloat cost = tok->tot_cost,
|
||||
cost_with_final = cost + final_cost;
|
||||
best_cost = std::min(cost, best_cost);
|
||||
best_cost_with_final = std::min(cost_with_final, best_cost_with_final);
|
||||
if (final_costs != NULL && final_cost != infinity)
|
||||
(*final_costs)[tok] = final_cost;
|
||||
}
|
||||
if (final_relative_cost != NULL) {
|
||||
if (best_cost == infinity && best_cost_with_final == infinity) {
|
||||
// Likely this will only happen if there are no tokens surviving.
|
||||
// This seems the least bad way to handle it.
|
||||
*final_relative_cost = infinity;
|
||||
} else {
|
||||
*final_relative_cost = best_cost_with_final - best_cost;
|
||||
}
|
||||
}
|
||||
if (final_best_cost != NULL) {
|
||||
if (best_cost_with_final != infinity) { // final-state exists.
|
||||
*final_best_cost = best_cost_with_final;
|
||||
} else { // no final-state exists.
|
||||
*final_best_cost = best_cost;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// PruneForwardLinksFinal is a version of PruneForwardLinks that we call
|
||||
// on the final frame. If there are final tokens active, it uses the final-probs
|
||||
// for pruning, otherwise it treats all tokens as final.
|
||||
void LatticeSimpleDecoder::PruneForwardLinksFinal() {
|
||||
KALDI_ASSERT(!active_toks_.empty());
|
||||
int32 frame_plus_one = active_toks_.size() - 1;
|
||||
|
||||
if (active_toks_[frame_plus_one].toks == NULL) // empty list; should not happen.
|
||||
KALDI_WARN << "No tokens alive at end of file\n";
|
||||
|
||||
typedef unordered_map<Token*, BaseFloat>::const_iterator IterType;
|
||||
ComputeFinalCosts(&final_costs_, &final_relative_cost_, &final_best_cost_);
|
||||
decoding_finalized_ = true;
|
||||
// We're about to delete some of the tokens active on the final frame, so we
|
||||
// clear cur_toks_ because otherwise it would then contain dangling pointers.
|
||||
cur_toks_.clear();
|
||||
|
||||
// Now go through tokens on this frame, pruning forward links... may have to
|
||||
// iterate a few times until there is no more change, because the list is not
|
||||
// in topological order. This is a modified version of the code in
|
||||
// PruneForwardLinks, but here we also take account of the final-probs.
|
||||
bool changed = true;
|
||||
BaseFloat delta = 1.0e-05;
|
||||
while (changed) {
|
||||
changed = false;
|
||||
for (Token *tok = active_toks_[frame_plus_one].toks;
|
||||
tok != NULL; tok = tok->next) {
|
||||
ForwardLink *link, *prev_link=NULL;
|
||||
// will recompute tok_extra_cost. It has a term in it that corresponds
|
||||
// to the "final-prob", so instead of initializing tok_extra_cost to infinity
|
||||
// below we set it to the difference between the (score+final_prob) of this token,
|
||||
// and the best such (score+final_prob).
|
||||
|
||||
BaseFloat final_cost;
|
||||
if (final_costs_.empty()) {
|
||||
final_cost = 0.0;
|
||||
} else {
|
||||
IterType iter = final_costs_.find(tok);
|
||||
if (iter != final_costs_.end())
|
||||
final_cost = iter->second;
|
||||
else
|
||||
final_cost = std::numeric_limits<BaseFloat>::infinity();
|
||||
}
|
||||
BaseFloat tok_extra_cost = tok->tot_cost + final_cost - final_best_cost_;
|
||||
// tok_extra_cost will be a "min" over either directly being final, or
|
||||
// being indirectly final through other links, and the loop below may
|
||||
// decrease its value:
|
||||
for (link = tok->links; link != NULL; ) {
|
||||
// See if we need to excise this link...
|
||||
Token *next_tok = link->next_tok;
|
||||
BaseFloat link_extra_cost = next_tok->extra_cost +
|
||||
((tok->tot_cost + link->acoustic_cost + link->graph_cost)
|
||||
- next_tok->tot_cost);
|
||||
if (link_extra_cost > config_.lattice_beam) { // excise link
|
||||
ForwardLink *next_link = link->next;
|
||||
if (prev_link != NULL) prev_link->next = next_link;
|
||||
else tok->links = next_link;
|
||||
delete link;
|
||||
link = next_link; // advance link but leave prev_link the same.
|
||||
} else { // keep the link and update the tok_extra_cost if needed.
|
||||
if (link_extra_cost < 0.0) { // this is just a precaution.
|
||||
if (link_extra_cost < -0.01)
|
||||
KALDI_WARN << "Negative extra_cost: " << link_extra_cost;
|
||||
link_extra_cost = 0.0;
|
||||
}
|
||||
if (link_extra_cost < tok_extra_cost)
|
||||
tok_extra_cost = link_extra_cost;
|
||||
prev_link = link;
|
||||
link = link->next;
|
||||
}
|
||||
}
|
||||
// prune away tokens worse than lattice_beam above best path. This step
|
||||
// was not necessary in the non-final case because then, this case
|
||||
// showed up as having no forward links. Here, the tok_extra_cost has
|
||||
// an extra component relating to the final-prob.
|
||||
if (tok_extra_cost > config_.lattice_beam)
|
||||
tok_extra_cost = std::numeric_limits<BaseFloat>::infinity();
|
||||
// to be pruned in PruneTokensForFrame
|
||||
|
||||
if (!ApproxEqual(tok->extra_cost, tok_extra_cost, delta))
|
||||
changed = true;
|
||||
tok->extra_cost = tok_extra_cost; // will be +infinity or <= lattice_beam_.
|
||||
}
|
||||
} // while changed
|
||||
}
|
||||
|
||||
BaseFloat LatticeSimpleDecoder::FinalRelativeCost() const {
|
||||
if (!decoding_finalized_) {
|
||||
BaseFloat relative_cost;
|
||||
ComputeFinalCosts(NULL, &relative_cost, NULL);
|
||||
return relative_cost;
|
||||
} else {
|
||||
// we're not allowed to call that function if FinalizeDecoding() has
|
||||
// been called; return a cached value.
|
||||
return final_relative_cost_;
|
||||
}
|
||||
}
|
||||
|
||||
// Prune away any tokens on this frame that have no forward links. [we don't do
|
||||
// this in PruneForwardLinks because it would give us a problem with dangling
|
||||
// pointers].
|
||||
void LatticeSimpleDecoder::PruneTokensForFrame(int32 frame) {
|
||||
KALDI_ASSERT(frame >= 0 && frame < active_toks_.size());
|
||||
Token *&toks = active_toks_[frame].toks;
|
||||
if (toks == NULL)
|
||||
KALDI_WARN << "No tokens alive [doing pruning]";
|
||||
Token *tok, *next_tok, *prev_tok = NULL;
|
||||
for (tok = toks; tok != NULL; tok = next_tok) {
|
||||
next_tok = tok->next;
|
||||
if (tok->extra_cost == std::numeric_limits<BaseFloat>::infinity()) {
|
||||
// Next token is unreachable from end of graph; excise tok from list
|
||||
// and delete tok.
|
||||
if (prev_tok != NULL) prev_tok->next = tok->next;
|
||||
else toks = tok->next;
|
||||
delete tok;
|
||||
num_toks_--;
|
||||
} else {
|
||||
prev_tok = tok;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Go backwards through still-alive tokens, pruning them, starting not from
|
||||
// the current frame (where we want to keep all tokens) but from the frame before
|
||||
// that. We go backwards through the frames and stop when we reach a point
|
||||
// where the delta-costs are not changing (and the delta controls when we consider
|
||||
// a cost to have "not changed").
|
||||
void LatticeSimpleDecoder::PruneActiveTokens(BaseFloat delta) {
|
||||
int32 cur_frame_plus_one = NumFramesDecoded();
|
||||
int32 num_toks_begin = num_toks_;
|
||||
// The index "f" below represents a "frame plus one", i.e. you'd have to subtract
|
||||
// one to get the corresponding index for the decodable object.
|
||||
for (int32 f = cur_frame_plus_one - 1; f >= 0; f--) {
|
||||
// Reason why we need to prune forward links in this situation:
|
||||
// (1) we have never pruned them
|
||||
// (2) we never pruned the forward links on the next frame, which
|
||||
//
|
||||
if (active_toks_[f].must_prune_forward_links) {
|
||||
bool extra_costs_changed = false, links_pruned = false;
|
||||
PruneForwardLinks(f, &extra_costs_changed, &links_pruned, delta);
|
||||
if (extra_costs_changed && f > 0)
|
||||
active_toks_[f-1].must_prune_forward_links = true;
|
||||
if (links_pruned)
|
||||
active_toks_[f].must_prune_tokens = true;
|
||||
active_toks_[f].must_prune_forward_links = false;
|
||||
}
|
||||
if (f+1 < cur_frame_plus_one &&
|
||||
active_toks_[f+1].must_prune_tokens) {
|
||||
PruneTokensForFrame(f+1);
|
||||
active_toks_[f+1].must_prune_tokens = false;
|
||||
}
|
||||
}
|
||||
KALDI_VLOG(3) << "PruneActiveTokens: pruned tokens from " << num_toks_begin
|
||||
<< " to " << num_toks_;
|
||||
}
|
||||
|
||||
|
||||
// FinalizeDecoding() is a version of PruneActiveTokens that we call
|
||||
// (optionally) on the final frame. Takes into account the final-prob of
|
||||
// tokens. This function used to be called PruneActiveTokensFinal().
|
||||
void LatticeSimpleDecoder::FinalizeDecoding() {
|
||||
int32 final_frame_plus_one = NumFramesDecoded();
|
||||
int32 num_toks_begin = num_toks_;
|
||||
PruneForwardLinksFinal();
|
||||
for (int32 f = final_frame_plus_one - 1; f >= 0; f--) {
|
||||
bool b1, b2; // values not used.
|
||||
BaseFloat dontcare = 0.0;
|
||||
PruneForwardLinks(f, &b1, &b2, dontcare);
|
||||
PruneTokensForFrame(f + 1);
|
||||
}
|
||||
PruneTokensForFrame(0);
|
||||
KALDI_VLOG(3) << "pruned tokens from " << num_toks_begin
|
||||
<< " to " << num_toks_;
|
||||
}
|
||||
|
||||
void LatticeSimpleDecoder::ProcessEmitting(DecodableInterface *decodable) {
|
||||
int32 frame = active_toks_.size() - 1; // frame is the frame-index
|
||||
// (zero-based) used to get likelihoods
|
||||
// from the decodable object.
|
||||
active_toks_.resize(active_toks_.size() + 1);
|
||||
prev_toks_.clear();
|
||||
cur_toks_.swap(prev_toks_);
|
||||
|
||||
// Processes emitting arcs for one frame. Propagates from
|
||||
// prev_toks_ to cur_toks_.
|
||||
BaseFloat cutoff = std::numeric_limits<BaseFloat>::infinity();
|
||||
for (unordered_map<StateId, Token*>::iterator iter = prev_toks_.begin();
|
||||
iter != prev_toks_.end();
|
||||
++iter) {
|
||||
StateId state = iter->first;
|
||||
Token *tok = iter->second;
|
||||
for (fst::ArcIterator<fst::Fst<Arc> > aiter(fst_, state);
|
||||
!aiter.Done();
|
||||
aiter.Next()) {
|
||||
const Arc &arc = aiter.Value();
|
||||
if (arc.ilabel != 0) { // propagate..
|
||||
BaseFloat ac_cost = -decodable->LogLikelihood(frame, arc.ilabel),
|
||||
graph_cost = arc.weight.Value(),
|
||||
cur_cost = tok->tot_cost,
|
||||
tot_cost = cur_cost + ac_cost + graph_cost;
|
||||
if (tot_cost >= cutoff) continue;
|
||||
else if (tot_cost + config_.beam < cutoff)
|
||||
cutoff = tot_cost + config_.beam;
|
||||
// AddToken adds the next_tok to cur_toks_ (if not already present).
|
||||
Token *next_tok = FindOrAddToken(arc.nextstate, frame + 1, tot_cost,
|
||||
true, NULL);
|
||||
|
||||
// Add ForwardLink from tok to next_tok (put on head of list tok->links)
|
||||
tok->links = new ForwardLink(next_tok, arc.ilabel, arc.olabel,
|
||||
graph_cost, ac_cost, tok->links);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LatticeSimpleDecoder::ProcessNonemitting() {
|
||||
KALDI_ASSERT(!active_toks_.empty());
|
||||
int32 frame = static_cast<int32>(active_toks_.size()) - 2;
|
||||
// Note: "frame" is the time-index we just processed, or -1 if
|
||||
// we are processing the nonemitting transitions before the
|
||||
// first frame (called from InitDecoding()).
|
||||
|
||||
// Processes nonemitting arcs for one frame. Propagates within
|
||||
// cur_toks_. Note-- this queue structure is is not very optimal as
|
||||
// it may cause us to process states unnecessarily (e.g. more than once),
|
||||
// but in the baseline code, turning this vector into a set to fix this
|
||||
// problem did not improve overall speed.
|
||||
std::vector<StateId> queue;
|
||||
BaseFloat best_cost = std::numeric_limits<BaseFloat>::infinity();
|
||||
for (unordered_map<StateId, Token*>::iterator iter = cur_toks_.begin();
|
||||
iter != cur_toks_.end();
|
||||
++iter) {
|
||||
StateId state = iter->first;
|
||||
if (fst_.NumInputEpsilons(state) != 0)
|
||||
queue.push_back(state);
|
||||
best_cost = std::min(best_cost, iter->second->tot_cost);
|
||||
}
|
||||
if (queue.empty()) {
|
||||
if (!warned_) {
|
||||
KALDI_ERR << "Error in ProcessEmitting: no surviving tokens: frame is "
|
||||
<< frame;
|
||||
warned_ = true;
|
||||
}
|
||||
}
|
||||
BaseFloat cutoff = best_cost + config_.beam;
|
||||
|
||||
while (!queue.empty()) {
|
||||
StateId state = queue.back();
|
||||
queue.pop_back();
|
||||
Token *tok = cur_toks_[state];
|
||||
// If "tok" has any existing forward links, delete them,
|
||||
// because we're about to regenerate them. This is a kind
|
||||
// of non-optimality (remember, this is the simple decoder),
|
||||
// but since most states are emitting it's not a huge issue.
|
||||
tok->DeleteForwardLinks();
|
||||
tok->links = NULL;
|
||||
for (fst::ArcIterator<fst::Fst<Arc> > aiter(fst_, state);
|
||||
!aiter.Done();
|
||||
aiter.Next()) {
|
||||
const Arc &arc = aiter.Value();
|
||||
if (arc.ilabel == 0) { // propagate nonemitting only...
|
||||
BaseFloat graph_cost = arc.weight.Value(),
|
||||
cur_cost = tok->tot_cost,
|
||||
tot_cost = cur_cost + graph_cost;
|
||||
if (tot_cost < cutoff) {
|
||||
bool changed;
|
||||
Token *new_tok = FindOrAddToken(arc.nextstate, frame + 1, tot_cost,
|
||||
false, &changed);
|
||||
|
||||
tok->links = new ForwardLink(new_tok, 0, arc.olabel,
|
||||
graph_cost, 0, tok->links);
|
||||
|
||||
// "changed" tells us whether the new token has a different
|
||||
// cost from before, or is new [if so, add into queue].
|
||||
if (changed && fst_.NumInputEpsilons(arc.nextstate) != 0)
|
||||
queue.push_back(arc.nextstate);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LatticeSimpleDecoder::ClearActiveTokens() { // a cleanup routine, at utt end/begin
|
||||
for (size_t i = 0; i < active_toks_.size(); i++) {
|
||||
// Delete all tokens alive on this frame, and any forward
|
||||
// links they may have.
|
||||
for (Token *tok = active_toks_[i].toks; tok != NULL; ) {
|
||||
tok->DeleteForwardLinks();
|
||||
Token *next_tok = tok->next;
|
||||
delete tok;
|
||||
num_toks_--;
|
||||
tok = next_tok;
|
||||
}
|
||||
}
|
||||
active_toks_.clear();
|
||||
KALDI_ASSERT(num_toks_ == 0);
|
||||
}
|
||||
|
||||
// PruneCurrentTokens deletes the tokens from the "toks" map, but not
|
||||
// from the active_toks_ list, which could cause dangling forward pointers
|
||||
// (will delete it during regular pruning operation).
|
||||
void LatticeSimpleDecoder::PruneCurrentTokens(BaseFloat beam, unordered_map<StateId, Token*> *toks) {
|
||||
if (toks->empty()) {
|
||||
KALDI_VLOG(2) << "No tokens to prune.\n";
|
||||
return;
|
||||
}
|
||||
BaseFloat best_cost = 1.0e+10; // positive == high cost == bad.
|
||||
for (unordered_map<StateId, Token*>::iterator iter = toks->begin();
|
||||
iter != toks->end(); ++iter) {
|
||||
best_cost =
|
||||
std::min(best_cost,
|
||||
static_cast<BaseFloat>(iter->second->tot_cost));
|
||||
}
|
||||
std::vector<StateId> retained;
|
||||
BaseFloat cutoff = best_cost + beam;
|
||||
for (unordered_map<StateId, Token*>::iterator iter = toks->begin();
|
||||
iter != toks->end(); ++iter) {
|
||||
if (iter->second->tot_cost < cutoff)
|
||||
retained.push_back(iter->first);
|
||||
}
|
||||
unordered_map<StateId, Token*> tmp;
|
||||
for (size_t i = 0; i < retained.size(); i++) {
|
||||
tmp[retained[i]] = (*toks)[retained[i]];
|
||||
}
|
||||
KALDI_VLOG(2) << "Pruned to "<<(retained.size())<<" toks.\n";
|
||||
tmp.swap(*toks);
|
||||
}
|
||||
|
||||
|
||||
} // end namespace kaldi.
|
||||
|
||||
|
||||
@@ -0,0 +1,318 @@
|
||||
// decoder/lattice-simple-decoder.h
|
||||
|
||||
// Copyright 2009-2012 Microsoft Corporation
|
||||
// 2012-2014 Johns Hopkins University (Author: Daniel Povey)
|
||||
// 2014 Guoguo Chen
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef KALDI_DECODER_LATTICE_SIMPLE_DECODER_H_
|
||||
#define KALDI_DECODER_LATTICE_SIMPLE_DECODER_H_
|
||||
|
||||
|
||||
#include "util/stl-utils.h"
|
||||
#include "fst/fstlib.h"
|
||||
#include "itf/decodable-itf.h"
|
||||
#include "fstext/fstext-lib.h"
|
||||
#include "lat/determinize-lattice-pruned.h"
|
||||
#include "lat/kaldi-lattice.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace kaldi {
|
||||
|
||||
struct LatticeSimpleDecoderConfig {
|
||||
BaseFloat beam;
|
||||
BaseFloat lattice_beam;
|
||||
int32 prune_interval;
|
||||
bool determinize_lattice; // not inspected by this class... used in
|
||||
// command-line program.
|
||||
bool prune_lattice;
|
||||
BaseFloat beam_ratio;
|
||||
BaseFloat prune_scale; // Note: we don't make this configurable on the command line,
|
||||
// it's not a very important parameter. It affects the
|
||||
// algorithm that prunes the tokens as we go.
|
||||
fst::DeterminizeLatticePhonePrunedOptions det_opts;
|
||||
|
||||
LatticeSimpleDecoderConfig(): beam(16.0),
|
||||
lattice_beam(10.0),
|
||||
prune_interval(25),
|
||||
determinize_lattice(true),
|
||||
beam_ratio(0.9),
|
||||
prune_scale(0.1) { }
|
||||
void Register(OptionsItf *opts) {
|
||||
det_opts.Register(opts);
|
||||
opts->Register("beam", &beam, "Decoding beam.");
|
||||
opts->Register("lattice-beam", &lattice_beam, "Lattice generation beam");
|
||||
opts->Register("prune-interval", &prune_interval, "Interval (in frames) at "
|
||||
"which to prune tokens");
|
||||
opts->Register("determinize-lattice", &determinize_lattice, "If true, "
|
||||
"determinize the lattice (in a special sense, keeping only "
|
||||
"best pdf-sequence for each word-sequence).");
|
||||
}
|
||||
void Check() const {
|
||||
KALDI_ASSERT(beam > 0.0 && lattice_beam > 0.0 && prune_interval > 0);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** Simplest possible decoder, included largely for didactic purposes and as a
|
||||
means to debug more highly optimized decoders. See \ref decoders_simple
|
||||
for more information.
|
||||
*/
|
||||
class LatticeSimpleDecoder {
|
||||
public:
|
||||
typedef fst::StdArc Arc;
|
||||
typedef Arc::Label Label;
|
||||
typedef Arc::StateId StateId;
|
||||
typedef Arc::Weight Weight;
|
||||
// instantiate this class once for each thing you have to decode.
|
||||
LatticeSimpleDecoder(const fst::Fst<fst::StdArc> &fst,
|
||||
const LatticeSimpleDecoderConfig &config):
|
||||
fst_(fst), config_(config), num_toks_(0) { config.Check(); }
|
||||
|
||||
~LatticeSimpleDecoder() { ClearActiveTokens(); }
|
||||
|
||||
const LatticeSimpleDecoderConfig &GetOptions() const {
|
||||
return config_;
|
||||
}
|
||||
|
||||
// Returns true if any kind of traceback is available (not necessarily from
|
||||
// a final state).
|
||||
bool Decode(DecodableInterface *decodable);
|
||||
|
||||
|
||||
/// says whether a final-state was active on the last frame. If it was not, the
|
||||
/// lattice (or traceback) will end with states that are not final-states.
|
||||
bool ReachedFinal() const {
|
||||
return FinalRelativeCost() != std::numeric_limits<BaseFloat>::infinity();
|
||||
}
|
||||
|
||||
/// InitDecoding initializes the decoding, and should only be used if you
|
||||
/// intend to call AdvanceDecoding(). If you call Decode(), you don't need
|
||||
/// to call this. You can call InitDecoding if you have already decoded an
|
||||
/// utterance and want to start with a new utterance.
|
||||
void InitDecoding();
|
||||
|
||||
/// This function may be optionally called after AdvanceDecoding(), when you
|
||||
/// do not plan to decode any further. It does an extra pruning step that
|
||||
/// will help to prune the lattices output by GetLattice and (particularly)
|
||||
/// GetRawLattice more accurately, particularly toward the end of the
|
||||
/// utterance. It does this by using the final-probs in pruning (if any
|
||||
/// final-state survived); it also does a final pruning step that visits all
|
||||
/// states (the pruning that is done during decoding may fail to prune states
|
||||
/// that are within kPruningScale = 0.1 outside of the beam). If you call
|
||||
/// this, you cannot call AdvanceDecoding again (it will fail), and you
|
||||
/// cannot call GetLattice() and related functions with use_final_probs =
|
||||
/// false.
|
||||
/// Used to be called PruneActiveTokensFinal().
|
||||
void FinalizeDecoding();
|
||||
|
||||
/// FinalRelativeCost() serves the same purpose as ReachedFinal(), but gives
|
||||
/// more information. It returns the difference between the best (final-cost
|
||||
/// plus cost) of any token on the final frame, and the best cost of any token
|
||||
/// on the final frame. If it is infinity it means no final-states were
|
||||
/// present on the final frame. It will usually be nonnegative. If it not
|
||||
/// too positive (e.g. < 5 is my first guess, but this is not tested) you can
|
||||
/// take it as a good indication that we reached the final-state with
|
||||
/// reasonable likelihood.
|
||||
BaseFloat FinalRelativeCost() const;
|
||||
|
||||
// Outputs an FST corresponding to the single best path
|
||||
// through the lattice. Returns true if result is nonempty
|
||||
// (using the return status is deprecated, it will become void).
|
||||
// If "use_final_probs" is true AND we reached the final-state
|
||||
// of the graph then it will include those as final-probs, else
|
||||
// it will treat all final-probs as one.
|
||||
bool GetBestPath(Lattice *lat,
|
||||
bool use_final_probs = true) const;
|
||||
|
||||
// Outputs an FST corresponding to the raw, state-level
|
||||
// tracebacks. Returns true if result is nonempty
|
||||
// (using the return status is deprecated, it will become void).
|
||||
// If "use_final_probs" is true AND we reached the final-state
|
||||
// of the graph then it will include those as final-probs, else
|
||||
// it will treat all final-probs as one.
|
||||
bool GetRawLattice(Lattice *lat,
|
||||
bool use_final_probs = true) const;
|
||||
|
||||
// This function is now deprecated, since now we do determinization from
|
||||
// outside the LatticeTrackingDecoder class.
|
||||
// Outputs an FST corresponding to the lattice-determinized
|
||||
// lattice (one path per word sequence). [will become deprecated,
|
||||
// users should determinize themselves.]
|
||||
bool GetLattice(CompactLattice *clat,
|
||||
bool use_final_probs = true) const;
|
||||
|
||||
inline int32 NumFramesDecoded() const { return active_toks_.size() - 1; }
|
||||
private:
|
||||
struct Token;
|
||||
// ForwardLinks are the links from a token to a token on the next frame.
|
||||
// or sometimes on the current frame (for input-epsilon links).
|
||||
struct ForwardLink {
|
||||
Token *next_tok; // the next token [or NULL if represents final-state]
|
||||
Label ilabel; // ilabel on link.
|
||||
Label olabel; // olabel on link.
|
||||
BaseFloat graph_cost; // graph cost of traversing link (contains LM, etc.)
|
||||
BaseFloat acoustic_cost; // acoustic cost (pre-scaled) of traversing link
|
||||
ForwardLink *next; // next in singly-linked list of forward links from a
|
||||
// token.
|
||||
ForwardLink(Token *next_tok, Label ilabel, Label olabel,
|
||||
BaseFloat graph_cost, BaseFloat acoustic_cost,
|
||||
ForwardLink *next):
|
||||
next_tok(next_tok), ilabel(ilabel), olabel(olabel),
|
||||
graph_cost(graph_cost), acoustic_cost(acoustic_cost),
|
||||
next(next) { }
|
||||
};
|
||||
|
||||
// Token is what's resident in a particular state at a particular time.
|
||||
// In this decoder a Token actually contains *forward* links.
|
||||
// When first created, a Token just has the (total) cost. We add forward
|
||||
// links from it when we process the next frame.
|
||||
struct Token {
|
||||
BaseFloat tot_cost; // would equal weight.Value()... cost up to this point.
|
||||
BaseFloat extra_cost; // >= 0. After calling PruneForwardLinks, this equals
|
||||
// the minimum difference between the cost of the best path this is on,
|
||||
// and the cost of the absolute best path, under the assumption
|
||||
// that any of the currently active states at the decoding front may
|
||||
// eventually succeed (e.g. if you were to take the currently active states
|
||||
// one by one and compute this difference, and then take the minimum).
|
||||
|
||||
ForwardLink *links; // Head of singly linked list of ForwardLinks
|
||||
|
||||
Token *next; // Next in list of tokens for this frame.
|
||||
|
||||
Token(BaseFloat tot_cost, BaseFloat extra_cost, ForwardLink *links,
|
||||
Token *next): tot_cost(tot_cost), extra_cost(extra_cost), links(links),
|
||||
next(next) { }
|
||||
Token() {}
|
||||
void DeleteForwardLinks() {
|
||||
ForwardLink *l = links, *m;
|
||||
while (l != NULL) {
|
||||
m = l->next;
|
||||
delete l;
|
||||
l = m;
|
||||
}
|
||||
links = NULL;
|
||||
}
|
||||
};
|
||||
|
||||
// head and tail of per-frame list of Tokens (list is in topological order),
|
||||
// and something saying whether we ever pruned it using PruneForwardLinks.
|
||||
struct TokenList {
|
||||
Token *toks;
|
||||
bool must_prune_forward_links;
|
||||
bool must_prune_tokens;
|
||||
TokenList(): toks(NULL), must_prune_forward_links(true),
|
||||
must_prune_tokens(true) { }
|
||||
};
|
||||
|
||||
|
||||
// FindOrAddToken either locates a token in cur_toks_, or if necessary inserts a new,
|
||||
// empty token (i.e. with no forward links) for the current frame. [note: it's
|
||||
// inserted if necessary into cur_toks_ and also into the singly linked list
|
||||
// of tokens active on this frame (whose head is at active_toks_[frame]).
|
||||
//
|
||||
// Returns the Token pointer. Sets "changed" (if non-NULL) to true
|
||||
// if the token was newly created or the cost changed.
|
||||
inline Token *FindOrAddToken(StateId state, int32 frame_plus_one,
|
||||
BaseFloat tot_cost, bool emitting, bool *changed);
|
||||
|
||||
// delta is the amount by which the extra_costs must
|
||||
// change before it sets "extra_costs_changed" to true. If delta is larger,
|
||||
// we'll tend to go back less far toward the beginning of the file.
|
||||
void PruneForwardLinks(int32 frame, bool *extra_costs_changed,
|
||||
bool *links_pruned,
|
||||
BaseFloat delta);
|
||||
|
||||
// PruneForwardLinksFinal is a version of PruneForwardLinks that we call
|
||||
// on the final frame. If there are final tokens active, it uses the final-probs
|
||||
// for pruning, otherwise it treats all tokens as final.
|
||||
void PruneForwardLinksFinal();
|
||||
|
||||
// Prune away any tokens on this frame that have no forward links. [we don't do
|
||||
// this in PruneForwardLinks because it would give us a problem with dangling
|
||||
// pointers].
|
||||
void PruneTokensForFrame(int32 frame);
|
||||
|
||||
// Go backwards through still-alive tokens, pruning them if the
|
||||
// forward+backward cost is more than lat_beam away from the best path. It's
|
||||
// possible to prove that this is "correct" in the sense that we won't lose
|
||||
// anything outside of lat_beam, regardless of what happens in the future.
|
||||
// delta controls when it considers a cost to have changed enough to continue
|
||||
// going backward and propagating the change. larger delta -> will recurse
|
||||
// less far.
|
||||
void PruneActiveTokens(BaseFloat delta);
|
||||
|
||||
void ProcessEmitting(DecodableInterface *decodable);
|
||||
|
||||
void ProcessNonemitting();
|
||||
|
||||
void ClearActiveTokens(); // a cleanup routine, at utt end/begin
|
||||
|
||||
// This function computes the final-costs for tokens active on the final
|
||||
// frame. It outputs to final-costs, if non-NULL, a map from the Token*
|
||||
// pointer to the final-prob of the corresponding state, or zero for all states if
|
||||
// none were final. It outputs to final_relative_cost, if non-NULL, the
|
||||
// difference between the best forward-cost including the final-prob cost, and
|
||||
// the best forward-cost without including the final-prob cost (this will
|
||||
// usually be positive), or infinity if there were no final-probs. It outputs
|
||||
// to final_best_cost, if non-NULL, the lowest for any token t active on the
|
||||
// final frame, of t + final-cost[t], where final-cost[t] is the final-cost
|
||||
// in the graph of the state corresponding to token t, or zero if there
|
||||
// were no final-probs active on the final frame.
|
||||
// You cannot call this after FinalizeDecoding() has been called; in that
|
||||
// case you should get the answer from class-member variables.
|
||||
void ComputeFinalCosts(unordered_map<Token*, BaseFloat> *final_costs,
|
||||
BaseFloat *final_relative_cost,
|
||||
BaseFloat *final_best_cost) const;
|
||||
|
||||
|
||||
// PruneCurrentTokens deletes the tokens from the "toks" map, but not
|
||||
// from the active_toks_ list, which could cause dangling forward pointers
|
||||
// (will delete it during regular pruning operation).
|
||||
void PruneCurrentTokens(BaseFloat beam, unordered_map<StateId, Token*> *toks);
|
||||
|
||||
unordered_map<StateId, Token*> cur_toks_;
|
||||
unordered_map<StateId, Token*> prev_toks_;
|
||||
std::vector<TokenList> active_toks_; // Lists of tokens, indexed by
|
||||
// frame_plus_one
|
||||
const fst::Fst<fst::StdArc> &fst_;
|
||||
LatticeSimpleDecoderConfig config_;
|
||||
int32 num_toks_; // current total #toks allocated...
|
||||
bool warned_;
|
||||
|
||||
|
||||
/// decoding_finalized_ is true if someone called FinalizeDecoding(). [note,
|
||||
/// calling this is optional]. If true, it's forbidden to decode more. Also,
|
||||
/// if this is set, then the output of ComputeFinalCosts() is in the next
|
||||
/// three variables. The reason we need to do this is that after
|
||||
/// FinalizeDecoding() calls PruneTokensForFrame() for the final frame, some
|
||||
/// of the tokens on the last frame are freed, so we free the list from
|
||||
/// cur_toks_ to avoid having dangling pointers hanging around.
|
||||
bool decoding_finalized_;
|
||||
/// For the meaning of the next 3 variables, see the comment for
|
||||
/// decoding_finalized_ above., and ComputeFinalCosts().
|
||||
unordered_map<Token*, BaseFloat> final_costs_;
|
||||
BaseFloat final_relative_cost_;
|
||||
BaseFloat final_best_cost_;
|
||||
};
|
||||
|
||||
|
||||
} // end namespace kaldi.
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,293 @@
|
||||
// decoder/simple-decoder.cc
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation
|
||||
// 2012-2013 Johns Hopkins University (author: Daniel Povey)
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "decoder/simple-decoder.h"
|
||||
#include "fstext/remove-eps-local.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace kaldi {
|
||||
|
||||
SimpleDecoder::~SimpleDecoder() {
|
||||
ClearToks(cur_toks_);
|
||||
ClearToks(prev_toks_);
|
||||
}
|
||||
|
||||
|
||||
bool SimpleDecoder::Decode(DecodableInterface *decodable) {
|
||||
InitDecoding();
|
||||
AdvanceDecoding(decodable);
|
||||
return (!cur_toks_.empty());
|
||||
}
|
||||
|
||||
void SimpleDecoder::InitDecoding() {
|
||||
// clean up from last time:
|
||||
ClearToks(cur_toks_);
|
||||
ClearToks(prev_toks_);
|
||||
// initialize decoding:
|
||||
StateId start_state = fst_.Start();
|
||||
KALDI_ASSERT(start_state != fst::kNoStateId);
|
||||
StdArc dummy_arc(0, 0, StdWeight::One(), start_state);
|
||||
cur_toks_[start_state] = new Token(dummy_arc, 0.0, NULL);
|
||||
num_frames_decoded_ = 0;
|
||||
ProcessNonemitting();
|
||||
}
|
||||
|
||||
void SimpleDecoder::AdvanceDecoding(DecodableInterface *decodable,
|
||||
int32 max_num_frames) {
|
||||
KALDI_ASSERT(num_frames_decoded_ >= 0 &&
|
||||
"You must call InitDecoding() before AdvanceDecoding()");
|
||||
int32 num_frames_ready = decodable->NumFramesReady();
|
||||
// num_frames_ready must be >= num_frames_decoded, or else
|
||||
// the number of frames ready must have decreased (which doesn't
|
||||
// make sense) or the decodable object changed between calls
|
||||
// (which isn't allowed).
|
||||
KALDI_ASSERT(num_frames_ready >= num_frames_decoded_);
|
||||
int32 target_frames_decoded = num_frames_ready;
|
||||
if (max_num_frames >= 0)
|
||||
target_frames_decoded = std::min(target_frames_decoded,
|
||||
num_frames_decoded_ + max_num_frames);
|
||||
while (num_frames_decoded_ < target_frames_decoded) {
|
||||
// note: ProcessEmitting() increments num_frames_decoded_
|
||||
ClearToks(prev_toks_);
|
||||
cur_toks_.swap(prev_toks_);
|
||||
ProcessEmitting(decodable);
|
||||
ProcessNonemitting();
|
||||
PruneToks(beam_, &cur_toks_);
|
||||
}
|
||||
}
|
||||
|
||||
bool SimpleDecoder::ReachedFinal() const {
|
||||
for (unordered_map<StateId, Token*>::const_iterator iter = cur_toks_.begin();
|
||||
iter != cur_toks_.end();
|
||||
++iter) {
|
||||
if (iter->second->cost_ != std::numeric_limits<BaseFloat>::infinity() &&
|
||||
fst_.Final(iter->first) != StdWeight::Zero())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
BaseFloat SimpleDecoder::FinalRelativeCost() const {
|
||||
// as a special case, if there are no active tokens at all (e.g. some kind of
|
||||
// pruning failure), return infinity.
|
||||
double infinity = std::numeric_limits<double>::infinity();
|
||||
if (cur_toks_.empty())
|
||||
return infinity;
|
||||
double best_cost = infinity,
|
||||
best_cost_with_final = infinity;
|
||||
for (unordered_map<StateId, Token*>::const_iterator iter = cur_toks_.begin();
|
||||
iter != cur_toks_.end();
|
||||
++iter) {
|
||||
// Note: Plus is taking the minimum cost, since we're in the tropical
|
||||
// semiring.
|
||||
best_cost = std::min(best_cost, iter->second->cost_);
|
||||
best_cost_with_final = std::min(best_cost_with_final,
|
||||
iter->second->cost_ +
|
||||
fst_.Final(iter->first).Value());
|
||||
}
|
||||
BaseFloat extra_cost = best_cost_with_final - best_cost;
|
||||
if (extra_cost != extra_cost) { // NaN. This shouldn't happen; it indicates some
|
||||
// kind of error, most likely.
|
||||
KALDI_WARN << "Found NaN (likely search failure in decoding)";
|
||||
return infinity;
|
||||
}
|
||||
// Note: extra_cost will be infinity if no states were final.
|
||||
return extra_cost;
|
||||
}
|
||||
|
||||
// Outputs an FST corresponding to the single best path
|
||||
// through the lattice.
|
||||
bool SimpleDecoder::GetBestPath(Lattice *fst_out, bool use_final_probs) const {
|
||||
fst_out->DeleteStates();
|
||||
Token *best_tok = NULL;
|
||||
bool is_final = ReachedFinal();
|
||||
if (!is_final) {
|
||||
for (unordered_map<StateId, Token*>::const_iterator iter = cur_toks_.begin();
|
||||
iter != cur_toks_.end();
|
||||
++iter)
|
||||
if (best_tok == NULL || *best_tok < *(iter->second) )
|
||||
best_tok = iter->second;
|
||||
} else {
|
||||
double infinity =std::numeric_limits<double>::infinity(),
|
||||
best_cost = infinity;
|
||||
for (unordered_map<StateId, Token*>::const_iterator iter = cur_toks_.begin();
|
||||
iter != cur_toks_.end();
|
||||
++iter) {
|
||||
double this_cost = iter->second->cost_ + fst_.Final(iter->first).Value();
|
||||
if (this_cost != infinity && this_cost < best_cost) {
|
||||
best_cost = this_cost;
|
||||
best_tok = iter->second;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (best_tok == NULL) return false; // No output.
|
||||
|
||||
std::vector<LatticeArc> arcs_reverse; // arcs in reverse order.
|
||||
for (Token *tok = best_tok; tok != NULL; tok = tok->prev_)
|
||||
arcs_reverse.push_back(tok->arc_);
|
||||
KALDI_ASSERT(arcs_reverse.back().nextstate == fst_.Start());
|
||||
arcs_reverse.pop_back(); // that was a "fake" token... gives no info.
|
||||
|
||||
StateId cur_state = fst_out->AddState();
|
||||
fst_out->SetStart(cur_state);
|
||||
for (ssize_t i = static_cast<ssize_t>(arcs_reverse.size())-1; i >= 0; i--) {
|
||||
LatticeArc arc = arcs_reverse[i];
|
||||
arc.nextstate = fst_out->AddState();
|
||||
fst_out->AddArc(cur_state, arc);
|
||||
cur_state = arc.nextstate;
|
||||
}
|
||||
if (is_final && use_final_probs)
|
||||
fst_out->SetFinal(cur_state,
|
||||
LatticeWeight(fst_.Final(best_tok->arc_.nextstate).Value(),
|
||||
0.0));
|
||||
else
|
||||
fst_out->SetFinal(cur_state, LatticeWeight::One());
|
||||
fst::RemoveEpsLocal(fst_out);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void SimpleDecoder::ProcessEmitting(DecodableInterface *decodable) {
|
||||
int32 frame = num_frames_decoded_;
|
||||
// Processes emitting arcs for one frame. Propagates from
|
||||
// prev_toks_ to cur_toks_.
|
||||
double cutoff = std::numeric_limits<BaseFloat>::infinity();
|
||||
for (unordered_map<StateId, Token*>::iterator iter = prev_toks_.begin();
|
||||
iter != prev_toks_.end();
|
||||
++iter) {
|
||||
StateId state = iter->first;
|
||||
Token *tok = iter->second;
|
||||
KALDI_ASSERT(state == tok->arc_.nextstate);
|
||||
for (fst::ArcIterator<fst::Fst<StdArc> > aiter(fst_, state);
|
||||
!aiter.Done();
|
||||
aiter.Next()) {
|
||||
const StdArc &arc = aiter.Value();
|
||||
if (arc.ilabel != 0) { // propagate..
|
||||
BaseFloat acoustic_cost = -decodable->LogLikelihood(frame, arc.ilabel);
|
||||
double total_cost = tok->cost_ + arc.weight.Value() + acoustic_cost;
|
||||
|
||||
if (total_cost >= cutoff) continue;
|
||||
if (total_cost + beam_ < cutoff)
|
||||
cutoff = total_cost + beam_;
|
||||
Token *new_tok = new Token(arc, acoustic_cost, tok);
|
||||
unordered_map<StateId, Token*>::iterator find_iter
|
||||
= cur_toks_.find(arc.nextstate);
|
||||
if (find_iter == cur_toks_.end()) {
|
||||
cur_toks_[arc.nextstate] = new_tok;
|
||||
} else {
|
||||
if ( *(find_iter->second) < *new_tok ) {
|
||||
Token::TokenDelete(find_iter->second);
|
||||
find_iter->second = new_tok;
|
||||
} else {
|
||||
Token::TokenDelete(new_tok);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
num_frames_decoded_++;
|
||||
}
|
||||
|
||||
void SimpleDecoder::ProcessNonemitting() {
|
||||
// Processes nonemitting arcs for one frame. Propagates within
|
||||
// cur_toks_.
|
||||
std::vector<StateId> queue;
|
||||
double infinity = std::numeric_limits<double>::infinity();
|
||||
double best_cost = infinity;
|
||||
for (unordered_map<StateId, Token*>::iterator iter = cur_toks_.begin();
|
||||
iter != cur_toks_.end();
|
||||
++iter) {
|
||||
queue.push_back(iter->first);
|
||||
best_cost = std::min(best_cost, iter->second->cost_);
|
||||
}
|
||||
double cutoff = best_cost + beam_;
|
||||
|
||||
while (!queue.empty()) {
|
||||
StateId state = queue.back();
|
||||
queue.pop_back();
|
||||
Token *tok = cur_toks_[state];
|
||||
KALDI_ASSERT(tok != NULL && state == tok->arc_.nextstate);
|
||||
for (fst::ArcIterator<fst::Fst<StdArc> > aiter(fst_, state);
|
||||
!aiter.Done();
|
||||
aiter.Next()) {
|
||||
const StdArc &arc = aiter.Value();
|
||||
if (arc.ilabel == 0) { // propagate nonemitting only...
|
||||
const BaseFloat acoustic_cost = 0.0;
|
||||
Token *new_tok = new Token(arc, acoustic_cost, tok);
|
||||
if (new_tok->cost_ > cutoff) {
|
||||
Token::TokenDelete(new_tok);
|
||||
} else {
|
||||
unordered_map<StateId, Token*>::iterator find_iter
|
||||
= cur_toks_.find(arc.nextstate);
|
||||
if (find_iter == cur_toks_.end()) {
|
||||
cur_toks_[arc.nextstate] = new_tok;
|
||||
queue.push_back(arc.nextstate);
|
||||
} else {
|
||||
if ( *(find_iter->second) < *new_tok ) {
|
||||
Token::TokenDelete(find_iter->second);
|
||||
find_iter->second = new_tok;
|
||||
queue.push_back(arc.nextstate);
|
||||
} else {
|
||||
Token::TokenDelete(new_tok);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
void SimpleDecoder::ClearToks(unordered_map<StateId, Token*> &toks) {
|
||||
for (unordered_map<StateId, Token*>::iterator iter = toks.begin();
|
||||
iter != toks.end(); ++iter) {
|
||||
Token::TokenDelete(iter->second);
|
||||
}
|
||||
toks.clear();
|
||||
}
|
||||
|
||||
// static
|
||||
void SimpleDecoder::PruneToks(BaseFloat beam, unordered_map<StateId, Token*> *toks) {
|
||||
if (toks->empty()) {
|
||||
KALDI_VLOG(2) << "No tokens to prune.\n";
|
||||
return;
|
||||
}
|
||||
double best_cost = std::numeric_limits<double>::infinity();
|
||||
for (unordered_map<StateId, Token*>::iterator iter = toks->begin();
|
||||
iter != toks->end(); ++iter)
|
||||
best_cost = std::min(best_cost, iter->second->cost_);
|
||||
std::vector<StateId> retained;
|
||||
double cutoff = best_cost + beam;
|
||||
for (unordered_map<StateId, Token*>::iterator iter = toks->begin();
|
||||
iter != toks->end(); ++iter) {
|
||||
if (iter->second->cost_ < cutoff)
|
||||
retained.push_back(iter->first);
|
||||
else
|
||||
Token::TokenDelete(iter->second);
|
||||
}
|
||||
unordered_map<StateId, Token*> tmp;
|
||||
for (size_t i = 0; i < retained.size(); i++) {
|
||||
tmp[retained[i]] = (*toks)[retained[i]];
|
||||
}
|
||||
KALDI_VLOG(2) << "Pruned to " << (retained.size()) << " toks.\n";
|
||||
tmp.swap(*toks);
|
||||
}
|
||||
|
||||
} // end namespace kaldi.
|
||||
@@ -0,0 +1,156 @@
|
||||
// decoder/simple-decoder.h
|
||||
|
||||
// Copyright 2009-2013 Microsoft Corporation; Lukas Burget;
|
||||
// Saarland University (author: Arnab Ghoshal);
|
||||
// Johns Hopkins University (author: Daniel Povey)
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef KALDI_DECODER_SIMPLE_DECODER_H_
|
||||
#define KALDI_DECODER_SIMPLE_DECODER_H_
|
||||
|
||||
|
||||
#include "util/stl-utils.h"
|
||||
#include "fst/fstlib.h"
|
||||
#include "lat/kaldi-lattice.h"
|
||||
#include "itf/decodable-itf.h"
|
||||
|
||||
namespace kaldi {
|
||||
|
||||
/** Simplest possible decoder, included largely for didactic purposes and as a
|
||||
means to debug more highly optimized decoders. See \ref decoders_simple
|
||||
for more information.
|
||||
*/
|
||||
class SimpleDecoder {
|
||||
public:
|
||||
typedef fst::StdArc StdArc;
|
||||
typedef StdArc::Weight StdWeight;
|
||||
typedef StdArc::Label Label;
|
||||
typedef StdArc::StateId StateId;
|
||||
|
||||
SimpleDecoder(const fst::Fst<fst::StdArc> &fst, BaseFloat beam): fst_(fst), beam_(beam) { }
|
||||
|
||||
~SimpleDecoder();
|
||||
|
||||
/// Decode this utterance.
|
||||
/// Returns true if any tokens reached the end of the file (regardless of
|
||||
/// whether they are in a final state); query ReachedFinal() after Decode()
|
||||
/// to see whether we reached a final state.
|
||||
bool Decode(DecodableInterface *decodable);
|
||||
|
||||
bool ReachedFinal() const;
|
||||
|
||||
// GetBestPath gets the decoding traceback. If "use_final_probs" is true
|
||||
// AND we reached a final state, it limits itself to final states;
|
||||
// otherwise it gets the most likely token not taking into account final-probs.
|
||||
// fst_out will be empty (Start() == kNoStateId) if nothing was available due to
|
||||
// search error.
|
||||
// If Decode() returned true, it is safe to assume GetBestPath will return true.
|
||||
// It returns true if the output lattice was nonempty (i.e. had states in it);
|
||||
// using the return value is deprecated.
|
||||
bool GetBestPath(Lattice *fst_out, bool use_final_probs = true) const;
|
||||
|
||||
/// *** The next functions are from the "new interface". ***
|
||||
|
||||
/// FinalRelativeCost() serves the same function as ReachedFinal(), but gives
|
||||
/// more information. It returns the difference between the best (final-cost plus
|
||||
/// cost) of any token on the final frame, and the best cost of any token
|
||||
/// on the final frame. If it is infinity it means no final-states were present
|
||||
/// on the final frame. It will usually be nonnegative.
|
||||
BaseFloat FinalRelativeCost() const;
|
||||
|
||||
/// InitDecoding initializes the decoding, and should only be used if you
|
||||
/// intend to call AdvanceDecoding(). If you call Decode(), you don't need
|
||||
/// to call this. You can call InitDecoding if you have already decoded an
|
||||
/// utterance and want to start with a new utterance.
|
||||
void InitDecoding();
|
||||
|
||||
/// This will decode until there are no more frames ready in the decodable
|
||||
/// object, but if max_num_frames is >= 0 it will decode no more than
|
||||
/// that many frames. If it returns false, then no tokens are alive,
|
||||
/// which is a kind of error state.
|
||||
void AdvanceDecoding(DecodableInterface *decodable,
|
||||
int32 max_num_frames = -1);
|
||||
|
||||
/// Returns the number of frames already decoded.
|
||||
int32 NumFramesDecoded() const { return num_frames_decoded_; }
|
||||
|
||||
private:
|
||||
|
||||
class Token {
|
||||
public:
|
||||
LatticeArc arc_; // We use LatticeArc so that we can separately
|
||||
// store the acoustic and graph cost, in case
|
||||
// we need to produce lattice-formatted output.
|
||||
Token *prev_;
|
||||
int32 ref_count_;
|
||||
double cost_; // accumulated total cost up to this point.
|
||||
Token(const StdArc &arc,
|
||||
BaseFloat acoustic_cost,
|
||||
Token *prev): prev_(prev), ref_count_(1) {
|
||||
arc_.ilabel = arc.ilabel;
|
||||
arc_.olabel = arc.olabel;
|
||||
arc_.weight = LatticeWeight(arc.weight.Value(), acoustic_cost);
|
||||
arc_.nextstate = arc.nextstate;
|
||||
if (prev) {
|
||||
prev->ref_count_++;
|
||||
cost_ = prev->cost_ + (arc.weight.Value() + acoustic_cost);
|
||||
} else {
|
||||
cost_ = arc.weight.Value() + acoustic_cost;
|
||||
}
|
||||
}
|
||||
bool operator < (const Token &other) {
|
||||
return cost_ > other.cost_;
|
||||
}
|
||||
|
||||
static void TokenDelete(Token *tok) {
|
||||
while (--tok->ref_count_ == 0) {
|
||||
Token *prev = tok->prev_;
|
||||
delete tok;
|
||||
if (prev == NULL) return;
|
||||
else tok = prev;
|
||||
}
|
||||
#ifdef KALDI_PARANOID
|
||||
KALDI_ASSERT(tok->ref_count_ > 0);
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
// ProcessEmitting decodes the frame num_frames_decoded_ of the
|
||||
// decodable object, then increments num_frames_decoded_.
|
||||
void ProcessEmitting(DecodableInterface *decodable);
|
||||
|
||||
void ProcessNonemitting();
|
||||
|
||||
unordered_map<StateId, Token*> cur_toks_;
|
||||
unordered_map<StateId, Token*> prev_toks_;
|
||||
const fst::Fst<fst::StdArc> &fst_;
|
||||
BaseFloat beam_;
|
||||
// Keep track of the number of frames decoded in the current file.
|
||||
int32 num_frames_decoded_;
|
||||
|
||||
static void ClearToks(unordered_map<StateId, Token*> &toks);
|
||||
|
||||
static void PruneToks(BaseFloat beam, unordered_map<StateId, Token*> *toks);
|
||||
|
||||
KALDI_DISALLOW_COPY_AND_ASSIGN(SimpleDecoder);
|
||||
};
|
||||
|
||||
|
||||
} // end namespace kaldi.
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,182 @@
|
||||
// decoder/training-graph-compiler.cc
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation
|
||||
// 2018 Johns Hopkins University (author: Daniel Povey)
|
||||
// 2021 Xiaomi Corporation (Author: Junbo Zhang)
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
#include "decoder/training-graph-compiler.h"
|
||||
#include "hmm/hmm-utils.h" // for GetHTransducer
|
||||
|
||||
namespace kaldi {
|
||||
|
||||
|
||||
TrainingGraphCompiler::TrainingGraphCompiler(const TransitionModel &trans_model,
|
||||
const ContextDependency &ctx_dep, // Does not maintain reference to this.
|
||||
fst::VectorFst<fst::StdArc> *lex_fst,
|
||||
const std::vector<int32> &disambig_syms,
|
||||
const TrainingGraphCompilerOptions &opts):
|
||||
trans_model_(trans_model), ctx_dep_(ctx_dep), lex_fst_(lex_fst),
|
||||
disambig_syms_(disambig_syms), opts_(opts) {
|
||||
using namespace fst;
|
||||
const std::vector<int32> &phone_syms = trans_model_.GetPhones(); // needed to create context fst.
|
||||
|
||||
KALDI_ASSERT(!phone_syms.empty());
|
||||
KALDI_ASSERT(IsSortedAndUniq(phone_syms));
|
||||
SortAndUniq(&disambig_syms_);
|
||||
for (int32 i = 0; i < disambig_syms_.size(); i++)
|
||||
if (std::binary_search(phone_syms.begin(), phone_syms.end(),
|
||||
disambig_syms_[i]))
|
||||
KALDI_ERR << "Disambiguation symbol " << disambig_syms_[i]
|
||||
<< " is also a phone.";
|
||||
|
||||
subsequential_symbol_ = 1 + phone_syms.back();
|
||||
if (!disambig_syms_.empty() && subsequential_symbol_ <= disambig_syms_.back())
|
||||
subsequential_symbol_ = 1 + disambig_syms_.back();
|
||||
|
||||
if (lex_fst == NULL) return;
|
||||
|
||||
{
|
||||
int32 N = ctx_dep.ContextWidth(),
|
||||
P = ctx_dep.CentralPosition();
|
||||
if (P != N-1)
|
||||
AddSubsequentialLoop(subsequential_symbol_, lex_fst_); // This is needed for
|
||||
// systems with right-context or we will not successfully compose
|
||||
// with C.
|
||||
}
|
||||
|
||||
{ // make sure lexicon is olabel sorted.
|
||||
fst::OLabelCompare<fst::StdArc> olabel_comp;
|
||||
fst::ArcSort(lex_fst_, olabel_comp);
|
||||
}
|
||||
}
|
||||
|
||||
bool TrainingGraphCompiler::CompileGraphFromText(
|
||||
const std::vector<int32> &transcript,
|
||||
fst::VectorFst<fst::StdArc> *out_fst) {
|
||||
using namespace fst;
|
||||
VectorFst<StdArc> word_fst;
|
||||
MakeLinearAcceptor(transcript, &word_fst);
|
||||
return CompileGraph(word_fst, out_fst);
|
||||
}
|
||||
|
||||
bool TrainingGraphCompiler::CompileGraphFromLG(const fst::VectorFst<fst::StdArc> &phone2word_fst,
|
||||
fst::VectorFst<fst::StdArc> *out_fst) {
|
||||
using namespace fst;
|
||||
|
||||
KALDI_ASSERT(phone2word_fst.Start() != kNoStateId);
|
||||
|
||||
const std::vector<int32> &phone_syms = trans_model_.GetPhones(); // needed to create context fst.
|
||||
|
||||
// inv_cfst will be expanded on the fly, as needed.
|
||||
InverseContextFst inv_cfst(subsequential_symbol_,
|
||||
phone_syms,
|
||||
disambig_syms_,
|
||||
ctx_dep_.ContextWidth(),
|
||||
ctx_dep_.CentralPosition());
|
||||
|
||||
|
||||
VectorFst<StdArc> ctx2word_fst;
|
||||
ComposeDeterministicOnDemandInverse(phone2word_fst, &inv_cfst, &ctx2word_fst);
|
||||
// now ctx2word_fst is C * LG, assuming phone2word_fst is written as LG.
|
||||
KALDI_ASSERT(ctx2word_fst.Start() != kNoStateId);
|
||||
|
||||
HTransducerConfig h_cfg;
|
||||
h_cfg.transition_scale = opts_.transition_scale;
|
||||
|
||||
std::vector<int32> disambig_syms_h; // disambiguation symbols on
|
||||
// input side of H.
|
||||
VectorFst<StdArc> *H = GetHTransducer(inv_cfst.IlabelInfo(),
|
||||
ctx_dep_,
|
||||
trans_model_,
|
||||
h_cfg,
|
||||
&disambig_syms_h);
|
||||
|
||||
VectorFst<StdArc> &trans2word_fst = *out_fst; // transition-id to word.
|
||||
TableCompose(*H, ctx2word_fst, &trans2word_fst);
|
||||
|
||||
KALDI_ASSERT(trans2word_fst.Start() != kNoStateId);
|
||||
|
||||
// Epsilon-removal and determinization combined. This will fail if not determinizable.
|
||||
DeterminizeStarInLog(&trans2word_fst);
|
||||
|
||||
if (!disambig_syms_h.empty()) {
|
||||
RemoveSomeInputSymbols(disambig_syms_h, &trans2word_fst);
|
||||
// we elect not to remove epsilons after this phase, as it is
|
||||
// a little slow.
|
||||
if (opts_.rm_eps)
|
||||
RemoveEpsLocal(&trans2word_fst);
|
||||
}
|
||||
|
||||
|
||||
// Encoded minimization.
|
||||
MinimizeEncoded(&trans2word_fst);
|
||||
|
||||
std::vector<int32> disambig;
|
||||
bool check_no_self_loops = true;
|
||||
AddSelfLoops(trans_model_,
|
||||
disambig,
|
||||
opts_.self_loop_scale,
|
||||
opts_.reorder,
|
||||
check_no_self_loops,
|
||||
&trans2word_fst);
|
||||
|
||||
delete H;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TrainingGraphCompiler::CompileGraph(const fst::VectorFst<fst::StdArc> &word_fst,
|
||||
fst::VectorFst<fst::StdArc> *out_fst) {
|
||||
using namespace fst;
|
||||
KALDI_ASSERT(lex_fst_ !=NULL);
|
||||
KALDI_ASSERT(out_fst != NULL);
|
||||
|
||||
VectorFst<StdArc> phone2word_fst;
|
||||
// TableCompose more efficient than compose.
|
||||
TableCompose(*lex_fst_, word_fst, &phone2word_fst, &lex_cache_);
|
||||
return CompileGraphFromLG(phone2word_fst, out_fst);
|
||||
}
|
||||
|
||||
bool TrainingGraphCompiler::CompileGraphsFromText(
|
||||
const std::vector<std::vector<int32> > &transcripts,
|
||||
std::vector<fst::VectorFst<fst::StdArc>*> *out_fsts) {
|
||||
using namespace fst;
|
||||
std::vector<const VectorFst<StdArc>* > word_fsts(transcripts.size());
|
||||
for (size_t i = 0; i < transcripts.size(); i++) {
|
||||
VectorFst<StdArc> *word_fst = new VectorFst<StdArc>();
|
||||
MakeLinearAcceptor(transcripts[i], word_fst);
|
||||
word_fsts[i] = word_fst;
|
||||
}
|
||||
bool ans = CompileGraphs(word_fsts, out_fsts);
|
||||
for (size_t i = 0; i < transcripts.size(); i++)
|
||||
delete word_fsts[i];
|
||||
return ans;
|
||||
}
|
||||
|
||||
bool TrainingGraphCompiler::CompileGraphs(
|
||||
const std::vector<const fst::VectorFst<fst::StdArc>* > &word_fsts,
|
||||
std::vector<fst::VectorFst<fst::StdArc>* > *out_fsts) {
|
||||
out_fsts->resize(word_fsts.size(), NULL);
|
||||
for (size_t i = 0; i < word_fsts.size(); i++) {
|
||||
fst::VectorFst<fst::StdArc> trans2word_fst;
|
||||
if (!CompileGraph(*(word_fsts[i]), &trans2word_fst)) return false;
|
||||
(*out_fsts)[i] = trans2word_fst.Copy();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
} // end namespace kaldi
|
||||
@@ -0,0 +1,117 @@
|
||||
// decoder/training-graph-compiler.h
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation
|
||||
// 2018 Johns Hopkins University (author: Daniel Povey)
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
#ifndef KALDI_DECODER_TRAINING_GRAPH_COMPILER_H_
|
||||
#define KALDI_DECODER_TRAINING_GRAPH_COMPILER_H_
|
||||
|
||||
#include "base/kaldi-common.h"
|
||||
#include "hmm/transition-model.h"
|
||||
#include "fst/fstlib.h"
|
||||
#include "fstext/fstext-lib.h"
|
||||
#include "tree/context-dep.h"
|
||||
|
||||
|
||||
namespace kaldi {
|
||||
|
||||
struct TrainingGraphCompilerOptions {
|
||||
|
||||
BaseFloat transition_scale;
|
||||
BaseFloat self_loop_scale;
|
||||
bool rm_eps;
|
||||
bool reorder; // (Dan-style graphs)
|
||||
|
||||
explicit TrainingGraphCompilerOptions(BaseFloat transition_scale = 1.0,
|
||||
BaseFloat self_loop_scale = 1.0,
|
||||
bool b = true) :
|
||||
transition_scale(transition_scale),
|
||||
self_loop_scale(self_loop_scale),
|
||||
rm_eps(false),
|
||||
reorder(b) { }
|
||||
|
||||
void Register(OptionsItf *opts) {
|
||||
opts->Register("transition-scale", &transition_scale, "Scale of transition "
|
||||
"probabilities (excluding self-loops)");
|
||||
opts->Register("self-loop-scale", &self_loop_scale, "Scale of self-loop vs. "
|
||||
"non-self-loop probability mass ");
|
||||
opts->Register("reorder", &reorder, "Reorder transition ids for greater decoding efficiency.");
|
||||
opts->Register("rm-eps", &rm_eps, "Remove [most] epsilons before minimization (only applicable "
|
||||
"if disambig symbols present)");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class TrainingGraphCompiler {
|
||||
public:
|
||||
TrainingGraphCompiler(const TransitionModel &trans_model, // Maintains reference to this object.
|
||||
const ContextDependency &ctx_dep, // And this.
|
||||
fst::VectorFst<fst::StdArc> *lex_fst, // Takes ownership of this object.
|
||||
// It should not contain disambiguation symbols or subsequential symbol,
|
||||
// but it should contain optional silence.
|
||||
const std::vector<int32> &disambig_syms, // disambig symbols in phone symbol table.
|
||||
const TrainingGraphCompilerOptions &opts);
|
||||
|
||||
|
||||
// CompileGraph compiles a single training graph its input is a
|
||||
// weighted acceptor (G) at the word level, its output is HCLG.
|
||||
// Note: G could actually be a transducer, it would also work.
|
||||
// This function is not const for technical reasons involving the cache.
|
||||
// if not for "table_compose" we could make it const.
|
||||
bool CompileGraph(const fst::VectorFst<fst::StdArc> &word_grammar,
|
||||
fst::VectorFst<fst::StdArc> *out_fst);
|
||||
|
||||
// Same as `CompileGraph`, but uses an external LG fst.
|
||||
bool CompileGraphFromLG(const fst::VectorFst<fst::StdArc> &phone2word_fst,
|
||||
fst::VectorFst<fst::StdArc> * out_fst);
|
||||
|
||||
// CompileGraphs allows you to compile a number of graphs at the same
|
||||
// time. This consumes more memory but is faster.
|
||||
bool CompileGraphs(
|
||||
const std::vector<const fst::VectorFst<fst::StdArc> *> &word_fsts,
|
||||
std::vector<fst::VectorFst<fst::StdArc> *> *out_fsts);
|
||||
|
||||
// This version creates an FST from the text and calls CompileGraph.
|
||||
bool CompileGraphFromText(const std::vector<int32> &transcript,
|
||||
fst::VectorFst<fst::StdArc> *out_fst);
|
||||
|
||||
// This function creates FSTs from the text and calls CompileGraphs.
|
||||
bool CompileGraphsFromText(
|
||||
const std::vector<std::vector<int32> > &word_grammar,
|
||||
std::vector<fst::VectorFst<fst::StdArc> *> *out_fsts);
|
||||
|
||||
|
||||
~TrainingGraphCompiler() { delete lex_fst_; }
|
||||
private:
|
||||
const TransitionModel &trans_model_;
|
||||
const ContextDependency &ctx_dep_;
|
||||
fst::VectorFst<fst::StdArc> *lex_fst_; // lexicon FST (an input; we take
|
||||
// ownership as we need to modify it).
|
||||
std::vector<int32> disambig_syms_; // disambig symbols (if any) in the phone
|
||||
int32 subsequential_symbol_; // search in ../fstext/context-fst.h for more info.
|
||||
// symbol table.
|
||||
fst::TableComposeCache<fst::Fst<fst::StdArc> > lex_cache_; // stores matcher..
|
||||
// this is one of Dan's extensions.
|
||||
|
||||
TrainingGraphCompilerOptions opts_;
|
||||
};
|
||||
|
||||
|
||||
|
||||
} // end namespace kaldi.
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,31 @@
|
||||
|
||||
# make "all" the target.
|
||||
all:
|
||||
|
||||
# Disable linking math libs because not needed here. Just for compilation speed.
|
||||
# no, it's now needed for context-fst-test.
|
||||
# MATHLIB = NONE
|
||||
|
||||
EXTRA_CXXFLAGS = -Wno-sign-compare
|
||||
|
||||
include ../kaldi.mk
|
||||
|
||||
BINFILES = fstdeterminizestar \
|
||||
fstrmsymbols fstisstochastic fstminimizeencoded fstmakecontextfst \
|
||||
fstmakecontextsyms fstaddsubsequentialloop fstaddselfloops \
|
||||
fstrmepslocal fstcomposecontext fsttablecompose fstrand \
|
||||
fstdeterminizelog fstphicompose fstcopy \
|
||||
fstpushspecial fsts-to-transcripts fsts-project fsts-union \
|
||||
fsts-concat make-grammar-fst
|
||||
|
||||
OBJFILES =
|
||||
|
||||
TESTFILES =
|
||||
|
||||
# actually, this library is currently empty. Everything is a header.
|
||||
LIBFILE =
|
||||
|
||||
ADDLIBS = ../decoder/kaldi-decoder.a ../fstext/kaldi-fstext.a \
|
||||
../util/kaldi-util.a ../matrix/kaldi-matrix.a ../base/kaldi-base.a
|
||||
|
||||
include ../makefiles/default_rules.mk
|
||||
@@ -0,0 +1,93 @@
|
||||
// fstbin/fstaddselfloops.cc
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
|
||||
#include "base/kaldi-common.h"
|
||||
#include "fst/fstlib.h"
|
||||
#include "fstext/determinize-star.h"
|
||||
#include "fstext/fstext-utils.h"
|
||||
#include "fstext/kaldi-fst-io.h"
|
||||
#include "util/parse-options.h"
|
||||
#include "util/simple-io-funcs.h"
|
||||
/* some test examples:
|
||||
pushd ~/tmpdir
|
||||
( echo 3; echo 4) > in.list
|
||||
( echo 5; echo 6) > out.list
|
||||
( echo "0 0 0 0"; echo "0 0" ) | fstcompile | fstaddselfloops in.list out.list | fstprint
|
||||
( echo "0 1 0 1"; echo " 0 2 1 0"; echo "1 0"; echo "2 0"; ) | fstcompile | fstaddselfloops in.list out.list | fstprint
|
||||
*/
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
try {
|
||||
using namespace kaldi;
|
||||
using namespace fst;
|
||||
using kaldi::int32;
|
||||
|
||||
const char *usage =
|
||||
"Adds self-loops to states of an FST to propagate disambiguation symbols through it\n"
|
||||
"They are added on each final state and each state with non-epsilon output symbols\n"
|
||||
"on at least one arc out of the state. Useful in conjunction with predeterminize\n"
|
||||
"\n"
|
||||
"Usage: fstaddselfloops in-disambig-list out-disambig-list [in.fst [out.fst] ]\n"
|
||||
"E.g: fstaddselfloops in.list out.list < in.fst > withloops.fst\n"
|
||||
"in.list and out.list are lists of integers, one per line, of the\n"
|
||||
"same length.\n";
|
||||
|
||||
ParseOptions po(usage);
|
||||
po.Read(argc, argv);
|
||||
|
||||
if (po.NumArgs() < 2 || po.NumArgs() > 4) {
|
||||
po.PrintUsage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
std::string disambig_in_rxfilename = po.GetArg(1),
|
||||
disambig_out_rxfilename = po.GetArg(2),
|
||||
fst_in_filename = po.GetOptArg(3),
|
||||
fst_out_filename = po.GetOptArg(4);
|
||||
|
||||
VectorFst<StdArc> *fst = ReadFstKaldi(fst_in_filename);
|
||||
|
||||
std::vector<int32> disambig_in;
|
||||
if (!ReadIntegerVectorSimple(disambig_in_rxfilename, &disambig_in))
|
||||
KALDI_ERR << "fstaddselfloops: Could not read disambiguation symbols from "
|
||||
<< kaldi::PrintableRxfilename(disambig_in_rxfilename);
|
||||
|
||||
std::vector<int32> disambig_out;
|
||||
if (!ReadIntegerVectorSimple(disambig_out_rxfilename, &disambig_out))
|
||||
KALDI_ERR << "fstaddselfloops: Could not read disambiguation symbols from "
|
||||
<< kaldi::PrintableRxfilename(disambig_out_rxfilename);
|
||||
|
||||
if (disambig_in.size() != disambig_out.size())
|
||||
KALDI_ERR << "fstaddselfloops: mismatch in size of disambiguation symbols";
|
||||
|
||||
AddSelfLoops(fst, disambig_in, disambig_out);
|
||||
|
||||
WriteFstKaldi(*fst, fst_out_filename);
|
||||
|
||||
delete fst;
|
||||
|
||||
return 0;
|
||||
} catch(const std::exception &e) {
|
||||
std::cerr << e.what();
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
// fstbin/fstaddsubsequentialloop.cc
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
|
||||
#include "base/kaldi-common.h"
|
||||
#include "util/kaldi-io.h"
|
||||
#include "util/parse-options.h"
|
||||
#include "util/text-utils.h"
|
||||
#include "fst/fstlib.h"
|
||||
#include "fstext/fstext-utils.h"
|
||||
#include "fstext/context-fst.h"
|
||||
#include "fstext/kaldi-fst-io.h"
|
||||
|
||||
|
||||
|
||||
/* some test examples:
|
||||
( echo "0 0 0 0"; echo "0 0" ) | fstcompile | fstaddsubsequentialloop 1 | fstprint
|
||||
( echo "0 1 0 0"; echo " 0 2 0 0"; echo "1 0"; echo "2 0"; ) | fstcompile | fstaddsubsequentialloop 1 | fstprint
|
||||
*/
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
try {
|
||||
using namespace kaldi;
|
||||
using namespace fst;
|
||||
using kaldi::int32;
|
||||
|
||||
const char *usage =
|
||||
"Minimizes FST after encoding [this algorithm applicable to all FSTs in tropical semiring]\n"
|
||||
"\n"
|
||||
"Usage: fstaddsubsequentialloop subseq_sym [in.fst [out.fst] ]\n"
|
||||
"E.g.: fstaddsubsequentialloop 52 < LG.fst > LG_sub.fst\n";
|
||||
|
||||
float delta = kDelta;
|
||||
ParseOptions po(usage);
|
||||
po.Register("delta", &delta,
|
||||
"Delta likelihood used for quantization of weights");
|
||||
po.Read(argc, argv);
|
||||
|
||||
if (po.NumArgs() < 1 || po.NumArgs() > 3) {
|
||||
po.PrintUsage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
int32 subseq_sym;
|
||||
if (!ConvertStringToInteger(po.GetArg(1), &subseq_sym))
|
||||
KALDI_ERR << "Invalid subsequential symbol "<<po.GetArg(1);
|
||||
|
||||
std::string fst_in_filename = po.GetOptArg(2);
|
||||
|
||||
std::string fst_out_filename = po.GetOptArg(3);
|
||||
|
||||
VectorFst<StdArc> *fst = ReadFstKaldi(fst_in_filename);
|
||||
|
||||
int32 h = HighestNumberedInputSymbol(*fst);
|
||||
if (subseq_sym <= h) {
|
||||
std::cerr << "fstaddsubsequentialloop.cc: subseq symbol does not seem right, "<<subseq_sym<<" <= "<<h<<'\n';
|
||||
}
|
||||
|
||||
AddSubsequentialLoop(subseq_sym, fst);
|
||||
|
||||
WriteFstKaldi(*fst, fst_out_filename);
|
||||
delete fst;
|
||||
return 0;
|
||||
} catch(const std::exception &e) {
|
||||
std::cerr << e.what();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
// fstbin/fstcomposecontext.cc
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
|
||||
#include "base/kaldi-common.h"
|
||||
#include "util/common-utils.h"
|
||||
#include "fst/fstlib.h"
|
||||
#include "fstext/context-fst.h"
|
||||
#include "fstext/grammar-context-fst.h"
|
||||
#include "fstext/fstext-utils.h"
|
||||
#include "fstext/kaldi-fst-io.h"
|
||||
|
||||
/*
|
||||
A couple of test examples:
|
||||
|
||||
pushd ~/tmpdir
|
||||
# (1) with no disambig syms.
|
||||
( echo "0 1 1 1"; echo "1 2 2 2"; echo "2 3 3 3"; echo "3 0" ) | fstcompile | fstcomposecontext ilabels.sym > tmp.fst
|
||||
( echo "<eps> 0"; echo "a 1"; echo "b 2"; echo "c 3" ) > phones.txt
|
||||
fstmakecontextsyms phones.txt ilabels.sym > context.txt
|
||||
fstprint --isymbols=context.txt --osymbols=phones.txt tmp.fst
|
||||
# and the result is:
|
||||
|
||||
WARNING (fstcomposecontext[5.4]:main():fstcomposecontext.cc:130) Disambiguation symbols list is empty; this likely indicates an error in data preparation.
|
||||
0 1 <eps> a
|
||||
1 2 <eps>/a/b b
|
||||
2 3 a/b/c c
|
||||
3 4 b/c/<eps> <eps>
|
||||
4
|
||||
|
||||
|
||||
# (2) with disambig syms:
|
||||
( echo 4; echo 5) > disambig.list
|
||||
( echo "<eps> 0"; echo "a 1"; echo "b 2"; echo "c 3"; echo "#0 4"; echo "#1 5") > phones.txt
|
||||
( echo "0 1 1 1"; echo "1 2 2 2"; echo " 2 3 4 4"; echo "3 4 3 3"; echo "4 5 5 5"; echo "5 0" ) | fstcompile > in.fst
|
||||
fstcomposecontext --read-disambig-syms=disambig.list ilabels.sym in.fst tmp.fst
|
||||
fstmakecontextsyms phones.txt ilabels.sym > context.txt
|
||||
cp phones.txt phones_disambig.txt; ( echo "#0 4"; echo "#1 5" ) >> phones_disambig.txt
|
||||
fstprint --isymbols=context.txt --osymbols=phones_disambig.txt tmp.fst
|
||||
|
||||
0 1 #-1 a
|
||||
1 2 <eps>/a/b b
|
||||
2 3 #0 #0
|
||||
3 4 a/b/c c
|
||||
4 5 #1 #1
|
||||
5 6 b/c/<eps> <eps>
|
||||
|
||||
*/
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
try {
|
||||
using namespace kaldi;
|
||||
using namespace fst;
|
||||
using kaldi::int32;
|
||||
/*
|
||||
# fstcomposecontext composes efficiently with a context fst
|
||||
# that it generates. Without --disambig-syms specified, it
|
||||
# assumes that all input symbols of in.fst are phones.
|
||||
# It adds the subsequential symbol itself (it does not
|
||||
# appear in the output so doesn't need to be specified by the user).
|
||||
# the disambig.list is a list of disambiguation symbols on the LHS
|
||||
# of in.fst. The symbols on the LHS of out.fst are indexes into
|
||||
# the ilabels.list file, which is a kaldi-format file containing a
|
||||
# vector<vector<int32> >, which specifies what the labels mean in
|
||||
# terms of windows of symbols.
|
||||
fstcomposecontext ilabels.sym [ in.fst [ out.fst ] ]
|
||||
--disambig-syms=disambig.list
|
||||
--context-size=3
|
||||
--central-position=1
|
||||
--binary=false
|
||||
*/
|
||||
|
||||
const char *usage =
|
||||
"Composes on the left with a dynamically created context FST\n"
|
||||
"\n"
|
||||
"Usage: fstcomposecontext <ilabels-output-file> [<in.fst> [<out.fst>] ]\n"
|
||||
"E.g: fstcomposecontext ilabels.sym < LG.fst > CLG.fst\n";
|
||||
|
||||
|
||||
ParseOptions po(usage);
|
||||
bool binary = true;
|
||||
std::string disambig_rxfilename,
|
||||
disambig_wxfilename;
|
||||
int32 context_width = 3, central_position = 1;
|
||||
int32 nonterm_phones_offset = -1;
|
||||
po.Register("binary", &binary,
|
||||
"If true, output ilabels-output-file in binary format");
|
||||
po.Register("read-disambig-syms", &disambig_rxfilename,
|
||||
"List of disambiguation symbols on input of in.fst");
|
||||
po.Register("write-disambig-syms", &disambig_wxfilename,
|
||||
"List of disambiguation symbols on input of out.fst");
|
||||
po.Register("context-size", &context_width, "Size of phone context window");
|
||||
po.Register("central-position", ¢ral_position,
|
||||
"Designated central position in context window");
|
||||
po.Register("nonterm-phones-offset", &nonterm_phones_offset,
|
||||
"The integer id of #nonterm_bos in your phones.txt, if present "
|
||||
"(only relevant for grammar-FST construction, see "
|
||||
"doc/grammar.dox");
|
||||
|
||||
po.Read(argc, argv);
|
||||
|
||||
if (po.NumArgs() < 1 || po.NumArgs() > 3) {
|
||||
po.PrintUsage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
std::string ilabels_out_filename = po.GetArg(1),
|
||||
fst_in_filename = po.GetOptArg(2),
|
||||
fst_out_filename = po.GetOptArg(3);
|
||||
|
||||
VectorFst<StdArc> *fst = ReadFstKaldi(fst_in_filename);
|
||||
|
||||
if ( (disambig_wxfilename != "") && (disambig_rxfilename == "") )
|
||||
KALDI_ERR << "fstcomposecontext: cannot specify --write-disambig-syms if "
|
||||
"not specifying --read-disambig-syms\n";
|
||||
|
||||
std::vector<int32> disambig_in;
|
||||
if (disambig_rxfilename != "")
|
||||
if (!ReadIntegerVectorSimple(disambig_rxfilename, &disambig_in))
|
||||
KALDI_ERR << "fstcomposecontext: Could not read disambiguation symbols from "
|
||||
<< PrintableRxfilename(disambig_rxfilename);
|
||||
|
||||
if (disambig_in.empty()) {
|
||||
KALDI_WARN << "Disambiguation symbols list is empty; this likely "
|
||||
<< "indicates an error in data preparation.";
|
||||
}
|
||||
|
||||
std::vector<std::vector<int32> > ilabels;
|
||||
VectorFst<StdArc> composed_fst;
|
||||
|
||||
// Work gets done here (see context-fst.h)
|
||||
if (nonterm_phones_offset < 0) {
|
||||
// The normal case.
|
||||
ComposeContext(disambig_in, context_width, central_position,
|
||||
fst, &composed_fst, &ilabels);
|
||||
} else {
|
||||
// The grammar-FST case. See ../doc/grammar.dox for an intro.
|
||||
if (context_width != 2 || central_position != 1) {
|
||||
KALDI_ERR << "Grammar-fst graph creation only supports models with left-"
|
||||
"biphone context. (--nonterm-phones-offset option was supplied).";
|
||||
}
|
||||
ComposeContextLeftBiphone(nonterm_phones_offset, disambig_in,
|
||||
*fst, &composed_fst, &ilabels);
|
||||
}
|
||||
WriteILabelInfo(Output(ilabels_out_filename, binary).Stream(),
|
||||
binary, ilabels);
|
||||
|
||||
if (disambig_wxfilename != "") {
|
||||
std::vector<int32> disambig_out;
|
||||
for (size_t i = 0; i < ilabels.size(); i++)
|
||||
if (ilabels[i].size() == 1 && ilabels[i][0] <= 0)
|
||||
disambig_out.push_back(static_cast<int32>(i));
|
||||
if (!WriteIntegerVectorSimple(disambig_wxfilename, disambig_out)) {
|
||||
std::cerr << "fstcomposecontext: Could not write disambiguation symbols to "
|
||||
<< PrintableWxfilename(disambig_wxfilename) << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
WriteFstKaldi(composed_fst, fst_out_filename);
|
||||
delete fst;
|
||||
return 0;
|
||||
} catch(const std::exception &e) {
|
||||
std::cerr << e.what();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
// fstbin/fstcopy.cc
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
|
||||
#include "base/kaldi-common.h"
|
||||
#include "util/common-utils.h"
|
||||
#include "fst/fstlib.h"
|
||||
#include "fstext/table-matcher.h"
|
||||
#include "fstext/fstext-utils.h"
|
||||
#include "fstext/kaldi-fst-io.h"
|
||||
|
||||
/*
|
||||
Test:
|
||||
the command below should print out something similar to the given
|
||||
input. and if you remove ,t from the output side it should print something
|
||||
binary.
|
||||
cat <<EOF | fstcopy ark,t:- ark,t:-
|
||||
foo
|
||||
0 1 9 9
|
||||
1 0.0
|
||||
|
||||
EOF
|
||||
|
||||
*/
|
||||
int main(int argc, char *argv[]) {
|
||||
try {
|
||||
using namespace kaldi;
|
||||
using namespace fst;
|
||||
using kaldi::int32;
|
||||
|
||||
const char *usage =
|
||||
"Copy tables/archives of FSTs, indexed by a string (e.g. utterance-id)\n"
|
||||
"\n"
|
||||
"Usage: fstcopy <fst-rspecifier> <fst-wspecifier>\n";
|
||||
|
||||
ParseOptions po(usage);
|
||||
|
||||
po.Read(argc, argv);
|
||||
|
||||
if (po.NumArgs() != 2) {
|
||||
po.PrintUsage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
std::string fst_rspecifier = po.GetArg(1),
|
||||
fst_wspecifier = po.GetArg(2);
|
||||
|
||||
SequentialTableReader<VectorFstHolder> fst_reader(fst_rspecifier);
|
||||
TableWriter<VectorFstHolder> fst_writer(fst_wspecifier);
|
||||
int32 n_done = 0;
|
||||
|
||||
for (; !fst_reader.Done(); fst_reader.Next(), n_done++)
|
||||
fst_writer.Write(fst_reader.Key(), fst_reader.Value());
|
||||
|
||||
KALDI_LOG << "Copied " << n_done << " FSTs.";
|
||||
return (n_done != 0 ? 0 : 1);
|
||||
} catch(const std::exception &e) {
|
||||
std::cerr << e.what();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
// fstbin/fstdeterminizelog.cc
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
|
||||
#include "base/kaldi-common.h"
|
||||
#include "util/kaldi-io.h"
|
||||
#include "util/parse-options.h"
|
||||
#include "util/text-utils.h"
|
||||
#include "fst/fstlib.h"
|
||||
#include "fstext/fstext-utils.h"
|
||||
#include "fstext/kaldi-fst-io.h"
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
try {
|
||||
using namespace kaldi;
|
||||
using namespace fst;
|
||||
using kaldi::int32;
|
||||
|
||||
const char *usage =
|
||||
"Determinizes in the log semiring\n"
|
||||
"\n"
|
||||
"Usage: fstdeterminizelog [in.fst [out.fst] ]\n"
|
||||
"\n"
|
||||
"See also fstdeterminizestar\n";
|
||||
|
||||
ParseOptions po(usage);
|
||||
po.Read(argc, argv);
|
||||
|
||||
if (po.NumArgs() > 2) {
|
||||
po.PrintUsage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
std::string fst_in_filename = po.GetOptArg(1),
|
||||
fst_out_filename = po.GetOptArg(2);
|
||||
|
||||
VectorFst<StdArc> *fst = ReadFstKaldi(fst_in_filename);
|
||||
|
||||
DeterminizeInLog(fst);
|
||||
|
||||
WriteFstKaldi(*fst, fst_out_filename);
|
||||
delete fst;
|
||||
return 0;
|
||||
} catch(const std::exception &e) {
|
||||
std::cerr << e.what();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
// fstbin/fstdeterminizestar.cc
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
|
||||
#include "base/kaldi-common.h"
|
||||
#include "fst/fstlib.h"
|
||||
#include "fstext/determinize-star.h"
|
||||
#include "fstext/fstext-utils.h"
|
||||
#include "fstext/kaldi-fst-io.h"
|
||||
#include "util/parse-options.h"
|
||||
|
||||
#if !defined(_MSC_VER) && !defined(__APPLE__)
|
||||
#include <signal.h> // Comment this line and the call to signal below if
|
||||
// it causes compilation problems. It is only to enable a debugging procedure
|
||||
// when determinization does not terminate. We are disabling this code if
|
||||
// compiling on Windows because signal.h is not available there, and on
|
||||
// MacOS due to a problem with <signal.h> in the initial release of Sierra.
|
||||
#endif
|
||||
|
||||
/* some test examples:
|
||||
( echo "0 0 0 0"; echo "0 0" ) | fstcompile | fstdeterminizestar | fstprint
|
||||
( echo "0 0 1 0"; echo "0 0" ) | fstcompile | fstdeterminizestar | fstprint
|
||||
( echo "0 0 1 0"; echo "0 1 1 0"; echo "0 0" ) | fstcompile | fstdeterminizestar | fstprint
|
||||
# this last one fails [correctly]:
|
||||
( echo "0 0 0 1"; echo "0 0" ) | fstcompile | fstdeterminizestar | fstprint
|
||||
|
||||
cd ~/tmpdir
|
||||
while true; do
|
||||
fstrand > 1.fst
|
||||
fstpredeterminize out.lst 1.fst | fstdeterminizestar | fstrmsymbols out.lst > 2.fst
|
||||
fstequivalent --random=true 1.fst 2.fst || echo "Test failed"
|
||||
echo -n "."
|
||||
done
|
||||
|
||||
Test of debugging [with non-determinizable input]:
|
||||
( echo " 0 0 1 0 1.0"; echo "0 1 1 0"; echo "1 1 1 0 0"; echo "0 2 2 0"; echo "2"; echo "1" ) | fstcompile | fstdeterminizestar
|
||||
kill -SIGUSR1 [the process-id of fstdeterminizestar]
|
||||
# prints out a bunch of debugging output showing the mess it got itself into.
|
||||
*/
|
||||
|
||||
|
||||
bool debug_location = false;
|
||||
void signal_handler(int) {
|
||||
debug_location = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
try {
|
||||
using namespace kaldi;
|
||||
using namespace fst;
|
||||
using kaldi::int32;
|
||||
|
||||
const char *usage =
|
||||
"Removes epsilons and determinizes in one step\n"
|
||||
"\n"
|
||||
"Usage: fstdeterminizestar [in.fst [out.fst] ]\n"
|
||||
"\n"
|
||||
"See also: fstdeterminizelog, lattice-determinize\n";
|
||||
|
||||
float delta = kDelta;
|
||||
int max_states = -1;
|
||||
bool use_log = false;
|
||||
ParseOptions po(usage);
|
||||
po.Register("use-log", &use_log, "Determinize in log semiring.");
|
||||
po.Register("delta", &delta, "Delta value used to determine equivalence of weights.");
|
||||
po.Register("max-states", &max_states, "Maximum number of states in determinized FST before it will abort.");
|
||||
po.Read(argc, argv);
|
||||
|
||||
if (po.NumArgs() > 2) {
|
||||
po.PrintUsage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
std::string fst_in_str = po.GetOptArg(1),
|
||||
fst_out_str = po.GetOptArg(2);
|
||||
|
||||
// This enables us to get traceback info from determinization that is
|
||||
// not seeming to terminate.
|
||||
#if !defined(_MSC_VER) && !defined(__APPLE__)
|
||||
signal(SIGUSR1, signal_handler);
|
||||
#endif
|
||||
// Normal case: just files.
|
||||
VectorFst<StdArc> *fst = ReadFstKaldi(fst_in_str);
|
||||
|
||||
ArcSort(fst, ILabelCompare<StdArc>()); // improves speed.
|
||||
if (use_log) {
|
||||
DeterminizeStarInLog(fst, delta, &debug_location, max_states);
|
||||
} else {
|
||||
VectorFst<StdArc> det_fst;
|
||||
DeterminizeStar(*fst, &det_fst, delta, &debug_location, max_states);
|
||||
*fst = det_fst; // will do shallow copy and then det_fst goes
|
||||
// out of scope anyway.
|
||||
}
|
||||
WriteFstKaldi(*fst, fst_out_str);
|
||||
delete fst;
|
||||
return 0;
|
||||
} catch(const std::exception &e) {
|
||||
std::cerr << e.what();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// fstbin/fstisstochastic.cc
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
|
||||
#include "base/kaldi-common.h"
|
||||
#include "util/kaldi-io.h"
|
||||
#include "util/parse-options.h"
|
||||
#include "fst/fstlib.h"
|
||||
#include "fstext/fstext-utils.h"
|
||||
#include "fstext/kaldi-fst-io.h"
|
||||
|
||||
// e.g. of test:
|
||||
// echo " 0 0" | fstcompile | fstisstochastic
|
||||
// should return 0 and print "0 0" [meaning, min and
|
||||
// max weight are one = exp(0)]
|
||||
// echo " 0 1" | fstcompile | fstisstochastic
|
||||
// should return 1, not stochastic, and print 1 1
|
||||
// (echo "0 0 0 0 0.693147 "; echo "0 1 0 0 0.693147 "; echo "1 0" ) | fstcompile | fstisstochastic
|
||||
// should return 0, stochastic; it prints "0 -1.78e-07" for me
|
||||
// (echo "0 0 0 0 0.693147 "; echo "0 1 0 0 0.693147 "; echo "1 0" ) | fstcompile | fstisstochastic --test-in-log=false
|
||||
// should return 1, not stochastic in tropical; it prints "0 0.693147" for me
|
||||
// (echo "0 0 0 0 0 "; echo "0 1 0 0 0 "; echo "1 0" ) | fstcompile | fstisstochastic --test-in-log=false
|
||||
// should return 0, stochastic in tropical; it prints "0 0" for me
|
||||
// (echo "0 0 0 0 0.693147 "; echo "0 1 0 0 0.693147 "; echo "1 0" ) | fstcompile | fstisstochastic --test-in-log=false --delta=1
|
||||
// returns 0 even though not stochastic because we gave it an absurdly large delta.
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
try {
|
||||
using namespace kaldi;
|
||||
using namespace fst;
|
||||
using kaldi::int32;
|
||||
|
||||
const char *usage =
|
||||
"Checks whether an FST is stochastic and exits with success if so.\n"
|
||||
"Prints out maximum error (in log units).\n"
|
||||
"\n"
|
||||
"Usage: fstisstochastic [ in.fst ]\n";
|
||||
|
||||
float delta = 0.01;
|
||||
bool test_in_log = true;
|
||||
|
||||
ParseOptions po(usage);
|
||||
po.Register("delta", &delta, "Maximum error to accept.");
|
||||
po.Register("test-in-log", &test_in_log, "Test stochasticity in log semiring.");
|
||||
po.Read(argc, argv);
|
||||
|
||||
if (po.NumArgs() > 1) {
|
||||
po.PrintUsage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
std::string fst_in_filename = po.GetOptArg(1);
|
||||
|
||||
Fst<StdArc> *fst = ReadFstKaldiGeneric(fst_in_filename);
|
||||
|
||||
bool ans;
|
||||
StdArc::Weight min, max;
|
||||
if (test_in_log) ans = IsStochasticFstInLog(*fst, delta, &min, &max);
|
||||
else ans = IsStochasticFst(*fst, delta, &min, &max);
|
||||
|
||||
std::cout << min.Value() << " " << max.Value() << '\n';
|
||||
delete fst;
|
||||
if (ans) return 0; // success;
|
||||
else return 1;
|
||||
} catch(const std::exception &e) {
|
||||
std::cerr << e.what();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
// fstbin/fstmakecontextfst.cc
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
|
||||
#include "base/kaldi-common.h"
|
||||
#include "util/kaldi-io.h"
|
||||
#include "util/common-utils.h"
|
||||
#include "fst/fstlib.h"
|
||||
#include "fstext/fstext-utils.h"
|
||||
#include "fstext/context-fst.h"
|
||||
#include "fstext/kaldi-fst-io.h"
|
||||
|
||||
|
||||
/* for example of testing setup, see fstmakecontextsymbols.cc */
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
try {
|
||||
using namespace kaldi;
|
||||
using namespace fst;
|
||||
using kaldi::int32;
|
||||
|
||||
const char *usage =
|
||||
"Constructs a context FST with a specified context-width and context-position.\n"
|
||||
"Outputs the context FST, and a file in Kaldi format that describes what the\n"
|
||||
"input labels mean. Note: this is very inefficient if there are a lot of phones,\n"
|
||||
"better to use fstcomposecontext instead\n"
|
||||
"\n"
|
||||
"Usage: fstmakecontextfst <phones-symbol-table> <subsequential-symbol> <ilabels-output-file> [<out-fst>]\n"
|
||||
"E.g.: fstmakecontextfst phones.txt 42 ilabels.sym > C.fst\n";
|
||||
|
||||
bool binary = true; // binary output to ilabels_output_file.
|
||||
std::string disambig_rxfilename, disambig_wxfilename;
|
||||
int32 context_width = 3, central_position = 1;
|
||||
|
||||
ParseOptions po(usage);
|
||||
po.Register("read-disambig-syms", &disambig_rxfilename,
|
||||
"List of disambiguation symbols to read");
|
||||
po.Register("write-disambig-syms", &disambig_wxfilename,
|
||||
"List of disambiguation symbols to write");
|
||||
po.Register("context-size", &context_width, "Size of phonetic context window");
|
||||
po.Register("central-position", ¢ral_position,
|
||||
"Designated central position in context window");
|
||||
po.Register("binary", &binary,
|
||||
"Write ilabels output file in binary Kaldi format");
|
||||
|
||||
po.Read(argc, argv);
|
||||
|
||||
if (po.NumArgs() < 3 || po.NumArgs() > 4) {
|
||||
po.PrintUsage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
std::string phones_symtab_filename = po.GetArg(1);
|
||||
int32 subseq_sym;
|
||||
if (!ConvertStringToInteger(po.GetArg(2), &subseq_sym))
|
||||
KALDI_ERR << "Invalid subsequential symbol " << po.GetArg(2);
|
||||
std::string ilabels_out_filename = po.GetArg(3);
|
||||
std::string fst_out_filename = po.GetOptArg(4);
|
||||
|
||||
|
||||
std::vector<kaldi::int32> phone_syms;
|
||||
{
|
||||
fst::SymbolTable *phones_symtab = NULL;
|
||||
{ // read phone symbol table.
|
||||
std::ifstream is(phones_symtab_filename.c_str());
|
||||
phones_symtab = fst::SymbolTable::ReadText(is, phones_symtab_filename);
|
||||
if (!phones_symtab) KALDI_ERR << "Could not read phones symbol-table file "<<phones_symtab_filename;
|
||||
}
|
||||
GetSymbols(*phones_symtab,
|
||||
false, // don't include eps,
|
||||
&phone_syms);
|
||||
delete phones_symtab;
|
||||
}
|
||||
|
||||
if ( (disambig_wxfilename != "") && (disambig_rxfilename == "") )
|
||||
KALDI_ERR << "fstmakecontextfst: cannot specify --write-disambig-syms if "
|
||||
"not specifying --read-disambig-syms\n";
|
||||
|
||||
std::vector<int32> disambig_in;
|
||||
if (disambig_rxfilename != "") {
|
||||
if (!ReadIntegerVectorSimple(disambig_rxfilename, &disambig_in))
|
||||
KALDI_ERR << "fstcomposecontext: Could not read disambiguation symbols from "
|
||||
<< PrintableRxfilename(disambig_rxfilename);
|
||||
}
|
||||
|
||||
if (std::binary_search(phone_syms.begin(), phone_syms.end(), subseq_sym)
|
||||
|| std::binary_search(disambig_in.begin(), disambig_in.end(), subseq_sym))
|
||||
KALDI_ERR << "Invalid subsequential symbol " << subseq_sym
|
||||
<< ", already a phone or disambiguation symbol.";
|
||||
|
||||
// 'loop_fst' will be an acceptor FST with single (initial and final) state, with
|
||||
// a loop for each phone and disambiguation symbol.
|
||||
StdVectorFst loop_fst;
|
||||
loop_fst.AddState(); // Add state zero.
|
||||
loop_fst.SetStart(0);
|
||||
loop_fst.SetFinal(0, TropicalWeight::One());
|
||||
for (size_t i = 0; i < phone_syms.size(); i++) {
|
||||
int32 sym = phone_syms[i];
|
||||
loop_fst.AddArc(0, StdArc(sym, sym, TropicalWeight::One(), 0));
|
||||
}
|
||||
|
||||
std::vector<std::vector<int32> > ilabels;
|
||||
VectorFst<StdArc> context_fst;
|
||||
|
||||
ComposeContext(disambig_in, context_width, central_position,
|
||||
&loop_fst, &context_fst, &ilabels, true);
|
||||
|
||||
WriteFstKaldi(context_fst, fst_out_filename);
|
||||
|
||||
WriteILabelInfo(Output(ilabels_out_filename, binary).Stream(),
|
||||
binary, ilabels);
|
||||
|
||||
if (disambig_wxfilename != "") {
|
||||
std::vector<int32> disambig_out;
|
||||
for (size_t i = 0; i < ilabels.size(); i++)
|
||||
if (ilabels[i].size() == 1 && ilabels[i][0] <= 0)
|
||||
disambig_out.push_back(static_cast<int32>(i));
|
||||
if (!WriteIntegerVectorSimple(disambig_wxfilename, disambig_out))
|
||||
KALDI_ERR << "fstcomposecontext: Could not write disambiguation symbols to "
|
||||
<< PrintableWxfilename(disambig_wxfilename);
|
||||
}
|
||||
return 0;
|
||||
} catch(const std::exception &e) {
|
||||
std::cerr << e.what();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
// fstbin/fstmakecontextsyms.cc
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "tree/context-dep.h"
|
||||
#include "util/common-utils.h"
|
||||
#include "fst/fstlib.h"
|
||||
#include "fstext/fstext-utils.h"
|
||||
#include "fstext/context-fst.h"
|
||||
|
||||
|
||||
/*
|
||||
Test for this and makecontextfst:
|
||||
mkdir -p ~/tmpdir
|
||||
pushd ~/tmpdir
|
||||
(echo "<eps> 0"; echo "a 1"; echo "b 2"; echo "#0 3"; echo "#1 4"; echo "#$ 5" ) > phones.txt
|
||||
( echo 3; echo 4 ) > disambig.list
|
||||
fstmakecontextfst --read-disambig-syms=disambig.list <(grep -v '#' phones.txt) 5 ilabels.int > C.fst
|
||||
fstmakecontextsyms phones.txt ilabels.int > context_syms.txt
|
||||
fstprint --isymbols=context_syms.txt --osymbols=phones.txt C.fst > C.txt
|
||||
|
||||
fstrandgen C.fst | fstprint --isymbols=context_syms.txt --osymbols=phones.txt
|
||||
|
||||
Example output:
|
||||
|
||||
fstrandgen C.fst | fstprint --isymbols=context_syms.txt --osymbols=phones.txt
|
||||
0 1 #-1 b
|
||||
1 2 <eps>/b/<eps> #$
|
||||
2 3 #1 #1
|
||||
3 4 #0 #0
|
||||
4 5 #0 #0
|
||||
5 6 #0 #0
|
||||
6 7 #0 #0
|
||||
7 8 #0 #0
|
||||
8 9 #1 #1
|
||||
9
|
||||
*/
|
||||
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
try {
|
||||
using namespace kaldi;
|
||||
using namespace fst;
|
||||
typedef fst::StdArc::Label Label;
|
||||
const char *usage = "Create input symbols for CLG\n"
|
||||
"Usage: fstmakecontextsyms phones-symtab ilabels_input_file [output-symtab.txt]\n"
|
||||
"E.g.: fstmakecontextsyms phones.txt ilabels.sym > context_symbols.txt\n";
|
||||
|
||||
ParseOptions po(usage);
|
||||
|
||||
std::string disambig_list_file = "",
|
||||
phone_separator = "/",
|
||||
initial_disambig = "#-1";
|
||||
|
||||
po.Register("phone-separator", &phone_separator,
|
||||
"Separator for phones in phone-in-context symbols.");
|
||||
po.Register("initial-disambig", &initial_disambig,
|
||||
"Name for special disambiguation symbol that occurs at start "
|
||||
"of context-dependent phone sequences");
|
||||
|
||||
po.Read(argc, argv);
|
||||
|
||||
if (po.NumArgs() < 2 || po.NumArgs() > 3) {
|
||||
po.PrintUsage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
std::string phones_symtab_filename = po.GetArg(1),
|
||||
ilabel_info_filename = po.GetArg(2),
|
||||
clg_symtab_filename = po.GetOptArg(3);
|
||||
|
||||
std::vector<std::vector<kaldi::int32> > ilabel_info;
|
||||
{
|
||||
bool binary;
|
||||
Input ki(ilabel_info_filename, &binary);
|
||||
ReadILabelInfo(ki.Stream(),
|
||||
binary, &ilabel_info);
|
||||
}
|
||||
|
||||
fst::SymbolTable *phones_symtab = NULL;
|
||||
{ // read phone symbol table.
|
||||
std::ifstream is(phones_symtab_filename.c_str());
|
||||
phones_symtab = fst::SymbolTable::ReadText(is, phones_symtab_filename);
|
||||
if (!phones_symtab) KALDI_ERR << "Could not read phones symbol-table file "<<phones_symtab_filename;
|
||||
}
|
||||
|
||||
fst::SymbolTable *clg_symtab =
|
||||
CreateILabelInfoSymbolTable(ilabel_info,
|
||||
*phones_symtab,
|
||||
phone_separator,
|
||||
initial_disambig);
|
||||
|
||||
if (clg_symtab_filename == "") {
|
||||
if (!clg_symtab->WriteText(std::cout))
|
||||
KALDI_ERR << "Cannot write symbol table to standard output.";
|
||||
} else {
|
||||
if (!clg_symtab->WriteText(clg_symtab_filename))
|
||||
KALDI_ERR << "Cannot open symbol table file "<<clg_symtab_filename<<" for writing.";
|
||||
}
|
||||
delete clg_symtab;
|
||||
delete phones_symtab;
|
||||
return 0;
|
||||
} catch(const std::exception &e) {
|
||||
std::cerr << e.what();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// fstbin/fstminimizeencoded.cc
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
|
||||
#include "base/kaldi-common.h"
|
||||
#include "util/kaldi-io.h"
|
||||
#include "util/parse-options.h"
|
||||
#include "util/text-utils.h"
|
||||
#include "fst/fstlib.h"
|
||||
#include "fstext/determinize-star.h"
|
||||
#include "fstext/fstext-utils.h"
|
||||
#include "fstext/kaldi-fst-io.h"
|
||||
|
||||
/* some test examples:
|
||||
( echo "0 0 0 0"; echo "0 0" ) | fstcompile | fstminimizeencoded | fstprint
|
||||
( echo "0 1 0 0"; echo " 0 2 0 0"; echo "1 0"; echo "2 0"; ) | fstcompile | fstminimizeencoded | fstprint
|
||||
*/
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
try {
|
||||
using namespace kaldi;
|
||||
using namespace fst;
|
||||
using kaldi::int32;
|
||||
|
||||
const char *usage =
|
||||
"Minimizes FST after encoding [similar to fstminimize, but no weight-pushing]\n"
|
||||
"\n"
|
||||
"Usage: fstminimizeencoded [in.fst [out.fst] ]\n";
|
||||
|
||||
float delta = kDelta;
|
||||
ParseOptions po(usage);
|
||||
po.Register("delta", &delta, "Delta likelihood used for quantization of weights");
|
||||
po.Read(argc, argv);
|
||||
|
||||
if (po.NumArgs() > 2) {
|
||||
po.PrintUsage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
std::string fst_in_filename = po.GetOptArg(1),
|
||||
fst_out_filename = po.GetOptArg(2);
|
||||
|
||||
VectorFst<StdArc> *fst = ReadFstKaldi(fst_in_filename);
|
||||
|
||||
MinimizeEncoded(fst, delta);
|
||||
|
||||
WriteFstKaldi(*fst, fst_out_filename);
|
||||
|
||||
delete fst;
|
||||
return 0;
|
||||
} catch(const std::exception &e) {
|
||||
std::cerr << e.what();
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
// fstbin/fstphicompose.cc
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
|
||||
#include "base/kaldi-common.h"
|
||||
#include "util/common-utils.h"
|
||||
#include "fst/fstlib.h"
|
||||
#include "fstext/table-matcher.h"
|
||||
#include "fstext/fstext-utils.h"
|
||||
#include "fstext/kaldi-fst-io.h"
|
||||
|
||||
/*
|
||||
The following commands represent a basic test of this program.
|
||||
|
||||
cat <<EOF | fstcompile > a.fst
|
||||
0 1 10 10
|
||||
0 1 11 11
|
||||
1
|
||||
EOF
|
||||
|
||||
cat <<EOF | fstcompile > g.fst
|
||||
0 1 10 10 2.0
|
||||
0 2 100 100 6.6
|
||||
2 1 10 10 0.0
|
||||
2 1 11 11 1.0
|
||||
1
|
||||
EOF
|
||||
fstcompose a.fst g.fst | fstprint
|
||||
# gives, as expected:
|
||||
# 0 1 10 10 2
|
||||
# 1
|
||||
fstphicompose 100 a.fst g.fst | fstprint
|
||||
# gives, again correctly:
|
||||
#0 1 10 10 2
|
||||
#0 1 11 11 7.5999999
|
||||
#1
|
||||
|
||||
Next, test that it's working as desired for final-probs,
|
||||
i.e. it takes the backoff arc when looking for a final-prob,
|
||||
only if no final-prob present at current state.
|
||||
|
||||
cat <<EOF | fstcompile > a.fst
|
||||
0 1 10 10
|
||||
0 1 11 11
|
||||
1
|
||||
EOF
|
||||
cat <<EOF | fstcompile > g.fst
|
||||
0 1 10 10 2.0
|
||||
0 3 11 11 2.0
|
||||
1 2 100 110 6.6
|
||||
3 10.0
|
||||
3 2 100 110 0.0
|
||||
2
|
||||
EOF
|
||||
fstphicompose 100 a.fst g.fst | fstprint
|
||||
# output is:
|
||||
#0 1 10 10 2
|
||||
#0 2 11 11 2
|
||||
#1 6.5999999
|
||||
#2 10
|
||||
|
||||
*/
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
try {
|
||||
using namespace kaldi;
|
||||
using namespace fst;
|
||||
using kaldi::int32;
|
||||
/*
|
||||
fstphicompose does composition, but treats the second FST
|
||||
specially (basically, like a backoff LM); whenever the
|
||||
composition algorithm fails to find a match for a label
|
||||
in the second FST, if it sees a phi transition it will
|
||||
take it instead, and look for a match at the destination.
|
||||
phi is the label on the input side of the backoff arc of
|
||||
the LM (the label on the output side doesn't matter).
|
||||
|
||||
Also modifies the second fst so that it treats final-probs
|
||||
"correctly", i.e. takes the failure transition when looking
|
||||
for a final-prob. This would not work if there were
|
||||
epsilons.
|
||||
*/
|
||||
|
||||
const char *usage =
|
||||
"Composition, where the right FST has \"failure\" (phi) transitions\n"
|
||||
"that are only taken where there was no match of a \"real\" label\n"
|
||||
"You supply the label corresponding to phi.\n"
|
||||
"\n"
|
||||
"Usage: fstphicompose phi-label (fst1-rxfilename|fst1-rspecifier) "
|
||||
"(fst2-rxfilename|fst2-rspecifier) [(out-rxfilename|out-rspecifier)]\n"
|
||||
"E.g.: fstphicompose 54 a.fst b.fst c.fst\n"
|
||||
"or: fstphicompose 11 ark:a.fsts G.fst ark:b.fsts\n";
|
||||
|
||||
ParseOptions po(usage);
|
||||
|
||||
po.Read(argc, argv);
|
||||
|
||||
if (po.NumArgs() < 3 || po.NumArgs() > 4) {
|
||||
po.PrintUsage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
std::string
|
||||
phi_str = po.GetArg(1),
|
||||
fst1_in_str = po.GetArg(2),
|
||||
fst2_in_str = po.GetArg(3),
|
||||
fst_out_str = po.GetOptArg(4);
|
||||
|
||||
bool is_table_1 =
|
||||
(ClassifyRspecifier(fst1_in_str, NULL, NULL) != kNoRspecifier),
|
||||
is_table_2 =
|
||||
(ClassifyRspecifier(fst2_in_str, NULL, NULL) != kNoRspecifier),
|
||||
is_table_out =
|
||||
(ClassifyWspecifier(fst_out_str, NULL, NULL, NULL) != kNoWspecifier);
|
||||
|
||||
int32 phi_label;
|
||||
if (!ConvertStringToInteger(phi_str, &phi_label)
|
||||
|| phi_label <= 0)
|
||||
KALDI_ERR << "Invalid first argument (phi label), expect positive integer.";
|
||||
|
||||
if (is_table_out != (is_table_1 || is_table_2))
|
||||
KALDI_ERR << "Incompatible combination of archives and files";
|
||||
|
||||
if (!is_table_1 && !is_table_2) { // Only dealing with files...
|
||||
VectorFst<StdArc> *fst1 = ReadFstKaldi(fst1_in_str);
|
||||
|
||||
VectorFst<StdArc> *fst2 = ReadFstKaldi(fst2_in_str);
|
||||
|
||||
PropagateFinal(phi_label, fst2); // makes it work correctly
|
||||
// w.r.t. final-probs.
|
||||
|
||||
VectorFst<StdArc> composed_fst;
|
||||
|
||||
PhiCompose(*fst1, *fst2, phi_label, &composed_fst);
|
||||
|
||||
delete fst1;
|
||||
delete fst2;
|
||||
|
||||
WriteFstKaldi(composed_fst, fst_out_str);
|
||||
return 0;
|
||||
} else if (is_table_1 && !is_table_2) {
|
||||
|
||||
VectorFst<StdArc> *fst2 = ReadFstKaldi(fst2_in_str);
|
||||
PropagateFinal(phi_label, fst2); // makes it work correctly
|
||||
// w.r.t. final-probs.
|
||||
SequentialTableReader<VectorFstHolder> fst1_reader(fst1_in_str);
|
||||
TableWriter<VectorFstHolder> fst_writer(fst_out_str);
|
||||
int32 n_done = 0;
|
||||
for (; !fst1_reader.Done(); fst1_reader.Next(), n_done++) {
|
||||
VectorFst<StdArc> fst1(fst1_reader.Value());
|
||||
VectorFst<StdArc> fst_out;
|
||||
PhiCompose(fst1, *fst2, phi_label, &fst_out);
|
||||
fst_writer.Write(fst1_reader.Key(), fst_out);
|
||||
}
|
||||
KALDI_LOG << "Composed " << n_done << " FSTs.";
|
||||
return (n_done != 0 ? 0 : 1);
|
||||
} else {
|
||||
KALDI_ERR << "The combination of tables/non-tables that you "
|
||||
<< "supplied is not currently supported. Either implement this, "
|
||||
<< "ask the maintainers to implement it, or call this program "
|
||||
<< "differently.";
|
||||
}
|
||||
} catch(const std::exception &e) {
|
||||
std::cerr << e.what();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// fstbin/fstpushspecial.cc
|
||||
|
||||
// Copyright 2012 Daniel Povey
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
|
||||
#include "base/kaldi-common.h"
|
||||
#include "util/kaldi-io.h"
|
||||
#include "util/parse-options.h"
|
||||
#include "util/text-utils.h"
|
||||
#include "fst/fstlib.h"
|
||||
#include "fstext/fstext-utils.h"
|
||||
#include "fstext/push-special.h"
|
||||
#include "fstext/kaldi-fst-io.h"
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
try {
|
||||
using namespace kaldi;
|
||||
using namespace fst;
|
||||
using kaldi::int32;
|
||||
|
||||
const char *usage =
|
||||
"Pushes weights in an FST such that all the states\n"
|
||||
"in the FST have arcs and final-probs with weights that\n"
|
||||
"sum to the same amount (viewed as being in the log semiring).\n"
|
||||
"Thus, the \"extra weight\" is distributed throughout the FST.\n"
|
||||
"Tolerance parameter --delta controls how exact this is, and the\n"
|
||||
"speed.\n"
|
||||
"\n"
|
||||
"Usage: fstpushspecial [options] [in.fst [out.fst] ]\n";
|
||||
|
||||
BaseFloat delta = kDelta;
|
||||
ParseOptions po(usage);
|
||||
po.Register("delta", &delta, "Delta cost: after pushing, all states will "
|
||||
"have a total weight that differs from the average by no more "
|
||||
"than this.");
|
||||
po.Read(argc, argv);
|
||||
|
||||
if (po.NumArgs() > 2) {
|
||||
po.PrintUsage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
std::string fst_in_filename = po.GetOptArg(1),
|
||||
fst_out_filename = po.GetOptArg(2);
|
||||
|
||||
VectorFst<StdArc> *fst = ReadFstKaldi(fst_in_filename);
|
||||
|
||||
PushSpecial(fst, delta);
|
||||
|
||||
WriteFstKaldi(*fst, fst_out_filename);
|
||||
delete fst;
|
||||
return 0;
|
||||
} catch(const std::exception &e) {
|
||||
std::cerr << e.what();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// fstbin/fstrand.cc
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
|
||||
#include "base/kaldi-common.h"
|
||||
#include "util/kaldi-io.h"
|
||||
#include "util/parse-options.h"
|
||||
#include "util/text-utils.h"
|
||||
#include "fstext/rand-fst.h"
|
||||
#include "time.h"
|
||||
#include "fstext/fstext-utils.h"
|
||||
#include "fstext/kaldi-fst-io.h"
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
try {
|
||||
using namespace fst;
|
||||
using kaldi::int32;
|
||||
|
||||
const char *usage =
|
||||
"Generate random FST\n"
|
||||
"\n"
|
||||
"Usage: fstrand [out.fst]\n";
|
||||
|
||||
srand(time(NULL));
|
||||
RandFstOptions opts;
|
||||
|
||||
|
||||
kaldi::ParseOptions po(usage);
|
||||
po.Register("allow-empty", &opts.allow_empty,
|
||||
"If true, we may generate an empty FST.");
|
||||
|
||||
po.Read(argc, argv);
|
||||
|
||||
if (po.NumArgs() > 1) {
|
||||
po.PrintUsage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
std::string fst_out_filename = po.GetOptArg(1);
|
||||
|
||||
VectorFst <StdArc> *rand_fst = RandFst<StdArc>(opts);
|
||||
|
||||
WriteFstKaldi(*rand_fst, fst_out_filename);
|
||||
delete rand_fst;
|
||||
return 0;
|
||||
} catch(const std::exception &e) {
|
||||
std::cerr << e.what();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
// fstbin/fstrmepslocal.cc
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
|
||||
#include "base/kaldi-common.h"
|
||||
#include "util/kaldi-io.h"
|
||||
#include "util/parse-options.h"
|
||||
#include "util/text-utils.h"
|
||||
#include "fst/fstlib.h"
|
||||
#include "fstext/determinize-star.h"
|
||||
#include "fstext/fstext-utils.h"
|
||||
#include "fstext/kaldi-fst-io.h"
|
||||
|
||||
|
||||
/*
|
||||
A test example:
|
||||
( echo "0 1 1 0"; echo "1 2 0 2"; echo "2 0"; ) | fstcompile | fstrmepslocal | fstprint
|
||||
# prints:
|
||||
# 0 1 1 2
|
||||
# 1
|
||||
( echo "0 1 0 0"; echo "0 0"; echo "1 0" ) | fstcompile | fstrmepslocal | fstprint
|
||||
# 0
|
||||
( echo "0 1 0 0"; echo "0 0"; echo "1 0" ) | fstcompile | fstrmepslocal | fstprint
|
||||
( echo "0 1 0 0"; echo "0 0"; echo "1 0" ) | fstcompile | fstrmepslocal --use-log=true | fstprint
|
||||
# 0 -0.693147182
|
||||
|
||||
*/
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
try {
|
||||
using namespace kaldi;
|
||||
using namespace fst;
|
||||
using kaldi::int32;
|
||||
|
||||
const char *usage =
|
||||
"Removes some (but not all) epsilons in an algorithm that will always reduce the number of\n"
|
||||
"arcs+states. Option to preserves equivalence in tropical or log semiring, and\n"
|
||||
"if in tropical, stochasticit in either log or tropical.\n"
|
||||
"\n"
|
||||
"Usage: fstrmepslocal [in.fst [out.fst] ]\n";
|
||||
|
||||
ParseOptions po(usage);
|
||||
bool use_log = false;
|
||||
bool stochastic_in_log = true;
|
||||
po.Register("use-log", &use_log,
|
||||
"Preserve equivalence in log semiring [false->tropical]\n");
|
||||
po.Register("stochastic-in-log", &stochastic_in_log,
|
||||
"Preserve stochasticity in log semiring [false->tropical]\n");
|
||||
po.Read(argc, argv);
|
||||
|
||||
if (po.NumArgs() > 2) {
|
||||
po.PrintUsage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
std::string fst_in_filename = po.GetOptArg(1),
|
||||
fst_out_filename = po.GetOptArg(2);
|
||||
|
||||
VectorFst<StdArc> *fst = ReadFstKaldi(fst_in_filename);
|
||||
|
||||
if (!use_log && stochastic_in_log) {
|
||||
RemoveEpsLocalSpecial(fst);
|
||||
} else if (use_log && !stochastic_in_log) {
|
||||
std::cerr << "fstrmsymbols: invalid combination of flags\n";
|
||||
return 1;
|
||||
} else if (use_log) {
|
||||
VectorFst<LogArc> log_fst;
|
||||
Cast(*fst, &log_fst);
|
||||
delete fst;
|
||||
RemoveEpsLocal(&log_fst);
|
||||
fst = new VectorFst<StdArc>;
|
||||
Cast(log_fst, fst);
|
||||
} else {
|
||||
RemoveEpsLocal(fst);
|
||||
}
|
||||
|
||||
WriteFstKaldi(*fst, fst_out_filename);
|
||||
delete fst;
|
||||
return 0;
|
||||
} catch(const std::exception &e) {
|
||||
std::cerr << e.what();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
// fstbin/fstrmsymbols.cc
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
|
||||
#include "base/kaldi-common.h"
|
||||
#include "util/common-utils.h"
|
||||
#include "fst/fstlib.h"
|
||||
#include "fstext/determinize-star.h"
|
||||
#include "fstext/fstext-utils.h"
|
||||
#include "fstext/kaldi-fst-io.h"
|
||||
|
||||
namespace fst {
|
||||
// we can move these functions elsewhere later, if they are needed in other
|
||||
// places.
|
||||
|
||||
template<class Arc, class I>
|
||||
void RemoveArcsWithSomeInputSymbols(const std::vector<I> &symbols_in,
|
||||
VectorFst<Arc> *fst) {
|
||||
typedef typename Arc::StateId StateId;
|
||||
|
||||
kaldi::ConstIntegerSet<I> symbol_set(symbols_in);
|
||||
|
||||
StateId num_states = fst->NumStates();
|
||||
StateId dead_state = fst->AddState();
|
||||
for (StateId s = 0; s < num_states; s++) {
|
||||
for (MutableArcIterator<VectorFst<Arc> > iter(fst, s);
|
||||
!iter.Done(); iter.Next()) {
|
||||
if (symbol_set.count(iter.Value().ilabel) != 0) {
|
||||
Arc arc = iter.Value();
|
||||
arc.nextstate = dead_state;
|
||||
iter.SetValue(arc);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Connect() will actually remove the arcs, and the dead state.
|
||||
Connect(fst);
|
||||
if (fst->NumStates() == 0)
|
||||
KALDI_WARN << "After Connect(), fst was empty.";
|
||||
}
|
||||
|
||||
template<class Arc, class I>
|
||||
void PenalizeArcsWithSomeInputSymbols(const std::vector<I> &symbols_in,
|
||||
float penalty,
|
||||
VectorFst<Arc> *fst) {
|
||||
typedef typename Arc::StateId StateId;
|
||||
typedef typename Arc::Label Label;
|
||||
typedef typename Arc::Weight Weight;
|
||||
|
||||
Weight penalty_weight(penalty);
|
||||
|
||||
kaldi::ConstIntegerSet<I> symbol_set(symbols_in);
|
||||
|
||||
StateId num_states = fst->NumStates();
|
||||
for (StateId s = 0; s < num_states; s++) {
|
||||
for (MutableArcIterator<VectorFst<Arc> > iter(fst, s);
|
||||
!iter.Done(); iter.Next()) {
|
||||
if (symbol_set.count(iter.Value().ilabel) != 0) {
|
||||
Arc arc = iter.Value();
|
||||
arc.weight = Times(arc.weight, penalty_weight);
|
||||
iter.SetValue(arc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
try {
|
||||
using namespace kaldi;
|
||||
using namespace fst;
|
||||
using kaldi::int32;
|
||||
|
||||
bool apply_to_output = false;
|
||||
bool remove_arcs = false;
|
||||
float penalty = -std::numeric_limits<BaseFloat>::infinity();
|
||||
|
||||
const char *usage =
|
||||
"With no options, replaces a subset of symbols with epsilon, wherever\n"
|
||||
"they appear on the input side of an FST."
|
||||
"With --remove-arcs=true, will remove arcs that contain these symbols\n"
|
||||
"on the input\n"
|
||||
"With --penalty=<float>, will add the specified penalty to the\n"
|
||||
"cost of any arc that has one of the given symbols on its input side\n"
|
||||
"In all cases, the option --apply-to-output=true (or for\n"
|
||||
"back-compatibility, --remove-from-output=true) makes this apply\n"
|
||||
"to the output side.\n"
|
||||
"\n"
|
||||
"Usage: fstrmsymbols [options] <in-disambig-list> [<in.fst> [<out.fst>]]\n"
|
||||
"E.g: fstrmsymbols in.list < in.fst > out.fst\n"
|
||||
"<in-disambig-list> is an rxfilename specifying a file containing list of integers\n"
|
||||
"representing symbols, in text form, one per line.\n";
|
||||
|
||||
ParseOptions po(usage);
|
||||
po.Register("remove-from-output", &apply_to_output, "If true, this applies to symbols "
|
||||
"on the output, not the input, side. (For back compatibility; use "
|
||||
"--apply-to-output insead)");
|
||||
po.Register("apply-to-output", &apply_to_output, "If true, this applies to symbols "
|
||||
"on the output, not the input, side.");
|
||||
po.Register("remove-arcs", &remove_arcs, "If true, instead of converting the symbol "
|
||||
"to <eps>, remove the arcs.");
|
||||
po.Register("penalty", &penalty, "If specified, instead of converting "
|
||||
"the symbol to <eps>, penalize the arc it is on by adding this "
|
||||
"value to its cost.");
|
||||
|
||||
|
||||
po.Read(argc, argv);
|
||||
|
||||
if (remove_arcs &&
|
||||
penalty != -std::numeric_limits<BaseFloat>::infinity())
|
||||
KALDI_ERR << "--remove-arc and --penalty options are mutually exclusive";
|
||||
|
||||
if (po.NumArgs() < 1 || po.NumArgs() > 3) {
|
||||
po.PrintUsage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
std::string disambig_rxfilename = po.GetArg(1),
|
||||
fst_rxfilename = po.GetOptArg(2),
|
||||
fst_wxfilename = po.GetOptArg(3);
|
||||
|
||||
VectorFst<StdArc> *fst = CastOrConvertToVectorFst(
|
||||
ReadFstKaldiGeneric(fst_rxfilename));
|
||||
|
||||
std::vector<int32> disambig_in;
|
||||
if (!ReadIntegerVectorSimple(disambig_rxfilename, &disambig_in))
|
||||
KALDI_ERR << "fstrmsymbols: Could not read disambiguation symbols from "
|
||||
<< (disambig_rxfilename == "" ? "standard input" : disambig_rxfilename);
|
||||
|
||||
if (apply_to_output) Invert(fst);
|
||||
if (remove_arcs) {
|
||||
RemoveArcsWithSomeInputSymbols(disambig_in, fst);
|
||||
} else if (penalty != -std::numeric_limits<BaseFloat>::infinity()) {
|
||||
PenalizeArcsWithSomeInputSymbols(disambig_in, penalty, fst);
|
||||
} else {
|
||||
RemoveSomeInputSymbols(disambig_in, fst);
|
||||
}
|
||||
if (apply_to_output) Invert(fst);
|
||||
|
||||
WriteFstKaldi(*fst, fst_wxfilename);
|
||||
|
||||
delete fst;
|
||||
return 0;
|
||||
} catch(const std::exception &e) {
|
||||
std::cerr << e.what();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* some test examples:
|
||||
|
||||
( echo "0 0 1 1"; echo " 0 0 3 2"; echo "0 0"; ) | fstcompile | fstrmsymbols "echo 3; echo 4|" | fstprint
|
||||
# should produce:
|
||||
# 0 0 1 1
|
||||
# 0 0 0 2
|
||||
# 0
|
||||
|
||||
( echo "0 0 1 1"; echo " 0 0 3 2"; echo "0 0"; ) | fstcompile | fstrmsymbols --apply-to-output=true "echo 2; echo 3|" | fstprint
|
||||
# should produce:
|
||||
# 0 0 1 1
|
||||
# 0 0 3 0
|
||||
# 0
|
||||
|
||||
|
||||
( echo "0 0 1 1"; echo " 0 0 3 2"; echo "0 0"; ) | fstcompile | fstrmsymbols --remove-arcs=true "echo 3; echo 4|" | fstprint
|
||||
# should produce:
|
||||
# 0 0 1 1
|
||||
# 0
|
||||
|
||||
( echo "0 0 1 1"; echo " 0 0 3 2"; echo "0 0"; ) | fstcompile | fstrmsymbols --penalty=2 "echo 3; echo 4; echo 5|" | fstprint
|
||||
# should produce:
|
||||
# 0 0 1 1
|
||||
# 0 0 3 2 2
|
||||
# 0
|
||||
|
||||
*/
|
||||
@@ -0,0 +1,112 @@
|
||||
// fstbin/fsts-concat.cc
|
||||
|
||||
// Copyright 2016 Johns Hopkins University (Authors: Jan "Yenda" Trmal)
|
||||
// 2018 Soapbox Labs (Author: Karel Vesely)
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
|
||||
#include "base/kaldi-common.h"
|
||||
#include "util/common-utils.h"
|
||||
#include "fstext/fstext-utils.h"
|
||||
#include "fstext/kaldi-fst-io.h"
|
||||
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
try {
|
||||
using namespace kaldi;
|
||||
using namespace fst;
|
||||
typedef kaldi::int32 int32;
|
||||
typedef kaldi::uint64 uint64;
|
||||
|
||||
const char *usage =
|
||||
"Reads kaldi archives with FSTs. Concatenates the fsts from all the rspecifiers.\n"
|
||||
"The fsts to concatenate must have same key. The sequencing is given by the position of arguments.\n"
|
||||
"\n"
|
||||
"Usage: fsts-concat [options] <fsts-rspecifier1> <fsts-rspecifier2> ... <fsts-wspecifier>\n"
|
||||
" e.g.: fsts-concat scp:fsts1.scp scp:fsts2.scp ... ark:fsts_out.ark\n"
|
||||
"\n"
|
||||
"see also: fstconcat (from the OpenFst toolkit)\n";
|
||||
|
||||
ParseOptions po(usage);
|
||||
|
||||
po.Read(argc, argv);
|
||||
|
||||
if (po.NumArgs() < 3) {
|
||||
po.PrintUsage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
std::string fsts_rspecifier = po.GetArg(1),
|
||||
fsts_wspecifier = po.GetArg(po.NumArgs());
|
||||
|
||||
SequentialTableReader<VectorFstHolder> fst_reader(fsts_rspecifier);
|
||||
std::vector<RandomAccessTableReader<VectorFstHolder>*> fst_readers;
|
||||
TableWriter<VectorFstHolder> fst_writer(fsts_wspecifier);
|
||||
|
||||
for (int32 i = 2; i < po.NumArgs(); i++)
|
||||
fst_readers.push_back(new RandomAccessTableReader<VectorFstHolder>(po.GetArg(i)));
|
||||
const int32 num_fst_readers = fst_readers.size();
|
||||
|
||||
int32 n_done = 0,
|
||||
n_skipped = 0;
|
||||
|
||||
for (; !fst_reader.Done(); fst_reader.Next()) {
|
||||
std::string key = fst_reader.Key();
|
||||
|
||||
// Check that the key exists in all 'fst_readers'.
|
||||
bool skip_key = false;
|
||||
for (int32 i = 0; i < num_fst_readers; i++) {
|
||||
if (!fst_readers[i]->HasKey(key)) {
|
||||
KALDI_WARN << "Skipping '" << key << "'"
|
||||
<< " due to missing the fst in " << (i+2) << "th <rspecifier> : "
|
||||
<< "'" << po.GetArg(i+2) << "'";
|
||||
skip_key = true;
|
||||
}
|
||||
}
|
||||
if (skip_key) {
|
||||
n_skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Concatenate!
|
||||
VectorFst<StdArc> fst_out = fst_readers.back()->Value(key);
|
||||
// Loop from (last-1) to first, as 'prepending' the fsts is faster,
|
||||
// see: http://www.openfst.org/twiki/bin/view/FST/ConcatDoc
|
||||
for (int32 i = num_fst_readers-2; i >= 0; i--) {
|
||||
fst::Concat(fst_readers[i]->Value(key), &fst_out);
|
||||
}
|
||||
// Finally, prepend the fst from the 'Sequential' reader.
|
||||
fst::Concat(fst_reader.Value(), &fst_out);
|
||||
|
||||
// Write the output.
|
||||
fst_writer.Write(key, fst_out);
|
||||
n_done++;
|
||||
}
|
||||
|
||||
// Cleanup.
|
||||
for (int32 i = 0; i < num_fst_readers; i++)
|
||||
delete fst_readers[i];
|
||||
fst_readers.clear();
|
||||
|
||||
KALDI_LOG << "Produced " << n_done << " FSTs by concatenating " << po.NumArgs()-1
|
||||
<< " streams " << "(" << n_skipped << " keys skipped).";
|
||||
return (n_done != 0 ? 0 : 1);
|
||||
} catch(const std::exception &e) {
|
||||
std::cerr << e.what();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
// fstbin/fsts-project.cc
|
||||
|
||||
// Copyright 2016 Johns Hopkins University (Authors: Jan "Yenda" Trmal)
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
|
||||
#include "base/kaldi-common.h"
|
||||
#include "util/common-utils.h"
|
||||
#include "fstext/fstext-utils.h"
|
||||
#include "fstext/kaldi-fst-io.h"
|
||||
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
try {
|
||||
using namespace kaldi;
|
||||
using namespace fst;
|
||||
typedef kaldi::int32 int32;
|
||||
typedef kaldi::uint64 uint64;
|
||||
|
||||
const char *usage =
|
||||
"Reads kaldi archive of FSTs; for each element, performs the project\n"
|
||||
"operation either on input (default) or on the output (if the option\n"
|
||||
"--project-output is true).\n"
|
||||
"\n"
|
||||
"Usage: fsts-project [options] <fsts-rspecifier> <fsts-wspecifier>\n"
|
||||
" e.g.: fsts-project ark:train.fsts ark,t:train.fsts\n"
|
||||
"\n"
|
||||
"see also: fstproject (from the OpenFst toolkit)\n";
|
||||
|
||||
ParseOptions po(usage);
|
||||
|
||||
bool project_output = false;
|
||||
|
||||
po.Register("project-output", &project_output,
|
||||
"If true, project output vs input");
|
||||
|
||||
po.Read(argc, argv);
|
||||
|
||||
if (po.NumArgs() != 2) {
|
||||
po.PrintUsage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
std::string fsts_rspecifier = po.GetArg(1),
|
||||
fsts_wspecifier = po.GetArg(2);
|
||||
|
||||
|
||||
SequentialTableReader<VectorFstHolder> fst_reader(fsts_rspecifier);
|
||||
TableWriter<VectorFstHolder> fst_writer(fsts_wspecifier);
|
||||
|
||||
int32 n_done = 0;
|
||||
for (; !fst_reader.Done(); fst_reader.Next()) {
|
||||
std::string key = fst_reader.Key();
|
||||
VectorFst<StdArc> fst(fst_reader.Value());
|
||||
|
||||
Project(&fst, project_output ? PROJECT_OUTPUT : PROJECT_INPUT);
|
||||
|
||||
fst_writer.Write(key, fst);
|
||||
n_done++;
|
||||
}
|
||||
|
||||
KALDI_LOG << "Projected " << n_done << " FSTs";
|
||||
return (n_done != 0 ? 0 : 1);
|
||||
} catch(const std::exception &e) {
|
||||
std::cerr << e.what();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
// fstbin/fsts-to-transcripts.cc
|
||||
|
||||
// Copyright 2012-2013 Johns Hopkins University (Authors: Guoguo Chen,
|
||||
// Daniel Povey)
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
|
||||
#include "base/kaldi-common.h"
|
||||
#include "util/common-utils.h"
|
||||
#include "fstext/fstext-utils.h"
|
||||
#include "fstext/kaldi-fst-io.h"
|
||||
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
try {
|
||||
using namespace kaldi;
|
||||
using namespace fst;
|
||||
typedef kaldi::int32 int32;
|
||||
typedef kaldi::uint64 uint64;
|
||||
|
||||
const char *usage =
|
||||
"Reads a table of FSTs; for each element, finds the best path and \n"
|
||||
"prints out the output-symbol sequence (if --output-side=true), or \n"
|
||||
"input-symbol sequence otherwise.\n"
|
||||
"\n"
|
||||
"Usage:\n"
|
||||
" fsts-to-transcripts [options] <fsts-rspecifier>"
|
||||
" <transcriptions-wspecifier>\n"
|
||||
"e.g.:\n"
|
||||
" fsts-to-transcripts ark:train.fsts ark,t:train.text\n";
|
||||
|
||||
ParseOptions po(usage);
|
||||
|
||||
bool output_side = true;
|
||||
|
||||
po.Register("output-side", &output_side, "If true, extract the symbols on "
|
||||
"the output side of the FSTs, else the input side.");
|
||||
|
||||
po.Read(argc, argv);
|
||||
|
||||
if (po.NumArgs() != 2) {
|
||||
po.PrintUsage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
std::string fst_rspecifier = po.GetArg(1),
|
||||
transcript_wspecifier = po.GetArg(2);
|
||||
|
||||
|
||||
SequentialTableReader<VectorFstHolder> fst_reader(fst_rspecifier);
|
||||
Int32VectorWriter transcript_writer(transcript_wspecifier);
|
||||
|
||||
int32 n_done = 0, n_err = 0;
|
||||
for (; !fst_reader.Done(); fst_reader.Next()) {
|
||||
std::string key = fst_reader.Key();
|
||||
const VectorFst<StdArc> &fst = fst_reader.Value();
|
||||
|
||||
|
||||
VectorFst<StdArc> shortest_path;
|
||||
ShortestPath(fst, &shortest_path); // the OpenFst algorithm ShortestPath.
|
||||
|
||||
if (shortest_path.NumStates() == 0) {
|
||||
KALDI_WARN << "Input FST (after shortest path) was empty. Producing "
|
||||
<< "no output for key " << key;
|
||||
n_err++;
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<int32> transcript;
|
||||
bool ans;
|
||||
if (output_side) ans = fst::GetLinearSymbolSequence<StdArc, int32>(
|
||||
shortest_path, NULL, &transcript, NULL);
|
||||
else
|
||||
ans = fst::GetLinearSymbolSequence<StdArc, int32>(
|
||||
shortest_path, &transcript, NULL, NULL);
|
||||
if (!ans) {
|
||||
KALDI_ERR << "GetLinearSymbolSequence returned false (code error);";
|
||||
}
|
||||
transcript_writer.Write(key, transcript);
|
||||
n_done++;
|
||||
}
|
||||
|
||||
KALDI_LOG << "Converted " << n_done << " FSTs, " << n_err << " with errors";
|
||||
return (n_done != 0 ? 0 : 1);
|
||||
} catch(const std::exception &e) {
|
||||
std::cerr << e.what();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
// fstbin/fsts-union.cc
|
||||
|
||||
// Copyright 2016 Johns Hopkins University (Authors: Jan "Yenda" Trmal)
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
|
||||
#include "base/kaldi-common.h"
|
||||
#include "util/common-utils.h"
|
||||
#include "fstext/fstext-utils.h"
|
||||
#include "fstext/kaldi-fst-io.h"
|
||||
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
try {
|
||||
using namespace kaldi;
|
||||
using namespace fst;
|
||||
typedef kaldi::int32 int32;
|
||||
typedef kaldi::uint64 uint64;
|
||||
|
||||
const char *usage =
|
||||
"Reads a kaldi archive of FSTs. Performs the FST operation union on\n"
|
||||
"all fsts sharing the same key. Assumes the archive is sorted by key.\n"
|
||||
"\n"
|
||||
"Usage: fsts-union [options] <fsts-rspecifier> <fsts-wspecifier>\n"
|
||||
" e.g.: fsts-union ark:keywords_tmp.fsts ark,t:keywords.fsts\n"
|
||||
"\n"
|
||||
"see also: fstunion (from the OpenFst toolkit)\n";
|
||||
|
||||
ParseOptions po(usage);
|
||||
|
||||
po.Read(argc, argv);
|
||||
|
||||
if (po.NumArgs() != 2) {
|
||||
po.PrintUsage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
std::string fsts_rspecifier = po.GetArg(1),
|
||||
fsts_wspecifier = po.GetArg(2);
|
||||
|
||||
|
||||
SequentialTableReader<VectorFstHolder> fst_reader(fsts_rspecifier);
|
||||
TableWriter<VectorFstHolder> fst_writer(fsts_wspecifier);
|
||||
|
||||
int32 n_out_done = 0,
|
||||
n_in_done = 0;
|
||||
std::string res_key = "";
|
||||
VectorFst<StdArc> res_fst;
|
||||
|
||||
for (; !fst_reader.Done(); fst_reader.Next()) {
|
||||
std::string key = fst_reader.Key();
|
||||
VectorFst<StdArc> fst(fst_reader.Value());
|
||||
|
||||
n_in_done++;
|
||||
if (key == res_key) {
|
||||
fst::Union(&res_fst, fst);
|
||||
} else {
|
||||
if (res_key != "") {
|
||||
VectorFst<StdArc> out_fst;
|
||||
fst::Determinize(res_fst, &out_fst);
|
||||
fst::Minimize(&out_fst);
|
||||
fst::RmEpsilon(&out_fst);
|
||||
fst_writer.Write(res_key, out_fst);
|
||||
n_out_done++;
|
||||
}
|
||||
res_fst = fst;
|
||||
res_key = key;
|
||||
}
|
||||
}
|
||||
if (res_key != "") {
|
||||
VectorFst<StdArc> out_fst;
|
||||
fst::Determinize(res_fst, &out_fst);
|
||||
fst::Minimize(&out_fst);
|
||||
fst::RmEpsilon(&out_fst);
|
||||
fst_writer.Write(res_key, out_fst);
|
||||
n_out_done++;
|
||||
}
|
||||
|
||||
KALDI_LOG << "Applied fst union on " << n_in_done
|
||||
<< " FSTs, produced " << n_out_done << " FSTs";
|
||||
return (n_out_done != 0 ? 0 : 1);
|
||||
} catch(const std::exception &e) {
|
||||
std::cerr << e.what();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
// fstbin/fsttablecompose.cc
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation
|
||||
// 2013 Johns Hopkins University (author: Daniel Povey)
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
|
||||
#include "base/kaldi-common.h"
|
||||
//#include "util/common-utils.h"
|
||||
#include "fst/fstlib.h"
|
||||
#include "fstext/table-matcher.h"
|
||||
#include "fstext/fstext-utils.h"
|
||||
#include "fstext/kaldi-fst-io.h"
|
||||
#include "util/parse-options.h"
|
||||
|
||||
/*
|
||||
cd ~/tmpdir
|
||||
while true; do
|
||||
fstrand | fstarcsort --sort_type=olabel > 1.fst; fstrand | fstarcsort > 2.fst
|
||||
fstcompose 1.fst 2.fst > 3a.fst
|
||||
fsttablecompose 1.fst 2.fst > 3b.fst
|
||||
fstequivalent --random=true 3a.fst 3b.fst || echo "Test failed"
|
||||
echo -n "."
|
||||
done
|
||||
|
||||
*/
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
try {
|
||||
using namespace kaldi;
|
||||
using namespace fst;
|
||||
using kaldi::int32;
|
||||
/*
|
||||
fsttablecompose should always give equivalent results to compose,
|
||||
but it is more efficient for certain kinds of inputs.
|
||||
In particular, it is useful when, say, the left FST has states
|
||||
that typically either have epsilon olabels, or
|
||||
one transition out for each of the possible symbols (as the
|
||||
olabel). The same with the input symbols of the right-hand FST
|
||||
is possible.
|
||||
*/
|
||||
|
||||
const char *usage =
|
||||
"Composition algorithm [between two FSTs of standard type, in tropical\n"
|
||||
"semiring] that is more efficient for certain cases-- in particular,\n"
|
||||
"where one of the FSTs (the left one, if --match-side=left) has large\n"
|
||||
"out-degree\n"
|
||||
"\n"
|
||||
"Usage: fsttablecompose (fst1-rxfilename|fst1-rspecifier) "
|
||||
"(fst2-rxfilename|fst2-rspecifier) [(out-rxfilename|out-rspecifier)]\n";
|
||||
|
||||
ParseOptions po(usage);
|
||||
|
||||
TableComposeOptions opts;
|
||||
std::string match_side = "left";
|
||||
std::string compose_filter = "sequence";
|
||||
|
||||
po.Register("connect", &opts.connect, "If true, trim FST before output.");
|
||||
po.Register("match-side", &match_side, "Side of composition to do table "
|
||||
"match, one of: \"left\" or \"right\".");
|
||||
po.Register("compose-filter", &compose_filter, "Composition filter to use, "
|
||||
"one of: \"alt_sequence\", \"auto\", \"match\", \"sequence\"");
|
||||
|
||||
po.Read(argc, argv);
|
||||
|
||||
if (match_side == "left") {
|
||||
opts.table_match_type = MATCH_OUTPUT;
|
||||
} else if (match_side == "right") {
|
||||
opts.table_match_type = MATCH_INPUT;
|
||||
} else {
|
||||
KALDI_ERR << "Invalid match-side option: " << match_side;
|
||||
}
|
||||
|
||||
if (compose_filter == "alt_sequence") {
|
||||
opts.filter_type = ALT_SEQUENCE_FILTER;
|
||||
} else if (compose_filter == "auto") {
|
||||
opts.filter_type = AUTO_FILTER;
|
||||
} else if (compose_filter == "match") {
|
||||
opts.filter_type = MATCH_FILTER;
|
||||
} else if (compose_filter == "sequence") {
|
||||
opts.filter_type = SEQUENCE_FILTER;
|
||||
} else {
|
||||
KALDI_ERR << "Invalid compose-filter option: " << compose_filter;
|
||||
}
|
||||
|
||||
if (po.NumArgs() < 2 || po.NumArgs() > 3) {
|
||||
po.PrintUsage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
std::string fst1_in_str = po.GetArg(1),
|
||||
fst2_in_str = po.GetArg(2),
|
||||
fst_out_str = po.GetOptArg(3);
|
||||
|
||||
|
||||
//// Note: the "table" in is_table_1 and similar variables has nothing
|
||||
//// to do with the "table" in "fsttablecompose"; is_table_1 relates to
|
||||
//// whether we are dealing with a single FST or a whole set of FSTs.
|
||||
//bool is_table_1 =
|
||||
// (ClassifyRspecifier(fst1_in_str, NULL, NULL) != kNoRspecifier),
|
||||
// is_table_2 =
|
||||
// (ClassifyRspecifier(fst2_in_str, NULL, NULL) != kNoRspecifier),
|
||||
// is_table_out =
|
||||
// (ClassifyWspecifier(fst_out_str, NULL, NULL, NULL) != kNoWspecifier);
|
||||
//if (is_table_out != (is_table_1 || is_table_2))
|
||||
// KALDI_ERR << "Incompatible combination of archives and files";
|
||||
//
|
||||
//if (!is_table_1 && !is_table_2) { // Only dealing with files...
|
||||
VectorFst<StdArc> *fst1 = ReadFstKaldi(fst1_in_str);
|
||||
|
||||
VectorFst<StdArc> *fst2 = ReadFstKaldi(fst2_in_str);
|
||||
|
||||
// Checks if <fst1> is olabel sorted and <fst2> is ilabel sorted.
|
||||
if (fst1->Properties(fst::kOLabelSorted, true) == 0) {
|
||||
KALDI_WARN << "The first FST is not olabel sorted.";
|
||||
}
|
||||
if (fst2->Properties(fst::kILabelSorted, true) == 0) {
|
||||
KALDI_WARN << "The second FST is not ilabel sorted.";
|
||||
}
|
||||
|
||||
VectorFst<StdArc> composed_fst;
|
||||
|
||||
TableCompose(*fst1, *fst2, &composed_fst, opts);
|
||||
|
||||
delete fst1;
|
||||
delete fst2;
|
||||
|
||||
WriteFstKaldi(composed_fst, fst_out_str);
|
||||
return 0;
|
||||
//} else if (!is_table_1 && is_table_2
|
||||
// && opts.table_match_type == MATCH_OUTPUT) {
|
||||
// // second arg is an archive, and match-side=left (default).
|
||||
// TableComposeCache<Fst<StdArc> > cache(opts);
|
||||
// VectorFst<StdArc> *fst1 = ReadFstKaldi(fst1_in_str);
|
||||
// SequentialTableReader<VectorFstHolder> fst2_reader(fst2_in_str);
|
||||
// TableWriter<VectorFstHolder> fst_writer(fst_out_str);
|
||||
// int32 n_done = 0;
|
||||
|
||||
// // Checks if <fst1> is olabel sorted.
|
||||
// if (fst1->Properties(fst::kOLabelSorted, true) == 0) {
|
||||
// KALDI_WARN << "The first FST is not olabel sorted.";
|
||||
// }
|
||||
// for (; !fst2_reader.Done(); fst2_reader.Next(), n_done++) {
|
||||
// VectorFst<StdArc> fst2(fst2_reader.Value());
|
||||
// VectorFst<StdArc> fst_out;
|
||||
// TableCompose(*fst1, fst2, &fst_out, &cache);
|
||||
// fst_writer.Write(fst2_reader.Key(), fst_out);
|
||||
// }
|
||||
// KALDI_LOG << "Composed " << n_done << " FSTs.";
|
||||
// return (n_done != 0 ? 0 : 1);
|
||||
//} else if (is_table_1 && is_table_2) {
|
||||
// SequentialTableReader<VectorFstHolder> fst1_reader(fst1_in_str);
|
||||
// RandomAccessTableReader<VectorFstHolder> fst2_reader(fst2_in_str);
|
||||
// TableWriter<VectorFstHolder> fst_writer(fst_out_str);
|
||||
// int32 n_done = 0, n_err = 0;
|
||||
// for (; !fst1_reader.Done(); fst1_reader.Next()) {
|
||||
// std::string key = fst1_reader.Key();
|
||||
// if (!fst2_reader.HasKey(key)) {
|
||||
// KALDI_WARN << "No such key " << key << " in second table.";
|
||||
// n_err++;
|
||||
// } else {
|
||||
// const VectorFst<StdArc> &fst1(fst1_reader.Value()),
|
||||
// &fst2(fst2_reader.Value(key));
|
||||
// VectorFst<StdArc> result;
|
||||
// TableCompose(fst1, fst2, &result, opts);
|
||||
// if (result.NumStates() == 0) {
|
||||
// KALDI_WARN << "Empty output for key " << key;
|
||||
// n_err++;
|
||||
// } else {
|
||||
// fst_writer.Write(key, result);
|
||||
// n_done++;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// KALDI_LOG << "Successfully composed " << n_done << " FSTs, errors or "
|
||||
// << "empty output on " << n_err;
|
||||
//} else {
|
||||
// KALDI_ERR << "The combination of tables/non-tables and match-type that you "
|
||||
// << "supplied is not currently supported. Either implement this, "
|
||||
// << "ask the maintainers to implement it, or call this program "
|
||||
// << "differently.";
|
||||
//}
|
||||
} catch(const std::exception &e) {
|
||||
std::cerr << e.what();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
// fstbin/make-grammar-fst.cc
|
||||
|
||||
// Copyright 2018 Johns Hopkins University (author: Daniel Povey)
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
|
||||
#include "base/kaldi-common.h"
|
||||
#include "util/common-utils.h"
|
||||
#include "fst/fstlib.h"
|
||||
#include "fstext/table-matcher.h"
|
||||
#include "fstext/kaldi-fst-io.h"
|
||||
#include "decoder/grammar-fst.h"
|
||||
|
||||
template<typename FST>
|
||||
void MakeGrammarFst(kaldi::ParseOptions po,
|
||||
int32 nonterm_phones_offset,
|
||||
bool write_as_grammar){
|
||||
using namespace kaldi;
|
||||
using namespace fst;
|
||||
using kaldi::int32;
|
||||
|
||||
std::string fst_out_str = po.GetArg(po.NumArgs());
|
||||
std::string top_fst_str = po.GetArg(1);
|
||||
|
||||
ConstFst<StdArc> *top_tmp_fst = ConstFst<StdArc>::Read(top_fst_str);
|
||||
std::shared_ptr<FST> top_fst(new FST(*top_tmp_fst));
|
||||
|
||||
std::vector<std::pair<int32, std::shared_ptr<FST> > > pairs;
|
||||
|
||||
int32 num_pairs = (po.NumArgs() - 2) / 2;
|
||||
for (int32 i = 1; i <= num_pairs; i++) {
|
||||
int32 nonterminal;
|
||||
std::string nonterm_str = po.GetArg(2*i);
|
||||
if (!ConvertStringToInteger(nonterm_str, &nonterminal) ||
|
||||
nonterminal <= 0)
|
||||
KALDI_ERR << "Expected positive integer as nonterminal, got: "
|
||||
<< nonterm_str;
|
||||
std::string fst_str = po.GetArg(2*i + 1);
|
||||
|
||||
ConstFst<StdArc> *tmp_fst = ConstFst<StdArc>::Read(fst_str);
|
||||
std::shared_ptr<FST> this_fst(new FST(*tmp_fst));
|
||||
|
||||
pairs.push_back(std::pair<int32, std::shared_ptr<FST> >(
|
||||
nonterminal, this_fst));
|
||||
};
|
||||
|
||||
GrammarFstTpl<FST> *grammar_fst = new GrammarFstTpl<FST>(nonterm_phones_offset,
|
||||
top_fst,
|
||||
pairs);
|
||||
|
||||
if (write_as_grammar) {
|
||||
bool binary = true; // GrammarFst does not support non-binary write.
|
||||
WriteKaldiObject(*grammar_fst, fst_out_str, binary);
|
||||
} else {
|
||||
VectorFst<StdArc> vfst;
|
||||
CopyToVectorFst<FST>(grammar_fst, &vfst);
|
||||
ConstFst<StdArc> cfst(vfst);
|
||||
// We don't have a wrapper in kaldi-fst-io.h for writing type
|
||||
// ConstFst<StdArc>, so do it manually.
|
||||
bool binary = true, write_binary_header = false; // suppress the ^@B
|
||||
Output ko(fst_out_str, binary, write_binary_header);
|
||||
FstWriteOptions wopts(kaldi::PrintableWxfilename(fst_out_str));
|
||||
cfst.Write(ko.Stream(), wopts);
|
||||
}
|
||||
|
||||
KALDI_LOG << "Created grammar FST and wrote it to "
|
||||
<< fst_out_str;
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
try {
|
||||
using namespace kaldi;
|
||||
using namespace fst;
|
||||
using kaldi::int32;
|
||||
|
||||
const char *usage =
|
||||
"Construct GrammarFst and write it to disk (or convert it to ConstFst\n"
|
||||
"and write that to disk instead). Mostly intended for demonstration\n"
|
||||
"and testing purposes (since it may be more convenient to construct\n"
|
||||
"GrammarFst from code). See kaldi-asr.org/doc/grammar.html\n"
|
||||
"Can also be used to prepares FSTs for this use, by calling\n"
|
||||
"PrepareForGrammarFst(), which does things like adding final-probs and\n"
|
||||
"making small structural tweaks to the FST\n"
|
||||
"\n"
|
||||
"Usage (1): make-grammar-fst [options] <top-level-fst> <symbol1> <fst1> \\\n"
|
||||
" [<symbol2> <fst2> ...]] <fst-out>\n"
|
||||
"\n"
|
||||
"<symbol1>, <symbol2> are the integer ids of the corresponding\n"
|
||||
" user-defined nonterminal symbols (e.g. #nonterm:contact_list) in the\n"
|
||||
" phones.txt file.\n"
|
||||
"e.g.: make-grammar-fst --nonterm-phones-offset=317 HCLG.fst \\\n"
|
||||
" 320 HCLG1.fst HCLG_grammar.fst\n"
|
||||
"\n"
|
||||
"Usage (2): make-grammar-fst <fst-in> <fst-out>\n"
|
||||
" Prepare individual FST for compilation into GrammarFst.\n"
|
||||
" E.g. make-grammar-fst HCLG.fst HCLGmod.fst. The outputs of this\n"
|
||||
" will then become the arguments <top-level-fst>, <fst1>, ... for usage\n"
|
||||
" pattern (1).\n"
|
||||
"\n"
|
||||
"The --nonterm-phones-offset option is required for both usage patterns.\n";
|
||||
|
||||
|
||||
ParseOptions po(usage);
|
||||
|
||||
|
||||
int32 nonterm_phones_offset = -1;
|
||||
bool write_as_grammar = true;
|
||||
bool make_mutable = false;
|
||||
|
||||
po.Register("nonterm-phones-offset", &nonterm_phones_offset,
|
||||
"Integer id of #nonterm_bos in phones.txt");
|
||||
po.Register("write-as-grammar", &write_as_grammar, "If true, "
|
||||
"write as GrammarFst object; if false, convert to "
|
||||
"ConstFst<StdArc> (readable by standard decoders) "
|
||||
"and write that.");
|
||||
po.Register("make-mutable", &make_mutable, "If true, "
|
||||
"Make a GrammarFst using StdVectorFst as the "
|
||||
"underlying FST instance type."
|
||||
"Use const ConstFst<StdArc> otherwise.");
|
||||
|
||||
po.Read(argc, argv);
|
||||
|
||||
if (po.NumArgs() < 2 || po.NumArgs() % 2 != 0) {
|
||||
po.PrintUsage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (nonterm_phones_offset < 0)
|
||||
KALDI_ERR << "The --nonterm-phones-offset option must be supplied "
|
||||
"and positive.";
|
||||
|
||||
if (po.NumArgs() == 2) {
|
||||
// this usage pattern calls PrepareForGrammarFst().
|
||||
VectorFst<StdArc> *fst = ReadFstKaldi(po.GetArg(1));
|
||||
PrepareForGrammarFst(nonterm_phones_offset, fst);
|
||||
// This will write it as VectorFst; to avoid it having to be converted to
|
||||
// ConstFst when read again by make-grammar-fst, you may want to pipe
|
||||
// through fstconvert --fst_type=const.
|
||||
WriteFstKaldi(*fst, po.GetArg(2));
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if (make_mutable) {
|
||||
MakeGrammarFst<StdVectorFst>(po, nonterm_phones_offset, write_as_grammar);
|
||||
} else {
|
||||
MakeGrammarFst<const ConstFst<StdArc> >(po, nonterm_phones_offset, write_as_grammar);
|
||||
}
|
||||
} catch(const std::exception &e) {
|
||||
std::cerr << e.what();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
|
||||
# make "all" the target.
|
||||
all:
|
||||
|
||||
# Disable linking math libs because not needed here. Just for compilation speed.
|
||||
# no, it's now needed for context-fst-test.
|
||||
# MATHLIB = NONE
|
||||
|
||||
EXTRA_CXXFLAGS = -Wno-sign-compare
|
||||
|
||||
include ../kaldi.mk
|
||||
|
||||
TESTFILES = determinize-star-test \
|
||||
pre-determinize-test trivial-factor-weight-test \
|
||||
context-fst-test factor-test table-matcher-test fstext-utils-test \
|
||||
remove-eps-local-test lattice-weight-test \
|
||||
determinize-lattice-test lattice-utils-test deterministic-fst-test \
|
||||
push-special-test epsilon-property-test prune-special-test
|
||||
|
||||
OBJFILES = push-special.o kaldi-fst-io.o context-fst.o grammar-context-fst.o
|
||||
|
||||
|
||||
LIBNAME = kaldi-fstext
|
||||
|
||||
# tree and matrix archives needed for test-context-fst
|
||||
# matrix archive needed for push-special.
|
||||
ADDLIBS = ../tree/kaldi-tree.a ../util/kaldi-util.a ../matrix/kaldi-matrix.a \
|
||||
../base/kaldi-base.a
|
||||
|
||||
include ../makefiles/default_rules.mk
|
||||
@@ -0,0 +1,256 @@
|
||||
// fstext/context-fst-test.cc
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "fstext/context-fst.h"
|
||||
#include "fstext/fst-test-utils.h"
|
||||
#include "tree/context-dep.h"
|
||||
#include "util/kaldi-io.h"
|
||||
#include "base/kaldi-math.h"
|
||||
|
||||
namespace fst
|
||||
{
|
||||
using std::vector;
|
||||
using std::cout;
|
||||
|
||||
// GenAcceptorFromSequence generates a linear acceptor (identical input+output symbols) that has this
|
||||
// sequence of symbols, and
|
||||
template<class Arc>
|
||||
static VectorFst<Arc> *GenAcceptorFromSequence(const vector<typename Arc::Label> &symbols, float cost) {
|
||||
typedef typename Arc::Weight Weight;
|
||||
typedef typename Arc::StateId StateId;
|
||||
|
||||
vector<float> split_cost(symbols.size()+1, 0.0); // for #-arcs + end-state.
|
||||
{ // compute split_cost. it must sum to "cost".
|
||||
std::set<int32> indices;
|
||||
size_t num_indices = 1 + (kaldi::Rand() % split_cost.size());
|
||||
while (indices.size() < num_indices) indices.insert(kaldi::Rand() % split_cost.size());
|
||||
for (std::set<int32>::iterator iter = indices.begin(); iter != indices.end(); ++iter) {
|
||||
split_cost[*iter] = cost / num_indices;
|
||||
}
|
||||
}
|
||||
|
||||
VectorFst<Arc> *fst = new VectorFst<Arc>();
|
||||
StateId cur_state = fst->AddState();
|
||||
fst->SetStart(cur_state);
|
||||
for (size_t i = 0; i < symbols.size(); i++) {
|
||||
StateId next_state = fst->AddState();
|
||||
Arc arc;
|
||||
arc.ilabel = symbols[i];
|
||||
arc.olabel = symbols[i];
|
||||
arc.nextstate = next_state;
|
||||
arc.weight = (Weight) split_cost[i];
|
||||
fst->AddArc(cur_state, arc);
|
||||
cur_state = next_state;
|
||||
|
||||
}
|
||||
fst->SetFinal(cur_state, (Weight)split_cost[symbols.size()]);
|
||||
return fst;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// CheckPhones is used to test the correctness of an FST that is the result of
|
||||
// composition with a ContextFst.
|
||||
template<class Arc>
|
||||
static float CheckPhones(const VectorFst<Arc> &linear_fst,
|
||||
const vector<typename Arc::Label> &phone_ids,
|
||||
const vector<typename Arc::Label> &disambig_ids,
|
||||
const vector<typename Arc::Label> &phone_seq,
|
||||
const vector<vector<typename Arc::Label> > &ilabel_info,
|
||||
int N, int P) {
|
||||
typedef typename Arc::Label Label;
|
||||
typedef typename Arc::StateId StateId;
|
||||
typedef typename Arc::Weight Weight;
|
||||
|
||||
assert(kaldi::IsSorted(phone_ids)); // so we can do binary_search.
|
||||
|
||||
|
||||
vector<int32> input_syms;
|
||||
vector<int32> output_syms;
|
||||
Weight tot_cost;
|
||||
bool ans = GetLinearSymbolSequence(linear_fst, &input_syms,
|
||||
&output_syms, &tot_cost);
|
||||
assert(ans); // should be linear.
|
||||
|
||||
vector<int32> phone_seq_check;
|
||||
for (size_t i = 0; i < output_syms.size(); i++)
|
||||
if (std::binary_search(phone_ids.begin(), phone_ids.end(), output_syms[i]))
|
||||
phone_seq_check.push_back(output_syms[i]);
|
||||
|
||||
assert(phone_seq_check == phone_seq);
|
||||
|
||||
vector<vector<int32> > input_syms_long;
|
||||
for (size_t i = 0; i < input_syms.size(); i++) {
|
||||
Label isym = input_syms[i];
|
||||
if (ilabel_info[isym].size() == 0) continue; // epsilon.
|
||||
if ( (ilabel_info[isym].size() == 1 &&
|
||||
ilabel_info[isym][0] <= 0) ) continue; // disambig.
|
||||
input_syms_long.push_back(ilabel_info[isym]);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < input_syms_long.size(); i++) {
|
||||
vector<int32> phone_context_window(N); // phone at pos i will be at pos P in this window.
|
||||
int pos = ((int)i) - P; // pos of first phone in window [ may be out of range] .
|
||||
for (int j = 0; j < N; j++, pos++) {
|
||||
if (static_cast<size_t>(pos) < phone_seq.size()) phone_context_window[j] = phone_seq[pos];
|
||||
else phone_context_window[j] = 0; // 0 is a special symbol that context-dep-itf expects to see
|
||||
// when no phone is present due to out-of-window. context-fst knows about this too.
|
||||
}
|
||||
assert(input_syms_long[i] == phone_context_window);
|
||||
}
|
||||
return tot_cost.Value();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
template<class Arc>
|
||||
static VectorFst<Arc> *GenRandPhoneSeq(vector<typename Arc::Label> &phone_syms,
|
||||
vector<typename Arc::Label> &disambig_syms,
|
||||
typename Arc::Label subsequential_symbol,
|
||||
int num_subseq_syms,
|
||||
float seq_prob,
|
||||
vector<typename Arc::Label> *phoneseq_out) {
|
||||
KALDI_ASSERT(phoneseq_out != NULL);
|
||||
typedef typename Arc::Label Label;
|
||||
// Generate an FST that is a random phone sequence, ending
|
||||
// with "num_subseq_syms" subsequential symbols. It will
|
||||
// have disambiguation symbols randomly interspersed throughout.
|
||||
// The number of phones is random (possibly zero).
|
||||
size_t len = (kaldi::Rand() % 4) * (kaldi::Rand() % 3); // up to 3*2=6 phones.
|
||||
float disambig_prob = 0.33;
|
||||
phoneseq_out->clear();
|
||||
vector<Label> syms; // the phones
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
while (kaldi::RandUniform() < disambig_prob) syms.push_back(disambig_syms[kaldi::Rand() % disambig_syms.size()]);
|
||||
Label phone_id = phone_syms[kaldi::Rand() % phone_syms.size()];
|
||||
phoneseq_out->push_back(phone_id); // record in output the underlying phone sequence.
|
||||
syms.push_back(phone_id);
|
||||
}
|
||||
for (size_t i = 0; static_cast<int32>(i) < num_subseq_syms; i++) {
|
||||
while (kaldi::RandUniform() < disambig_prob) syms.push_back(disambig_syms[kaldi::Rand() % disambig_syms.size()]);
|
||||
syms.push_back(subsequential_symbol);
|
||||
}
|
||||
while (kaldi::RandUniform() < disambig_prob) syms.push_back(disambig_syms[kaldi::Rand() % disambig_syms.size()]);
|
||||
|
||||
// OK, now have the symbols of the FST as a vector.
|
||||
return GenAcceptorFromSequence<Arc>(syms, seq_prob);
|
||||
}
|
||||
|
||||
// Don't instantiate with log semiring, as RandEquivalent may fail.
|
||||
// TestContestFst also test ReadILabelInfo and WriteILabelInfo.
|
||||
static void TestContextFst(bool verbose, bool use_matcher) {
|
||||
typedef StdArc Arc;
|
||||
typedef Arc::Label Label;
|
||||
typedef Arc::StateId StateId;
|
||||
typedef Arc::Weight Weight;
|
||||
|
||||
// Generate a random set of phones.
|
||||
size_t num_phones = 1 + kaldi::Rand() % 10;
|
||||
std::set<int32> phones_set;
|
||||
while (phones_set.size() < num_phones) phones_set.insert(1 + kaldi::Rand() % (num_phones + 5)); // don't use 0 [== epsilon]
|
||||
vector<int32> phones;
|
||||
kaldi::CopySetToVector(phones_set, &phones);
|
||||
|
||||
int N = 1 + kaldi::Rand() % 4; // Context size, in range 1..4.
|
||||
int P = kaldi::Rand() % N; // 1.. N-1.
|
||||
if (verbose) std::cout << "N = "<< N << ", P = "<<P<<'\n';
|
||||
|
||||
Label subsequential_symbol = 1000;
|
||||
vector<int32> disambig_syms;
|
||||
for (size_t i =0; i < 5; i++) disambig_syms.push_back(500 + i);
|
||||
vector<int32> phone_syms;
|
||||
for (size_t i = 0; i < phones.size();i++) phone_syms.push_back(phones[i]);
|
||||
|
||||
|
||||
InverseContextFst inv_cfst(subsequential_symbol,
|
||||
phones, disambig_syms,
|
||||
N, P);
|
||||
|
||||
|
||||
/* Now create random phone-sequences and compose them with the context FST.
|
||||
*/
|
||||
|
||||
for (size_t p = 0; p < 10; p++) {
|
||||
vector<int32> phone_seq;
|
||||
int num_subseq = N - P - 1; // zero if P == N-1, i.e. P is last element, i.e. left-context only.
|
||||
float tot_cost = 20.0 * kaldi::RandUniform();
|
||||
VectorFst<Arc> *f = GenRandPhoneSeq<Arc>(phone_syms, disambig_syms, subsequential_symbol, num_subseq, tot_cost, &phone_seq);
|
||||
if (verbose) {
|
||||
std::cout << "Sequence FST is:\n";
|
||||
{ // Try to print the fst.
|
||||
FstPrinter<Arc> fstprinter(*f, NULL, NULL, NULL, false, true, "\t");
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}
|
||||
}
|
||||
|
||||
VectorFst<Arc> fst_composed;
|
||||
|
||||
ComposeDeterministicOnDemandInverse(*f, &inv_cfst, &fst_composed);
|
||||
|
||||
|
||||
// Testing WriteILabelInfo and ReadILabelInfo.
|
||||
{
|
||||
bool binary = (kaldi::Rand() % 2 == 0);
|
||||
WriteILabelInfo(kaldi::Output("tmpf", binary).Stream(),
|
||||
binary, inv_cfst.IlabelInfo());
|
||||
|
||||
bool binary_in;
|
||||
vector<vector<int32> > ilabel_info;
|
||||
kaldi::Input ki("tmpf", &binary_in);
|
||||
ReadILabelInfo(ki.Stream(),
|
||||
binary_in, &ilabel_info);
|
||||
assert(ilabel_info == inv_cfst.IlabelInfo());
|
||||
}
|
||||
|
||||
|
||||
if (verbose) {
|
||||
std::cout << "Composed FST is:\n";
|
||||
{ // Try to print the fst.
|
||||
FstPrinter<Arc> fstprinter(fst_composed, NULL, NULL, NULL, false, true, "\t");
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}
|
||||
}
|
||||
|
||||
// now check the composed FST.
|
||||
float tot_cost_check = CheckPhones<Arc>(fst_composed,
|
||||
phone_syms,
|
||||
disambig_syms,
|
||||
phone_seq,
|
||||
inv_cfst.IlabelInfo(),
|
||||
N, P);
|
||||
kaldi::AssertEqual(tot_cost, tot_cost_check);
|
||||
|
||||
delete f;
|
||||
}
|
||||
|
||||
unlink("tmpf");
|
||||
}
|
||||
|
||||
|
||||
} // namespace fst
|
||||
|
||||
int main() {
|
||||
|
||||
for (int i = 0;i < 16;i++) {
|
||||
bool verbose = (i < 4);
|
||||
bool use_matcher = ( (i/4) % 2 == 0);
|
||||
fst::TestContextFst(verbose, use_matcher);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,401 @@
|
||||
// fstext/context-fst.cc
|
||||
|
||||
// Copyright 2018 Johns Hopkins University (author: Daniel Povey)
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "fstext/context-fst.h"
|
||||
#include "base/kaldi-error.h"
|
||||
|
||||
namespace fst {
|
||||
using std::vector;
|
||||
|
||||
|
||||
InverseContextFst::InverseContextFst(
|
||||
Label subsequential_symbol,
|
||||
const vector<int32>& phones,
|
||||
const vector<int32>& disambig_syms,
|
||||
int32 context_width,
|
||||
int32 central_position):
|
||||
context_width_(context_width),
|
||||
central_position_(central_position),
|
||||
phone_syms_(phones),
|
||||
disambig_syms_(disambig_syms),
|
||||
subsequential_symbol_(subsequential_symbol) {
|
||||
|
||||
{ // This block checks the inputs.
|
||||
KALDI_ASSERT(subsequential_symbol != 0
|
||||
&& disambig_syms_.count(subsequential_symbol) == 0
|
||||
&& phone_syms_.count(subsequential_symbol) == 0);
|
||||
if (phone_syms_.empty())
|
||||
KALDI_WARN << "Context FST created but there are no phone symbols: probably "
|
||||
"input FST was empty.";
|
||||
KALDI_ASSERT(phone_syms_.count(0) == 0 && disambig_syms_.count(0) == 0 &&
|
||||
central_position_ >= 0 && central_position_ < context_width_);
|
||||
for (size_t i = 0; i < phones.size(); i++) {
|
||||
KALDI_ASSERT(disambig_syms_.count(phones[i]) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
// empty vector, will be the ilabel_info vector that corresponds to epsilon,
|
||||
// in case our FST needs to output epsilons.
|
||||
vector<int32> empty_vec;
|
||||
Label epsilon_label = FindLabel(empty_vec);
|
||||
|
||||
// epsilon_vec is the phonetic context window we have at the very start of a
|
||||
// sequence, meaning "no real phones have been seen yet".
|
||||
vector<int32> epsilon_vec(context_width_ - 1, 0);
|
||||
StateId start_state = FindState(epsilon_vec);
|
||||
|
||||
KALDI_ASSERT(epsilon_label == 0 && start_state == 0);
|
||||
|
||||
if (context_width_ > central_position_ + 1 && !disambig_syms_.empty()) {
|
||||
// We add a symbol whose sequence representation is [ 0 ], and whose
|
||||
// symbol-id is 1. This is treated as a disambiguation symbol, we call it
|
||||
// #-1 in printed form. It is necessary to ensure that all determinizable
|
||||
// LG's will have determinizable CLG's. The problem it fixes is quite
|
||||
// subtle-- it relates to reordering of disambiguation symbols (they appear
|
||||
// earlier in CLG than in LG, relative to phones), and the fact that if a
|
||||
// disambig symbol appears at the very start of a sequence in CLG, it's not
|
||||
// clear exatly where it appeared on the corresponding sequence at the input
|
||||
// of LG.
|
||||
vector<int32> pseudo_eps_vec;
|
||||
pseudo_eps_vec.push_back(0);
|
||||
pseudo_eps_symbol_= FindLabel(pseudo_eps_vec);
|
||||
KALDI_ASSERT(pseudo_eps_symbol_ == 1);
|
||||
} else {
|
||||
pseudo_eps_symbol_ = 0; // use actual epsilon.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void InverseContextFst::ShiftSequenceLeft(Label label,
|
||||
std::vector<int32> *phone_seq) {
|
||||
if (!phone_seq->empty()) {
|
||||
phone_seq->erase(phone_seq->begin());
|
||||
phone_seq->push_back(label);
|
||||
}
|
||||
}
|
||||
|
||||
void InverseContextFst::GetFullPhoneSequence(
|
||||
const std::vector<int32> &seq, Label label,
|
||||
std::vector<int32> *full_phone_sequence) {
|
||||
int32 context_width = context_width_;
|
||||
full_phone_sequence->reserve(context_width);
|
||||
full_phone_sequence->insert(full_phone_sequence->end(),
|
||||
seq.begin(), seq.end());
|
||||
full_phone_sequence->push_back(label);
|
||||
for (int32 i = central_position_ + 1; i < context_width; i++) {
|
||||
if ((*full_phone_sequence)[i] == subsequential_symbol_) {
|
||||
(*full_phone_sequence)[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
InverseContextFst::Weight InverseContextFst::Final(StateId s) {
|
||||
KALDI_ASSERT(static_cast<size_t>(s) < state_seqs_.size());
|
||||
|
||||
const vector<int32> &phone_context = state_seqs_[s];
|
||||
|
||||
KALDI_ASSERT(phone_context.size() == context_width_ - 1);
|
||||
|
||||
bool has_final_prob;
|
||||
|
||||
if (central_position_ < context_width_ - 1) {
|
||||
has_final_prob = (phone_context[central_position_] == subsequential_symbol_);
|
||||
// if phone_context[central_position_] != subsequential_symbol_ then we have
|
||||
// pending phones-in-context that we still need to output, so we need to
|
||||
// consume more subsequential symbols before we can terminate.
|
||||
} else {
|
||||
has_final_prob = true;
|
||||
}
|
||||
return has_final_prob ? Weight::One() : Weight::Zero();
|
||||
}
|
||||
|
||||
bool InverseContextFst::GetArc(StateId s, Label ilabel, Arc *arc) {
|
||||
KALDI_ASSERT(ilabel != 0 && static_cast<size_t>(s) < state_seqs_.size() &&
|
||||
state_seqs_[s].size() == context_width_ - 1);
|
||||
|
||||
if (IsDisambigSymbol(ilabel)) {
|
||||
// A disambiguation-symbol self-loop arc.
|
||||
CreateDisambigArc(s, ilabel, arc);
|
||||
return true;
|
||||
} else if (IsPhoneSymbol(ilabel)) {
|
||||
const vector<int32> &seq = state_seqs_[s];
|
||||
if (!seq.empty() && seq.back() == subsequential_symbol_) {
|
||||
return false; // A real phone is not allowed to follow the subsequential
|
||||
// symbol.
|
||||
}
|
||||
|
||||
// next_seq will be 'seq' shifted left by 1, with 'ilabel' appended.
|
||||
vector<int32> next_seq(seq);
|
||||
ShiftSequenceLeft(ilabel, &next_seq);
|
||||
|
||||
// full-seq will be the full context window of size context_width_.
|
||||
vector<int32> full_seq;
|
||||
GetFullPhoneSequence(seq, ilabel, &full_seq);
|
||||
|
||||
StateId next_s = FindState(next_seq);
|
||||
|
||||
CreatePhoneOrEpsArc(s, next_s, ilabel, full_seq, arc);
|
||||
return true;
|
||||
} else if (ilabel == subsequential_symbol_) {
|
||||
const vector<int32> &seq = state_seqs_[s];
|
||||
|
||||
if (central_position_ + 1 == context_width_ ||
|
||||
seq[central_position_] == subsequential_symbol_) {
|
||||
// We already had "enough" subsequential symbols in a row and don't want to
|
||||
// accept any more, or we'd be making the subsequential symbol the central phone.
|
||||
return false;
|
||||
}
|
||||
|
||||
// full-seq will be the full context window of size context_width_.
|
||||
vector<int32> full_seq;
|
||||
GetFullPhoneSequence(seq, ilabel, &full_seq);
|
||||
|
||||
vector<int32> next_seq(seq);
|
||||
ShiftSequenceLeft(ilabel, &next_seq);
|
||||
StateId next_s = FindState(next_seq);
|
||||
|
||||
CreatePhoneOrEpsArc(s, next_s, ilabel, full_seq, arc);
|
||||
return true;
|
||||
} else {
|
||||
KALDI_ERR << "ContextFst: CreateArc, invalid ilabel supplied [confusion "
|
||||
<< "about phone list or disambig symbols?]: " << ilabel;
|
||||
}
|
||||
return false; // won't get here. suppress compiler error.
|
||||
}
|
||||
|
||||
|
||||
void InverseContextFst::CreateDisambigArc(StateId s, Label ilabel, Arc *arc) {
|
||||
// Creates a self-loop arc corresponding to the disambiguation symbol.
|
||||
vector<int32> label_info; // This will be a vector containing just [ -olabel ].
|
||||
label_info.push_back(-ilabel); // olabel is a disambiguation symbol. Use its negative
|
||||
// so we can more easily distinguish them from phones.
|
||||
Label olabel = FindLabel(label_info);
|
||||
arc->ilabel = ilabel;
|
||||
arc->olabel = olabel;
|
||||
arc->weight = Weight::One();
|
||||
arc->nextstate = s; // self-loop.
|
||||
}
|
||||
|
||||
void InverseContextFst::CreatePhoneOrEpsArc(StateId src, StateId dest,
|
||||
Label ilabel,
|
||||
const vector<int32> &phone_seq,
|
||||
Arc *arc) {
|
||||
KALDI_PARANOID_ASSERT(phone_seq[central_position_] != subsequential_symbol_);
|
||||
|
||||
arc->ilabel = ilabel;
|
||||
arc->weight = Weight::One();
|
||||
arc->nextstate = dest;
|
||||
if (phone_seq[central_position_] == 0) {
|
||||
// This can happen at the beginning of the graph. In this case we don't
|
||||
// output a real phone, we createdt an epsilon arc (but sometimes we need to
|
||||
// use a special disambiguation symbol instead of epsilon).
|
||||
arc->olabel = pseudo_eps_symbol_;
|
||||
} else {
|
||||
// We have a phone in the central position.
|
||||
arc->olabel = FindLabel(phone_seq);
|
||||
}
|
||||
}
|
||||
|
||||
StdArc::StateId InverseContextFst::FindState(const vector<int32> &seq) {
|
||||
// Finds state-id corresponding to this vector of phones. Inserts it if
|
||||
// necessary.
|
||||
KALDI_ASSERT(static_cast<int32>(seq.size()) == context_width_ - 1);
|
||||
VectorToStateMap::const_iterator iter = state_map_.find(seq);
|
||||
if (iter == state_map_.end()) { // Not already in map.
|
||||
StateId this_state_id = (StateId)state_seqs_.size();
|
||||
state_seqs_.push_back(seq);
|
||||
state_map_[seq] = this_state_id;
|
||||
return this_state_id;
|
||||
} else {
|
||||
return iter->second;
|
||||
}
|
||||
}
|
||||
|
||||
StdArc::Label InverseContextFst::FindLabel(const vector<int32> &label_vec) {
|
||||
// Finds the ilabel corresponding to this vector (creates a new ilabel if
|
||||
// necessary).
|
||||
VectorToLabelMap::const_iterator iter = ilabel_map_.find(label_vec);
|
||||
if (iter == ilabel_map_.end()) { // Not already in map.
|
||||
Label this_label = ilabel_info_.size();
|
||||
ilabel_info_.push_back(label_vec);
|
||||
ilabel_map_[label_vec] = this_label;
|
||||
return this_label;
|
||||
} else {
|
||||
return iter->second;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ComposeContext(const vector<int32> &disambig_syms_in,
|
||||
int32 context_width, int32 central_position,
|
||||
VectorFst<StdArc> *ifst,
|
||||
VectorFst<StdArc> *ofst,
|
||||
vector<vector<int32> > *ilabels_out,
|
||||
bool project_ifst) {
|
||||
KALDI_ASSERT(ifst != NULL && ofst != NULL);
|
||||
KALDI_ASSERT(context_width > 0);
|
||||
KALDI_ASSERT(central_position >= 0);
|
||||
KALDI_ASSERT(central_position < context_width);
|
||||
|
||||
vector<int32> disambig_syms(disambig_syms_in);
|
||||
std::sort(disambig_syms.begin(), disambig_syms.end());
|
||||
|
||||
vector<int32> all_syms;
|
||||
GetInputSymbols(*ifst, false/*no eps*/, &all_syms);
|
||||
std::sort(all_syms.begin(), all_syms.end());
|
||||
vector<int32> phones;
|
||||
for (size_t i = 0; i < all_syms.size(); i++)
|
||||
if (!std::binary_search(disambig_syms.begin(),
|
||||
disambig_syms.end(), all_syms[i]))
|
||||
phones.push_back(all_syms[i]);
|
||||
|
||||
// Get subsequential symbol that does not clash with
|
||||
// any disambiguation symbol or symbol in the FST.
|
||||
int32 subseq_sym = 1;
|
||||
if (!all_syms.empty())
|
||||
subseq_sym = std::max(subseq_sym, all_syms.back() + 1);
|
||||
if (!disambig_syms.empty())
|
||||
subseq_sym = std::max(subseq_sym, disambig_syms.back() + 1);
|
||||
|
||||
// if central_position == context_width-1, it's left-context, and no
|
||||
// subsequential symbol is needed.
|
||||
if (central_position != context_width-1) {
|
||||
AddSubsequentialLoop(subseq_sym, ifst);
|
||||
if (project_ifst) {
|
||||
fst::Project(ifst, fst::PROJECT_INPUT);
|
||||
}
|
||||
}
|
||||
|
||||
InverseContextFst inv_c(subseq_sym, phones, disambig_syms,
|
||||
context_width, central_position);
|
||||
|
||||
// The following statement is equivalent to the following
|
||||
// (if FSTs had the '*' operator for composition):
|
||||
// (*ofst) = inv(inv_c) * (*ifst)
|
||||
ComposeDeterministicOnDemandInverse(*ifst, &inv_c, ofst);
|
||||
|
||||
inv_c.SwapIlabelInfo(ilabels_out);
|
||||
}
|
||||
|
||||
void AddSubsequentialLoop(StdArc::Label subseq_symbol,
|
||||
MutableFst<StdArc> *fst) {
|
||||
typedef StdArc Arc;
|
||||
typedef typename Arc::StateId StateId;
|
||||
typedef typename Arc::Weight Weight;
|
||||
|
||||
vector<StateId> final_states;
|
||||
for (StateIterator<MutableFst<Arc> > siter(*fst); !siter.Done(); siter.Next()) {
|
||||
StateId s = siter.Value();
|
||||
if (fst->Final(s) != Weight::Zero()) final_states.push_back(s);
|
||||
}
|
||||
|
||||
StateId superfinal = fst->AddState();
|
||||
Arc arc(subseq_symbol, 0, Weight::One(), superfinal);
|
||||
fst->AddArc(superfinal, arc); // loop at superfinal.
|
||||
fst->SetFinal(superfinal, Weight::One());
|
||||
|
||||
for (size_t i = 0; i < final_states.size(); i++) {
|
||||
StateId s = final_states[i];
|
||||
fst->AddArc(s, Arc(subseq_symbol, 0, fst->Final(s), superfinal));
|
||||
// No, don't remove the final-weights of the original states..
|
||||
// this is so we can add the subsequential loop in cases where
|
||||
// there is no context, and it won't hurt.
|
||||
// fst->SetFinal(s, Weight::Zero());
|
||||
arc.nextstate = final_states[i];
|
||||
}
|
||||
}
|
||||
|
||||
void WriteILabelInfo(std::ostream &os, bool binary,
|
||||
const vector<vector<int32> > &info) {
|
||||
int32 size = info.size();
|
||||
kaldi::WriteBasicType(os, binary, size);
|
||||
for (int32 i = 0; i < size; i++) {
|
||||
kaldi::WriteIntegerVector(os, binary, info[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ReadILabelInfo(std::istream &is, bool binary,
|
||||
vector<vector<int32> > *info) {
|
||||
int32 size = info->size();
|
||||
kaldi::ReadBasicType(is, binary, &size);
|
||||
info->resize(size);
|
||||
for (int32 i = 0; i < size; i++) {
|
||||
kaldi::ReadIntegerVector(is, binary, &((*info)[i]));
|
||||
}
|
||||
}
|
||||
|
||||
SymbolTable *CreateILabelInfoSymbolTable(const vector<vector<int32> > &info,
|
||||
const SymbolTable &phones_symtab,
|
||||
std::string separator,
|
||||
std::string initial_disambig) { // e.g. separator = "/", initial-disambig="#-1"
|
||||
KALDI_ASSERT(!info.empty() && info[0].empty());
|
||||
SymbolTable *ans = new SymbolTable("ilabel-info-symtab");
|
||||
int64 s = ans->AddSymbol(phones_symtab.Find(static_cast<int64>(0)));
|
||||
assert(s == 0);
|
||||
for (size_t i = 1; i < info.size(); i++) {
|
||||
if (info[i].size() == 0) {
|
||||
KALDI_ERR << "Invalid ilabel-info";
|
||||
}
|
||||
if (info[i].size() == 1 &&
|
||||
info[i][0] <= 0) {
|
||||
if (info[i][0] == 0) { // special symbol at start that we want to call #-1.
|
||||
s = ans->AddSymbol(initial_disambig);
|
||||
if (s != i) {
|
||||
KALDI_ERR << "Disambig symbol " << initial_disambig
|
||||
<< " already in vocab";
|
||||
}
|
||||
} else {
|
||||
std::string disambig_sym = phones_symtab.Find(-info[i][0]);
|
||||
if (disambig_sym == "") {
|
||||
KALDI_ERR << "Disambig symbol " << -info[i][0]
|
||||
<< " not in phone symbol-table";
|
||||
}
|
||||
s = ans->AddSymbol(disambig_sym);
|
||||
if (s != i) {
|
||||
KALDI_ERR << "Disambig symbol " << disambig_sym
|
||||
<< " already in vocab";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// is a phone-context-window.
|
||||
std::string newsym;
|
||||
for (size_t j = 0; j < info[i].size(); j++) {
|
||||
std::string phonesym = phones_symtab.Find(info[i][j]);
|
||||
if (phonesym == "") {
|
||||
KALDI_ERR << "Symbol " << info[i][j]
|
||||
<< " not in phone symbol-table";
|
||||
}
|
||||
if (j != 0) newsym += separator;
|
||||
newsym += phonesym;
|
||||
}
|
||||
int64 s = ans->AddSymbol(newsym);
|
||||
if (s != static_cast<int64>(i)) {
|
||||
KALDI_ERR << "Some problem with duplicate symbols";
|
||||
}
|
||||
}
|
||||
}
|
||||
return ans;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
} // end namespace fst
|
||||
@@ -0,0 +1,340 @@
|
||||
// fstext/context-fst.h
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation
|
||||
// 2018 Johns Hopkins University (author: Daniel Povey)
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// This file includes material from the OpenFST Library v1.2.7 available at
|
||||
// http://www.openfst.org and released under the Apache License Version 2.0.
|
||||
//
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// Copyright 2005-2010 Google, Inc.
|
||||
// Author: riley@google.com (Michael Riley)
|
||||
|
||||
|
||||
|
||||
#ifndef KALDI_FSTEXT_CONTEXT_FST_H_
|
||||
#define KALDI_FSTEXT_CONTEXT_FST_H_
|
||||
|
||||
/* This header defines a context FST "C" (the "C" in "HCLG") which transduces
|
||||
from symbols representing phone context windows (e.g. "a, b, c") to
|
||||
individual phones, e.g. "a". Search for "hbka.pdf" ("Speech Recognition
|
||||
with Weighted Finite State Transducers") by M. Mohri, for more context.
|
||||
*/
|
||||
|
||||
#include <unordered_map>
|
||||
using std::unordered_map;
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <fst/fstlib.h>
|
||||
#include <fst/fst-decl.h>
|
||||
|
||||
#include "util/const-integer-set.h"
|
||||
#include "fstext/deterministic-fst.h"
|
||||
|
||||
namespace fst {
|
||||
|
||||
|
||||
|
||||
|
||||
/// Utility function for writing ilabel-info vectors to disk.
|
||||
void WriteILabelInfo(std::ostream &os, bool binary,
|
||||
const std::vector<std::vector<int32> > &ilabel_info);
|
||||
|
||||
/// Utility function for reading ilabel-info vectors from disk.
|
||||
void ReadILabelInfo(std::istream &is, bool binary,
|
||||
std::vector<std::vector<int32> > *ilabel_info);
|
||||
|
||||
|
||||
/// The following function is mainly of use for printing and debugging.
|
||||
SymbolTable *CreateILabelInfoSymbolTable(const std::vector<std::vector<int32> > &ilabel_info,
|
||||
const SymbolTable &phones_symtab,
|
||||
std::string separator,
|
||||
std::string disambig_prefix); // e.g. separator = "/", disambig_prefix = "#"
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Used in the command-line tool fstcomposecontext. It creates a context FST and
|
||||
composes it on the left with "ifst" to make "ofst". It outputs the label
|
||||
information to ilabels_out. "ifst" is mutable because we need to add the
|
||||
subsequential loop.
|
||||
|
||||
@param [in] disambig_syms List of disambiguation symbols, e.g. the integer
|
||||
ids of #0, #1, #2 ... in the phones.txt.
|
||||
@param [in] context_width Size of context window, e.g. 3 for triphone.
|
||||
@param [in] central_position Central position in phonetic context window
|
||||
(zero-based index), e.g. 1 for triphone.
|
||||
@param [in,out] ifst The FST we are composing with C (e.g. LG.fst), mustable because
|
||||
we need to add the subsequential loop to it.
|
||||
@param [out] ofst Composed output FST (would be CLG.fst).
|
||||
@param [out] ilabels_out Vector, indexed by ilabel of CLG.fst, providing information
|
||||
about the meaning of that ilabel; see
|
||||
"http://kaldi-asr.org/doc/tree_externals.html#tree_ilabel".
|
||||
@param [in] project_ifst This is intended only to be set to true
|
||||
in the program 'fstmakecontextfst'... if true, it will
|
||||
project on the input after adding the subsequential loop
|
||||
to 'ifst', which allows us to reconstruct the context
|
||||
fst C.fst.
|
||||
*/
|
||||
void ComposeContext(const std::vector<int32> &disambig_syms,
|
||||
int32 context_width, int32 central_position,
|
||||
VectorFst<StdArc> *ifst,
|
||||
VectorFst<StdArc> *ofst,
|
||||
std::vector<std::vector<int32> > *ilabels_out,
|
||||
bool project_ifst = false);
|
||||
|
||||
|
||||
/**
|
||||
Modifies an FST so that it transuces the same paths, but the input side of the
|
||||
paths can all have the subsequential symbol '$' appended to them any number of
|
||||
times (we could easily specify the number of times, but accepting any number of
|
||||
repetitions is just more convenient). The actual way we do this is for each
|
||||
final state, we add a transition with weight equal to the final-weight of that
|
||||
state, with input-symbol '$' and output-symbols \<eps\>, and ending in a new
|
||||
super-final state that has unit final-probability and a unit-weight self-loop
|
||||
with '$' on its input and \<eps\> on its output. The reason we don't just
|
||||
add a loop to each final-state has to do with preserving stochasticity
|
||||
(see \ref fst_algo_stochastic). We keep the final-probability in all the
|
||||
original final-states rather than setting them to zero, so the resulting FST
|
||||
can accept zero '$' symbols at the end (in case we had no right context).
|
||||
*/
|
||||
void AddSubsequentialLoop(StdArc::Label subseq_symbol,
|
||||
MutableFst<StdArc> *fst);
|
||||
|
||||
|
||||
/*
|
||||
InverseContextFst represents the inverse of the context FST "C" (the "C" in
|
||||
"HCLG") which transduces from symbols representing phone context windows
|
||||
(e.g. "a, b, c") to individual phones, e.g. "a". So InverseContextFst
|
||||
transduces from phones to symbols representing phone context windows. The
|
||||
point is that the inverse is deterministic, so the DeterministicOnDemandFst
|
||||
interface is applicable, which turns out to be a convenient way to implement
|
||||
this.
|
||||
|
||||
This doesn't implement the full Fst interface, it implements the
|
||||
DeterministicOnDemandFst interface which is much simpler and which is
|
||||
sufficient for what we need to do with this.
|
||||
|
||||
Search for "hbka.pdf" ("Speech Recognition with Weighted Finite State
|
||||
Transducers") by M. Mohri, for more context.
|
||||
*/
|
||||
|
||||
class InverseContextFst: public DeterministicOnDemandFst<StdArc> {
|
||||
public:
|
||||
typedef StdArc Arc;
|
||||
typedef typename StdArc::StateId StateId;
|
||||
typedef typename StdArc::Weight Weight;
|
||||
typedef typename StdArc::Label Label;
|
||||
|
||||
/**
|
||||
Constructor.
|
||||
@param [in] subsequential_symbol The integer id of the 'subsequential symbol'
|
||||
(usually represented as '$') that terminates sequences on the
|
||||
output of C.fst (input of InverseContextFst). Search for
|
||||
"quential" in https://cs.nyu.edu/~mohri/pub/hbka.pdf.
|
||||
This may just be the first unused integer id. Must be nonzer.
|
||||
@param [in] phones List of integer ids of phones, as you would see in phones.txt
|
||||
@param [in] disambig_syms List of integer ids of disambiguation symbols,
|
||||
e.g. the ids of #0, #1, #2 in phones.txt
|
||||
@param [in] context_width Size of context window, e.g. 3 for triphone.
|
||||
@param [in] central_position Central position in context window (zero-based),
|
||||
e.g. 1 for triphone.
|
||||
See \ref graph_context for more details.
|
||||
*/
|
||||
InverseContextFst(Label subsequential_symbol,
|
||||
const std::vector<int32>& phones,
|
||||
const std::vector<int32>& disambig_syms,
|
||||
int32 context_width,
|
||||
int32 central_position);
|
||||
|
||||
|
||||
virtual StateId Start() { return 0; }
|
||||
|
||||
virtual Weight Final(StateId s);
|
||||
|
||||
/// Note: ilabel must not be epsilon.
|
||||
virtual bool GetArc(StateId s, Label ilabel, Arc *arc);
|
||||
|
||||
~InverseContextFst() { }
|
||||
|
||||
// Returns a reference to a vector<vector<int32> > with information about all
|
||||
// the input symbols of C (i.e. all the output symbols of this
|
||||
// InverseContextFst). See
|
||||
// "http://kaldi-asr.org/doc/tree_externals.html#tree_ilabel".
|
||||
const std::vector<std::vector<int32> > &IlabelInfo() const {
|
||||
return ilabel_info_;
|
||||
}
|
||||
|
||||
// A way to destructively obtain the ilabel-info. Only do this if you
|
||||
// are just about to destroy this object.
|
||||
void SwapIlabelInfo(std::vector<std::vector<int32> > *vec) { ilabel_info_.swap(*vec); }
|
||||
|
||||
private:
|
||||
|
||||
/// Returns the state-id corresponding to this vector of phones; creates the
|
||||
/// state it if necessary. Requires seq.size() == context_width_ - 1.
|
||||
StateId FindState(const std::vector<int32> &seq);
|
||||
|
||||
/// Finds the label index corresponding to this context-window of phones
|
||||
/// (likely of width context_width_). Inserts it into the
|
||||
/// ilabel_info_/ilabel_map_ tables if necessary.
|
||||
Label FindLabel(const std::vector<int32> &label_info);
|
||||
|
||||
inline bool IsDisambigSymbol(Label lab) { return (disambig_syms_.count(lab) != 0); }
|
||||
|
||||
inline bool IsPhoneSymbol(Label lab) { return (phone_syms_.count(lab) != 0); }
|
||||
|
||||
/// Create disambiguation-symbol self-loop arc; where 'ilabel' must correspond to
|
||||
/// a disambiguation symbol. Called from CreateArc().
|
||||
inline void CreateDisambigArc(StateId s, Label ilabel, Arc *arc);
|
||||
|
||||
/// Creates an arc, this function is to be called only when 'ilabel'
|
||||
/// corresponds to a phone. Called from CreateArc(). The olabel may end be
|
||||
/// epsilon, instead of a phone-in-context, if the system has right context
|
||||
/// and we are very near the beginning of the phone sequence.
|
||||
inline void CreatePhoneOrEpsArc(StateId src, StateId dst, Label ilabel,
|
||||
const std::vector<int32> &phone_seq, Arc *arc);
|
||||
|
||||
|
||||
/// If phone_seq is nonempty then this function it left by one and appends
|
||||
/// 'label' to it, otherwise it does nothing. We expect (but do not check)
|
||||
/// that phone_seq->size() == context_width_ - 1.
|
||||
inline void ShiftSequenceLeft(Label label, std::vector<int32> *phone_seq);
|
||||
|
||||
/// This utility function does something equivalent to the following 3 steps:
|
||||
/// *full_phone_sequence = seq;
|
||||
/// full_phone_sequence->append(label)
|
||||
/// Replace any values equal to 'subsequential_symbol_' in
|
||||
/// full_phone_sequence with zero (this is to avoid having to keep track of
|
||||
/// the value of 'subsequential_symbol_' outside of this program).
|
||||
/// This function assumes that seq.size() == context_width_ - 1, and also that
|
||||
/// 'subsequential_symbol_' does not appear in positions 0 through
|
||||
/// central_position_ of 'seq'.
|
||||
inline void GetFullPhoneSequence(const std::vector<int32> &seq, Label label,
|
||||
std::vector<int32> *full_phone_sequence);
|
||||
|
||||
// Map type to map from vectors of int32 (representing phonetic contexts,
|
||||
// which will be of dimension context_width - 1) to StateId (corresponding to
|
||||
// the state index in this FST).
|
||||
typedef unordered_map<std::vector<int32>, StateId,
|
||||
kaldi::VectorHasher<int32> > VectorToStateMap;
|
||||
|
||||
// Map type to map from vectors of int32 (representing ilabel-info,
|
||||
// see http://kaldi-asr.org/doc/tree_externals.html#tree_ilabel) to
|
||||
// Label (the output label in this FST).
|
||||
typedef unordered_map<std::vector<int32>, Label,
|
||||
kaldi::VectorHasher<int32> > VectorToLabelMap;
|
||||
|
||||
|
||||
// Sometimes called N, context_width_ this is the width of the
|
||||
// phonetic context, e.g. 3 for triphone, 2 for biphone, one for monophone.
|
||||
// It is a user-specified value.
|
||||
int32 context_width_;
|
||||
|
||||
// Sometimes called P, central_position_ is is the (zero-based) "central
|
||||
// position" in the context window, meaning the phone that is "in" a certain
|
||||
// context. The most widely used values of (context-width, central-position)
|
||||
// are: (3,1) for triphone, (1,0) for monophone, and (2, 1) for left biphone.
|
||||
// This is also specified by the user. As an example, in the left-biphone
|
||||
// [ 5, 6 ], we view it as "the phone numbered 6 with the phone numbered 5 as
|
||||
// its left-context".
|
||||
int32 central_position_;
|
||||
|
||||
// The following three variables were also passed in by the caller:
|
||||
|
||||
// 'phone_syms_' are a set of phone-ids, typically 1, 2, .. num_phones.
|
||||
kaldi::ConstIntegerSet<Label> phone_syms_;
|
||||
|
||||
// disambig_syms_ is the set of integer ids of the disambiguation symbols,
|
||||
// usually represented in text form as #0, #1, #2, etc. These are inserted
|
||||
// into the grammar (for #0) and the lexicon (for #1, #2, ...) in order to
|
||||
// make the composed FSTs determinizable. They are treated "specially" by the
|
||||
// context FST in that they are not part of the context, they are just "passed
|
||||
// through" via self-loops. See the Mohri chapter mrentioned above for more
|
||||
// information.
|
||||
kaldi::ConstIntegerSet<Label> disambig_syms_;
|
||||
|
||||
// subsequential_symbol_, represented as "$" in the Mohri chapter mentioned
|
||||
// above, is something which terminates phonetic sequences to force out the
|
||||
// last phones-in-context. In our implementation it's added to det(LG) as a
|
||||
// self-loop on final states before composing with C.
|
||||
// (c.f. AddSubsequentialLoop()).
|
||||
Label subsequential_symbol_;
|
||||
|
||||
|
||||
// pseudo_eps_symbol_, which in printed form we refer to as "#-1", is a symbol that
|
||||
// appears on the ilabels of the context transducer C, i.e. the olabels of this
|
||||
// FST which is C's inverse. It is a symbol we introduce to solve a special problem
|
||||
// in systems with right-context (context_width_ > central_position_ + 1) that
|
||||
// use disambiguation symbols. It exists to prevent CLG from being nondeterminizable.
|
||||
//
|
||||
// The issue is that, in this case, the disambiguation symbols are shifted
|
||||
// left w.r.t. the phones, and there becomes an ambiguity, if a disambiguation
|
||||
// symbol appears at the start of a sequence on the input of CLG, about
|
||||
// whether it was at the very start of the input of LG, or just after, say,
|
||||
// the first real phone. This can lead to determinization failure under
|
||||
// certain circumstances. What we do if we need pseudo_eps_symbol_ to be not
|
||||
// epsilon, we create a special symbol with symbol-id 1 and sequence
|
||||
// representation (ilabels entry) [ 0 ] .
|
||||
int32 pseudo_eps_symbol_;
|
||||
|
||||
// maps from vector<int32>, representing phonetic contexts of length
|
||||
// context_width_ - 1, to StateId. (The states of the "C" fst correspond to
|
||||
// phonetic contexts, but we only create them as and when they are needed).
|
||||
VectorToStateMap state_map_;
|
||||
|
||||
// The inverse of 'state_map_': gives us the phonetic context corresponding to
|
||||
// each state-id.
|
||||
std::vector<std::vector<int32> > state_seqs_;
|
||||
|
||||
// maps from vector<int32>, representing phonetic contexts of length
|
||||
// context_width_ - 1, to Label. These are actually the output labels of this
|
||||
// InverseContextFst (because of the "Inverse" part), but for historical
|
||||
// reasons and because we've used the term ilabels" in the documentation, we
|
||||
// still call these "ilabels").
|
||||
VectorToLabelMap ilabel_map_;
|
||||
|
||||
// ilabel_info_ is the reverse map of ilabel_map_.
|
||||
// Indexed by olabel (although we call this ilabel_info_ for historical
|
||||
// reasons and because is for the ilabels of C), ilabel_info_[i] gives
|
||||
// information about the meaning of each symbol on the input of C
|
||||
// aka the output of inv(C).
|
||||
// See "http://kaldi-asr.org/doc/tree_externals.html#tree_ilabel".
|
||||
std::vector<std::vector<int32> > ilabel_info_;
|
||||
|
||||
};
|
||||
|
||||
} // namespace fst
|
||||
|
||||
|
||||
#endif // KALDI_FSTEXT_CONTEXT_FST_H_
|
||||
@@ -0,0 +1,512 @@
|
||||
// fstext/deterministic-fst-inl.h
|
||||
|
||||
// Copyright 2011-2012 Gilles Boulianne
|
||||
// 2014 Telepoint Global Hosting Service, LLC. (Author: David Snyder)
|
||||
// 2012-2015 Johns Hopkins University (author: Daniel Povey)
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef KALDI_FSTEXT_DETERMINISTIC_FST_INL_H_
|
||||
#define KALDI_FSTEXT_DETERMINISTIC_FST_INL_H_
|
||||
#include "base/kaldi-common.h"
|
||||
#include "fstext/fstext-utils.h"
|
||||
|
||||
|
||||
namespace fst {
|
||||
// Do not include this file directly. It is included by deterministic-fst.h.
|
||||
|
||||
template<class Arc>
|
||||
typename Arc::StateId
|
||||
BackoffDeterministicOnDemandFst<Arc>::GetBackoffState(StateId s,
|
||||
Weight *w) {
|
||||
ArcIterator<Fst<Arc> > aiter(fst_, s);
|
||||
if (aiter.Done()) // no arcs.
|
||||
return kNoStateId;
|
||||
const Arc &arc = aiter.Value();
|
||||
if (arc.ilabel == 0) {
|
||||
*w = arc.weight;
|
||||
return arc.nextstate;
|
||||
} else {
|
||||
return kNoStateId;
|
||||
}
|
||||
}
|
||||
|
||||
template<class Arc>
|
||||
typename Arc::Weight BackoffDeterministicOnDemandFst<Arc>::Final(StateId state) {
|
||||
Weight w = fst_.Final(state);
|
||||
if (w != Weight::Zero()) return w;
|
||||
Weight backoff_w;
|
||||
StateId backoff_state = GetBackoffState(state, &backoff_w);
|
||||
if (backoff_state == kNoStateId) return Weight::Zero();
|
||||
else return Times(backoff_w, this->Final(backoff_state));
|
||||
}
|
||||
|
||||
template<class Arc>
|
||||
BackoffDeterministicOnDemandFst<Arc>::BackoffDeterministicOnDemandFst(
|
||||
const Fst<Arc> &fst): fst_(fst) {
|
||||
#ifdef KALDI_PARANOID
|
||||
KALDI_ASSERT(fst_.Properties(kILabelSorted|kIDeterministic, true) ==
|
||||
(kILabelSorted|kIDeterministic) &&
|
||||
"Input FST is not i-label sorted and deterministic.");
|
||||
#endif
|
||||
}
|
||||
|
||||
template<class Arc>
|
||||
bool BackoffDeterministicOnDemandFst<Arc>::GetArc(
|
||||
StateId s, Label ilabel, Arc *oarc) {
|
||||
KALDI_ASSERT(ilabel != 0); // We don't allow GetArc for epsilon.
|
||||
|
||||
SortedMatcher<Fst<Arc> > sm(fst_, MATCH_INPUT, 1);
|
||||
sm.SetState(s);
|
||||
if (sm.Find(ilabel)) {
|
||||
const Arc &arc = sm.Value();
|
||||
*oarc = arc;
|
||||
return true;
|
||||
} else {
|
||||
Weight backoff_w;
|
||||
StateId backoff_state = GetBackoffState(s, &backoff_w);
|
||||
if (backoff_state == kNoStateId) return false;
|
||||
if (!this->GetArc(backoff_state, ilabel, oarc)) return false;
|
||||
oarc->weight = Times(oarc->weight, backoff_w);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
template<class Arc>
|
||||
UnweightedNgramFst<Arc>::UnweightedNgramFst(int n): n_(n) {
|
||||
// Starting state is an empty vector
|
||||
std::vector<Label> start_state;
|
||||
state_vec_.push_back(start_state);
|
||||
start_state_ = 0;
|
||||
state_map_[start_state] = 0;
|
||||
}
|
||||
|
||||
template<class Arc>
|
||||
bool UnweightedNgramFst<Arc>::GetArc(
|
||||
StateId s, Label ilabel, Arc *oarc) {
|
||||
|
||||
// The state ids increment with each state we encounter.
|
||||
// if the assert fails, then we are trying to access
|
||||
// unseen states that are not immediately traversable.
|
||||
KALDI_ASSERT(static_cast<size_t>(s) < state_vec_.size());
|
||||
std::vector<Label> seq = state_vec_[s];
|
||||
// Update state info.
|
||||
seq.push_back(ilabel);
|
||||
if (seq.size() > n_-1) {
|
||||
// Remove oldest word in the history.
|
||||
seq.erase(seq.begin());
|
||||
}
|
||||
std::pair<const std::vector<Label>, StateId> new_state(
|
||||
seq,
|
||||
static_cast<Label>(state_vec_.size()));
|
||||
// Now get state id for destination state.
|
||||
typedef typename MapType::iterator IterType;
|
||||
std::pair<IterType, bool> result = state_map_.insert(new_state);
|
||||
if (result.second == true) {
|
||||
state_vec_.push_back(seq);
|
||||
}
|
||||
oarc->weight = Weight::One(); // Because the FST is unweightd.
|
||||
oarc->ilabel = ilabel;
|
||||
oarc->olabel = ilabel;
|
||||
oarc->nextstate = result.first->second; // The next state id.
|
||||
// All arcs can be matched.
|
||||
return true;
|
||||
}
|
||||
|
||||
template<class Arc>
|
||||
typename Arc::Weight UnweightedNgramFst<Arc>::Final(StateId state) {
|
||||
KALDI_ASSERT(state < static_cast<StateId>(state_vec_.size()));
|
||||
return Weight::One();
|
||||
}
|
||||
|
||||
template<class Arc>
|
||||
ComposeDeterministicOnDemandFst<Arc>::ComposeDeterministicOnDemandFst(
|
||||
DeterministicOnDemandFst<Arc> *fst1,
|
||||
DeterministicOnDemandFst<Arc> *fst2): fst1_(fst1), fst2_(fst2) {
|
||||
KALDI_ASSERT(fst1 != NULL && fst2 != NULL);
|
||||
if (fst1_->Start() == -1 || fst2_->Start() == -1) {
|
||||
start_state_ = -1;
|
||||
next_state_ = 0; // actually we don't care about this value.
|
||||
} else {
|
||||
start_state_ = 0;
|
||||
std::pair<StateId,StateId> start_pair(fst1_->Start(), fst2_->Start());
|
||||
state_map_[start_pair] = start_state_;
|
||||
state_vec_.push_back(start_pair);
|
||||
next_state_ = 1;
|
||||
}
|
||||
}
|
||||
|
||||
template<class Arc>
|
||||
typename Arc::Weight ComposeDeterministicOnDemandFst<Arc>::Final(StateId s) {
|
||||
KALDI_ASSERT(s < static_cast<StateId>(state_vec_.size()));
|
||||
const std::pair<StateId, StateId> &pr (state_vec_[s]);
|
||||
return Times(fst1_->Final(pr.first), fst2_->Final(pr.second));
|
||||
}
|
||||
|
||||
template<class Arc>
|
||||
bool ComposeDeterministicOnDemandFst<Arc>::GetArc(StateId s, Label ilabel,
|
||||
Arc *oarc) {
|
||||
typedef typename MapType::iterator IterType;
|
||||
KALDI_ASSERT(ilabel != 0 &&
|
||||
"This program expects epsilon-free compact lattices as input");
|
||||
KALDI_ASSERT(s < static_cast<StateId>(state_vec_.size()));
|
||||
const std::pair<StateId, StateId> pr (state_vec_[s]);
|
||||
|
||||
Arc arc1;
|
||||
if (!fst1_->GetArc(pr.first, ilabel, &arc1)) return false;
|
||||
if (arc1.olabel == 0) { // There is no output label on the
|
||||
// arc, so only the first state changes.
|
||||
std::pair<const std::pair<StateId, StateId>, StateId> new_value(
|
||||
std::pair<StateId, StateId>(arc1.nextstate, pr.second),
|
||||
next_state_);
|
||||
|
||||
std::pair<IterType, bool> result = state_map_.insert(new_value);
|
||||
oarc->ilabel = ilabel;
|
||||
oarc->olabel = 0;
|
||||
oarc->nextstate = result.first->second;
|
||||
oarc->weight = arc1.weight;
|
||||
if (result.second == true) { // was inserted
|
||||
next_state_++;
|
||||
const std::pair<StateId, StateId> &new_pair (new_value.first);
|
||||
state_vec_.push_back(new_pair);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// There is an output label, so we need to traverse an arc on the
|
||||
// second fst also.
|
||||
Arc arc2;
|
||||
if (!fst2_->GetArc(pr.second, arc1.olabel, &arc2)) return false;
|
||||
std::pair<const std::pair<StateId, StateId>, StateId> new_value(
|
||||
std::pair<StateId, StateId>(arc1.nextstate, arc2.nextstate),
|
||||
next_state_);
|
||||
std::pair<IterType, bool> result =
|
||||
state_map_.insert(new_value);
|
||||
oarc->ilabel = ilabel;
|
||||
oarc->olabel = arc2.olabel;
|
||||
oarc->nextstate = result.first->second;
|
||||
oarc->weight = Times(arc1.weight, arc2.weight);
|
||||
if (result.second == true) { // was inserted
|
||||
next_state_++;
|
||||
const std::pair<StateId, StateId> &new_pair (new_value.first);
|
||||
state_vec_.push_back(new_pair);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template<class Arc>
|
||||
inline size_t CacheDeterministicOnDemandFst<Arc>::GetIndex(
|
||||
StateId src_state, Label ilabel) {
|
||||
const StateId p1 = 26597, p2 = 50329; // these are two
|
||||
// values that I drew at random from a table of primes.
|
||||
// note: num_cached_arcs_ > 0.
|
||||
|
||||
// We cast to size_t before the modulus, to ensure the
|
||||
// result is positive.
|
||||
return static_cast<size_t>(src_state * p1 + ilabel * p2) %
|
||||
static_cast<size_t>(num_cached_arcs_);
|
||||
}
|
||||
|
||||
template<class Arc>
|
||||
CacheDeterministicOnDemandFst<Arc>::CacheDeterministicOnDemandFst(
|
||||
DeterministicOnDemandFst<Arc> *fst,
|
||||
StateId num_cached_arcs): fst_(fst),
|
||||
num_cached_arcs_(num_cached_arcs),
|
||||
cached_arcs_(num_cached_arcs) {
|
||||
KALDI_ASSERT(num_cached_arcs > 0);
|
||||
for (StateId i = 0; i < num_cached_arcs; i++)
|
||||
cached_arcs_[i].first = kNoStateId; // Invalidate all elements of the cache.
|
||||
}
|
||||
|
||||
template<class Arc>
|
||||
bool CacheDeterministicOnDemandFst<Arc>::GetArc(StateId s, Label ilabel,
|
||||
Arc *oarc) {
|
||||
// Note: we don't cache anything in case a requested arc does not exist.
|
||||
// In the uses that we imagine this will be put to, essentially all the
|
||||
// requested arcs will exist. This only affects efficiency.
|
||||
KALDI_ASSERT(s >= 0 && ilabel != 0);
|
||||
size_t index = this->GetIndex(s, ilabel);
|
||||
if (cached_arcs_[index].first == s &&
|
||||
cached_arcs_[index].second.ilabel == ilabel) {
|
||||
*oarc = cached_arcs_[index].second;
|
||||
return true;
|
||||
} else {
|
||||
Arc arc;
|
||||
if (fst_->GetArc(s, ilabel, &arc)) {
|
||||
cached_arcs_[index].first = s;
|
||||
cached_arcs_[index].second = arc;
|
||||
*oarc = arc;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<class Arc>
|
||||
LmExampleDeterministicOnDemandFst<Arc>::LmExampleDeterministicOnDemandFst(
|
||||
void *lm, Label bos_symbol, Label eos_symbol):
|
||||
lm_(lm), bos_symbol_(bos_symbol), eos_symbol_(eos_symbol) {
|
||||
std::vector<Label> begin_state; // history state corresponding to beginning of sentence
|
||||
begin_state.push_back(bos_symbol); // Depending how your LM is set up, you might
|
||||
// want to have a history vector with more than one bos_symbol on it.
|
||||
|
||||
state_vec_.push_back(begin_state);
|
||||
start_state_ = 0;
|
||||
state_map_[begin_state] = 0;
|
||||
}
|
||||
|
||||
template<class Arc>
|
||||
typename Arc::Weight LmExampleDeterministicOnDemandFst<Arc>::Final(StateId s) {
|
||||
KALDI_ASSERT(static_cast<size_t>(s) < state_vec_.size());
|
||||
// In a real version you would probably use the following variable somehow
|
||||
// (commenting it because it's generating warnings).
|
||||
// const std::vector<Label> &wseq = state_vec_[s];
|
||||
float log_prob = -0.5; // e.g. log_prob = lm->GetLogProb(wseq, eos_symbol_);
|
||||
return Weight(-log_prob); // assuming weight is FloatWeight.
|
||||
}
|
||||
|
||||
template<class Arc>
|
||||
bool LmExampleDeterministicOnDemandFst<Arc>::GetArc(
|
||||
StateId s, Label ilabel, Arc *oarc) {
|
||||
KALDI_ASSERT(static_cast<size_t>(s) < state_vec_.size());
|
||||
std::vector<Label> wseq = state_vec_[s];
|
||||
float log_prob = -0.25; // e.g. log_prob = lm->GetLogProb(wseq, ilabel);
|
||||
wseq.push_back(ilabel); // the code might be different if your histories are the
|
||||
// other way around.
|
||||
|
||||
while (0) { // e.g. while !lm->HistoryStateExists(wseq)
|
||||
wseq.erase(wseq.begin(), wseq.begin() + 1); // remove most distant element of history.
|
||||
// note: if your histories are the other way round, you might just do
|
||||
// wseq.pop() here.
|
||||
}
|
||||
if (log_prob == -std::numeric_limits<float>::infinity()) { // assume this
|
||||
// is what happens if prob of the word is zero. Some LMs will never
|
||||
// return zero.
|
||||
return false; // no arc.
|
||||
}
|
||||
std::pair<const std::vector<Label>, StateId> new_value(
|
||||
wseq,
|
||||
static_cast<Label>(state_vec_.size()));
|
||||
|
||||
// Now get state id for destination state.
|
||||
typedef typename MapType::iterator IterType;
|
||||
std::pair<IterType, bool> result = state_map_.insert(new_value);
|
||||
if (result.second == true) // was inserted
|
||||
state_vec_.push_back(wseq);
|
||||
oarc->ilabel = ilabel;
|
||||
oarc->olabel = ilabel;
|
||||
oarc->nextstate = result.first->second; // the next-state id.
|
||||
oarc->weight = Weight(-log_prob);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
template<class Arc>
|
||||
void ComposeDeterministicOnDemand(const Fst<Arc> &fst1,
|
||||
DeterministicOnDemandFst<Arc> *fst2,
|
||||
MutableFst<Arc> *fst_composed) {
|
||||
typedef typename Arc::Weight Weight;
|
||||
typedef typename Arc::StateId StateId;
|
||||
typedef std::pair<StateId, StateId> StatePair;
|
||||
typedef unordered_map<StatePair, StateId,
|
||||
kaldi::PairHasher<StateId> > MapType;
|
||||
typedef typename MapType::iterator IterType;
|
||||
|
||||
fst_composed->DeleteStates();
|
||||
|
||||
MapType state_map;
|
||||
std::queue<StatePair> state_queue;
|
||||
|
||||
// Set start state in fst_composed.
|
||||
StateId s1 = fst1.Start(),
|
||||
s2 = fst2->Start(),
|
||||
start_state = fst_composed->AddState();
|
||||
StatePair start_pair(s1, s2);
|
||||
state_queue.push(start_pair);
|
||||
fst_composed->SetStart(start_state);
|
||||
// A mapping between pairs of states in fst1 and fst2 and the corresponding
|
||||
// state in fst_composed.
|
||||
std::pair<const StatePair, StateId> start_map(start_pair, start_state);
|
||||
std::pair<IterType, bool> result = state_map.insert(start_map);
|
||||
KALDI_ASSERT(result.second == true);
|
||||
|
||||
while (!state_queue.empty()) {
|
||||
StatePair q = state_queue.front();
|
||||
StateId q1 = q.first,
|
||||
q2 = q.second;
|
||||
state_queue.pop();
|
||||
// If the product of the final weights of the two fsts is non-zero then
|
||||
// we can set a final-prob in fst_composed
|
||||
Weight final_weight = Times(fst1.Final(q1), fst2->Final(q2));
|
||||
if (final_weight != Weight::Zero()) {
|
||||
KALDI_ASSERT(state_map.find(q) != state_map.end());
|
||||
fst_composed->SetFinal(state_map[q], final_weight);
|
||||
}
|
||||
|
||||
// for each pair of edges from fst1 and fst2 at q1 and q2.
|
||||
for (ArcIterator<Fst<Arc> > aiter(fst1, q1); !aiter.Done(); aiter.Next()) {
|
||||
const Arc &arc1 = aiter.Value();
|
||||
Arc arc2;
|
||||
StatePair next_pair;
|
||||
StateId next_state1 = arc1.nextstate,
|
||||
next_state2,
|
||||
next_state;
|
||||
// If there is an epsilon on the arc of fst1 we transition to the next
|
||||
// state but keep fst2 at the current state.
|
||||
if (arc1.olabel == 0) {
|
||||
next_state2 = q2;
|
||||
} else {
|
||||
bool match = fst2->GetArc(q2, arc1.olabel, &arc2);
|
||||
if (!match) // There is no matching arc -> nothing to do.
|
||||
continue;
|
||||
next_state2 = arc2.nextstate;
|
||||
}
|
||||
next_pair = StatePair(next_state1, next_state2);
|
||||
IterType sitr = state_map.find(next_pair);
|
||||
// If sitr == state_map.end() then the state isn't in fst_composed yet.
|
||||
if (sitr == state_map.end()) {
|
||||
next_state = fst_composed->AddState();
|
||||
std::pair<const StatePair, StateId> new_state(
|
||||
next_pair, next_state);
|
||||
std::pair<IterType, bool> result = state_map.insert(new_state);
|
||||
// Since we already checked if state_map contained new_state,
|
||||
// it should always be added if we reach here.
|
||||
KALDI_ASSERT(result.second == true);
|
||||
state_queue.push(next_pair);
|
||||
// If sitr != state_map.end() then the next state is already in
|
||||
// the state_map.
|
||||
} else {
|
||||
next_state = sitr->second;
|
||||
}
|
||||
if (arc1.olabel == 0) {
|
||||
fst_composed->AddArc(state_map[q], Arc(arc1.ilabel, 0, arc1.weight,
|
||||
next_state));
|
||||
} else {
|
||||
fst_composed->AddArc(state_map[q], Arc(arc1.ilabel, arc2.olabel,
|
||||
Times(arc1.weight, arc2.weight), next_state));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// we are doing *fst_composed = Compose(Inverse(*left), right).
|
||||
template<class Arc>
|
||||
void ComposeDeterministicOnDemandInverse(const Fst<Arc> &right,
|
||||
DeterministicOnDemandFst<Arc> *left,
|
||||
MutableFst<Arc> *fst_composed) {
|
||||
typedef typename Arc::Weight Weight;
|
||||
typedef typename Arc::StateId StateId;
|
||||
typedef std::pair<StateId, StateId> StatePair;
|
||||
typedef unordered_map<StatePair, StateId,
|
||||
kaldi::PairHasher<StateId> > MapType;
|
||||
typedef typename MapType::iterator IterType;
|
||||
|
||||
fst_composed->DeleteStates();
|
||||
|
||||
// the queue and map contain pairs (state-in-left, state-in-right)
|
||||
MapType state_map;
|
||||
std::queue<StatePair> state_queue;
|
||||
|
||||
// Set start state in fst_composed.
|
||||
StateId s_left = left->Start(),
|
||||
s_right = right.Start();
|
||||
if (s_left == kNoStateId || s_right == kNoStateId)
|
||||
return; // Empty result.
|
||||
StatePair start_pair(s_left, s_right);
|
||||
StateId start_state = fst_composed->AddState();
|
||||
state_queue.push(start_pair);
|
||||
fst_composed->SetStart(start_state);
|
||||
// A mapping between pairs of states in *left and right, and the corresponding
|
||||
// state in fst_composed.
|
||||
std::pair<const StatePair, StateId> start_map(start_pair, start_state);
|
||||
std::pair<IterType, bool> result = state_map.insert(start_map);
|
||||
KALDI_ASSERT(result.second == true);
|
||||
|
||||
while (!state_queue.empty()) {
|
||||
StatePair q = state_queue.front();
|
||||
StateId q_left = q.first,
|
||||
q_right = q.second;
|
||||
state_queue.pop();
|
||||
// If the product of the final weights of the two fsts is non-zero then
|
||||
// we can set a final-prob in fst_composed
|
||||
Weight final_weight = Times(left->Final(q_left), right.Final(q_right));
|
||||
if (final_weight != Weight::Zero()) {
|
||||
KALDI_ASSERT(state_map.find(q) != state_map.end());
|
||||
fst_composed->SetFinal(state_map[q], final_weight);
|
||||
}
|
||||
|
||||
for (ArcIterator<Fst<Arc> > aiter(right, q_right); !aiter.Done(); aiter.Next()) {
|
||||
const Arc &arc_right = aiter.Value();
|
||||
Arc arc_left;
|
||||
StatePair next_pair;
|
||||
StateId next_state_right = arc_right.nextstate,
|
||||
next_state_left,
|
||||
next_state;
|
||||
// If there is an epsilon on the input side of the rigth arc, we
|
||||
// transition to the next state of the output but keep 'left' at the
|
||||
// current state.
|
||||
if (arc_right.ilabel == 0) {
|
||||
next_state_left = q_left;
|
||||
} else {
|
||||
bool match = left->GetArc(q_left, arc_right.ilabel, &arc_left);
|
||||
if (!match) // There is no matching arc -> nothing to do.
|
||||
continue;
|
||||
// the next 'swap' is because we are composing with the inverse of
|
||||
// *left. Just removing the swap statement wouldn't let us compose
|
||||
// with non-inverted *left though, because the GetArc function call
|
||||
// above interprets the second argument as an ilabel not an olabel.
|
||||
std::swap(arc_left.ilabel, arc_left.olabel);
|
||||
next_state_left = arc_left.nextstate;
|
||||
}
|
||||
next_pair = StatePair(next_state_left, next_state_right);
|
||||
IterType sitr = state_map.find(next_pair);
|
||||
// If sitr == state_map.end() then the state isn't in fst_composed yet.
|
||||
if (sitr == state_map.end()) {
|
||||
next_state = fst_composed->AddState();
|
||||
std::pair<const StatePair, StateId> new_state(
|
||||
next_pair, next_state);
|
||||
std::pair<IterType, bool> result = state_map.insert(new_state);
|
||||
// Since we already checked if state_map contained new_state,
|
||||
// it should always be added if we reach here.
|
||||
KALDI_ASSERT(result.second == true);
|
||||
state_queue.push(next_pair);
|
||||
// If sitr != state_map.end() then the next state is already in
|
||||
// the state_map.
|
||||
} else {
|
||||
next_state = sitr->second;
|
||||
}
|
||||
if (arc_right.ilabel == 0) {
|
||||
// we didn't get an actual arc from the left FST.
|
||||
fst_composed->AddArc(state_map[q], Arc(0, arc_right.olabel,
|
||||
arc_right.weight,
|
||||
next_state));
|
||||
} else {
|
||||
fst_composed->AddArc(state_map[q],
|
||||
Arc(arc_left.ilabel, arc_right.olabel,
|
||||
Times(arc_left.weight, arc_right.weight),
|
||||
next_state));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // end namespace fst
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,232 @@
|
||||
// fstext/deterministic-fst-test.cc
|
||||
|
||||
// Copyright 2009-2011 Gilles Boulianne
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "fstext/deterministic-fst.h"
|
||||
#include "fstext/fst-test-utils.h"
|
||||
#include "util/kaldi-io.h"
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
namespace fst {
|
||||
using std::cout;
|
||||
using std::cerr;
|
||||
using std::endl;
|
||||
|
||||
bool FileExists(std::string strFilename) {
|
||||
struct stat stFileInfo;
|
||||
bool blnReturn;
|
||||
int intStat;
|
||||
|
||||
// Attempt to get the file attributes
|
||||
intStat = stat(strFilename.c_str(), &stFileInfo);
|
||||
if (intStat == 0) {
|
||||
// We were able to get the file attributes
|
||||
// so the file obviously exists.
|
||||
blnReturn = true;
|
||||
} else {
|
||||
// We were not able to get the file attributes.
|
||||
// This may mean that we don't have permission to
|
||||
// access the folder which contains this file. If you
|
||||
// need to do that level of checking, lookup the
|
||||
// return values of stat which will give you
|
||||
// more details on why stat failed.
|
||||
blnReturn = false;
|
||||
}
|
||||
|
||||
return blnReturn;
|
||||
}
|
||||
|
||||
// Simplify writing
|
||||
typedef fst::StdArc StdArc;
|
||||
typedef fst::StdArc::Label Label;
|
||||
typedef fst::StdArc::StateId StateId;
|
||||
typedef fst::StdVectorFst StdVectorFst;
|
||||
typedef fst::StdArc::Weight Weight;
|
||||
|
||||
|
||||
// something that looks like a language model FST with epsilon backoffs
|
||||
StdVectorFst* CreateBackoffFst() {
|
||||
StdVectorFst *fst = new StdVectorFst();
|
||||
fst->AddState(); // state 0
|
||||
fst->SetStart(0);
|
||||
fst->AddArc(0, StdArc(10, 10, 0.0, 1));
|
||||
|
||||
fst->AddState(); // state 1
|
||||
fst->AddArc(1, StdArc(12, 12, 0.0, 4));
|
||||
fst->AddArc(1, StdArc(0,0, 0.1,2)); // backoff from 1 to 2
|
||||
|
||||
fst->AddState(); // state 2
|
||||
fst->AddArc(2, StdArc(13, 13, 0.2, 4));
|
||||
fst->AddArc(2, StdArc(0,0, 0.3,3)); // backoff from 2 to 3
|
||||
|
||||
fst->AddState(); // state 3
|
||||
fst->AddArc(3, StdArc(14, 14, 0.4, 4));
|
||||
|
||||
fst->AddState(); // state 4
|
||||
fst->AddArc(4, StdArc(15, 15, 0.5, 5));
|
||||
|
||||
fst->AddState(); // state 5
|
||||
fst->SetFinal(5, 0.6);
|
||||
|
||||
return fst;
|
||||
}
|
||||
|
||||
// what the resulting DeterministicOnDemand FST should be like
|
||||
StdVectorFst* CreateResultFst() {
|
||||
StdVectorFst *fst = new StdVectorFst();
|
||||
fst->AddState(); // state 0
|
||||
fst->SetStart(0);
|
||||
fst->AddArc(0, StdArc(10, 10, 0.0, 1));
|
||||
|
||||
fst->AddState(); // state 1
|
||||
fst->AddArc(1, StdArc(12, 12, 0.0, 4));
|
||||
fst->AddArc(1, StdArc(13,13,0.3,4)); // went through 1 backoff
|
||||
fst->AddArc(1, StdArc(14,14,0.8,4)); // went through 2 backoffs
|
||||
|
||||
fst->AddState(); // state 2
|
||||
fst->AddState(); // state 3
|
||||
|
||||
fst->AddState(); // state 4
|
||||
fst->AddArc(4, StdArc(15, 15, 0.5, 5));
|
||||
|
||||
fst->AddState(); // state 5
|
||||
fst->SetFinal(5, 0.6);
|
||||
|
||||
return fst;
|
||||
}
|
||||
|
||||
void DeleteTestFst(StdVectorFst *fst) {
|
||||
delete fst;
|
||||
}
|
||||
|
||||
// Follow paths from an input fst representing a string
|
||||
// (poor man's composition)
|
||||
Weight WalkSinglePath(StdVectorFst *ifst, DeterministicOnDemandFst<StdArc> *dfst) {
|
||||
StdArc oarc; // = new StdArc();
|
||||
StateId isrc=ifst->Start();
|
||||
StateId dsrc=dfst->Start();
|
||||
Weight totalCost = Weight::One();
|
||||
|
||||
while (ifst->Final(isrc) == Weight::Zero()) { // while not final
|
||||
fst::ArcIterator<StdVectorFst> aiter(*ifst, isrc);
|
||||
const StdArc &iarc = aiter.Value();
|
||||
if (dfst->GetArc(dsrc, iarc.olabel, &oarc)) {
|
||||
Weight cost = Times(iarc.weight, oarc.weight);
|
||||
// cout << " Matched label "<<iarc.olabel<<" at summed cost "<<cost<<endl;
|
||||
totalCost = Times(totalCost, cost);
|
||||
} else {
|
||||
cout << " Can't match arc ["<<iarc.ilabel<<","<<iarc.olabel<<","<<iarc.weight<<"] from "<<isrc<<endl;
|
||||
exit(1);
|
||||
}
|
||||
isrc = iarc.nextstate;
|
||||
KALDI_LOG << "Setting dsrc = " << oarc.nextstate;
|
||||
dsrc = oarc.nextstate;
|
||||
}
|
||||
totalCost = Times(totalCost, dfst->Final(dsrc));
|
||||
|
||||
cout << " Total cost: " << totalCost << endl;
|
||||
return totalCost;
|
||||
}
|
||||
|
||||
|
||||
void TestBackoffAndCache() {
|
||||
// Build from existing fst
|
||||
cout << "Test with single generated backoff FST" << endl;
|
||||
StdVectorFst *nfst = CreateBackoffFst();
|
||||
StdVectorFst *rfst = CreateResultFst();
|
||||
|
||||
// before using, make sure that it is input sorted
|
||||
ArcSort(nfst, StdILabelCompare());
|
||||
BackoffDeterministicOnDemandFst<StdArc> dfst1a(*nfst);
|
||||
CacheDeterministicOnDemandFst<StdArc> dfst1(&dfst1a);
|
||||
|
||||
// Compare all arcs in dfst1 with expected result
|
||||
for (StateIterator<StdVectorFst> riter(*rfst); !riter.Done(); riter.Next()) {
|
||||
StateId rsrc = riter.Value();
|
||||
// verify that states have same weight (or final status)
|
||||
assert(ApproxEqual(rfst->Final(rsrc), dfst1.Final(rsrc)));
|
||||
for (ArcIterator<StdVectorFst> aiter(*rfst, rsrc); !aiter.Done(); aiter.Next()) {
|
||||
StdArc rarc = aiter.Value();
|
||||
StdArc darc;
|
||||
if (dfst1.GetArc(rsrc, rarc.ilabel, &darc)) {
|
||||
assert(ApproxEqual(rarc.weight, darc.weight, 0.001));
|
||||
assert(rarc.ilabel==darc.ilabel);
|
||||
assert(rarc.olabel==darc.olabel);
|
||||
assert(rarc.nextstate == darc.nextstate);
|
||||
cerr << " Got same arc at state "<<rsrc<<": "<<rarc.ilabel<<" "<<darc.ilabel<<endl;
|
||||
} else {
|
||||
cerr << "Couldn't find arc "<<rarc.ilabel<<" for state "<<rsrc<<endl;
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
delete nfst;
|
||||
delete rfst;
|
||||
}
|
||||
|
||||
void TestCompose() {
|
||||
cout << "Test with single generated backoff FST" << endl;
|
||||
StdVectorFst *nfst = CreateBackoffFst();
|
||||
StdVectorFst *rfst = CreateResultFst();
|
||||
|
||||
StdVectorFst composed_fst;
|
||||
Compose(*rfst, *rfst, &composed_fst);
|
||||
|
||||
// before using, make sure that it is input sorted
|
||||
ArcSort(nfst, StdILabelCompare());
|
||||
BackoffDeterministicOnDemandFst<StdArc> dfst1a(*nfst);
|
||||
ComposeDeterministicOnDemandFst<StdArc> dfst1b(&dfst1a, &dfst1a);
|
||||
CacheDeterministicOnDemandFst<StdArc> dfst1(&dfst1b);
|
||||
|
||||
typedef StdArc::StateId StateId;
|
||||
std::map<StateId, StateId> state_map;
|
||||
state_map[composed_fst.Start()] = dfst1.Start();
|
||||
|
||||
VectorFst<StdArc> path_fst;
|
||||
ShortestPath(composed_fst, &path_fst);
|
||||
|
||||
BackoffDeterministicOnDemandFst<StdArc> dfst2(composed_fst);
|
||||
|
||||
Weight w1 = WalkSinglePath(&path_fst, &dfst1),
|
||||
w2 = WalkSinglePath(&path_fst, &dfst2);
|
||||
KALDI_ASSERT(ApproxEqual(w1, w2));
|
||||
|
||||
delete rfst;
|
||||
delete nfst;
|
||||
|
||||
{ // Mostly checking for compilation errors here.
|
||||
LmExampleDeterministicOnDemandFst<StdArc> lm_eg(NULL, 2, 3);
|
||||
KALDI_ASSERT(lm_eg.Start() == 0);
|
||||
KALDI_ASSERT(lm_eg.Final(0).Value() == 0.5); // I made it this value.
|
||||
StdArc arc;
|
||||
bool b = lm_eg.GetArc(0, 100, &arc);
|
||||
KALDI_ASSERT(b && arc.nextstate == 1 && arc.ilabel == 100 && arc.olabel == 100
|
||||
&& arc.weight.Value() == 0.25);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
int main() {
|
||||
using namespace fst;
|
||||
TestBackoffAndCache();
|
||||
TestCompose();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,335 @@
|
||||
// fstext/deterministic-fst.h
|
||||
|
||||
// Copyright 2011-2012 Gilles Boulianne
|
||||
// 2014 Telepoint Global Hosting Service, LLC. (Author: David Snyder)
|
||||
// 2012-2015 Johns Hopkins University (author: Daniel Povey)
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// This file includes material from the OpenFST Library v1.2.7 available at
|
||||
// http://www.openfst.org and released under the Apache License Version 2.0.
|
||||
//
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// Copyright 2005-2010 Google, Inc.
|
||||
// Author: riley@google.com (Michael Riley)
|
||||
|
||||
#ifndef KALDI_FSTEXT_DETERMINISTIC_FST_H_
|
||||
#define KALDI_FSTEXT_DETERMINISTIC_FST_H_
|
||||
|
||||
/* This header defines the DeterministicOnDemand interface,
|
||||
which is an FST with a special interface that allows
|
||||
only a single arc with a non-epsilon input symbol
|
||||
out of each state.
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <fst/fstlib.h>
|
||||
#include <fst/fst-decl.h>
|
||||
|
||||
#include "util/stl-utils.h"
|
||||
|
||||
namespace fst {
|
||||
|
||||
/// \addtogroup deterministic_fst_group "Classes and functions related to on-demand deterministic FST's"
|
||||
/// @{
|
||||
|
||||
|
||||
/// class DeterministicOnDemandFst is an "FST-like" base-class. It does not
|
||||
/// actually inherit from any Fst class because its interface is not exactly the
|
||||
/// same; it's much smaller. It assumes that the FST can have only one arc for
|
||||
/// any given input symbol, which makes the GetArc function below possible.
|
||||
/// (The FST is also assumed to be free of input epsilons). Note: we don't use
|
||||
/// "const" in this interface, because it creates problems when we do things
|
||||
/// like caching.
|
||||
template<class Arc>
|
||||
class DeterministicOnDemandFst {
|
||||
public:
|
||||
typedef typename Arc::StateId StateId;
|
||||
typedef typename Arc::Weight Weight;
|
||||
typedef typename Arc::Label Label;
|
||||
|
||||
virtual StateId Start() = 0;
|
||||
|
||||
virtual Weight Final(StateId s) = 0;
|
||||
|
||||
/// Note: ilabel must not be epsilon.
|
||||
virtual bool GetArc(StateId s, Label ilabel, Arc *oarc) = 0;
|
||||
|
||||
virtual ~DeterministicOnDemandFst() { }
|
||||
};
|
||||
|
||||
/**
|
||||
This class wraps an Fst, representing a language model, using the interface
|
||||
for "BackoffDeterministicOnDemandFst". We expect that backoff arcs in the
|
||||
language model will have the epsilon label (label 0) on the arcs, and that
|
||||
there will be no other epsilons in the language model. We follow the epsilon
|
||||
arcs as long as a particular arc (or a final-prob) is not found at the
|
||||
current state.
|
||||
*/
|
||||
template<class Arc>
|
||||
class BackoffDeterministicOnDemandFst: public DeterministicOnDemandFst<Arc> {
|
||||
public:
|
||||
typedef typename Arc::Weight Weight;
|
||||
typedef typename Arc::StateId StateId;
|
||||
typedef typename Arc::Label Label;
|
||||
|
||||
explicit BackoffDeterministicOnDemandFst(const Fst<Arc> &fst);
|
||||
|
||||
StateId Start() { return fst_.Start(); }
|
||||
|
||||
Weight Final(StateId s);
|
||||
|
||||
bool GetArc(StateId s, Label ilabel, Arc *oarc);
|
||||
|
||||
private:
|
||||
inline StateId GetBackoffState(StateId s, Weight *w);
|
||||
|
||||
const Fst<Arc> &fst_;
|
||||
};
|
||||
|
||||
/**
|
||||
Class ScaleDeterministicOnDemandFst takes another DeterministicOnDemandFst
|
||||
and scales the weights (like applying a language-model scale). For instance,
|
||||
to subtract existing LM scores from a lattice you could use this with
|
||||
a negative weight; and to interpolate LMs you can also use this with
|
||||
weights less than one.
|
||||
|
||||
It's specialized for StdArc because there is no generic way to scale weights.
|
||||
*/
|
||||
class ScaleDeterministicOnDemandFst: public DeterministicOnDemandFst<StdArc> {
|
||||
public:
|
||||
typedef StdArc::Weight Weight;
|
||||
typedef StdArc::StateId StateId;
|
||||
typedef StdArc::Label Label;
|
||||
|
||||
// Constructor does not take ownership of 'det_fst'.
|
||||
ScaleDeterministicOnDemandFst(float scale,
|
||||
DeterministicOnDemandFst<StdArc> *det_fst):
|
||||
scale_(scale), det_fst_(*det_fst) { }
|
||||
|
||||
StateId Start() { return det_fst_.Start(); }
|
||||
|
||||
Weight Final(StateId s) {
|
||||
// Note: Weight is indirectly a typedef to TropicalWeight.
|
||||
Weight final = det_fst_.Final(s);
|
||||
if (final == Weight::Zero()) return Weight::Zero();
|
||||
else return TropicalWeight(final.Value() * scale_);
|
||||
}
|
||||
|
||||
inline bool GetArc(StateId s, Label ilabel, StdArc *oarc) {
|
||||
if (det_fst_.GetArc(s, ilabel, oarc)) {
|
||||
oarc->weight = TropicalWeight(oarc->weight.Value() * scale_);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
float scale_;
|
||||
DeterministicOnDemandFst<StdArc> &det_fst_;
|
||||
};
|
||||
|
||||
/**
|
||||
The class UnweightedNgramFst is a DeterministicOnDemandFst whose states encode
|
||||
an n-gram history. Conceptually, for n-gram order n and k labels, the FST is an
|
||||
unweighted acceptor with about k^(n-1) states (ignoring end effects). However,
|
||||
the FST is created on demand and doesn't need the label vocabulary; GetArc
|
||||
matches on any input label. This class is primarily used together with
|
||||
ComposeDeterministicOnDemandFst to expand the n-gram history of lattices, ensuring
|
||||
that each arc has a sufficiently long unique word history.
|
||||
*/
|
||||
template<class Arc>
|
||||
class UnweightedNgramFst: public DeterministicOnDemandFst<Arc> {
|
||||
public:
|
||||
typedef typename Arc::Weight Weight;
|
||||
typedef typename Arc::StateId StateId;
|
||||
typedef typename Arc::Label Label;
|
||||
|
||||
UnweightedNgramFst(int n);
|
||||
|
||||
StateId Start() { return start_state_; };
|
||||
|
||||
Weight Final(StateId s);
|
||||
|
||||
bool GetArc(StateId s, Label ilabel, Arc *oarc);
|
||||
|
||||
private:
|
||||
typedef unordered_map<std::vector<Label>,
|
||||
StateId, kaldi::VectorHasher<Label> > MapType;
|
||||
// The order of the n-gram.
|
||||
int n_;
|
||||
MapType state_map_;
|
||||
StateId start_state_;
|
||||
// Map from history-state to pair.
|
||||
std::vector<std::vector<Label> > state_vec_;
|
||||
};
|
||||
|
||||
template<class Arc>
|
||||
class ComposeDeterministicOnDemandFst: public DeterministicOnDemandFst<Arc> {
|
||||
public:
|
||||
typedef typename Arc::StateId StateId;
|
||||
typedef typename Arc::Weight Weight;
|
||||
typedef typename Arc::Label Label;
|
||||
|
||||
/// Note: constructor does not "take ownership" of the input fst's. The input
|
||||
/// fst's should be treated as const, in that their contents do not change,
|
||||
/// but they are not const as the DeterministicOnDemandFst's data-access
|
||||
/// functions are not const, for reasons relating to caching.
|
||||
ComposeDeterministicOnDemandFst(DeterministicOnDemandFst<Arc> *fst1,
|
||||
DeterministicOnDemandFst<Arc> *fst2);
|
||||
|
||||
virtual StateId Start() { return start_state_; }
|
||||
|
||||
virtual Weight Final(StateId s);
|
||||
|
||||
virtual bool GetArc(StateId s, Label ilabel, Arc *oarc);
|
||||
|
||||
private:
|
||||
DeterministicOnDemandFst<Arc> *fst1_;
|
||||
DeterministicOnDemandFst<Arc> *fst2_;
|
||||
typedef unordered_map<std::pair<StateId, StateId>, StateId, kaldi::PairHasher<StateId> > MapType;
|
||||
MapType state_map_;
|
||||
std::vector<std::pair<StateId, StateId> > state_vec_; // maps from
|
||||
// StateId to pair.
|
||||
StateId next_state_;
|
||||
StateId start_state_;
|
||||
};
|
||||
|
||||
template<class Arc>
|
||||
class CacheDeterministicOnDemandFst: public DeterministicOnDemandFst<Arc> {
|
||||
public:
|
||||
typedef typename Arc::StateId StateId;
|
||||
typedef typename Arc::Weight Weight;
|
||||
typedef typename Arc::Label Label;
|
||||
|
||||
/// We don't take ownership of this pointer. The argument is "really" const.
|
||||
CacheDeterministicOnDemandFst(DeterministicOnDemandFst<Arc> *fst,
|
||||
StateId num_cached_arcs = 100000);
|
||||
|
||||
virtual StateId Start() { return fst_->Start(); }
|
||||
|
||||
/// We don't bother caching the final-probs, just the arcs.
|
||||
virtual Weight Final(StateId s) { return fst_->Final(s); }
|
||||
|
||||
virtual bool GetArc(StateId s, Label ilabel, Arc *oarc);
|
||||
|
||||
private:
|
||||
// Get index for cached arc.
|
||||
inline size_t GetIndex(StateId src_state, Label ilabel);
|
||||
|
||||
DeterministicOnDemandFst<Arc> *fst_;
|
||||
StateId num_cached_arcs_;
|
||||
std::vector<std::pair<StateId, Arc> > cached_arcs_;
|
||||
};
|
||||
|
||||
|
||||
/// This class is for didactic purposes, it does not really do anything.
|
||||
/// It shows how you would wrap a language model. Note: you should probably
|
||||
/// have <s> and </s> not be real words in your LM, but <s> correspond somehow
|
||||
/// to the initial-state of the LM, and </s> be encoded in the final-probs.
|
||||
template<class Arc>
|
||||
class LmExampleDeterministicOnDemandFst: public DeterministicOnDemandFst<Arc> {
|
||||
public:
|
||||
typedef typename Arc::StateId StateId;
|
||||
typedef typename Arc::Weight Weight;
|
||||
typedef typename Arc::Label Label;
|
||||
|
||||
LmExampleDeterministicOnDemandFst(void *lm,
|
||||
Label bos_symbol,
|
||||
Label eos_symbol);
|
||||
|
||||
|
||||
virtual StateId Start() { return start_state_; }
|
||||
|
||||
/// We don't bother caching the final-probs, just the arcs.
|
||||
virtual Weight Final(StateId s);
|
||||
|
||||
virtual bool GetArc(StateId s, Label ilabel, Arc *oarc);
|
||||
|
||||
private:
|
||||
// Get index for cached arc.
|
||||
inline size_t GetIndex(StateId src_state, Label ilabel);
|
||||
|
||||
typedef unordered_map<std::vector<Label>, StateId, kaldi::VectorHasher<Label> > MapType;
|
||||
void *lm_;
|
||||
Label bos_symbol_; // beginning of sentence symbol
|
||||
Label eos_symbol_; // end of sentence symbol.
|
||||
// This example code does not handle <UNK>; we assume the LM has the same vocab as
|
||||
// the recognizer.
|
||||
MapType state_map_;
|
||||
StateId start_state_;
|
||||
std::vector<std::vector<Label> > state_vec_; // maps from history-state to pair.
|
||||
|
||||
void *lm; // wouldn't really be void.
|
||||
};
|
||||
|
||||
|
||||
// Compose an FST (which may be a lattice) with a DeterministicOnDemandFst and
|
||||
// store the result in fst_composed. This is mainly used for expanding lattice
|
||||
// n-gram histories, where fst1 is a lattice and fst2 is an UnweightedNgramFst.
|
||||
// This does not call Connect.
|
||||
template<class Arc>
|
||||
void ComposeDeterministicOnDemand(const Fst<Arc> &fst1,
|
||||
DeterministicOnDemandFst<Arc> *fst2,
|
||||
MutableFst<Arc> *fst_composed);
|
||||
|
||||
/**
|
||||
This function does
|
||||
'*fst_composed = Compose(Inverse(*fst2), fst1)'
|
||||
Note that the arguments are reversed; this is unfortunate but it's
|
||||
because the fst2 argument needs to be non-const and non-const arguments
|
||||
must follow const ones.
|
||||
This is the counterpart to ComposeDeterministicOnDemand, used for
|
||||
the case where the DeterministicOnDemandFst is on the left. The
|
||||
reason why we need to make the left-hand argument to compose the
|
||||
inverse of 'fst2' (i.e. with the input and output symbols swapped),
|
||||
is that the DeterministicOnDemandFst interface only supports lookup
|
||||
by ilabel (see its function GetArc).
|
||||
This does not call Connect().
|
||||
*/
|
||||
template<class Arc>
|
||||
void ComposeDeterministicOnDemandInverse(const Fst<Arc> &fst1,
|
||||
DeterministicOnDemandFst<Arc> *fst2,
|
||||
MutableFst<Arc> *fst_composed);
|
||||
|
||||
|
||||
|
||||
|
||||
/// @}
|
||||
|
||||
} // namespace fst
|
||||
|
||||
#include "deterministic-fst-inl.h"
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,174 @@
|
||||
// fstext/determinize-lattice-test.cc
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "fstext/determinize-lattice.h"
|
||||
#include "fstext/lattice-utils.h"
|
||||
#include "fstext/fst-test-utils.h"
|
||||
#include "base/kaldi-math.h"
|
||||
|
||||
namespace fst {
|
||||
using std::vector;
|
||||
using std::cout;
|
||||
|
||||
void TestLatticeStringRepository() {
|
||||
typedef int32 IntType;
|
||||
|
||||
LatticeStringRepository<IntType> sr;
|
||||
typedef LatticeStringRepository<IntType>::Entry Entry;
|
||||
|
||||
for(int i = 0; i < 100; i++) {
|
||||
int len = kaldi::Rand() % 5;
|
||||
vector<IntType> str(len), str2(kaldi::Rand() % 4);
|
||||
const Entry *e = NULL;
|
||||
for(int i = 0; i < len; i++) {
|
||||
str[i] = kaldi::Rand() % 5;
|
||||
e = sr.Successor(e, str[i]);
|
||||
}
|
||||
sr.ConvertToVector(e, &str2);
|
||||
assert(str == str2);
|
||||
|
||||
int len2 = kaldi::Rand() % 5;
|
||||
str2.resize(len2);
|
||||
const Entry *f = sr.EmptyString(); // NULL
|
||||
for(int i = 0; i < len2; i++) {
|
||||
str2[i] = kaldi::Rand() % 5;
|
||||
f = sr.Successor(f, str2[i]);
|
||||
}
|
||||
vector<IntType> prefix, prefix2(kaldi::Rand() % 10),
|
||||
prefix3;
|
||||
for(int i = 0; i < len && i < len2; i++) {
|
||||
if (str[i] == str2[i]) prefix.push_back(str[i]);
|
||||
else break;
|
||||
}
|
||||
const Entry *g = sr.CommonPrefix(e, f);
|
||||
sr.ConvertToVector(g, &prefix2);
|
||||
sr.ConvertToVector(e, &prefix3);
|
||||
sr.ReduceToCommonPrefix(f, &prefix3);
|
||||
assert(prefix == prefix2);
|
||||
assert(prefix == prefix3);
|
||||
assert(sr.IsPrefixOf(g, e));
|
||||
assert(sr.IsPrefixOf(g, f));
|
||||
if (str.size() > prefix.size())
|
||||
assert(!sr.IsPrefixOf(e, g));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// test that determinization proceeds correctly on general
|
||||
// FSTs (not guaranteed determinzable, but we use the
|
||||
// max-states option to stop it getting out of control).
|
||||
template<class Arc> void TestDeterminizeLattice() {
|
||||
typedef typename Arc::Weight Weight;
|
||||
typedef int32 Int;
|
||||
typedef ArcTpl<CompactLatticeWeightTpl<Weight, Int> > CompactArc;
|
||||
|
||||
for(int i = 0; i < 100; i++) {
|
||||
RandFstOptions opts;
|
||||
opts.n_states = 4;
|
||||
opts.n_arcs = 10;
|
||||
opts.n_final = 2;
|
||||
opts.allow_empty = false;
|
||||
opts.weight_multiplier = 0.5; // impt for the randomly generated weights
|
||||
// to be exactly representable in float,
|
||||
// or this test fails because numerical differences can cause symmetry in
|
||||
// weights to be broken, which causes the wrong path to be chosen as far
|
||||
// as the string part is concerned.
|
||||
|
||||
VectorFst<Arc> *fst = RandFst<Arc>();
|
||||
std::cout << "FST before lattice-determinizing is:\n";
|
||||
{
|
||||
FstPrinter<Arc> fstprinter(*fst, NULL, NULL, NULL, false, true, "\t");
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}
|
||||
VectorFst<Arc> det_fst;
|
||||
try {
|
||||
DeterminizeLatticeOptions lat_opts;
|
||||
lat_opts.max_mem = 100;
|
||||
|
||||
if (!DeterminizeLattice<TropicalWeight, int32>(*fst, &det_fst, lat_opts, NULL))
|
||||
throw std::runtime_error("could not determinize");
|
||||
std::cout << "FST after lattice-determinizing is:\n";
|
||||
{
|
||||
FstPrinter<Arc> fstprinter(det_fst, NULL, NULL, NULL, false, true, "\t");
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}
|
||||
assert(det_fst.Properties(kIDeterministic, true) & kIDeterministic);
|
||||
// OK, now determinize it a different way and check equivalence.
|
||||
// [note: it's not normal determinization, it's taking the best path
|
||||
// for any input-symbol sequence....
|
||||
VectorFst<CompactArc> compact_fst, compact_det_fst;
|
||||
ConvertLattice<Weight, Int>(*fst, &compact_fst, false);
|
||||
std::cout << "Compact FST is:\n";
|
||||
{
|
||||
FstPrinter<CompactArc> fstprinter(compact_fst, NULL, NULL, NULL, false, true, "\t");
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}
|
||||
if (kaldi::Rand() % 2 == 1)
|
||||
ConvertLattice<Weight, Int>(det_fst, &compact_det_fst, false);
|
||||
else
|
||||
if (!DeterminizeLattice<TropicalWeight, int32>(*fst, &compact_det_fst, lat_opts, NULL))
|
||||
throw std::runtime_error("could not determinize");
|
||||
|
||||
std::cout << "Compact version of determinized FST is:\n";
|
||||
{
|
||||
FstPrinter<CompactArc> fstprinter(compact_det_fst, NULL, NULL, NULL, false, true, "\t");
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}
|
||||
|
||||
assert(RandEquivalent(compact_det_fst, compact_fst, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length, max*/));
|
||||
} catch (...) {
|
||||
std::cout << "Failed to lattice-determinize this FST (probably not determinizable)\n";
|
||||
}
|
||||
delete fst;
|
||||
}
|
||||
}
|
||||
|
||||
// test that determinization proceeds correctly on acyclic FSTs
|
||||
// (guaranteed determinizable in this sense).
|
||||
template<class Arc> void TestDeterminizeLattice2() {
|
||||
RandFstOptions opts;
|
||||
opts.acyclic = true;
|
||||
for(int i = 0; i < 100; i++) {
|
||||
VectorFst<Arc> *fst = RandFst<Arc>(opts);
|
||||
std::cout << "FST before lattice-determinizing is:\n";
|
||||
{
|
||||
FstPrinter<Arc> fstprinter(*fst, NULL, NULL, NULL, false, true, "\t");
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}
|
||||
VectorFst<Arc> ofst;
|
||||
DeterminizeLattice<TropicalWeight, int32>(*fst, &ofst);
|
||||
std::cout << "FST after lattice-determinizing is:\n";
|
||||
{
|
||||
FstPrinter<Arc> fstprinter(ofst, NULL, NULL, NULL, false, true, "\t");
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}
|
||||
delete fst;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // end namespace fst
|
||||
|
||||
int main() {
|
||||
using namespace fst;
|
||||
TestLatticeStringRepository();
|
||||
TestDeterminizeLattice<StdArc>();
|
||||
TestDeterminizeLattice2<StdArc>();
|
||||
std::cout << "Tests succeeded\n";
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
// fstext/determinize-lattice.h
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef KALDI_FSTEXT_DETERMINIZE_LATTICE_H_
|
||||
#define KALDI_FSTEXT_DETERMINIZE_LATTICE_H_
|
||||
#include <fst/fstlib.h>
|
||||
#include <fst/fst-decl.h>
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include "fstext/lattice-weight.h"
|
||||
|
||||
namespace fst {
|
||||
|
||||
/// \addtogroup fst_extensions
|
||||
/// @{
|
||||
|
||||
|
||||
// For example of usage, see test-determinize-lattice.cc
|
||||
|
||||
/*
|
||||
DeterminizeLattice implements a special form of determinization
|
||||
with epsilon removal, optimized for a phase of lattice generation.
|
||||
Its input is an FST with weight-type BaseWeightType (usually a pair of floats,
|
||||
with a lexicographical type of order, such as LatticeWeightTpl<float>).
|
||||
Typically this would be a state-level lattice, with input symbols equal to
|
||||
words, and output-symbols equal to p.d.f's (so like the inverse of HCLG). Imagine representing this as an
|
||||
acceptor of type CompactLatticeWeightTpl<float>, in which the input/output
|
||||
symbols are words, and the weights contain the original weights together with
|
||||
strings (with zero or one symbol in them) containing the original output labels
|
||||
(the p.d.f.'s). We determinize this using acceptor determinization with
|
||||
epsilon removal. Remember (from lattice-weight.h) that
|
||||
CompactLatticeWeightTpl has a special kind of semiring where we always take
|
||||
the string corresponding to the best cost (of type BaseWeightType), and
|
||||
discard the other. This corresponds to taking the best output-label sequence
|
||||
(of p.d.f.'s) for each input-label sequence (of words). We couldn't use the
|
||||
Gallic weight for this, or it would die as soon as it detected that the input
|
||||
FST was non-functional. In our case, any acyclic FST (and many cyclic ones)
|
||||
can be determinized.
|
||||
We assume that there is a function
|
||||
Compare(const BaseWeightType &a, const BaseWeightType &b)
|
||||
that returns (-1, 0, 1) according to whether (a < b, a == b, a > b) in the
|
||||
total order on the BaseWeightType... this information should be the
|
||||
same as NaturalLess would give, but it's more efficient to do it this way.
|
||||
You can define this for things like TropicalWeight if you need to instantiate
|
||||
this class for that weight type.
|
||||
|
||||
We implement this determinization in a special way to make it efficient for
|
||||
the types of FSTs that we will apply it to. One issue is that if we
|
||||
explicitly represent the strings (in CompactLatticeWeightTpl) as vectors of
|
||||
type vector<IntType>, the algorithm takes time quadratic in the length of
|
||||
words (in states), because propagating each arc involves copying a whole
|
||||
vector (of integers representing p.d.f.'s). Instead we use a hash structure
|
||||
where each string is a pointer (Entry*), and uses a hash from (Entry*,
|
||||
IntType), to the successor string (and a way to get the latest IntType and the
|
||||
ancestor Entry*). [this is the class LatticeStringRepository].
|
||||
|
||||
Another issue is that rather than representing a determinized-state as a
|
||||
collection of (state, weight), we represent it in a couple of reduced forms.
|
||||
Suppose a determinized-state is a collection of (state, weight) pairs; call
|
||||
this the "canonical representation". Note: these collections are always
|
||||
normalized to remove any common weight and string part. Define end-states as
|
||||
the subset of states that have an arc out of them with a label on, or are
|
||||
final. If we represent a determinized-state a the set of just its (end-state,
|
||||
weight) pairs, this will be a valid and more compact representation, and will
|
||||
lead to a smaller set of determinized states (like early minimization). Call
|
||||
this collection of (end-state, weight) pairs the "minimal representation". As
|
||||
a mechanism to reduce compute, we can also consider another representation.
|
||||
In the determinization algorithm, we start off with a set of (begin-state,
|
||||
weight) pairs (where the "begin-states" are initial or have a label on the
|
||||
transition into them), and the "canonical representation" consists of the
|
||||
epsilon-closure of this set (i.e. follow epsilons). Call this set of
|
||||
(begin-state, weight) pairs, appropriately normalized, the "initial
|
||||
representation". If two initial representations are the same, the "canonical
|
||||
representation" and hence the "minimal representation" will be the same. We
|
||||
can use this to reduce compute. Note that if two initial representations are
|
||||
different, this does not preclude the other representations from being the same.
|
||||
|
||||
*/
|
||||
|
||||
struct DeterminizeLatticeOptions {
|
||||
float delta; // A small offset used to measure equality of weights.
|
||||
int max_mem; // If >0, determinization will fail and return false
|
||||
// when the algorithm's (approximate) memory consumption crosses this threshold.
|
||||
int max_loop; // If >0, can be used to detect non-determinizable input
|
||||
// (a case that wouldn't be caught by max_mem).
|
||||
DeterminizeLatticeOptions(): delta(kDelta),
|
||||
max_mem(-1),
|
||||
max_loop(-1) { }
|
||||
};
|
||||
|
||||
/**
|
||||
This function implements the normal version of DeterminizeLattice, in which
|
||||
the output strings are represented using sequences of arcs, where all but
|
||||
the first one has an epsilon on the input side. The debug_ptr argument is
|
||||
an optional pointer to a bool that, if it becomes true while the algorithm
|
||||
is executing, the algorithm will print a traceback and terminate (used in
|
||||
fstdeterminizestar.cc debug non-terminating determinization). More
|
||||
efficient if ifst is arc-sorted on input label. If the number of arcs gets
|
||||
more than max_states, it will throw std::runtime_error (otherwise this code
|
||||
does not use exceptions). This is mainly useful for debug. */
|
||||
template<class Weight, class IntType>
|
||||
bool DeterminizeLattice(
|
||||
const Fst<ArcTpl<Weight> > &ifst,
|
||||
MutableFst<ArcTpl<Weight> > *ofst,
|
||||
DeterminizeLatticeOptions opts = DeterminizeLatticeOptions(),
|
||||
bool *debug_ptr = NULL);
|
||||
|
||||
|
||||
/* This is a version of DeterminizeLattice with a slightly more "natural" output format,
|
||||
where the output sequences are encoded using the CompactLatticeArcTpl template
|
||||
(i.e. the sequences of output symbols are represented directly as strings)
|
||||
More efficient if ifst is arc-sorted on input label.
|
||||
If the #arcs gets more than max_arcs, it will throw std::runtime_error (otherwise
|
||||
this code does not use exceptions). This is mainly useful for debug.
|
||||
*/
|
||||
template<class Weight, class IntType>
|
||||
bool DeterminizeLattice(
|
||||
const Fst<ArcTpl<Weight> >&ifst,
|
||||
MutableFst<ArcTpl<CompactLatticeWeightTpl<Weight, IntType> > > *ofst,
|
||||
DeterminizeLatticeOptions opts = DeterminizeLatticeOptions(),
|
||||
bool *debug_ptr = NULL);
|
||||
|
||||
|
||||
|
||||
|
||||
/// @} end "addtogroup fst_extensions"
|
||||
|
||||
} // end namespace fst
|
||||
|
||||
#include "fstext/determinize-lattice-inl.h"
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,508 @@
|
||||
// fstext/determinize-star-test.cc
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation
|
||||
// 2015 Hainan Xu
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "base/kaldi-math.h"
|
||||
#include "fstext/pre-determinize.h"
|
||||
#include "fstext/determinize-star.h"
|
||||
#include "fstext/trivial-factor-weight.h"
|
||||
#include "fstext/fst-test-utils.h"
|
||||
|
||||
|
||||
namespace fst
|
||||
{
|
||||
|
||||
// test that determinization proceeds correctly on general
|
||||
// FSTs (not guaranteed determinzable, but we use the
|
||||
// max-states option to stop it getting out of control).
|
||||
template<class Arc> void TestDeterminizeGeneral() {
|
||||
int max_states = 100; // don't allow more det-states than this.
|
||||
for(int i = 0; i < 100; i++) {
|
||||
VectorFst<Arc> *fst = RandFst<Arc>();
|
||||
std::cout << "FST before determinizing is:\n";
|
||||
{
|
||||
FstPrinter<Arc> fstprinter(*fst, NULL, NULL, NULL, false, true, "\t");
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}
|
||||
VectorFst<Arc> ofst;
|
||||
try {
|
||||
DeterminizeStar<Fst<Arc> >(*fst, &ofst, kDelta, NULL, max_states);
|
||||
std::cout << "FST after determinizing is:\n";
|
||||
{
|
||||
FstPrinter<Arc> fstprinter(ofst, NULL, NULL, NULL, false, true, "\t");
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}
|
||||
assert(RandEquivalent(*fst, ofst, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length, max*/));
|
||||
} catch (...) {
|
||||
std::cout << "Failed to determinize *this FST (probably not determinizable)\n";
|
||||
}
|
||||
delete fst;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Don't instantiate with log semiring, as RandEquivalent may fail.
|
||||
template<class Arc> void TestDeterminize() {
|
||||
typedef typename Arc::Label Label;
|
||||
typedef typename Arc::StateId StateId;
|
||||
typedef typename Arc::Weight Weight;
|
||||
|
||||
VectorFst<Arc> *fst = new VectorFst<Arc>();
|
||||
int n_syms = 2 + kaldi::Rand() % 5, n_states = 3 + kaldi::Rand() % 10, n_arcs = 5 + kaldi::Rand() % 30, n_final = 1 + kaldi::Rand()%3; // Up to 2 unique symbols.
|
||||
std::cout << "Testing pre-determinize with "<<n_syms<<" symbols, "<<n_states<<" states and "<<n_arcs<<" arcs and "<<n_final<<" final states.\n";
|
||||
SymbolTable *sptr = NULL;
|
||||
|
||||
std::vector<Label> all_syms; // including epsilon.
|
||||
// Put symbols in the symbol table from 1..n_syms-1.
|
||||
for (size_t i = 0;i < (size_t)n_syms;i++)
|
||||
all_syms.push_back(i);
|
||||
|
||||
// Create states.
|
||||
std::vector<StateId> all_states;
|
||||
for (size_t i = 0;i < (size_t)n_states;i++) {
|
||||
StateId this_state = fst->AddState();
|
||||
if (i == 0) fst->SetStart(i);
|
||||
all_states.push_back(this_state);
|
||||
}
|
||||
// Set final states.
|
||||
for (size_t j = 0;j < (size_t)n_final;j++) {
|
||||
StateId id = all_states[kaldi::Rand() % n_states];
|
||||
Weight weight = (Weight)(0.33*(kaldi::Rand() % 5) );
|
||||
printf("calling SetFinal with %d and %f\n", id, weight.Value());
|
||||
fst->SetFinal(id, weight);
|
||||
}
|
||||
// Create arcs.
|
||||
for (size_t i = 0;i < (size_t)n_arcs;i++) {
|
||||
Arc a;
|
||||
a.nextstate = all_states[kaldi::Rand() % n_states];
|
||||
a.ilabel = all_syms[kaldi::Rand() % n_syms];
|
||||
a.olabel = all_syms[kaldi::Rand() % n_syms]; // same input+output vocab.
|
||||
a.weight = (Weight) (0.33*(kaldi::Rand() % 2));
|
||||
StateId start_state = all_states[kaldi::Rand() % n_states];
|
||||
fst->AddArc(start_state, a);
|
||||
}
|
||||
|
||||
std::cout <<" printing before trimming\n";
|
||||
{
|
||||
FstPrinter<Arc> fstprinter(*fst, sptr, sptr, NULL, false, true, "\t");
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}
|
||||
// Trim resulting FST.
|
||||
Connect(fst);
|
||||
|
||||
std::cout <<" printing after trimming\n";
|
||||
{
|
||||
FstPrinter<Arc> fstprinter(*fst, sptr, sptr, NULL, false, true, "\t");
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}
|
||||
|
||||
VectorFst<Arc> *fst_copy_orig = new VectorFst<Arc>(*fst);
|
||||
|
||||
std::vector<Label> extra_syms;
|
||||
if (fst->Start() != kNoStateId) { // "Connect" did not make it empty....
|
||||
PreDeterminize(fst, 1000, &extra_syms);
|
||||
}
|
||||
|
||||
std::cout <<" printing after predeterminization\n";
|
||||
{
|
||||
FstPrinter<Arc> fstprinter(*fst, sptr, sptr, NULL, false, true, "\t");
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}
|
||||
|
||||
|
||||
{ // Remove epsilon. All default args.
|
||||
bool connect = true;
|
||||
Weight weight_threshold = Weight::Zero();
|
||||
int64 nstate = -1; // Relates to pruning.
|
||||
double delta = kDelta; // I think a small weight value. Relates to some kind of pruning,
|
||||
// I guess. But with no epsilon cycles, probably doensn't matter.
|
||||
RmEpsilon(fst, connect, weight_threshold, nstate, delta);
|
||||
}
|
||||
|
||||
std::cout <<" printing after epsilon removal\n";
|
||||
{
|
||||
FstPrinter<Arc> fstprinter(*fst, sptr, sptr, NULL, false, true, "\t");
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}
|
||||
VectorFst<Arc> ofst_orig;
|
||||
VectorFst<Arc> ofst_star;
|
||||
|
||||
{
|
||||
printf("Determinizing with baseline\n");
|
||||
DeterminizeOptions<Arc> opts; // Default options.
|
||||
Determinize(*fst, &ofst_orig, opts);
|
||||
}
|
||||
|
||||
{
|
||||
printf("Determinizing with DeterminizeStar\n");
|
||||
DeterminizeStar(*fst, &ofst_star);
|
||||
}
|
||||
|
||||
{
|
||||
std::cout <<" printing after determinization [baseline]\n";
|
||||
FstPrinter<Arc> fstprinter(ofst_orig, sptr, sptr, NULL, false, true, "\t");
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
assert(ofst_orig.Properties(kIDeterministic, true) == kIDeterministic);
|
||||
}
|
||||
|
||||
{
|
||||
std::cout <<" printing after determinization [star]\n";
|
||||
FstPrinter<Arc> fstprinter(ofst_star, sptr, sptr, NULL, false, true, "\t");
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
assert(ofst_star.Properties(kIDeterministic, true) == kIDeterministic);
|
||||
}
|
||||
|
||||
assert(RandEquivalent(ofst_orig, ofst_star, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
|
||||
|
||||
int64 num_removed = DeleteISymbols(&ofst_star, extra_syms);
|
||||
std::cout <<" printing after removing "<<num_removed<<" instances of extra symbols\n";
|
||||
{
|
||||
FstPrinter<Arc> fstprinter(ofst_star, sptr, sptr, NULL, false, true, "\t");
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}
|
||||
|
||||
std::cout <<" Checking equivalent to original FST.\n";
|
||||
// giving Rand() as a seed stops the random number generator from always being reset to
|
||||
// the same point each time, while maintaining determinism of the test.
|
||||
assert(RandEquivalent(ofst_star, *fst_copy_orig, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
|
||||
|
||||
delete fst;
|
||||
delete fst_copy_orig;
|
||||
}
|
||||
|
||||
// Don't call this-- the test will fail due to the FST being non-functional.
|
||||
template<class Arc> void TestDeterminize2() {
|
||||
for(int i = 0; i < 10; i++) {
|
||||
RandFstOptions opts;
|
||||
opts.acyclic = true;
|
||||
VectorFst<Arc> *ifst = RandFst<Arc>(opts);
|
||||
VectorFst<Arc> ofst;
|
||||
Determinize(*ifst, &ofst);
|
||||
assert(RandEquivalent(*ifst, ofst, 5, 0.01, kaldi::Rand(), 100));
|
||||
delete ifst;
|
||||
}
|
||||
}
|
||||
|
||||
template<class Arc> void TestPush() {
|
||||
typedef typename Arc::Label Label;
|
||||
typedef typename Arc::StateId StateId;
|
||||
typedef typename Arc::Weight Weight;
|
||||
|
||||
VectorFst<Arc> *fst = new VectorFst<Arc>();
|
||||
int n_syms = 2 + kaldi::Rand() % 5, n_states = 3 + kaldi::Rand() % 10, n_arcs = 5 + kaldi::Rand() % 30, n_final = 1 + kaldi::Rand()%3; // Up to 2 unique symbols.
|
||||
std::cout << "Testing pre-determinize with "<<n_syms<<" symbols, "<<n_states<<" states and "<<n_arcs<<" arcs and "<<n_final<<" final states.\n";
|
||||
SymbolTable *sptr = NULL;
|
||||
|
||||
std::vector<Label> all_syms; // including epsilon.
|
||||
// Put symbols in the symbol table from 1..n_syms-1.
|
||||
for (size_t i = 0;i < (size_t)n_syms;i++)
|
||||
all_syms.push_back(i);
|
||||
|
||||
// Create states.
|
||||
std::vector<StateId> all_states;
|
||||
for (size_t i = 0;i < (size_t)n_states;i++) {
|
||||
StateId this_state = fst->AddState();
|
||||
if (i == 0) fst->SetStart(i);
|
||||
all_states.push_back(this_state);
|
||||
}
|
||||
// Set final states.
|
||||
for (size_t j = 0;j < (size_t)n_final;j++) {
|
||||
StateId id = all_states[kaldi::Rand() % n_states];
|
||||
Weight weight = (Weight)(0.33*(kaldi::Rand() % 5) );
|
||||
printf("calling SetFinal with %d and %f\n", id, weight.Value());
|
||||
fst->SetFinal(id, weight);
|
||||
}
|
||||
// Create arcs.
|
||||
for (size_t i = 0;i < (size_t)n_arcs;i++) {
|
||||
Arc a;
|
||||
a.nextstate = all_states[kaldi::Rand() % n_states];
|
||||
a.ilabel = all_syms[kaldi::Rand() % n_syms];
|
||||
a.olabel = all_syms[kaldi::Rand() % n_syms]; // same input+output vocab.
|
||||
a.weight = (Weight) (0.33*(kaldi::Rand() % 2));
|
||||
StateId start_state = all_states[kaldi::Rand() % n_states];
|
||||
fst->AddArc(start_state, a);
|
||||
}
|
||||
|
||||
std::cout <<" printing before trimming\n";
|
||||
{
|
||||
FstPrinter<Arc> fstprinter(*fst, sptr, sptr, NULL, false, true, "\t");
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}
|
||||
// Trim resulting FST.
|
||||
Connect(fst);
|
||||
|
||||
std::cout <<" printing after trimming\n";
|
||||
{
|
||||
FstPrinter<Arc> fstprinter(*fst, sptr, sptr, NULL, false, true, "\t");
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}
|
||||
|
||||
VectorFst<Arc> *fst_copy_orig = new VectorFst<Arc>(*fst);
|
||||
|
||||
std::vector<Label> extra_syms;
|
||||
if (fst->Start() != kNoStateId) { // "Connect" did not make it empty....
|
||||
PreDeterminize(fst, 1000, &extra_syms);
|
||||
}
|
||||
|
||||
VectorFst<Arc> fst_pushed;
|
||||
std::cout << "Pushing FST\n";
|
||||
Push<Arc, REWEIGHT_TO_INITIAL>(*fst, &fst_pushed, kPushWeights|kPushLabels, kDelta);
|
||||
|
||||
std::cout <<" printing after pushing\n";
|
||||
{
|
||||
FstPrinter<Arc> fstprinter(fst_pushed, sptr, sptr, NULL, false, true, "\t");
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}
|
||||
|
||||
assert(RandEquivalent(*fst, fst_pushed, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
|
||||
|
||||
delete fst;
|
||||
delete fst_copy_orig;
|
||||
}
|
||||
|
||||
// Don't instantiate with log semiring, as RandEquivalent may fail.
|
||||
template<class Arc> void TestMinimize() {
|
||||
typedef typename Arc::Label Label;
|
||||
typedef typename Arc::StateId StateId;
|
||||
typedef typename Arc::Weight Weight;
|
||||
|
||||
VectorFst<Arc> *fst = new VectorFst<Arc>();
|
||||
int n_syms = 2 + kaldi::Rand() % 5, n_states = 3 + kaldi::Rand() % 10, n_arcs = 5 + kaldi::Rand() % 30, n_final = 1 + kaldi::Rand()%3; // Up to 2 unique symbols.
|
||||
std::cout << "Testing pre-determinize with "<<n_syms<<" symbols, "<<n_states<<" states and "<<n_arcs<<" arcs and "<<n_final<<" final states.\n";
|
||||
SymbolTable *sptr =NULL;
|
||||
|
||||
std::vector<Label> all_syms; // including epsilon.
|
||||
// Put symbols in the symbol table from 1..n_syms-1.
|
||||
for (size_t i = 0;i < (size_t)n_syms;i++)
|
||||
all_syms.push_back(i);
|
||||
|
||||
// Create states.
|
||||
std::vector<StateId> all_states;
|
||||
for (size_t i = 0;i < (size_t)n_states;i++) {
|
||||
StateId this_state = fst->AddState();
|
||||
if (i == 0) fst->SetStart(i);
|
||||
all_states.push_back(this_state);
|
||||
}
|
||||
// Set final states.
|
||||
for (size_t j = 0;j < (size_t)n_final;j++) {
|
||||
StateId id = all_states[kaldi::Rand() % n_states];
|
||||
Weight weight = (Weight)(0.33*(kaldi::Rand() % 5) );
|
||||
printf("calling SetFinal with %d and %f\n", id, weight.Value());
|
||||
fst->SetFinal(id, weight);
|
||||
}
|
||||
// Create arcs.
|
||||
for (size_t i = 0;i < (size_t)n_arcs;i++) {
|
||||
Arc a;
|
||||
a.nextstate = all_states[kaldi::Rand() % n_states];
|
||||
a.ilabel = all_syms[kaldi::Rand() % n_syms];
|
||||
a.olabel = all_syms[kaldi::Rand() % n_syms]; // same input+output vocab.
|
||||
a.weight = (Weight) (0.33*(kaldi::Rand() % 2));
|
||||
StateId start_state = all_states[kaldi::Rand() % n_states];
|
||||
fst->AddArc(start_state, a);
|
||||
}
|
||||
|
||||
std::cout <<" printing before trimming\n";
|
||||
{
|
||||
FstPrinter<Arc> fstprinter(*fst, sptr, sptr, NULL, false, true, "\t");
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}
|
||||
// Trim resulting FST.
|
||||
Connect(fst);
|
||||
|
||||
std::cout <<" printing after trimming\n";
|
||||
{
|
||||
FstPrinter<Arc> fstprinter(*fst, sptr, sptr, NULL, false, true, "\t");
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}
|
||||
|
||||
VectorFst<Arc> *fst_copy_orig = new VectorFst<Arc>(*fst);
|
||||
|
||||
std::vector<Label> extra_syms;
|
||||
if (fst->Start() != kNoStateId) { // "Connect" did not make it empty....
|
||||
PreDeterminize(fst, 1000, &extra_syms);
|
||||
}
|
||||
|
||||
std::cout <<" printing after predeterminization\n";
|
||||
{
|
||||
FstPrinter<Arc> fstprinter(*fst, sptr, sptr, NULL, false, true, "\t");
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}
|
||||
|
||||
|
||||
{ // Remove epsilon. All default args.
|
||||
bool connect = true;
|
||||
Weight weight_threshold = Weight::Zero();
|
||||
int64 nstate = -1; // Relates to pruning.
|
||||
double delta = kDelta; // I think a small weight value. Relates to some kind of pruning,
|
||||
// I guess. But with no epsilon cycles, probably doensn't matter.
|
||||
RmEpsilon(fst, connect, weight_threshold, nstate, delta);
|
||||
}
|
||||
|
||||
std::cout <<" printing after epsilon removal\n";
|
||||
{
|
||||
FstPrinter<Arc> fstprinter(*fst, sptr, sptr, NULL, false, true, "\t");
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}
|
||||
VectorFst<Arc> ofst_orig;
|
||||
VectorFst<Arc> ofst_star;
|
||||
|
||||
{
|
||||
printf("Determinizing with baseline\n");
|
||||
DeterminizeOptions<Arc> opts; // Default options.
|
||||
Determinize(*fst, &ofst_orig, opts);
|
||||
}
|
||||
{
|
||||
std::cout <<" printing after determinization [baseline]\n";
|
||||
FstPrinter<Arc> fstprinter(ofst_orig, sptr, sptr, NULL, false, true, "\t");
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
printf("Determinizing with DeterminizeStar to Gallic semiring\n");
|
||||
VectorFst<GallicArc<Arc> > gallic_fst;
|
||||
|
||||
DeterminizeStar(*fst, &gallic_fst);
|
||||
{
|
||||
std::cout <<" printing after determinization by DeterminizeStar [in gallic]\n";
|
||||
FstPrinter<GallicArc< Arc> > fstprinter(gallic_fst, sptr, sptr, NULL, false, true, "\t");
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}
|
||||
|
||||
|
||||
printf("Pushing weights\n");
|
||||
Push(&gallic_fst, REWEIGHT_TO_INITIAL, kDelta);
|
||||
|
||||
{
|
||||
std::cout <<" printing after pushing weights [in gallic]\n";
|
||||
FstPrinter<GallicArc< Arc> > fstprinter(gallic_fst, sptr, sptr, NULL, false, true, "\t");
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}
|
||||
|
||||
|
||||
printf("Minimizing [in Gallic]\n");
|
||||
Minimize(&gallic_fst);
|
||||
{
|
||||
std::cout <<" printing after minimization [in gallic]\n";
|
||||
FstPrinter<GallicArc< Arc> > fstprinter(gallic_fst, sptr, sptr, NULL, false, true, "\t");
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}
|
||||
|
||||
printf("Converting gallic back to regular [my approach]\n");
|
||||
TrivialFactorWeightFst< GallicArc<Arc, GALLIC_LEFT>, GallicFactor<typename Arc::Label,
|
||||
typename Arc::Weight, GALLIC_LEFT> > fwfst(gallic_fst);
|
||||
{
|
||||
std::cout <<" printing factor-weight FST\n";
|
||||
FstPrinter<GallicArc< Arc> > fstprinter(fwfst, sptr, sptr, NULL, false, true, "\t");
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}
|
||||
|
||||
Map(fwfst, &ofst_star, FromGallicMapper<Arc, GALLIC_LEFT>());
|
||||
|
||||
{
|
||||
std::cout <<" printing after converting back to regular FST\n";
|
||||
FstPrinter<Arc> fstprinter(ofst_star, sptr, sptr, NULL, false, true, "\t");
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
assert(RandEquivalent(ofst_orig, ofst_star, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
|
||||
|
||||
|
||||
int64 num_removed = DeleteISymbols(&ofst_star, extra_syms);
|
||||
std::cout <<" printing after removing "<<num_removed<<" instances of extra symbols\n";
|
||||
{
|
||||
FstPrinter<Arc> fstprinter(ofst_star, sptr, sptr, NULL, false, true, "\t");
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}
|
||||
|
||||
std::cout <<" Checking equivalent to original FST.\n";
|
||||
// giving Rand() as a seed stops the random number generator from always being reset to
|
||||
// the same point each time, while maintaining determinism of the test.
|
||||
assert(RandEquivalent(ofst_star, *fst_copy_orig, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
|
||||
|
||||
delete fst;
|
||||
delete fst_copy_orig;
|
||||
}
|
||||
|
||||
|
||||
template<class Arc, class inttype> void TestStringRepository() {
|
||||
typedef typename Arc::Label Label;
|
||||
|
||||
StringRepository<Label, inttype> sr;
|
||||
|
||||
int N = 100;
|
||||
if (sizeof(inttype) == 1) N = 64;
|
||||
std::vector<std::vector<Label> > strings(N);
|
||||
std::vector<inttype> ids(N);
|
||||
|
||||
for (int i = 0;i < N;i++) {
|
||||
size_t len = kaldi::Rand() % 4;
|
||||
std::vector<Label> vec;
|
||||
for (size_t j = 0;j < len;j++) vec.push_back( (kaldi::Rand()%10) + 150*(kaldi::Rand()%2)); // make it have reasonable range.
|
||||
if (i < 500 && vec.size() == 0) ids[i] = sr.IdOfEmpty();
|
||||
else if (i < 500 && vec.size() == 1) ids[i] = sr.IdOfLabel(vec[0]);
|
||||
else ids[i] = sr.IdOfSeq(vec);
|
||||
|
||||
strings[i] = vec;
|
||||
}
|
||||
|
||||
for (int i = 0;i < N;i++) {
|
||||
std::vector<Label> tmpv;
|
||||
tmpv.push_back(10); // just put in garbage.
|
||||
sr.SeqOfId(ids[i], &tmpv);
|
||||
assert(tmpv == strings[i]);
|
||||
assert(sr.IdOfSeq(strings[i]) == ids[i]);
|
||||
if (strings[i].size() == 0) assert(ids[i] == sr.IdOfEmpty());
|
||||
if (strings[i].size() == 1) assert(ids[i] == sr.IdOfLabel(strings[i][0]));
|
||||
|
||||
if (sizeof(inttype) != 1) {
|
||||
size_t prefix_len = kaldi::Rand() % (strings[i].size() + 1);
|
||||
inttype s2 = sr.RemovePrefix(ids[i], prefix_len);
|
||||
std::vector<Label> vec2;
|
||||
sr.SeqOfId(s2, &vec2);
|
||||
for (size_t j = 0;j < strings[i].size()-prefix_len;j++) {
|
||||
assert(vec2[j] == strings[i][j+prefix_len]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // end namespace fst
|
||||
|
||||
|
||||
int main() {
|
||||
for (int i = 0;i < 3;i++) { // We would need more iterations to check
|
||||
// this properly.
|
||||
fst::TestStringRepository<fst::StdArc, int>();
|
||||
fst::TestStringRepository<fst::StdArc, unsigned int>();
|
||||
// Not for use with char, but this helps reveal some kinds of bugs.
|
||||
fst::TestStringRepository<fst::StdArc, unsigned char>();
|
||||
fst::TestStringRepository<fst::StdArc, char>();
|
||||
fst::TestDeterminizeGeneral<fst::StdArc>();
|
||||
fst::TestDeterminize<fst::StdArc>();
|
||||
// fst::TestDeterminize2<fst::StdArc>();
|
||||
fst::TestPush<fst::StdArc>();
|
||||
fst::TestMinimize<fst::StdArc>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
// fstext/determinize-star.h
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation
|
||||
// 2014 Guoguo Chen
|
||||
// 2015 Hainan Xu
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef KALDI_FSTEXT_DETERMINIZE_STAR_H_
|
||||
#define KALDI_FSTEXT_DETERMINIZE_STAR_H_
|
||||
#include <fst/fstlib.h>
|
||||
#include <fst/fst-decl.h>
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include <stdexcept> // this algorithm uses exceptions
|
||||
|
||||
namespace fst {
|
||||
|
||||
/// \addtogroup fst_extensions
|
||||
/// @{
|
||||
|
||||
|
||||
// For example of usage, see test-determinize-star.cc
|
||||
|
||||
/*
|
||||
DeterminizeStar implements determinization with epsilon removal, which we
|
||||
distinguish with a star.
|
||||
|
||||
We define a determinized* FST as one in which no state has more than one
|
||||
transition with the same input-label. Epsilon input labels are not allowed
|
||||
except starting from states that have exactly one arc exiting them (and are
|
||||
not final). [In the normal definition of determinized, epsilon-input labels
|
||||
are not allowed at all, whereas in Mohri's definition, epsilons are treated
|
||||
as ordinary symbols]. The determinized* definition is intended to simulate
|
||||
the effect of allowing strings of output symbols at each state.
|
||||
|
||||
The algorithm implemented here takes an Fst<Arc>, and a pointer to a
|
||||
MutableFst<Arc> where it puts its output. The weight type is assumed to be a
|
||||
float-weight. It does epsilon removal and determinization.
|
||||
This algorithm may fail if the input has epsilon cycles under
|
||||
certain circumstances (i.e. the semiring is non-idempotent, e.g. the log
|
||||
semiring, or there are negative cost epsilon cycles).
|
||||
|
||||
This implementation is much less fancy than the one in fst/determinize.h, and
|
||||
does not have an "on-demand" version.
|
||||
|
||||
The algorithm is a fairly normal determinization algorithm. We keep in
|
||||
memory the subsets of states, together with their leftover strings and their
|
||||
weights. The only difference is we detect input epsilon transitions and
|
||||
treat them "specially".
|
||||
*/
|
||||
|
||||
|
||||
// This algorithm will be slightly faster if you sort the input fst on input label.
|
||||
|
||||
/**
|
||||
This function implements the normal version of DeterminizeStar, in which the
|
||||
output strings are represented using sequences of arcs, where all but the
|
||||
first one has an epsilon on the input side. The debug_ptr argument is an
|
||||
optional pointer to a bool that, if it becomes true while the algorithm is
|
||||
executing, the algorithm will print a traceback and terminate (used in
|
||||
fstdeterminizestar.cc debug non-terminating determinization).
|
||||
If max_states is positive, it will stop determinization and throw an
|
||||
exception as soon as the max-states is reached. This can be useful in test.
|
||||
If allow_partial is true, the algorithm will output partial results when the
|
||||
specified max_states is reached (when larger than zero), instead of throwing
|
||||
out an error.
|
||||
|
||||
Caution, the return status is un-intuitive: this function will return false if
|
||||
determinization completed normally, and true if it was stopped early by
|
||||
reaching the 'max-states' limit, and a partial FST was generated.
|
||||
*/
|
||||
template<class F>
|
||||
bool DeterminizeStar(F &ifst, MutableFst<typename F::Arc> *ofst,
|
||||
float delta = kDelta,
|
||||
bool *debug_ptr = NULL,
|
||||
int max_states = -1,
|
||||
bool allow_partial = false);
|
||||
|
||||
|
||||
|
||||
/* This is a version of DeterminizeStar with a slightly more "natural" output format,
|
||||
where the output sequences are encoded using the GallicArc (i.e. the output symbols
|
||||
are strings.
|
||||
If max_states is positive, it will stop determinization and throw an
|
||||
exception as soon as the max-states is reached. This can be useful in test.
|
||||
If allow_partial is true, the algorithm will output partial results when the
|
||||
specified max_states is reached (when larger than zero), instead of throwing
|
||||
out an error.
|
||||
|
||||
Caution, the return status is un-intuitive: this function will return false if
|
||||
determinization completed normally, and true if it was stopped early by
|
||||
reaching the 'max-states' limit, and a partial FST was generated.
|
||||
*/
|
||||
template<class F>
|
||||
bool DeterminizeStar(F &ifst, MutableFst<GallicArc<typename F::Arc> > *ofst,
|
||||
float delta = kDelta, bool *debug_ptr = NULL,
|
||||
int max_states = -1,
|
||||
bool allow_partial = false);
|
||||
|
||||
|
||||
/// @} end "addtogroup fst_extensions"
|
||||
|
||||
} // end namespace fst
|
||||
|
||||
#include "fstext/determinize-star-inl.h"
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,126 @@
|
||||
// fstext/epsilon-property-inl.h
|
||||
|
||||
// Copyright 2014 Johns Hopkins University (Author: Daniel Povey)
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef KALDI_FSTEXT_EPSILON_PROPERTY_INL_H_
|
||||
#define KALDI_FSTEXT_EPSILON_PROPERTY_INL_H_
|
||||
|
||||
namespace fst {
|
||||
|
||||
|
||||
|
||||
template<class Arc>
|
||||
void ComputeStateInfo(const VectorFst<Arc> &fst,
|
||||
std::vector<char> *epsilon_info) {
|
||||
typedef typename Arc::StateId StateId;
|
||||
typedef VectorFst<Arc> Fst;
|
||||
epsilon_info->clear();
|
||||
epsilon_info->resize(fst.NumStates(), static_cast<char>(0));
|
||||
for (StateId s = 0; s < fst.NumStates(); s++) {
|
||||
for (ArcIterator<Fst> aiter(fst, s); !aiter.Done(); aiter.Next()) {
|
||||
const Arc &arc = aiter.Value();
|
||||
if (arc.ilabel == 0 && arc.olabel == 0) {
|
||||
(*epsilon_info)[arc.nextstate] |= static_cast<char>(kStateHasEpsilonArcsEntering);
|
||||
(*epsilon_info)[s] |= static_cast<char>(kStateHasEpsilonArcsLeaving);
|
||||
} else {
|
||||
(*epsilon_info)[arc.nextstate] |= static_cast<char>(kStateHasNonEpsilonArcsEntering);
|
||||
(*epsilon_info)[s] |= static_cast<char>(kStateHasNonEpsilonArcsLeaving);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<class Arc>
|
||||
void EnsureEpsilonProperty(VectorFst<Arc> *fst) {
|
||||
|
||||
typedef typename Arc::StateId StateId;
|
||||
typedef typename Arc::Weight Weight;
|
||||
typedef VectorFst<Arc> Fst;
|
||||
std::vector<char> epsilon_info;
|
||||
ComputeStateInfo(*fst, &epsilon_info);
|
||||
|
||||
|
||||
StateId num_states_old = fst->NumStates();
|
||||
StateId non_coaccessible_state = fst->AddState();
|
||||
|
||||
/// new_state_vec is for those states that have both epsilon and
|
||||
/// non-epsilon arcs entering. For these states, we'll create a new
|
||||
/// state for the non-epsilon arcs to enter and put it in this array,
|
||||
/// and we'll put an epsilon transition from the new state to the old state.
|
||||
std::vector<StateId> new_state_vec(num_states_old, kNoStateId);
|
||||
for (StateId s = 0; s < num_states_old; s++) {
|
||||
if ((epsilon_info[s] & kStateHasEpsilonArcsEntering) != 0 &&
|
||||
(epsilon_info[s] & kStateHasNonEpsilonArcsEntering) != 0) {
|
||||
assert(s != fst->Start()); // a type of cyclic FST we can't handle
|
||||
// easily.
|
||||
StateId new_state = fst->AddState();
|
||||
new_state_vec[s] = new_state;
|
||||
fst->AddArc(new_state, Arc(0, 0, Weight::One(), s));
|
||||
}
|
||||
}
|
||||
|
||||
/// First modify arcs to point to states in new_state_vec when
|
||||
/// necessary.
|
||||
for (StateId s = 0; s < num_states_old; s++) {
|
||||
for (MutableArcIterator<Fst> aiter(fst, s);
|
||||
!aiter.Done(); aiter.Next()) {
|
||||
Arc arc = aiter.Value();
|
||||
if (arc.ilabel != 0 || arc.olabel != 0) { // non-epsilon arc
|
||||
StateId replacement_state;
|
||||
if (arc.nextstate >= 0 && arc.nextstate < num_states_old &&
|
||||
(replacement_state = new_state_vec[arc.nextstate]) !=
|
||||
kNoStateId) {
|
||||
arc.nextstate = replacement_state;
|
||||
aiter.SetValue(arc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Now handle the situation where states have both epsilon and non-epsilon
|
||||
/// arcs leaving.
|
||||
for (StateId s = 0; s < num_states_old; s++) {
|
||||
if ((epsilon_info[s] & kStateHasEpsilonArcsLeaving) != 0 &&
|
||||
(epsilon_info[s] & kStateHasNonEpsilonArcsLeaving) != 0) {
|
||||
// state has non-epsilon and epsilon arcs leaving.
|
||||
// create a new state and move the non-epsilon arcs to leave
|
||||
// from there instead.
|
||||
StateId new_state = fst->AddState();
|
||||
for (MutableArcIterator<Fst> aiter(fst, s); !aiter.Done();
|
||||
aiter.Next()) {
|
||||
Arc arc = aiter.Value();
|
||||
if (arc.ilabel != 0 || arc.olabel != 0) { // non-epsilon arc.
|
||||
assert(arc.nextstate != s); // we don't handle cyclic FSTs.
|
||||
// move this arc to leave from the new state:
|
||||
fst->AddArc(new_state, arc);
|
||||
arc.nextstate = non_coaccessible_state;
|
||||
aiter.SetValue(arc); // invalidate the arc, Connect() will remove it.
|
||||
}
|
||||
}
|
||||
// Create an epsilon arc to the new state.
|
||||
fst->AddArc(s, Arc(0, 0, Weight::One(), new_state));
|
||||
}
|
||||
}
|
||||
Connect(fst); // Removes arcs to the non-coaccessible state.
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace fst.
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,58 @@
|
||||
// fstext/epsilon-property-test.cc
|
||||
|
||||
// Copyright 2014 Johns Hopkins University (Author: Daniel Povey)
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
|
||||
#include "fstext/rand-fst.h"
|
||||
#include "fstext/epsilon-property.h"
|
||||
|
||||
|
||||
namespace fst {
|
||||
|
||||
void TestEnsureEpsilonProperty() {
|
||||
|
||||
for (int32 i = 0; i < 10; i++) {
|
||||
RandFstOptions opts;
|
||||
opts.acyclic = true;
|
||||
VectorFst<LogArc> *fst = RandFst<LogArc>(opts);
|
||||
VectorFst<LogArc> fst2(*fst); // copy it...
|
||||
EnsureEpsilonProperty(&fst2);
|
||||
|
||||
std::vector<char> info;
|
||||
ComputeStateInfo(fst2, &info);
|
||||
for (size_t i = 0; i < info.size(); i++) {
|
||||
char c = info[i];
|
||||
assert(!((c & kStateHasEpsilonArcsEntering) != 0 &&
|
||||
(c & kStateHasNonEpsilonArcsEntering) != 0));
|
||||
assert(!((c & kStateHasEpsilonArcsLeaving) != 0 &&
|
||||
(c & kStateHasNonEpsilonArcsLeaving) != 0));
|
||||
}
|
||||
assert(RandEquivalent(fst2, *fst, 5, 0.01, kaldi::Rand(), 10));
|
||||
delete fst;
|
||||
}
|
||||
}
|
||||
|
||||
} // end namespace fst
|
||||
|
||||
int main() {
|
||||
using namespace fst;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
TestEnsureEpsilonProperty();
|
||||
}
|
||||
std::cout << "Test OK\n";
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// fstext/epsilon-property.h
|
||||
|
||||
// Copyright 2014 Johns Hopkins University (Author: Daniel Povey)
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef KALDI_FSTEXT_EPSILON_PROPERTY_H_
|
||||
#define KALDI_FSTEXT_EPSILON_PROPERTY_H_
|
||||
#include <fst/fstlib.h>
|
||||
#include <fst/fst-decl.h>
|
||||
|
||||
namespace fst {
|
||||
|
||||
enum {
|
||||
kStateHasEpsilonArcsEntering = 0x1,
|
||||
kStateHasNonEpsilonArcsEntering = 0x2,
|
||||
kStateHasEpsilonArcsLeaving = 0x4,
|
||||
kStateHasNonEpsilonArcsLeaving = 0x8
|
||||
}; // use 'char' for this enum.
|
||||
|
||||
/// This function will set epsilon_info to have size equal to the
|
||||
/// NumStates() of the FST, containing a logical-or of the enum
|
||||
/// values kStateHasEpsilonArcsEntering, kStateHasNonEpsilonArcsEntering,
|
||||
/// kStateHasEpsilonArcsLeaving, and kStateHasNonEpsilonArcsLeaving.
|
||||
/// The meaning should be obvious. Note: an epsilon arc is defined
|
||||
/// as an arc where ilabel == olabel == 0.
|
||||
template<class Arc>
|
||||
void ComputeStateInfo(const VectorFst<Arc> &fst,
|
||||
std::vector<char> *epsilon_info);
|
||||
|
||||
/// This function modifies the fst (while maintaining equivalence) in such a way
|
||||
/// that, after the modification, all states of the FST which have epsilon-arcs
|
||||
/// entering them, have no non-epsilon arcs entering them, and all states which
|
||||
/// have epsilon-arcs leaving them, have no non-epsilon arcs leaving them. It does
|
||||
/// this by creating extra states and adding extra epsilon transitions. An epsilon
|
||||
/// arc is defined as an arc where both the ilabel and the olabel are epsilons.
|
||||
/// This function may fail with KALDI_ASSERT for certain cyclic FSTs, but is safe
|
||||
/// in the acyclic case.
|
||||
template<class Arc>
|
||||
void EnsureEpsilonProperty(VectorFst<Arc> *fst);
|
||||
|
||||
} // end namespace fst
|
||||
|
||||
|
||||
#include "fstext/epsilon-property-inl.h"
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,284 @@
|
||||
// fstext/factor-inl.h
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef KALDI_FSTEXT_FACTOR_INL_H_
|
||||
#define KALDI_FSTEXT_FACTOR_INL_H_
|
||||
|
||||
#include "util/stl-utils.h"
|
||||
// Do not include this file directly. It is included by factor.h.
|
||||
|
||||
namespace fst {
|
||||
|
||||
// GetStateProperties takes in an FST and a number "max_state" which is the
|
||||
// highest numbered state in the FST (this could be fst.NumStates()-1 for an
|
||||
// ExpandedFst, or derived from some kind of traversal). It outputs a vector
|
||||
// numbered from 0..max_state, of type FstStateProperties which is a bitmask
|
||||
// with information about the states.
|
||||
|
||||
// GetStateProperties has not been tested directly (only implicitly via
|
||||
// testing Factor).
|
||||
template<class Arc>
|
||||
void GetStateProperties(const Fst<Arc> &fst,
|
||||
typename Arc::StateId max_state,
|
||||
std::vector<StatePropertiesType> *props) {
|
||||
typedef typename Arc::StateId StateId;
|
||||
typedef typename Arc::Weight Weight;
|
||||
assert(props != NULL);
|
||||
props->clear();
|
||||
if (fst.Start() < 0) return; // Empty fst.
|
||||
props->resize(max_state+1, 0);
|
||||
assert(fst.Start() <= max_state);
|
||||
(*props)[fst.Start()] |= kStateInitial;
|
||||
for (StateId s = 0; s <= max_state; s++) {
|
||||
StatePropertiesType &s_info = (*props)[s];
|
||||
for (ArcIterator<Fst<Arc> > aiter(fst, s); !aiter.Done(); aiter.Next()) {
|
||||
const Arc &arc = aiter.Value();
|
||||
if (arc.ilabel != 0) s_info |= kStateIlabelsOut;
|
||||
if (arc.olabel != 0) s_info |= kStateOlabelsOut;
|
||||
StateId nexts = arc.nextstate;
|
||||
assert(nexts <= max_state); // or input was invalid.
|
||||
StatePropertiesType &nexts_info = (*props)[nexts];
|
||||
if (s_info&kStateArcsOut) s_info |= kStateMultipleArcsOut;
|
||||
s_info |= kStateArcsOut;
|
||||
if (nexts_info&kStateArcsIn) nexts_info |= kStateMultipleArcsIn;
|
||||
nexts_info |= kStateArcsIn;
|
||||
}
|
||||
if (fst.Final(s) != Weight::Zero()) s_info |= kStateFinal;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
template<class Arc, class I>
|
||||
void Factor(const Fst<Arc> &fst, MutableFst<Arc> *ofst,
|
||||
std::vector<std::vector<I> > *symbols_out) {
|
||||
KALDI_ASSERT_IS_INTEGER_TYPE(I);
|
||||
typedef typename Arc::StateId StateId;
|
||||
typedef typename Arc::Label Label;
|
||||
typedef typename Arc::Weight Weight;
|
||||
assert(symbols_out != NULL);
|
||||
ofst->DeleteStates();
|
||||
if (fst.Start() < 0) return; // empty FST.
|
||||
std::vector<StateId> order;
|
||||
DfsOrderVisitor<Arc> dfs_order_visitor(&order);
|
||||
DfsVisit(fst, &dfs_order_visitor);
|
||||
assert(order.size() > 0);
|
||||
StateId max_state = *(std::max_element(order.begin(), order.end()));
|
||||
std::vector<StatePropertiesType> state_properties;
|
||||
GetStateProperties(fst, max_state, &state_properties);
|
||||
|
||||
std::vector<bool> remove(max_state+1); // if true, will remove this state.
|
||||
|
||||
// Now identify states that will be removed (made the middle of a chain).
|
||||
// The basic rule is that if the FstStateProperties equals
|
||||
// (kStateArcsIn|kStateArcsOut) or (kStateArcsIn|kStateArcsOut|kStateIlabelsOut),
|
||||
// then it is in the middle of a chain. This eliminates state with
|
||||
// multiple input or output arcs, final states, and states with arcs out
|
||||
// that have olabels [we assume these are pushed to the left, so occur on the
|
||||
// 1st arc of a chain.
|
||||
|
||||
for (StateId i = 0; i <= max_state; i++)
|
||||
remove[i] = (state_properties[i] == (kStateArcsIn|kStateArcsOut)
|
||||
|| state_properties[i] == (kStateArcsIn|kStateArcsOut|kStateIlabelsOut));
|
||||
std::vector<StateId> state_mapping(max_state+1, kNoStateId);
|
||||
|
||||
typedef unordered_map<std::vector<I>, Label, kaldi::VectorHasher<I> > SymbolMapType;
|
||||
SymbolMapType symbol_mapping;
|
||||
Label symbol_counter = 0;
|
||||
{
|
||||
std::vector<I> eps;
|
||||
symbol_mapping[eps] = symbol_counter++;
|
||||
}
|
||||
std::vector<I> this_sym; // a temporary used inside the loop.
|
||||
for (size_t i = 0; i < order.size(); i++) {
|
||||
StateId state = order[i];
|
||||
if (!remove[state]) { // Process this state...
|
||||
StateId &new_state = state_mapping[state];
|
||||
if (new_state == kNoStateId) new_state = ofst->AddState();
|
||||
for (ArcIterator<Fst<Arc> > aiter(fst, state); !aiter.Done(); aiter.Next()) {
|
||||
Arc arc = aiter.Value();
|
||||
if (arc.ilabel == 0) this_sym.clear();
|
||||
else {
|
||||
this_sym.resize(1);
|
||||
this_sym[0] = arc.ilabel;
|
||||
}
|
||||
while (remove[arc.nextstate]) {
|
||||
ArcIterator<Fst<Arc> > aiter2(fst, arc.nextstate);
|
||||
assert(!aiter2.Done());
|
||||
const Arc &nextarc = aiter2.Value();
|
||||
arc.weight = Times(arc.weight, nextarc.weight);
|
||||
assert(nextarc.olabel == 0);
|
||||
if (nextarc.ilabel != 0) this_sym.push_back(nextarc.ilabel);
|
||||
assert(static_cast<Label>(static_cast<I>(nextarc.ilabel))
|
||||
== nextarc.ilabel); // check within integer range.
|
||||
arc.nextstate = nextarc.nextstate;
|
||||
}
|
||||
StateId &new_nextstate = state_mapping[arc.nextstate];
|
||||
if (new_nextstate == kNoStateId) new_nextstate = ofst->AddState();
|
||||
arc.nextstate = new_nextstate;
|
||||
if (symbol_mapping.count(this_sym) != 0) arc.ilabel = symbol_mapping[this_sym];
|
||||
else arc.ilabel = symbol_mapping[this_sym] = symbol_counter++;
|
||||
ofst->AddArc(new_state, arc);
|
||||
}
|
||||
if (fst.Final(state) != Weight::Zero())
|
||||
ofst->SetFinal(new_state, fst.Final(state));
|
||||
}
|
||||
}
|
||||
ofst->SetStart(state_mapping[fst.Start()]);
|
||||
|
||||
// Now output the symbol sequences.
|
||||
symbols_out->resize(symbol_counter);
|
||||
for (typename SymbolMapType::const_iterator iter = symbol_mapping.begin();
|
||||
iter != symbol_mapping.end(); ++iter) {
|
||||
(*symbols_out)[iter->second] = iter->first;
|
||||
}
|
||||
}
|
||||
|
||||
template<class Arc>
|
||||
void Factor(const Fst<Arc> &fst, MutableFst<Arc> *ofst1,
|
||||
MutableFst<Arc> *ofst2) {
|
||||
typedef typename Arc::Label Label;
|
||||
std::vector<std::vector<Label> > symbols;
|
||||
Factor(fst, ofst2, &symbols);
|
||||
CreateFactorFst(symbols, ofst1);
|
||||
}
|
||||
|
||||
template<class Arc, class I>
|
||||
void ExpandInputSequences(const std::vector<std::vector<I> > &sequences,
|
||||
MutableFst<Arc> *fst) {
|
||||
KALDI_ASSERT_IS_INTEGER_TYPE(I);
|
||||
typedef typename Arc::StateId StateId;
|
||||
typedef typename Arc::Label Label;
|
||||
typedef typename Arc::Weight Weight;
|
||||
fst->SetInputSymbols(NULL);
|
||||
size_t size = sequences.size();
|
||||
if (sequences.size() > 0) assert(sequences[0].size() == 0); // should be eps.
|
||||
StateId num_states_at_start = fst->NumStates();
|
||||
for (StateId s = 0; s < num_states_at_start; s++) {
|
||||
StateId num_arcs = fst->NumArcs(s);
|
||||
for (StateId aidx = 0; aidx < num_arcs; aidx++) {
|
||||
ArcIterator<MutableFst<Arc> > aiter(*fst, s);
|
||||
aiter.Seek(aidx);
|
||||
Arc arc = aiter.Value();
|
||||
|
||||
Label ilabel = arc.ilabel;
|
||||
Label dest_state = arc.nextstate;
|
||||
if (ilabel != 0) { // non-eps [nothing to do if eps]...
|
||||
assert(ilabel < static_cast<Label>(size));
|
||||
size_t len = sequences[ilabel].size();
|
||||
if (len <= 1) {
|
||||
if (len == 0) arc.ilabel = 0;
|
||||
else arc.ilabel = sequences[ilabel][0];
|
||||
MutableArcIterator<MutableFst<Arc> > mut_aiter(fst, s);
|
||||
mut_aiter.Seek(aidx);
|
||||
mut_aiter.SetValue(arc);
|
||||
} else { // len>=2. Must create new states...
|
||||
StateId curstate = -1; // keep compiler happy: this value never used.
|
||||
for (size_t n = 0; n < len; n++) { // adding/modifying "len" arcs.
|
||||
StateId nextstate;
|
||||
if (n < len-1) {
|
||||
nextstate = fst->AddState();
|
||||
assert(nextstate >= num_states_at_start);
|
||||
} else nextstate = dest_state; // going back to original arc's
|
||||
// destination.
|
||||
if (n == 0) {
|
||||
arc.ilabel = sequences[ilabel][0];
|
||||
arc.nextstate = nextstate;
|
||||
MutableArcIterator<MutableFst<Arc> > mut_aiter(fst, s);
|
||||
mut_aiter.Seek(aidx);
|
||||
mut_aiter.SetValue(arc);
|
||||
} else {
|
||||
arc.ilabel = sequences[ilabel][n];
|
||||
arc.olabel = 0;
|
||||
arc.weight = Weight::One();
|
||||
arc.nextstate = nextstate;
|
||||
fst->AddArc(curstate, arc);
|
||||
}
|
||||
curstate = nextstate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
template<class Arc, class I>
|
||||
void CreateFactorFst(const std::vector<std::vector<I> > &sequences,
|
||||
MutableFst<Arc> *fst) {
|
||||
KALDI_ASSERT_IS_INTEGER_TYPE(I);
|
||||
typedef typename Arc::StateId StateId;
|
||||
typedef typename Arc::Label Label;
|
||||
typedef typename Arc::Weight Weight;
|
||||
|
||||
assert(fst != NULL);
|
||||
fst->DeleteStates();
|
||||
StateId loopstate = fst->AddState();
|
||||
assert(loopstate == 0);
|
||||
fst->SetStart(0);
|
||||
fst->SetFinal(0, Weight::One());
|
||||
if (sequences.size() != 0) assert(sequences[0].size() == 0); // can't replace epsilon...
|
||||
|
||||
for (Label olabel = 1; olabel < static_cast<Label>(sequences.size()); olabel++) {
|
||||
size_t len = sequences[olabel].size();
|
||||
if (len == 0) {
|
||||
Arc arc(0, olabel, Weight::One(), loopstate);
|
||||
fst->AddArc(loopstate, arc);
|
||||
} else {
|
||||
StateId curstate = loopstate;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
StateId nextstate = (i == len-1 ? loopstate : fst->AddState());
|
||||
Arc arc(sequences[olabel][i], (i == 0 ? olabel : 0), Weight::One(), nextstate);
|
||||
fst->AddArc(curstate, arc);
|
||||
curstate = nextstate;
|
||||
}
|
||||
}
|
||||
}
|
||||
fst->SetProperties(kOLabelSorted, kOLabelSorted);
|
||||
}
|
||||
|
||||
|
||||
template<class Arc, class I>
|
||||
void CreateMapFst(const std::vector<I> &symbol_map,
|
||||
MutableFst<Arc> *fst) {
|
||||
KALDI_ASSERT_IS_INTEGER_TYPE(I);
|
||||
typedef typename Arc::StateId StateId;
|
||||
typedef typename Arc::Label Label;
|
||||
typedef typename Arc::Weight Weight;
|
||||
|
||||
assert(fst != NULL);
|
||||
fst->DeleteStates();
|
||||
StateId loopstate = fst->AddState();
|
||||
assert(loopstate == 0);
|
||||
fst->SetStart(0);
|
||||
fst->SetFinal(0, Weight::One());
|
||||
assert(symbol_map.empty() || symbol_map[0] == 0); // FST cannot map epsilon to something else.
|
||||
for (Label olabel = 1; olabel < static_cast<Label>(symbol_map.size()); olabel++) {
|
||||
Arc arc(symbol_map[olabel], olabel, Weight::One(), loopstate);
|
||||
fst->AddArc(loopstate, arc);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
} // end namespace fst.
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,191 @@
|
||||
// fstext/factor-test.cc
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
|
||||
#include "fstext/factor.h"
|
||||
#include "fstext/fstext-utils.h"
|
||||
#include "fstext/fst-test-utils.h"
|
||||
#include "base/kaldi-math.h"
|
||||
|
||||
|
||||
namespace fst
|
||||
{
|
||||
using std::vector;
|
||||
|
||||
// Don't instantiate with log semiring, as RandEquivalent may fail.
|
||||
template<class Arc> static void TestFactor() {
|
||||
typedef typename Arc::Label Label;
|
||||
typedef typename Arc::StateId StateId;
|
||||
typedef typename Arc::Weight Weight;
|
||||
|
||||
VectorFst<Arc> fst;
|
||||
int n_syms = 2 + kaldi::Rand() % 5, n_arcs = 5 + kaldi::Rand() % 30, n_final = 1 + kaldi::Rand()%10;
|
||||
|
||||
SymbolTable symtab("my-symbol-table"), *sptr = &symtab;
|
||||
|
||||
vector<Label> all_syms; // including epsilon.
|
||||
// Put symbols in the symbol table from 1..n_syms-1.
|
||||
for (size_t i = 0;i < (size_t)n_syms;i++) {
|
||||
std::stringstream ss;
|
||||
if (i == 0) ss << "<eps>";
|
||||
else ss<<i;
|
||||
Label cur_lab = sptr->AddSymbol(ss.str());
|
||||
assert(cur_lab == (Label)i);
|
||||
all_syms.push_back(cur_lab);
|
||||
}
|
||||
assert(all_syms[0] == 0);
|
||||
|
||||
fst.AddState();
|
||||
int cur_num_states = 1;
|
||||
for (int i = 0; i < n_arcs; i++) {
|
||||
StateId src_state = kaldi::Rand() % cur_num_states;
|
||||
StateId dst_state;
|
||||
if (kaldi::RandUniform() < 0.1) dst_state = kaldi::Rand() % cur_num_states;
|
||||
else {
|
||||
dst_state = cur_num_states++; fst.AddState();
|
||||
}
|
||||
Arc arc;
|
||||
if (kaldi::RandUniform() < 0.5) arc.ilabel = all_syms[kaldi::Rand()%all_syms.size()];
|
||||
else arc.ilabel = 0;
|
||||
if (kaldi::RandUniform() < 0.5) arc.olabel = all_syms[kaldi::Rand()%all_syms.size()];
|
||||
else arc.olabel = 0;
|
||||
arc.weight = (Weight) (0 + 0.1*(kaldi::Rand() % 5));
|
||||
arc.nextstate = dst_state;
|
||||
fst.AddArc(src_state, arc);
|
||||
}
|
||||
for (int i = 0; i < n_final; i++) {
|
||||
fst.SetFinal(kaldi::Rand() % cur_num_states, (Weight) (0 + 0.1*(kaldi::Rand() % 5)));
|
||||
}
|
||||
|
||||
if (kaldi::RandUniform() < 0.8) fst.SetStart(0); // usually leads to nicer examples.
|
||||
else fst.SetStart(kaldi::Rand() % cur_num_states);
|
||||
|
||||
std::cout <<" printing before trimming\n";
|
||||
{
|
||||
FstPrinter<Arc> fstprinter(fst, sptr, sptr, NULL, false, true, "\t");
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}
|
||||
// Trim resulting FST.
|
||||
Connect(&fst);
|
||||
|
||||
std::cout <<" printing after trimming\n";
|
||||
{
|
||||
FstPrinter<Arc> fstprinter(fst, sptr, sptr, NULL, false, true, "\t");
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}
|
||||
|
||||
if (fst.Start() == kNoStateId) return; // "Connect" made it empty.
|
||||
|
||||
VectorFst<Arc> fst_pushed;
|
||||
Push<Arc, REWEIGHT_TO_INITIAL>(fst, &fst_pushed, kPushLabels);
|
||||
|
||||
VectorFst<Arc> fst_factored;
|
||||
vector<vector<typename Arc::Label> > symbols;
|
||||
|
||||
Factor(fst, &fst_factored, &symbols);
|
||||
|
||||
// Check no epsilons in "symbols".
|
||||
for (size_t i = 0; i < symbols.size(); i++)
|
||||
assert(symbols[i].size() == 0 || *(std::min(symbols[i].begin(), symbols[i].end())) > 0);
|
||||
|
||||
VectorFst<Arc> fst_factored_pushed;
|
||||
vector<vector<typename Arc::Label> > symbols_pushed;
|
||||
Factor(fst_pushed, &fst_factored_pushed, &symbols_pushed);
|
||||
|
||||
std::cout << "Unfactored has "<<fst.NumStates()<<" states, factored has "<<fst_factored.NumStates()<<", and pushed+factored has "<<fst_factored_pushed.NumStates()<<'\n';
|
||||
|
||||
assert(fst_factored.NumStates() <= fst.NumStates());
|
||||
// assert(fst_factored_pushed.NumStates() <= fst_factored.NumStates()); // pushing should only help. [ no, it doesn't]
|
||||
assert(fst_factored_pushed.NumStates() <= fst_pushed.NumStates());
|
||||
|
||||
VectorFst<Arc> fst_factored_copy(fst_factored);
|
||||
|
||||
VectorFst<Arc> fst_factored_unfactored(fst_factored);
|
||||
ExpandInputSequences(symbols, &fst_factored_unfactored);
|
||||
|
||||
VectorFst<Arc> factor_fst;
|
||||
CreateFactorFst(symbols, &factor_fst);
|
||||
VectorFst<Arc> fst_factored_unfactored2;
|
||||
Compose(factor_fst, fst_factored, &fst_factored_unfactored2);
|
||||
|
||||
ExpandInputSequences(symbols_pushed, &fst_factored_pushed);
|
||||
|
||||
assert(RandEquivalent(fst, fst_factored_unfactored, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
|
||||
|
||||
assert(RandEquivalent(fst, fst_factored_unfactored2, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
|
||||
|
||||
assert(RandEquivalent(fst, fst_factored_pushed, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
|
||||
|
||||
{ // Have tested for equivalence; now do another test: that FactorFst actually finds all
|
||||
// the factors. Do this by inserting factors using ExpandInputSequences and making sure it gets
|
||||
// rid of them all.
|
||||
Label max_label = *(std::max_element(all_syms.begin(), all_syms.end()));
|
||||
vector<vector<Label> > new_labels(max_label+1);
|
||||
for (Label l = 1; l < static_cast<Label>(new_labels.size()); l++) {
|
||||
int n = kaldi::Rand() % 5;
|
||||
for (int i = 0; i < n; i++) new_labels[l].push_back(kaldi::Rand() % 100);
|
||||
}
|
||||
VectorFst<Arc> fst_expanded(fst);
|
||||
ExpandInputSequences(new_labels, &fst_expanded);
|
||||
|
||||
vector<vector<Label> > factors;
|
||||
VectorFst<Arc> fst_reduced;
|
||||
Factor(fst_expanded, &fst_reduced, &factors);
|
||||
assert(fst_reduced.NumStates() <= fst.NumStates()); // Checking that it found all the factors.
|
||||
}
|
||||
|
||||
{ // This block test MapInputSymbols [but relies on the correctness of Factor
|
||||
// and ExpandInputSequences to do so].
|
||||
|
||||
std::map<Label, Label> symbols_reverse_map; // from new->old.
|
||||
symbols_reverse_map[0] = 0; // map eps to eps.
|
||||
for (Label i = 1; i < static_cast<Label>(symbols.size()); i++) {
|
||||
Label new_i;
|
||||
do {
|
||||
new_i = kaldi::Rand() % (symbols.size() + 20);
|
||||
} while (symbols_reverse_map.count(new_i) == 1);
|
||||
symbols_reverse_map[new_i] = i;
|
||||
}
|
||||
vector<vector<Label> > symbols_new;
|
||||
vector<Label> symbol_map(symbols.size()); // from old->new.
|
||||
typename std::map<Label, Label>::iterator iter = symbols_reverse_map.begin();
|
||||
for (; iter != symbols_reverse_map.end(); iter++) {
|
||||
Label new_label = iter->first, old_label = iter->second;
|
||||
if (new_label >= static_cast<Label>(symbols_new.size())) symbols_new.resize(new_label+1);
|
||||
symbols_new[new_label] = symbols[old_label];
|
||||
symbol_map[old_label] = new_label;
|
||||
}
|
||||
MapInputSymbols(symbol_map, &fst_factored_copy);
|
||||
ExpandInputSequences(symbols_new, &fst_factored_copy);
|
||||
assert(RandEquivalent(fst, fst_factored_copy,
|
||||
5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/,
|
||||
100/*path length-- max?*/));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
} // namespace fst
|
||||
|
||||
int main() {
|
||||
using namespace fst;
|
||||
for (int i = 0;i < 25;i++) {
|
||||
TestFactor<fst::StdArc>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
// fstext/factor.h
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef KALDI_FSTEXT_FACTOR_H_
|
||||
#define KALDI_FSTEXT_FACTOR_H_
|
||||
|
||||
/*
|
||||
This header declares the Factor function, which takes an FST and
|
||||
compresses it by detecting linear chains of states, and creating
|
||||
special input symbols that represent these chains. It outputs enough
|
||||
information to be able to reconstruct the original sequences [i.e.
|
||||
the mapping between the new symbols, and sequences of the original
|
||||
symbols]. It ensures that the original symbols all have the same
|
||||
number as a corresponding "new" symbol representing a sequence of length
|
||||
one; this enables certain optimizations later on.
|
||||
*/
|
||||
|
||||
|
||||
#include <fst/fstlib.h>
|
||||
#include <fst/fst-decl.h>
|
||||
#include "util/const-integer-set.h"
|
||||
|
||||
namespace fst {
|
||||
|
||||
|
||||
/**
|
||||
Factor identifies linear chains of states with an olabel (if any)
|
||||
only on the first arc of the chain, and possibly a sequence of
|
||||
ilabels; it outputs an FST with different symbols on the input
|
||||
that represent sequences of the original input symbols; it outputs
|
||||
the mapping from the new symbol to sequences of original symbols,
|
||||
as "symbols" [zero is reserved for epsilon].
|
||||
|
||||
As a side effect it also sorts the FST in depth-first order. Factor will
|
||||
usually do the best job when the olabels have been pushed to the left,
|
||||
i.e. if you make a call like
|
||||
|
||||
Push<Arc, REWEIGHT_TO_INITIAL>(fsta, &fstb, kPushLabels);
|
||||
|
||||
This is because it only creates a chain with olabels on the first arc of the
|
||||
chain (or a chain with no olabels). [it's possible to construct cases where
|
||||
pushing makes things worse, though]. After Factor, the composition of *ofst
|
||||
with the result of calling CreateFactorFst(*symbols) should be equivalent to
|
||||
fst. Alternatively, calling ExpandInputSequences with ofst and *symbols
|
||||
would produce something equivalent to fst.
|
||||
*/
|
||||
|
||||
template<class Arc, class I>
|
||||
void Factor(const Fst<Arc> &fst, MutableFst<Arc> *ofst,
|
||||
std::vector<std::vector<I> > *symbols);
|
||||
|
||||
|
||||
/// This is a more conventional interface of Factor that outputs
|
||||
/// the result as two FSTs.
|
||||
template<class Arc>
|
||||
void Factor(const Fst<Arc> &fst, MutableFst<Arc> *ofst1,
|
||||
MutableFst<Arc> *ofst2);
|
||||
|
||||
|
||||
|
||||
/// ExpandInputSequences expands out the input symbols into sequences of input
|
||||
/// symbols. It creates linear chains of states for each arc that had >1
|
||||
/// augmented symbol on it. It also sets the input symbol table to NULL, since
|
||||
/// in case you did have a symbol table there it would no longer be valid. It
|
||||
/// leaves any weight and output symbols on the first arc of the chain.
|
||||
template<class Arc, class I>
|
||||
void ExpandInputSequences(const std::vector<std::vector<I> > &sequences,
|
||||
MutableFst<Arc> *fst);
|
||||
|
||||
|
||||
/// The function CreateFactorFst will create an FST that expands out the
|
||||
/// "factors" that are the indices of the "sequences" array, into linear sequences
|
||||
/// of symbols. There is a single start and end state (state 0), and for each
|
||||
/// nonzero index i into the array "sequences", there is an arc from state 0 that
|
||||
/// has output-label i, and enters a chain of states with output epsilons and input
|
||||
/// labels corresponding to the remaining elements of the sequences, terminating
|
||||
/// again in state 0. This FST is output-deterministic and sorted on olabel.
|
||||
/// Composing an FST on the left with the output of this function, should be the
|
||||
/// same as calling "ExpandInputSequences". Use TableCompose (see table-matcher.h)
|
||||
/// for efficiency.
|
||||
template<class Arc, class I>
|
||||
void CreateFactorFst(const std::vector<std::vector<I> > &sequences,
|
||||
MutableFst<Arc> *fst);
|
||||
|
||||
|
||||
/// CreateMapFst will create an FST representing this symbol_map. The
|
||||
/// FST has a single loop state with single-arc loops with
|
||||
/// isymbol = symbol_map[i], osymbol = i. The resulting FST applies this
|
||||
/// map to the input symbols of something we compose with it on the right.
|
||||
/// Must have symbol_map[0] == 0.
|
||||
template<class Arc, class I>
|
||||
void CreateMapFst(const std::vector<I> &symbol_map,
|
||||
MutableFst<Arc> *fst);
|
||||
|
||||
|
||||
enum StatePropertiesEnum
|
||||
{ kStateFinal = 0x1,
|
||||
kStateInitial = 0x2,
|
||||
kStateArcsIn = 0x4,
|
||||
kStateMultipleArcsIn = 0x8,
|
||||
kStateArcsOut = 0x10,
|
||||
kStateMultipleArcsOut = 0x20,
|
||||
kStateOlabelsOut = 0x40,
|
||||
kStateIlabelsOut = 0x80 };
|
||||
|
||||
typedef unsigned char StatePropertiesType;
|
||||
|
||||
/**
|
||||
This function works out various properties of the states in the
|
||||
FST, using the bit properties defined in StatePropertiesEnum. */
|
||||
template<class Arc>
|
||||
void GetStateProperties(const Fst<Arc> &fst,
|
||||
typename Arc::StateId max_state,
|
||||
std::vector<StatePropertiesType> *props);
|
||||
|
||||
|
||||
|
||||
template<class Arc>
|
||||
class DfsOrderVisitor {
|
||||
// visitor class that gives the user the dfs order,
|
||||
// c.f. dfs-visit.h. Used in factor-fst-impl.h
|
||||
typedef typename Arc::StateId StateId;
|
||||
public:
|
||||
DfsOrderVisitor(std::vector<StateId> *order): order_(order) { order->clear(); }
|
||||
void InitVisit(const Fst<Arc> &fst) {}
|
||||
bool InitState(StateId s, StateId) { order_->push_back(s); return true; }
|
||||
bool TreeArc(StateId, const Arc&) { return true; }
|
||||
bool BackArc(StateId, const Arc&) { return true; }
|
||||
bool ForwardOrCrossArc(StateId, const Arc&) { return true; }
|
||||
void FinishState(StateId, StateId, const Arc *) { }
|
||||
void FinishVisit() { }
|
||||
private:
|
||||
std::vector<StateId> *order_;
|
||||
};
|
||||
|
||||
|
||||
|
||||
} // namespace fst
|
||||
|
||||
#include "factor-inl.h"
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,33 @@
|
||||
// fstext/fst-test-utils.h
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef KALDI_FSTEXT_FST_TEST_UTILS_H_
|
||||
#define KALDI_FSTEXT_FST_TEST_UTILS_H_
|
||||
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
// Just some #includes.
|
||||
#include "fst/script/print-impl.h"
|
||||
#include "fstext/rand-fst.h"
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// fstext/fstext-lib.h
|
||||
|
||||
// Copyright 2009-2012 Microsoft Corporation Johns Hopkins University (author: Daniel Povey)
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef KALDI_FSTEXT_FSTEXT_LIB_H_
|
||||
#define KALDI_FSTEXT_FSTEXT_LIB_H_
|
||||
#include "fst/fstlib.h"
|
||||
#include "fstext/context-fst.h"
|
||||
#include "fstext/determinize-star.h"
|
||||
#include "fstext/factor.h"
|
||||
#include "fstext/fst-test-utils.h"
|
||||
#include "fstext/fstext-utils.h"
|
||||
#include "fstext/pre-determinize.h"
|
||||
#include "fstext/table-matcher.h"
|
||||
#include "fstext/trivial-factor-weight.h"
|
||||
#include "fstext/lattice-weight.h"
|
||||
#include "fstext/lattice-utils.h"
|
||||
#include "fstext/determinize-lattice.h"
|
||||
#include "fstext/deterministic-fst.h"
|
||||
#include "fstext/kaldi-fst-io.h"
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,434 @@
|
||||
// fstext/fstext-utils-test.cc
|
||||
|
||||
// Copyright 2009-2012 Microsoft Corporation Daniel Povey
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "base/kaldi-common.h" // for exceptions
|
||||
#include "fstext/fstext-utils.h"
|
||||
#include "fstext/fst-test-utils.h"
|
||||
#include "util/stl-utils.h"
|
||||
#include "base/kaldi-math.h"
|
||||
|
||||
namespace fst
|
||||
{
|
||||
using std::vector;
|
||||
using std::cout;
|
||||
|
||||
template<class Arc, class I>
|
||||
void TestMakeLinearAcceptor() {
|
||||
typedef typename Arc::Label Label;
|
||||
typedef typename Arc::StateId StateId;
|
||||
typedef typename Arc::Weight Weight;
|
||||
|
||||
int len = kaldi::Rand() % 10;
|
||||
vector<I> vec;
|
||||
vector<I> vec_nozeros;
|
||||
for (int i = 0; i < len; i++) {
|
||||
int j = kaldi::Rand() % len;
|
||||
vec.push_back(j);
|
||||
if (j != 0) vec_nozeros.push_back(j);
|
||||
}
|
||||
|
||||
|
||||
VectorFst<Arc> vfst;
|
||||
MakeLinearAcceptor(vec, &vfst);
|
||||
vector<I> vec2;
|
||||
vector<I> vec3;
|
||||
Weight w;
|
||||
GetLinearSymbolSequence(vfst, &vec2, &vec3, &w);
|
||||
assert(w == Weight::One());
|
||||
assert(vec_nozeros == vec2);
|
||||
assert(vec_nozeros == vec3);
|
||||
|
||||
if (vec2.size() != 0 || vec3.size() != 0) { // This test might not work
|
||||
// for empty sequences...
|
||||
{
|
||||
vector<VectorFst<Arc> > fstvec;
|
||||
NbestAsFsts(vfst, 1, &fstvec);
|
||||
KALDI_ASSERT(fstvec.size() == 1);
|
||||
assert(RandEquivalent(vfst, fstvec[0], 2/*paths*/, 0.01/*delta*/,
|
||||
kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
|
||||
}
|
||||
}
|
||||
bool include_eps = (kaldi::Rand() % 2 == 0);
|
||||
if (!include_eps) vec = vec_nozeros;
|
||||
kaldi::SortAndUniq(&vec);
|
||||
|
||||
vector<I> vec4;
|
||||
GetInputSymbols(vfst, include_eps, &vec4);
|
||||
assert(vec4 == vec);
|
||||
vector<I> vec5;
|
||||
GetInputSymbols(vfst, include_eps, &vec5);
|
||||
}
|
||||
|
||||
|
||||
template<class Arc> void TestDeterminizeStarInLog() {
|
||||
VectorFst<Arc> *fst = RandFst<Arc>();
|
||||
VectorFst<Arc> fst_copy(fst);
|
||||
typename Arc::Label next_sym = 1 + HighestNumberedInputSymbol(*fst);
|
||||
vector<typename Arc::Label> syms;
|
||||
PreDeterminize(fst, NULL, "#", next_sym, &syms);
|
||||
|
||||
|
||||
}
|
||||
|
||||
// Don't instantiate with log semiring, as RandEquivalent may fail.
|
||||
template<class Arc> void TestSafeDeterminizeWrapper() { // also tests SafeDeterminizeMinimizeWrapper().
|
||||
typedef typename Arc::Label Label;
|
||||
typedef typename Arc::StateId StateId;
|
||||
typedef typename Arc::Weight Weight;
|
||||
|
||||
VectorFst<Arc> *fst = new VectorFst<Arc>();
|
||||
int n_syms = 2 + kaldi::Rand() % 5, n_states = 3 + kaldi::Rand() % 10, n_arcs = 5 + kaldi::Rand() % 30, n_final = 1 + kaldi::Rand()%3; // Up to 2 unique symbols.
|
||||
cout << "Testing pre-determinize with "<<n_syms<<" symbols, "<<n_states<<" states and "<<n_arcs<<" arcs and "<<n_final<<" final states.\n";
|
||||
SymbolTable *sptr = new SymbolTable("my-symbol-table");
|
||||
sptr->AddSymbol("<eps>");
|
||||
delete sptr;
|
||||
sptr = new SymbolTable("my-symbol-table");
|
||||
|
||||
vector<Label> all_syms; // including epsilon.
|
||||
// Put symbols in the symbol table from 1..n_syms-1.
|
||||
for (size_t i = 0;i < (size_t)n_syms;i++) {
|
||||
std::stringstream ss;
|
||||
if (i == 0) ss << "<eps>";
|
||||
else ss<<i;
|
||||
Label cur_lab = sptr->AddSymbol(ss.str());
|
||||
assert(cur_lab == (Label)i);
|
||||
all_syms.push_back(cur_lab);
|
||||
}
|
||||
assert(all_syms[0] == 0);
|
||||
|
||||
// Create states.
|
||||
vector<StateId> all_states;
|
||||
for (size_t i = 0;i < (size_t)n_states;i++) {
|
||||
StateId this_state = fst->AddState();
|
||||
if (i == 0) fst->SetStart(i);
|
||||
all_states.push_back(this_state);
|
||||
}
|
||||
// Set final states.
|
||||
for (size_t j = 0;j < (size_t)n_final;j++) {
|
||||
StateId id = all_states[kaldi::Rand() % n_states];
|
||||
Weight weight = (Weight)(0.33*(kaldi::Rand() % 5) );
|
||||
printf("calling SetFinal with %d and %f\n", id, weight.Value());
|
||||
fst->SetFinal(id, weight);
|
||||
}
|
||||
// Create arcs.
|
||||
for (size_t i = 0;i < (size_t)n_arcs;i++) {
|
||||
Arc a;
|
||||
a.nextstate = all_states[kaldi::Rand() % n_states];
|
||||
a.ilabel = all_syms[kaldi::Rand() % n_syms];
|
||||
a.olabel = all_syms[kaldi::Rand() % n_syms]; // same input+output vocab.
|
||||
a.weight = (Weight) (0.33*(kaldi::Rand() % 2));
|
||||
StateId start_state = all_states[kaldi::Rand() % n_states];
|
||||
fst->AddArc(start_state, a);
|
||||
}
|
||||
|
||||
std::cout <<" printing before trimming\n";
|
||||
{
|
||||
FstPrinter<Arc> fstprinter(*fst, sptr, sptr, NULL, false, true, "\t");
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}
|
||||
// Trim resulting FST.
|
||||
Connect(fst);
|
||||
|
||||
std::cout <<" printing after trimming\n";
|
||||
{
|
||||
FstPrinter<Arc> fstprinter(*fst, sptr, sptr, NULL, false, true, "\t");
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}
|
||||
|
||||
VectorFst<Arc> *fst_copy_orig = new VectorFst<Arc>(*fst);
|
||||
|
||||
VectorFst<Arc> *fst_det = new VectorFst<Arc>;
|
||||
|
||||
vector<Label> extra_syms;
|
||||
if (fst->Start() != kNoStateId) { // "Connect" did not make it empty....
|
||||
if (kaldi::Rand() % 2 == 0)
|
||||
SafeDeterminizeWrapper(fst_copy_orig, fst_det);
|
||||
else {
|
||||
if (kaldi::Rand() % 2 == 0)
|
||||
SafeDeterminizeMinimizeWrapper(fst_copy_orig, fst_det);
|
||||
else
|
||||
SafeDeterminizeMinimizeWrapperInLog(fst_copy_orig, fst_det);
|
||||
}
|
||||
|
||||
// no because does shortest-dist on weights even if not pushing on them.
|
||||
// PushInLog<REWEIGHT_TO_INITIAL>(fst_det, kPushLabels); // will always succeed.
|
||||
KALDI_LOG << "Num states [orig]: " << fst->NumStates() << "[det]" << fst_det->NumStates();
|
||||
assert(RandEquivalent(*fst, *fst_det, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
|
||||
}
|
||||
delete fst;
|
||||
delete fst_copy_orig;
|
||||
delete fst_det;
|
||||
delete sptr;
|
||||
}
|
||||
|
||||
|
||||
// Don't instantiate with log semiring, as RandEquivalent may fail.
|
||||
void TestPushInLog() { // also tests SafeDeterminizeMinimizeWrapper().
|
||||
typedef StdArc Arc;
|
||||
typedef Arc::Label Label;
|
||||
typedef Arc::StateId StateId;
|
||||
typedef Arc::Weight Weight;
|
||||
|
||||
VectorFst<Arc> *fst = RandFst<Arc>();
|
||||
VectorFst<Arc> fst2(*fst);
|
||||
PushInLog<REWEIGHT_TO_INITIAL>(&fst2, kPushLabels|kPushWeights, 0.01); // speed it up using large delta.
|
||||
assert(RandEquivalent(*fst, fst2, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
|
||||
|
||||
delete fst;
|
||||
}
|
||||
|
||||
|
||||
|
||||
template<class Arc> void TestAcceptorMinimize() {
|
||||
typedef typename Arc::Label Label;
|
||||
typedef typename Arc::StateId StateId;
|
||||
typedef typename Arc::Weight Weight;
|
||||
|
||||
VectorFst<Arc> *fst = RandFst<Arc>();
|
||||
|
||||
Project(fst, PROJECT_INPUT);
|
||||
RemoveWeights(fst);
|
||||
|
||||
VectorFst<Arc> fst2(*fst);
|
||||
internal::AcceptorMinimize(&fst2);
|
||||
|
||||
assert(RandEquivalent(*fst, fst2, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
|
||||
|
||||
delete fst;
|
||||
}
|
||||
|
||||
|
||||
template<class Arc> void TestMakeSymbolsSame() {
|
||||
|
||||
VectorFst<Arc> *fst = RandFst<Arc>();
|
||||
bool foll = (kaldi::Rand() % 2 == 0);
|
||||
bool is_symbol = (kaldi::Rand() % 2 == 0);
|
||||
|
||||
|
||||
VectorFst<Arc> fst2(*fst);
|
||||
|
||||
if (foll) {
|
||||
MakeFollowingInputSymbolsSame(is_symbol, &fst2);
|
||||
assert(FollowingInputSymbolsAreSame(is_symbol, fst2));
|
||||
} else {
|
||||
MakePrecedingInputSymbolsSame(is_symbol, &fst2);
|
||||
assert(PrecedingInputSymbolsAreSame(is_symbol, fst2));
|
||||
}
|
||||
|
||||
|
||||
assert(RandEquivalent(*fst, fst2, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
|
||||
|
||||
delete fst;
|
||||
}
|
||||
|
||||
|
||||
template<class Arc>
|
||||
struct TestFunctor {
|
||||
typedef int32 Result;
|
||||
typedef typename Arc::Label Arg;
|
||||
Result operator () (Arg a) const {
|
||||
if (a == kNoLabel) return -1;
|
||||
else if (a == 0) return 0;
|
||||
else {
|
||||
return 1 + ((a-1) % 10);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template<class Arc> void TestMakeSymbolsSameClass() {
|
||||
|
||||
VectorFst<Arc> *fst = RandFst<Arc>();
|
||||
bool foll = (kaldi::Rand() % 2 == 0);
|
||||
bool is_symbol = (kaldi::Rand() % 2 == 0);
|
||||
|
||||
|
||||
VectorFst<Arc> fst2(*fst);
|
||||
|
||||
TestFunctor<Arc> f;
|
||||
if (foll) {
|
||||
MakeFollowingInputSymbolsSameClass(is_symbol, &fst2, f);
|
||||
assert(FollowingInputSymbolsAreSameClass(is_symbol, fst2, f));
|
||||
} else {
|
||||
MakePrecedingInputSymbolsSameClass(is_symbol, &fst2, f);
|
||||
assert(PrecedingInputSymbolsAreSameClass(is_symbol, fst2, f));
|
||||
}
|
||||
|
||||
assert(RandEquivalent(*fst, fst2, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
|
||||
|
||||
delete fst;
|
||||
}
|
||||
|
||||
|
||||
// MakeLoopFstCompare is as MakeLoopFst but implmented differently [ less efficiently
|
||||
// but more clearly], so we can check for equivalence.
|
||||
template<class Arc>
|
||||
VectorFst<Arc>* MakeLoopFstCompare(const vector<const ExpandedFst<Arc> *> &fsts) {
|
||||
VectorFst<Arc> *ans = new VectorFst<Arc>;
|
||||
typedef typename Arc::Label Label;
|
||||
typedef typename Arc::StateId StateId;
|
||||
typedef typename Arc::Weight Weight;
|
||||
|
||||
for (Label i = 0; i < fsts.size(); i++) {
|
||||
if (fsts[i] != NULL) {
|
||||
VectorFst<Arc> i_fst; // accepts symbol i on output.
|
||||
i_fst.AddState(); i_fst.AddState();
|
||||
i_fst.SetStart(0); i_fst.SetFinal(1, Weight::One());
|
||||
i_fst.AddArc(0, Arc(0, i, Weight::One(), 1));
|
||||
VectorFst<Arc> other_fst(*(fsts[i])); // copy it.
|
||||
ClearSymbols(false, true, &other_fst); // Clear output symbols so symbols
|
||||
// are on input side.
|
||||
Concat(&i_fst, other_fst); // now i_fst is "i_fst [concat] other_fst".
|
||||
Union(ans, i_fst);
|
||||
}
|
||||
}
|
||||
Closure(ans, CLOSURE_STAR);
|
||||
return ans;
|
||||
}
|
||||
|
||||
|
||||
template<class Arc> void TestMakeLoopFst() {
|
||||
|
||||
int num_fsts = kaldi::Rand() % 10;
|
||||
vector<const ExpandedFst<Arc>* > fsts(num_fsts, (const ExpandedFst<Arc>*)NULL);
|
||||
for (int i = 0; i < num_fsts; i++) {
|
||||
if (kaldi::Rand() % 2 == 0) { // put an fst there.
|
||||
VectorFst<Arc> *fst = RandFst<Arc>();
|
||||
Project(fst, PROJECT_INPUT); // make input & output labels the same.
|
||||
fsts[i] = fst;
|
||||
} else { // this is to test that it works with the caching.
|
||||
fsts[i] = fsts[i/2];
|
||||
}
|
||||
}
|
||||
|
||||
VectorFst<Arc> *fst1 = MakeLoopFst(fsts),
|
||||
*fst2 = MakeLoopFstCompare(fsts);
|
||||
|
||||
assert(fst1->Properties(kOLabelSorted, kOLabelSorted) != 0);
|
||||
|
||||
assert(RandEquivalent(*fst1, *fst2, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
|
||||
delete fst1;
|
||||
delete fst2;
|
||||
std::sort(fsts.begin(), fsts.end());
|
||||
fsts.erase(std::unique(fsts.begin(), fsts.end()), fsts.end());
|
||||
for (int i = 0; i < (int)fsts.size(); i++)
|
||||
delete fsts[i];
|
||||
}
|
||||
|
||||
|
||||
|
||||
template<class Arc>
|
||||
void TestEqualAlign() {
|
||||
for (size_t i = 0; i < 4; i++) {
|
||||
RandFstOptions opts;
|
||||
opts.allow_empty = false;
|
||||
VectorFst<Arc> *fst = RandFst<Arc>();
|
||||
int length = 10 + kaldi::Rand() % 20;
|
||||
|
||||
VectorFst<Arc> fst_path;
|
||||
if (EqualAlign(*fst, length, kaldi::Rand(), &fst_path)) {
|
||||
std::cout << "EqualAlign succeeded\n";
|
||||
vector<int32> isymbol_seq, osymbol_seq;
|
||||
typename Arc::Weight weight;
|
||||
GetLinearSymbolSequence(fst_path, &isymbol_seq, &osymbol_seq, &weight);
|
||||
assert(isymbol_seq.size() == length);
|
||||
Invert(&fst_path);
|
||||
VectorFst<Arc> fst_composed;
|
||||
Compose(fst_path, *fst, &fst_composed);
|
||||
assert(fst_composed.Start() != kNoStateId); // make sure nonempty.
|
||||
} else {
|
||||
std::cout << "EqualAlign did not generate alignment\n";
|
||||
}
|
||||
delete fst;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template<class Arc> void Print(const Fst<Arc> &fst, std::string message) {
|
||||
std::cout << message << "\n";
|
||||
FstPrinter<Arc> fstprinter(fst, NULL, NULL, NULL, false, true, "\t");
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}
|
||||
|
||||
|
||||
template<class Arc>
|
||||
void TestRemoveUselessArcs() {
|
||||
for (size_t i = 0; i < 4; i++) {
|
||||
RandFstOptions opts;
|
||||
opts.allow_empty = false;
|
||||
VectorFst<Arc> *fst = RandFst<Arc>();
|
||||
// Print(*fst, "[testremoveuselessarcs]:fst:");
|
||||
UniformArcSelector<Arc> selector;
|
||||
RandGenOptions<UniformArcSelector<Arc> > randgen_opts(selector);
|
||||
VectorFst<Arc> fst_path;
|
||||
RandGen(*fst, &fst_path, randgen_opts);
|
||||
Project(&fst_path, PROJECT_INPUT);
|
||||
// Print(fst_path, "[testremoveuselessarcs]:fstpath:");
|
||||
|
||||
VectorFst<Arc> fst_nouseless(*fst);
|
||||
RemoveUselessArcs(&fst_nouseless);
|
||||
// Print(fst_nouseless, "[testremoveuselessarcs]:fst_nouseless:");
|
||||
|
||||
VectorFst<Arc> orig_composed,
|
||||
nouseless_composed;
|
||||
Compose(fst_path, *fst, &orig_composed);
|
||||
Compose(fst_path, fst_nouseless, &nouseless_composed);
|
||||
|
||||
// Print(orig_composed, "[testremoveuselessarcs]:orig_composed");
|
||||
// Print(nouseless_composed, "[testremoveuselessarcs]:nouseless_composed");
|
||||
|
||||
VectorFst<Arc> orig_bestpath,
|
||||
nouseless_bestpath;
|
||||
ShortestPath(orig_composed, &orig_bestpath);
|
||||
ShortestPath(nouseless_composed, &nouseless_bestpath);
|
||||
// Print(orig_bestpath, "[testremoveuselessarcs]:orig_bestpath");
|
||||
// Print(nouseless_bestpath, "[testremoveuselessarcs]:nouseless_bestpath");
|
||||
|
||||
typename Arc::Weight worig, wnouseless;
|
||||
GetLinearSymbolSequence<Arc, int>(orig_bestpath, NULL, NULL, &worig);
|
||||
GetLinearSymbolSequence<Arc, int>(nouseless_bestpath, NULL, NULL, &wnouseless);
|
||||
assert(ApproxEqual(worig, wnouseless, kDelta));
|
||||
|
||||
// assert(RandEquivalent(orig_bestpath, nouseless_bestpath, 5/*paths*/, 0.01/*delta*/, Rand()/*seed*/, 100/*path length-- max?*/));
|
||||
delete fst;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // end namespace fst
|
||||
|
||||
|
||||
int main() {
|
||||
for (int i = 0; i < 5; i++) {
|
||||
fst::TestMakeLinearAcceptor<fst::StdArc, int>(); // this also tests GetLinearSymbolSequence, GetInputSymbols and GetOutputSymbols.
|
||||
fst::TestMakeLinearAcceptor<fst::StdArc, int32>();
|
||||
fst::TestMakeLinearAcceptor<fst::StdArc, uint32>();
|
||||
fst::TestSafeDeterminizeWrapper<fst::StdArc>();
|
||||
fst::TestAcceptorMinimize<fst::StdArc>();
|
||||
fst::TestMakeSymbolsSame<fst::StdArc>();
|
||||
fst::TestMakeSymbolsSame<fst::LogArc>();
|
||||
fst::TestMakeSymbolsSameClass<fst::StdArc>();
|
||||
fst::TestMakeSymbolsSameClass<fst::LogArc>();
|
||||
fst::TestMakeLoopFst<fst::StdArc>();
|
||||
fst::TestMakeLoopFst<fst::LogArc>();
|
||||
fst::TestEqualAlign<fst::StdArc>();
|
||||
fst::TestEqualAlign<fst::LogArc>();
|
||||
fst::TestRemoveUselessArcs<fst::StdArc>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,427 @@
|
||||
// fstext/fstext-utils.h
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation
|
||||
// 2012-2013 Johns Hopkins University (Author: Daniel Povey)
|
||||
// 2013 Guoguo Chen
|
||||
// 2014 Telepoint Global Hosting Service, LLC. (Author: David Snyder)
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef KALDI_FSTEXT_FSTEXT_UTILS_H_
|
||||
#define KALDI_FSTEXT_FSTEXT_UTILS_H_
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include <fst/fstlib.h>
|
||||
#include <fst/fst-decl.h>
|
||||
#include "fstext/determinize-star.h"
|
||||
#include "fstext/remove-eps-local.h"
|
||||
#include "base/kaldi-common.h" // for error reporting macros.
|
||||
#include "util/text-utils.h" // for SplitStringToVector
|
||||
#include "fst/script/print-impl.h"
|
||||
|
||||
namespace fst {
|
||||
|
||||
/// Returns the highest numbered output symbol id of the FST (or zero
|
||||
/// for an empty FST.
|
||||
template<class Arc>
|
||||
typename Arc::Label HighestNumberedOutputSymbol(const Fst<Arc> &fst);
|
||||
|
||||
/// Returns the highest numbered input symbol id of the FST (or zero
|
||||
/// for an empty FST.
|
||||
template<class Arc>
|
||||
typename Arc::Label HighestNumberedInputSymbol(const Fst<Arc> &fst);
|
||||
|
||||
/// Returns the total number of arcs in an FST.
|
||||
template<class Arc>
|
||||
typename Arc::StateId NumArcs(const ExpandedFst<Arc> &fst);
|
||||
|
||||
/// GetInputSymbols gets the list of symbols on the input of fst
|
||||
/// (including epsilon, if include_eps == true), as a sorted, unique
|
||||
/// list.
|
||||
template<class Arc, class I>
|
||||
void GetInputSymbols(const Fst<Arc> &fst,
|
||||
bool include_eps,
|
||||
std::vector<I> *symbols);
|
||||
|
||||
/// GetOutputSymbols gets the list of symbols on the output of fst
|
||||
/// (including epsilon, if include_eps == true)
|
||||
template<class Arc, class I>
|
||||
void GetOutputSymbols(const Fst<Arc> &fst,
|
||||
bool include_eps,
|
||||
std::vector<I> *symbols);
|
||||
|
||||
/// ClearSymbols sets all the symbols on the input and/or
|
||||
/// output side of the FST to zero, as specified.
|
||||
/// It does not alter the symbol tables.
|
||||
template<class Arc>
|
||||
void ClearSymbols(bool clear_input,
|
||||
bool clear_output,
|
||||
MutableFst<Arc> *fst);
|
||||
|
||||
template<class I>
|
||||
void GetSymbols(const SymbolTable &symtab,
|
||||
bool include_eps,
|
||||
std::vector<I> *syms_out);
|
||||
|
||||
|
||||
|
||||
inline
|
||||
void DeterminizeStarInLog(VectorFst<StdArc> *fst, float delta = kDelta, bool *debug_ptr = NULL,
|
||||
int max_states = -1);
|
||||
|
||||
|
||||
// e.g. of using this function: PushInLog<REWEIGHT_TO_INITIAL>(fst, kPushWeights|kPushLabels);
|
||||
|
||||
template<ReweightType rtype> // == REWEIGHT_TO_{INITIAL, FINAL}
|
||||
void PushInLog(VectorFst<StdArc> *fst, uint32 ptype, float delta = kDelta) {
|
||||
|
||||
// PushInLog pushes the FST
|
||||
// and returns a new pushed FST (labels and weights pushed to the left).
|
||||
VectorFst<LogArc> *fst_log = new VectorFst<LogArc>; // Want to determinize in log semiring.
|
||||
Cast(*fst, fst_log);
|
||||
VectorFst<StdArc> tmp;
|
||||
*fst = tmp; // free up memory.
|
||||
VectorFst<LogArc> *fst_pushed_log = new VectorFst<LogArc>;
|
||||
Push<LogArc, rtype>(*fst_log, fst_pushed_log, ptype, delta);
|
||||
Cast(*fst_pushed_log, fst);
|
||||
delete fst_log;
|
||||
delete fst_pushed_log;
|
||||
}
|
||||
|
||||
// Minimizes after encoding; applicable to all FSTs. It is like what you get
|
||||
// from the Minimize() function, except it will not push the weights, or the
|
||||
// symbols. This is better for our recipes, as we avoid ever pushing the
|
||||
// weights. However, it will only minimize optimally if your graphs are such
|
||||
// that the symbols are as far to the left as they can go, and the weights
|
||||
// in combinable paths are the same... hard to formalize this, but it's something
|
||||
// that is satisified by our normal FSTs.
|
||||
template<class Arc>
|
||||
void MinimizeEncoded(VectorFst<Arc> *fst, float delta = kDelta) {
|
||||
|
||||
Map(fst, QuantizeMapper<Arc>(delta));
|
||||
EncodeMapper<Arc> encoder(kEncodeLabels | kEncodeWeights, ENCODE);
|
||||
Encode(fst, &encoder);
|
||||
internal::AcceptorMinimize(fst);
|
||||
Decode(fst, encoder);
|
||||
}
|
||||
|
||||
|
||||
/// GetLinearSymbolSequence gets the symbol sequence from a linear FST.
|
||||
/// If the FST is not just a linear sequence, it returns false. If it is
|
||||
/// a linear sequence (including the empty FST), it returns true. In this
|
||||
/// case it outputs the symbol
|
||||
/// sequences as "isymbols_out" and "osymbols_out" (removing epsilons), and
|
||||
/// the total weight as "tot_weight". The total weight will be Weight::Zero()
|
||||
/// if the FST is empty. If any of the output pointers are NULL, it does not
|
||||
/// create that output.
|
||||
template<class Arc, class I>
|
||||
bool GetLinearSymbolSequence(const Fst<Arc> &fst,
|
||||
std::vector<I> *isymbols_out,
|
||||
std::vector<I> *osymbols_out,
|
||||
typename Arc::Weight *tot_weight_out);
|
||||
|
||||
|
||||
/// This function converts an FST with a special structure, which is
|
||||
/// output by the OpenFst functions ShortestPath and RandGen, and converts
|
||||
/// them into a std::vector of separate FSTs. This special structure is that
|
||||
/// the only state that has more than one (arcs-out or final-prob) is the
|
||||
/// start state. fsts_out is resized to the appropriate size.
|
||||
template<class Arc>
|
||||
void ConvertNbestToVector(const Fst<Arc> &fst,
|
||||
std::vector<VectorFst<Arc> > *fsts_out);
|
||||
|
||||
|
||||
/// Takes the n-shortest-paths (using ShortestPath), but outputs
|
||||
/// the result as a vector of up to n fsts. This function will
|
||||
/// size the "fsts_out" vector to however many paths it got
|
||||
/// (which will not exceed n). n must be >= 1.
|
||||
template<class Arc>
|
||||
void NbestAsFsts(const Fst<Arc> &fst,
|
||||
size_t n,
|
||||
std::vector<VectorFst<Arc> > *fsts_out);
|
||||
|
||||
|
||||
|
||||
|
||||
/// Creates unweighted linear acceptor from symbol sequence.
|
||||
template<class Arc, class I>
|
||||
void MakeLinearAcceptor(const std::vector<I> &labels, MutableFst<Arc> *ofst);
|
||||
|
||||
|
||||
|
||||
/// Creates an unweighted acceptor with a linear structure, with alternatives
|
||||
/// at each position. Epsilon is treated like a normal symbol here.
|
||||
/// Each position in "labels" must have at least one alternative.
|
||||
template<class Arc, class I>
|
||||
void MakeLinearAcceptorWithAlternatives(const std::vector<std::vector<I> > &labels,
|
||||
MutableFst<Arc> *ofst);
|
||||
|
||||
|
||||
/// Does PreDeterminize and DeterminizeStar and then removes the disambiguation symbols.
|
||||
/// This is a form of determinization that will never blow up.
|
||||
/// Note that ifst is non-const and can be considered to be destroyed by this
|
||||
/// operation.
|
||||
/// Does not do epsilon removal (RemoveEpsLocal)-- this is so it's safe to cast to
|
||||
/// log and do this, and maintain equivalence in tropical.
|
||||
|
||||
template<class Arc>
|
||||
void SafeDeterminizeWrapper(MutableFst<Arc> *ifst, MutableFst<Arc> *ofst, float delta = kDelta);
|
||||
|
||||
|
||||
/// SafeDeterminizeMinimizeWapper is as SafeDeterminizeWrapper except that it also
|
||||
/// minimizes (encoded minimization, which is safe). This algorithm will destroy "ifst".
|
||||
template<class Arc>
|
||||
void SafeDeterminizeMinimizeWrapper(MutableFst<Arc> *ifst, VectorFst<Arc> *ofst, float delta = kDelta);
|
||||
|
||||
|
||||
/// SafeDeterminizeMinimizeWapperInLog is as SafeDeterminizeMinimizeWrapper except
|
||||
/// it first casts tothe log semiring.
|
||||
void SafeDeterminizeMinimizeWrapperInLog(VectorFst<StdArc> *ifst, VectorFst<StdArc> *ofst, float delta = kDelta);
|
||||
|
||||
|
||||
|
||||
/// RemoveSomeInputSymbols removes any symbol that appears in "to_remove", from
|
||||
/// the input side of the FST, replacing them with epsilon.
|
||||
template<class Arc, class I>
|
||||
void RemoveSomeInputSymbols(const std::vector<I> &to_remove,
|
||||
MutableFst<Arc> *fst);
|
||||
|
||||
// MapInputSymbols will replace any input symbol i that is between 0 and
|
||||
// symbol_map.size()-1, with symbol_map[i]. It removes the input symbol
|
||||
// table of the FST.
|
||||
template<class Arc, class I>
|
||||
void MapInputSymbols(const std::vector<I> &symbol_map,
|
||||
MutableFst<Arc> *fst);
|
||||
|
||||
|
||||
template<class Arc>
|
||||
void RemoveWeights(MutableFst<Arc> *fst);
|
||||
|
||||
|
||||
|
||||
|
||||
/// Returns true if and only if the FST is such that the input symbols
|
||||
/// on arcs entering any given state all have the same value.
|
||||
/// if "start_is_epsilon", treat start-state as an epsilon input arc
|
||||
/// [i.e. ensure only epsilon can enter start-state].
|
||||
template<class Arc>
|
||||
bool PrecedingInputSymbolsAreSame(bool start_is_epsilon, const Fst<Arc> &fst);
|
||||
|
||||
|
||||
/// This is as PrecedingInputSymbolsAreSame, but with a functor f that maps labels to classes.
|
||||
/// The function tests whether the symbols preceding any given state are in the same
|
||||
/// class.
|
||||
/// Formally, f is of a type F that has an operator of type
|
||||
/// F::Result F::operator() (F::Arg a) const;
|
||||
/// where F::Result is an integer type and F::Arc can be constructed from Arc::Label.
|
||||
/// this must apply to valid labels and also to kNoLabel (so we can have a marker for
|
||||
/// the invalid labels.
|
||||
template<class Arc, class F>
|
||||
bool PrecedingInputSymbolsAreSameClass(bool start_is_epsilon, const Fst<Arc> &fst, const F &f);
|
||||
|
||||
|
||||
/// Returns true if and only if the FST is such that the input symbols
|
||||
/// on arcs exiting any given state all have the same value.
|
||||
/// If end_is_epsilon, treat end-state as an epsilon output arc [i.e. ensure
|
||||
/// end-states cannot have non-epsilon output transitions.]
|
||||
template<class Arc>
|
||||
bool FollowingInputSymbolsAreSame(bool end_is_epsilon, const Fst<Arc> &fst);
|
||||
|
||||
|
||||
template<class Arc, class F>
|
||||
bool FollowingInputSymbolsAreSameClass(bool end_is_epsilon, const Fst<Arc> &fst, const F &f);
|
||||
|
||||
|
||||
/// MakePrecedingInputSymbolsSame ensures that all arcs entering any given fst
|
||||
/// state have the same input symbol. It does this by detecting states
|
||||
/// that have differing input symbols going in, and inserting, for each of
|
||||
/// the preceding arcs with non-epsilon input symbol, a new dummy state that
|
||||
/// has an epsilon link to the fst state.
|
||||
/// If "start_is_epsilon", ensure that start-state can have only epsilon-links
|
||||
/// into it.
|
||||
template<class Arc>
|
||||
void MakePrecedingInputSymbolsSame(bool start_is_epsilon, MutableFst<Arc> *fst);
|
||||
|
||||
|
||||
/// As MakePrecedingInputSymbolsSame, but takes a functor object that maps labels to classes.
|
||||
template<class Arc, class F>
|
||||
void MakePrecedingInputSymbolsSameClass(bool start_is_epsilon, MutableFst<Arc> *fst, const F &f);
|
||||
|
||||
|
||||
/// MakeFollowingInputSymbolsSame ensures that all arcs exiting any given fst
|
||||
/// state have the same input symbol. It does this by detecting states that have
|
||||
/// differing input symbols on arcs that exit it, and inserting, for each of the
|
||||
/// following arcs with non-epsilon input symbol, a new dummy state that has an
|
||||
/// input-epsilon link from the fst state. The output symbol and weight stay on the
|
||||
/// link to the dummy state (in order to keep the FST output-deterministic and
|
||||
/// stochastic, if it already was).
|
||||
/// If end_is_epsilon, treat "being a final-state" like having an epsilon output
|
||||
/// link.
|
||||
template<class Arc>
|
||||
void MakeFollowingInputSymbolsSame(bool end_is_epsilon, MutableFst<Arc> *fst);
|
||||
|
||||
/// As MakeFollowingInputSymbolsSame, but takes a functor object that maps labels to classes.
|
||||
template<class Arc, class F>
|
||||
void MakeFollowingInputSymbolsSameClass(bool end_is_epsilon, MutableFst<Arc> *fst, const F &f);
|
||||
|
||||
|
||||
|
||||
|
||||
/// MakeLoopFst creates an FST that has a state that is both initial and
|
||||
/// final (weight == Weight::One()), and for each non-NULL pointer fsts[i],
|
||||
/// it has an arc out whose output-symbol is i and which goes to a
|
||||
/// sub-graph whose input language is equivalent to fsts[i], where the
|
||||
/// final-state becomes a transition to the loop-state. Each fst in "fsts"
|
||||
/// should be an acceptor. The fst MakeLoopFst returns is output-deterministic,
|
||||
/// but not output-epsilon free necessarily, and arcs are sorted on output label.
|
||||
/// Note: if some of the pointers in the input vector "fsts" have the same
|
||||
/// value, "MakeLoopFst" uses this to speed up the computation.
|
||||
|
||||
/// Formally: suppose I is the set of indexes i such that fsts[i] != NULL.
|
||||
/// Let L[i] be the language that the acceptor fsts[i] accepts.
|
||||
/// Let the language K be the set of input-output pairs i:l such
|
||||
/// that i in I and l in L[i]. Then the FST returned by MakeLoopFst
|
||||
/// accepts the language K*, where * is the Kleene closure (CLOSURE_STAR)
|
||||
/// of K.
|
||||
|
||||
/// We could have implemented this via a combination of "project",
|
||||
/// "concat", "union" and "closure". But that FST would have been
|
||||
/// less well optimized and would have a lot of final-states.
|
||||
|
||||
template<class Arc>
|
||||
VectorFst<Arc>* MakeLoopFst(const std::vector<const ExpandedFst<Arc> *> &fsts);
|
||||
|
||||
|
||||
/// ApplyProbabilityScale is applicable to FSTs in the log or tropical semiring.
|
||||
/// It multiplies the arc and final weights by "scale" [this is not the Mul
|
||||
/// operation of the semiring, it's actual multiplication, which is equivalent
|
||||
/// to taking a power in the semiring].
|
||||
template<class Arc>
|
||||
void ApplyProbabilityScale(float scale, MutableFst<Arc> *fst);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// EqualAlign is similar to RandGen, but it generates a sequence with exactly "length"
|
||||
/// input symbols. It returns true on success, false on failure (failure is partly
|
||||
/// random but should never happen in practice for normal speech models.)
|
||||
/// It generates a random path through the input FST, finds out which subset of the
|
||||
/// states it visits along the way have self-loops with inupt symbols on them, and
|
||||
/// outputs a path with exactly enough self-loops to have the requested number
|
||||
/// of input symbols.
|
||||
/// Note that EqualAlign does not use the probabilities on the FST. It just uses
|
||||
/// equal probabilities in the first stage of selection (since the output will anyway
|
||||
/// not be a truly random sample from the FST).
|
||||
/// The input fst "ifst" must be connected or this may enter an infinite loop.
|
||||
template<class Arc>
|
||||
bool EqualAlign(const Fst<Arc> &ifst, typename Arc::StateId length,
|
||||
int rand_seed, MutableFst<Arc> *ofst, int num_retries = 10);
|
||||
|
||||
|
||||
|
||||
// RemoveUselessArcs removes arcs such that there is no input symbol
|
||||
// sequence for which the best path through the FST would contain
|
||||
// those arcs [for these purposes, epsilon is not treated as a real symbol].
|
||||
// This is mainly geared towards decoding-graph FSTs which may contain
|
||||
// transitions that have less likely words on them that would never be
|
||||
// taken. We do not claim that this algorithm removes all such arcs;
|
||||
// it just does the best job it can.
|
||||
// Only works for tropical (not log) semiring as it uses
|
||||
// NaturalLess.
|
||||
template<class Arc>
|
||||
void RemoveUselessArcs(MutableFst<Arc> *fst);
|
||||
|
||||
|
||||
// PhiCompose is a version of composition where
|
||||
// the right hand FST (fst2) is treated as a backoff
|
||||
// LM, with the phi symbol (e.g. #0) treated as a
|
||||
// "failure transition", only taken when we don't
|
||||
// have a match for the requested symbol.
|
||||
template<class Arc>
|
||||
void PhiCompose(const Fst<Arc> &fst1,
|
||||
const Fst<Arc> &fst2,
|
||||
typename Arc::Label phi_label,
|
||||
MutableFst<Arc> *fst);
|
||||
|
||||
// PropagateFinal propagates final-probs through
|
||||
// "phi" transitions (note that here, phi_label may
|
||||
// be epsilon if you want). If you have a backoff LM
|
||||
// with special symbols ("phi") on the backoff arcs
|
||||
// instead of epsilon, you may use PhiCompose to compose
|
||||
// with it, but this won't do the right thing w.r.t.
|
||||
// final probabilities. You should first call PropagateFinal
|
||||
// on the FST with phi's i it (fst2 in PhiCompose above),
|
||||
// to fix this. If a state does not have a final-prob,
|
||||
// but has a phi transition, it makes the state's final-prob
|
||||
// (phi-prob * final-prob-of-dest-state), and does this
|
||||
// recursively i.e. follows phi transitions on the dest state
|
||||
// first. It behaves as if there were a super-final state
|
||||
// with a special symbol leading to it, from each currently
|
||||
// final state. Note that this may not behave as desired
|
||||
// if there are epsilons in your FST; it might be better
|
||||
// to remove those before calling this function.
|
||||
|
||||
template<class Arc>
|
||||
void PropagateFinal(typename Arc::Label phi_label,
|
||||
MutableFst<Arc> *fst);
|
||||
|
||||
// RhoCompose is a version of composition where
|
||||
// the right hand FST (fst2) has speciall "rho transitions"
|
||||
// which are taken whenever no normal transition matches; these
|
||||
// transitions will be rewritten with whatever symbol was on
|
||||
// the first FST.
|
||||
template<class Arc>
|
||||
void RhoCompose(const Fst<Arc> &fst1,
|
||||
const Fst<Arc> &fst2,
|
||||
typename Arc::Label rho_label,
|
||||
MutableFst<Arc> *fst);
|
||||
|
||||
/** This function returns true if, in the semiring of the FST, the sum (within
|
||||
the semiring) of all the arcs out of each state in the FST is one, to within
|
||||
delta. After MakeStochasticFst, this should be true (for a connected FST).
|
||||
|
||||
@param fst [in] the FST that we are testing.
|
||||
@param delta [in] the tolerance to within which we test equality to 1.
|
||||
@param min_sum [out] if non, NULL, contents will be set to the minimum sum of weights.
|
||||
@param max_sum [out] if non, NULL, contents will be set to the maximum sum of weights.
|
||||
@return Returns true if the FST is stochastic, and false otherwise.
|
||||
*/
|
||||
|
||||
template<class Arc>
|
||||
bool IsStochasticFst(const Fst<Arc> &fst,
|
||||
float delta = kDelta, // kDelta = 1.0/1024.0 by default.
|
||||
typename Arc::Weight *min_sum = NULL,
|
||||
typename Arc::Weight *max_sum = NULL);
|
||||
|
||||
|
||||
|
||||
|
||||
// IsStochasticFstInLog makes sure it's stochastic after casting to log.
|
||||
inline bool IsStochasticFstInLog(const Fst<StdArc> &fst,
|
||||
float delta = kDelta, // kDelta = 1.0/1024.0 by default.
|
||||
StdArc::Weight *min_sum = NULL,
|
||||
StdArc::Weight *max_sum = NULL);
|
||||
|
||||
|
||||
} // end namespace fst
|
||||
|
||||
|
||||
#include "fstext/fstext-utils-inl.h"
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,228 @@
|
||||
// fstext/grammar-context-fst.cc
|
||||
|
||||
// Copyright 2018 Johns Hopkins University (author: Daniel Povey)
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "fstext/grammar-context-fst.h"
|
||||
#include "base/kaldi-error.h"
|
||||
#include "util/stl-utils.h"
|
||||
|
||||
namespace fst {
|
||||
using std::vector;
|
||||
|
||||
InverseLeftBiphoneContextFst::InverseLeftBiphoneContextFst(
|
||||
Label nonterm_phones_offset,
|
||||
const vector<int32>& phones,
|
||||
const vector<int32>& disambig_syms):
|
||||
nonterm_phones_offset_(nonterm_phones_offset),
|
||||
phone_syms_(phones),
|
||||
disambig_syms_(disambig_syms) {
|
||||
|
||||
{ // This block does some checks.
|
||||
std::vector<int32> all_inputs(phones);
|
||||
all_inputs.insert(all_inputs.end(), disambig_syms.begin(),
|
||||
disambig_syms.end());
|
||||
all_inputs.push_back(nonterm_phones_offset);
|
||||
size_t size = all_inputs.size();
|
||||
kaldi::SortAndUniq(&all_inputs);
|
||||
if (all_inputs.size() != size) {
|
||||
KALDI_ERR << "There was overlap between disambig symbols, phones, "
|
||||
"and/or --nonterm-phones-offset";
|
||||
}
|
||||
if (all_inputs.front() <= 0)
|
||||
KALDI_ERR << "Symbols <= 0 were passed in as phones, disambig-syms, "
|
||||
"or nonterm-phones-offset.";
|
||||
if (all_inputs.back() != nonterm_phones_offset) {
|
||||
// the value passed --nonterm-phones-offset is not higher numbered
|
||||
// than all the phones and disambig syms... do some more checking.
|
||||
for (int32 i = 1; i < 4; i++) {
|
||||
int32 symbol = nonterm_phones_offset + i;
|
||||
// None of the symbols --nonterm-phones-offset + {kNontermBos, kNontermBegin,
|
||||
// kNontermEnd, kNontermReenter, kNontermUserDefined}
|
||||
// (i.e. the special symbols plus the first user-defined symbol) may be
|
||||
// listed as phones or disambig symbols... this doesn't make sense. We
|
||||
// do allow disambig symbols to be higher-numbered than the nonterminal
|
||||
// sybols, just in case that happens to be needed, but they can't overlap.
|
||||
if (std::binary_search(all_inputs.begin(), all_inputs.end(), symbol)) {
|
||||
KALDI_ERR << "The symbol " << symbol
|
||||
<< " = --nonterm-phones-offset + " << i
|
||||
<< " was listed as a phone or disambig symbol.";
|
||||
}
|
||||
}
|
||||
}
|
||||
if (phone_syms_.empty())
|
||||
KALDI_WARN << "Context FST created but there are no phone symbols: probably "
|
||||
"input FST was empty.";
|
||||
}
|
||||
|
||||
// empty vector, will be the ilabel_info vector that corresponds to epsilon,
|
||||
// in case our FST needs to output epsilons.
|
||||
vector<int32> empty_vec;
|
||||
Label epsilon_label = FindLabel(empty_vec);
|
||||
// Make sure that a label is assigned for epsilon.
|
||||
KALDI_ASSERT(epsilon_label == 0);
|
||||
}
|
||||
|
||||
|
||||
InverseLeftBiphoneContextFst::Weight InverseLeftBiphoneContextFst::Final(StateId s) {
|
||||
if (s == 0 || phone_syms_.count(s) != 0 ||
|
||||
s == GetPhoneSymbolFor(kNontermEnd))
|
||||
return Weight::One();
|
||||
else
|
||||
return Weight::Zero();
|
||||
}
|
||||
|
||||
bool InverseLeftBiphoneContextFst::GetArc(
|
||||
StateId s, Label ilabel, Arc *arc) {
|
||||
// it's a rule of the DeterministicOnDemandFst that the ilabel cannot be zero.q
|
||||
KALDI_ASSERT(ilabel != 0);
|
||||
|
||||
arc->ilabel = ilabel;
|
||||
arc->weight = Weight::One();
|
||||
|
||||
if (s == 0 || phone_syms_.count(s) != 0) {
|
||||
// This is an epsilon or phone state.
|
||||
if (phone_syms_.count(ilabel) != 0) {
|
||||
// The ilabel is a phone.
|
||||
std::vector<int32> context_window(2);
|
||||
context_window[0] = s;
|
||||
context_window[1] = ilabel;
|
||||
arc->olabel = FindLabel(context_window);
|
||||
arc->nextstate = ilabel;
|
||||
return true;
|
||||
} else if (disambig_syms_.count(ilabel) != 0) {
|
||||
// the ilabel is a disambiguation symbol. Make a self-loop arc that
|
||||
// replicates the disambiguation symbol on the input.
|
||||
// The ilabel-info vector for disambig symbols is just a single element
|
||||
// consisting of the negative of the disambig symbols (for easier
|
||||
// identification from code).
|
||||
std::vector<int32> this_ilabel_info(1);
|
||||
this_ilabel_info[0] = -ilabel;
|
||||
arc->olabel = FindLabel(this_ilabel_info);
|
||||
arc->nextstate = s;
|
||||
return true;
|
||||
} else if (ilabel == GetPhoneSymbolFor(kNontermBegin) &&
|
||||
s == 0) {
|
||||
// We were at the start state and saw the symbol #nonterm_begin.
|
||||
// Output nothing, but transition to the special #nonterm_begin state.
|
||||
// when we're in that state, arcs for phones generate special
|
||||
// osymbols corresponding to pairs like (#nonterm_begin, p1).
|
||||
arc->olabel = 0;
|
||||
arc->nextstate = GetPhoneSymbolFor(kNontermBegin);
|
||||
return true;
|
||||
} else if (ilabel == GetPhoneSymbolFor(kNontermEnd)) {
|
||||
// we saw #nonterm_end.
|
||||
std::vector<int32> this_ilabel_info(2);
|
||||
this_ilabel_info[0] = -(GetPhoneSymbolFor(kNontermEnd));
|
||||
this_ilabel_info[1] = (s != 0 ? s : GetPhoneSymbolFor(kNontermBos));
|
||||
arc->olabel = FindLabel(this_ilabel_info);
|
||||
arc->nextstate = GetPhoneSymbolFor(kNontermEnd);
|
||||
return true;
|
||||
} else if (ilabel >= GetPhoneSymbolFor(kNontermUserDefined)) {
|
||||
// Assume this ilabel is a user-defined nonterminal.
|
||||
// Transition to the state kNontermUserDefined, with an olabel
|
||||
// (#nonterm:foo, p1) where 'p1' is the current left-context.
|
||||
std::vector<int32> this_ilabel_info(2);
|
||||
this_ilabel_info[0] = -ilabel;
|
||||
this_ilabel_info[1] = (s != 0 ? s : GetPhoneSymbolFor(kNontermBos));
|
||||
arc->olabel = FindLabel(this_ilabel_info);
|
||||
// the destination state is not specific to this user-defined symbol, it's
|
||||
// a generic destination state.
|
||||
arc->nextstate = GetPhoneSymbolFor(kNontermUserDefined);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else if (s == GetPhoneSymbolFor(kNontermBegin)) {
|
||||
if (phone_syms_.count(ilabel) != 0 || ilabel == GetPhoneSymbolFor(kNontermBos)) {
|
||||
std::vector<int32> this_ilabel_info(2);
|
||||
this_ilabel_info[0] = -GetPhoneSymbolFor(kNontermBegin);
|
||||
this_ilabel_info[1] = ilabel;
|
||||
arc->nextstate = (ilabel == GetPhoneSymbolFor(kNontermBos) ? 0 : ilabel);
|
||||
arc->olabel = FindLabel(this_ilabel_info);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else if (s == GetPhoneSymbolFor(kNontermEnd)) {
|
||||
return false;
|
||||
} else if (s == GetPhoneSymbolFor(kNontermUserDefined)) {
|
||||
if (phone_syms_.count(ilabel) != 0 || ilabel == GetPhoneSymbolFor(kNontermBos)) {
|
||||
std::vector<int32> this_ilabel_info(2);
|
||||
this_ilabel_info[0] = -GetPhoneSymbolFor(kNontermReenter);
|
||||
this_ilabel_info[1] = ilabel;
|
||||
arc->nextstate = (ilabel == GetPhoneSymbolFor(kNontermBos) ? 0 : ilabel);
|
||||
arc->olabel = FindLabel(this_ilabel_info);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// likely code error.
|
||||
KALDI_ERR << "Invalid state encountered";
|
||||
return false; // won't get here. suppress compiler error.
|
||||
}
|
||||
}
|
||||
|
||||
StdArc::Label InverseLeftBiphoneContextFst::FindLabel(const vector<int32> &label_vec) {
|
||||
// Finds the ilabel corresponding to this vector (creates a new ilabel if
|
||||
// necessary).
|
||||
VectorToLabelMap::const_iterator iter = ilabel_map_.find(label_vec);
|
||||
if (iter == ilabel_map_.end()) { // Not already in map.
|
||||
Label this_label = ilabel_info_.size();
|
||||
ilabel_info_.push_back(label_vec);
|
||||
ilabel_map_[label_vec] = this_label;
|
||||
return this_label;
|
||||
} else {
|
||||
return iter->second;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ComposeContextLeftBiphone(
|
||||
int32 nonterm_phones_offset,
|
||||
const vector<int32> &disambig_syms_in,
|
||||
const VectorFst<StdArc> &ifst,
|
||||
VectorFst<StdArc> *ofst,
|
||||
std::vector<std::vector<int32> > *ilabels) {
|
||||
|
||||
vector<int32> disambig_syms(disambig_syms_in);
|
||||
std::sort(disambig_syms.begin(), disambig_syms.end());
|
||||
|
||||
vector<int32> all_syms;
|
||||
GetInputSymbols(ifst, false/*no eps*/, &all_syms);
|
||||
std::sort(all_syms.begin(), all_syms.end());
|
||||
vector<int32> phones;
|
||||
for (size_t i = 0; i < all_syms.size(); i++)
|
||||
if (!std::binary_search(disambig_syms.begin(),
|
||||
disambig_syms.end(), all_syms[i]) &&
|
||||
all_syms[i] < nonterm_phones_offset)
|
||||
phones.push_back(all_syms[i]);
|
||||
|
||||
|
||||
InverseLeftBiphoneContextFst inv_c(nonterm_phones_offset,
|
||||
phones, disambig_syms);
|
||||
|
||||
// The following statement is equivalent to the following
|
||||
// (if FSTs had the '*' operator for composition):
|
||||
// (*ofst) = inv(inv_c) * (*ifst)
|
||||
ComposeDeterministicOnDemandInverse(ifst, &inv_c, ofst);
|
||||
|
||||
inv_c.SwapIlabelInfo(ilabels);
|
||||
}
|
||||
|
||||
} // end namespace fst
|
||||
@@ -0,0 +1,287 @@
|
||||
// fstext/grammar-context-fst.h
|
||||
|
||||
// Copyright 2018 Johns Hopkins University (author: Daniel Povey)
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
|
||||
#ifndef KALDI_FSTEXT_GRAMMAR_CONTEXT_FST_H_
|
||||
#define KALDI_FSTEXT_GRAMMAR_CONTEXT_FST_H_
|
||||
|
||||
/* This header defines a special form of the context FST "C" (the "C" in "HCLG")
|
||||
that integrates with our framework for building dynamic graphs for grammars
|
||||
that are too big to statically create, or graphs with on-the-fly pieces that
|
||||
you want to create at recognition time without building the whole graph.
|
||||
|
||||
This framework is limited to only work with models with left-biphone context.
|
||||
(Fortunately this doesn't impact results, as our best models are all 'chain'
|
||||
models with left biphone context).
|
||||
|
||||
The main code exported from here is the class InverseLeftBiphoneContextFst,
|
||||
which is similar to the InverseContextFst defined in context-fst.h, but
|
||||
is limited to left-biphone context and also supports certain special
|
||||
extensions we need to compile grammars.
|
||||
|
||||
See \ref grammar (../doc/grammar.dox) for high-level
|
||||
documentation on how this framework works.
|
||||
*/
|
||||
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <fst/fstlib.h>
|
||||
#include <fst/fst-decl.h>
|
||||
|
||||
#include "util/const-integer-set.h"
|
||||
#include "fstext/deterministic-fst.h"
|
||||
#include "fstext/context-fst.h"
|
||||
|
||||
namespace fst {
|
||||
|
||||
|
||||
/**
|
||||
An anonymous enum to define some values for symbols used in our grammar-fst
|
||||
framework. Please understand this with reference to the documentation in
|
||||
\ref grammar (../doc/grammar.dox). This enum defines
|
||||
the values of nonterminal-related symbols in phones.txt. They are not
|
||||
the actual values-- they will be shifted by adding the value
|
||||
nonterm_phones_offset which is passed in by the command-line flag
|
||||
--nonterm-phones-offset.
|
||||
|
||||
*/
|
||||
|
||||
enum NonterminalValues {
|
||||
kNontermBos = 0, // #nonterm_bos
|
||||
kNontermBegin = 1, // #nonterm_begin
|
||||
kNontermEnd = 2, // #nonterm_end
|
||||
kNontermReenter = 3, // #nonterm_reenter
|
||||
kNontermUserDefined = 4, // the lowest-numbered user-defined nonterminal, e.g. #nonterm:foo
|
||||
// kNontermMediumNumber and kNontermBigNumber come into the encoding of
|
||||
// nonterminal-related symbols in HCLG.fst. The only hard constraint on them
|
||||
// is that kNontermBigNumber must be bigger than the biggest transition-id in
|
||||
// your system, and kNontermMediumNumber must be >0. These values were chosen
|
||||
// for ease of human inspection of numbers encoded with them.
|
||||
kNontermMediumNumber = 1000,
|
||||
kNontermBigNumber = 10000000
|
||||
};
|
||||
|
||||
|
||||
|
||||
// Returns the smallest multiple of 1000 that is strictly greater than
|
||||
// nonterm_phones_offset. Used in the encoding of special symbol in HCLG;
|
||||
// they are encoded as
|
||||
// special_symbol =
|
||||
// kNontermBigNumber + (nonterminal * encoding_multiple) + phone_index
|
||||
inline int32 GetEncodingMultiple(int32 nonterm_phones_offset) {
|
||||
int32 medium_number = static_cast<int32>(kNontermMediumNumber);
|
||||
return medium_number *
|
||||
((nonterm_phones_offset + medium_number) / medium_number);
|
||||
}
|
||||
|
||||
/**
|
||||
This is a variant of the function ComposeContext() which is to be used
|
||||
with our "grammar FST" framework (see \ref graph_context, i.e.
|
||||
../doc/grammar.dox, for more details). This does not take
|
||||
the 'context_width' and 'central_position' arguments because they are
|
||||
assumed to be 2 and 1 respectively (meaning, left-biphone phonetic context).
|
||||
|
||||
This function creates a context FST and composes it on the left with "ifst"
|
||||
to make "ofst".
|
||||
|
||||
@param [in] nonterm_phones_offset The integer id of the symbol
|
||||
#nonterm_bos in the phones.txt file. You can just set this
|
||||
to a large value (like 1 million) if you are not actually using
|
||||
nonterminals (e.g. for testing purposes).
|
||||
@param [in] disambig_syms List of disambiguation symbols, e.g. the integer
|
||||
ids of #0, #1, #2 ... in the phones.txt.
|
||||
@param [in,out] ifst The FST we are composing with C (e.g. LG.fst).
|
||||
@param [out] ofst Composed output FST (would be CLG.fst).
|
||||
@param [out] ilabels Vector, indexed by ilabel of CLG.fst, providing information
|
||||
about the meaning of that ilabel; see \ref tree_ilabel
|
||||
(http://kaldi-asr.org/doc/tree_externals.html#tree_ilabel)
|
||||
and also \ref grammar_special_clg
|
||||
(http://kaldi-asr.org/doc/grammar#grammar_special_clg).
|
||||
*/
|
||||
void ComposeContextLeftBiphone(
|
||||
int32 nonterm_phones_offset,
|
||||
const std::vector<int32> &disambig_syms,
|
||||
const VectorFst<StdArc> &ifst,
|
||||
VectorFst<StdArc> *ofst,
|
||||
std::vector<std::vector<int32> > *ilabels);
|
||||
|
||||
|
||||
|
||||
/*
|
||||
InverseLeftBiphoneContextFst represents the inverse of the context FST "C" (the "C" in
|
||||
"HCLG") which transduces from symbols representing phone context windows
|
||||
(e.g. "a, b, c") to individual phones, e.g. "a". So InverseContextFst
|
||||
transduces from phones to symbols representing phone context windows. The
|
||||
point is that the inverse is deterministic, so the DeterministicOnDemandFst
|
||||
interface is applicable, which turns out to be a convenient way to implement
|
||||
this.
|
||||
|
||||
This doesn't implement the full Fst interface, it implements the
|
||||
DeterministicOnDemandFst interface which is much simpler and which is
|
||||
sufficient for what we need to do with this.
|
||||
|
||||
Search for "hbka.pdf" ("Speech Recognition with Weighted Finite State
|
||||
Transducers") by M. Mohri, for more context.
|
||||
*/
|
||||
|
||||
class InverseLeftBiphoneContextFst: public DeterministicOnDemandFst<StdArc> {
|
||||
public:
|
||||
typedef StdArc Arc;
|
||||
typedef typename StdArc::StateId StateId;
|
||||
typedef typename StdArc::Weight Weight;
|
||||
typedef typename StdArc::Label Label;
|
||||
|
||||
/**
|
||||
Constructor. This does not take the arguments 'context_width' or
|
||||
'central_position' because they are assumed to be (2, 1) meaning a
|
||||
system with left-biphone context; and there is no subsequential
|
||||
symbol because it is not needed in systems without right context.
|
||||
|
||||
@param [in] nonterm_phones_offset The integer id of the symbol
|
||||
#nonterm_bos in the phones.txt file. You can just set this to
|
||||
a large value (like 1 million) if you are not actually using
|
||||
nonterminals (e.g. for testing purposes).
|
||||
@param [in] phones List of integer ids of phones, as you would see in phones.txt
|
||||
@param [in] disambig_syms List of integer ids of disambiguation symbols,
|
||||
e.g. the ids of #0, #1, #2 in phones.txt
|
||||
|
||||
See \ref graph_context for more details.
|
||||
*/
|
||||
InverseLeftBiphoneContextFst(Label nonterm_phones_offset,
|
||||
const std::vector<int32>& phones,
|
||||
const std::vector<int32>& disambig_syms);
|
||||
|
||||
/**
|
||||
Here is a note on the state space of InverseLeftBiphoneContextFst;
|
||||
see \ref grammar_special_c which has some documentation on this.
|
||||
|
||||
The state space uses the same numbering as phones.txt.
|
||||
|
||||
State 0 means the beginning-of-sequence state, where there is no left
|
||||
context.
|
||||
|
||||
For each phone p in the list 'phones' passed to the constructor (i.e. in
|
||||
the set passed to the constructor), the state 'p' corresponds to a
|
||||
left-context of that phone.
|
||||
|
||||
If p is equal to nonterm_phones_offset_ + kNontermBegin (i.e. the
|
||||
integer form of `\#nonterm_begin`), then this is the state we transition
|
||||
to when we see that symbol starting from left-context==0 (no context). The
|
||||
transition to this special state will have epsilon on the output. (talking
|
||||
here about inv(C), not C, so input/output are reversed).
|
||||
The state is nonfinal and when we see a regular phone p1 or #nonterm_bos, instead of
|
||||
outputting that phone in context, we output the pair (#nonterm_begin,p1) or
|
||||
(#nonterm_begin,#nonterm_bos). This state is not final.
|
||||
|
||||
If p is equal to nonterm_phones_offset_ + kNontermUserDefined, then this
|
||||
is the state we transition to when we see any user-defined nonterminal.
|
||||
Transitions to this special state have olabels of the form (#nonterm:foo,p1)
|
||||
where p1 is the preceding context (with #nonterm_begin if that context was
|
||||
0); transitions out of it have olabels of the form (#nonterm_reenter,p2), where
|
||||
p2 is the phone on the ilabel of that transition. Again: talking about inv(C).
|
||||
This state is not final.
|
||||
|
||||
If p is equal to nonterm_phones_offset_ + kNontermEnd, then this is
|
||||
the state we transition to when we see the ilabel #nonterm_end. The olabels
|
||||
on the transitions to it (talking here about inv(C), so ilabels and olabels
|
||||
are reversed) are of the form (#nonterm_end, p1) where p1 corresponds to the
|
||||
context we were in. This state is final.
|
||||
*/
|
||||
|
||||
|
||||
virtual StateId Start() { return 0; }
|
||||
|
||||
virtual Weight Final(StateId s);
|
||||
|
||||
/// Note: ilabel must not be epsilon.
|
||||
virtual bool GetArc(StateId s, Label ilabel, Arc *arc);
|
||||
|
||||
~InverseLeftBiphoneContextFst() { }
|
||||
|
||||
// Returns a reference to a vector<vector<int32> > with information about all
|
||||
// the input symbols of C (i.e. all the output symbols of this
|
||||
// InverseContextFst). See
|
||||
// "http://kaldi-asr.org/doc/tree_externals.html#tree_ilabel".
|
||||
const std::vector<std::vector<int32> > &IlabelInfo() const {
|
||||
return ilabel_info_;
|
||||
}
|
||||
|
||||
// A way to destructively obtain the ilabel-info. Only do this if you
|
||||
// are just about to destroy this object.
|
||||
void SwapIlabelInfo(std::vector<std::vector<int32> > *vec) { ilabel_info_.swap(*vec); }
|
||||
|
||||
private:
|
||||
|
||||
inline int32 GetPhoneSymbolFor(enum NonterminalValues n) {
|
||||
return nonterm_phones_offset_ + static_cast<int32>(n);
|
||||
}
|
||||
|
||||
/// Finds the label index corresponding to this context-window of phones
|
||||
/// (likely of width context_width_). Inserts it into the
|
||||
/// ilabel_info_/ilabel_map_ tables if necessary.
|
||||
Label FindLabel(const std::vector<int32> &label_info);
|
||||
|
||||
|
||||
// Map type to map from vectors of int32 (representing ilabel-info,
|
||||
// see http://kaldi-asr.org/doc/tree_externals.html#tree_ilabel) to
|
||||
// Label (the output label in this FST).
|
||||
typedef unordered_map<std::vector<int32>, Label,
|
||||
kaldi::VectorHasher<int32> > VectorToLabelMap;
|
||||
|
||||
|
||||
// The following three variables were also passed in by the caller:
|
||||
int32 nonterm_phones_offset_;
|
||||
|
||||
// 'phone_syms_' are a set of phone-ids, typically 1, 2, .. num_phones.
|
||||
kaldi::ConstIntegerSet<Label> phone_syms_;
|
||||
|
||||
// disambig_syms_ is the set of integer ids of the disambiguation symbols,
|
||||
// usually represented in text form as #0, #1, #2, etc. These are inserted
|
||||
// into the grammar (for #0) and the lexicon (for #1, #2, ...) in order to
|
||||
// make the composed FSTs determinizable. They are treated "specially" by the
|
||||
// context FST in that they are not part of the context, they are just "passed
|
||||
// through" via self-loops. See the Mohri chapter mrentioned above for more
|
||||
// information.
|
||||
kaldi::ConstIntegerSet<Label> disambig_syms_;
|
||||
|
||||
|
||||
// maps from vector<int32>, representing phonetic contexts of length
|
||||
// context_width_ - 1, to Label. These are actually the output labels of this
|
||||
// InverseContextFst (because of the "Inverse" part), but for historical
|
||||
// reasons and because we've used the term ilabels" in the documentation, we
|
||||
// still call these "ilabels").
|
||||
VectorToLabelMap ilabel_map_;
|
||||
|
||||
// ilabel_info_ is the reverse map of ilabel_map_.
|
||||
// Indexed by olabel (although we call this ilabel_info_ for historical
|
||||
// reasons and because is for the ilabels of C), ilabel_info_[i] gives
|
||||
// information about the meaning of each symbol on the input of C
|
||||
// aka the output of inv(C).
|
||||
// See "http://kaldi-asr.org/doc/tree_externals.html#tree_ilabel".
|
||||
std::vector<std::vector<int32> > ilabel_info_;
|
||||
|
||||
};
|
||||
|
||||
} // namespace fst
|
||||
|
||||
|
||||
#endif // KALDI_FSTEXT_GRAMMAR_CONTEXT_FST_H_
|
||||
@@ -0,0 +1,211 @@
|
||||
// fstext/kaldi-fst-io-inl.h
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation
|
||||
// 2012-2015 Johns Hopkins University (Author: Daniel Povey)
|
||||
// 2013 Guoguo Chen
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef KALDI_FSTEXT_KALDI_FST_IO_INL_H_
|
||||
#define KALDI_FSTEXT_KALDI_FST_IO_INL_H_
|
||||
|
||||
#include "util/text-utils.h"
|
||||
|
||||
namespace fst {
|
||||
|
||||
|
||||
template <class Arc>
|
||||
void WriteFstKaldi(std::ostream &os, bool binary,
|
||||
const VectorFst<Arc> &t) {
|
||||
bool ok;
|
||||
if (binary) {
|
||||
// Binary-mode writing.
|
||||
ok = t.Write(os, FstWriteOptions());
|
||||
} else {
|
||||
// Text-mode output. Note: we expect that t.InputSymbols() and
|
||||
// t.OutputSymbols() would always return NULL. The corresponding input
|
||||
// routine would not work if the FST actually had symbols attached. Write a
|
||||
// newline to start the FST; in a table, the first line of the FST will
|
||||
// appear on its own line.
|
||||
os << '\n';
|
||||
bool acceptor = false, write_one = false;
|
||||
FstPrinter<Arc> printer(t, t.InputSymbols(), t.OutputSymbols(),
|
||||
NULL, acceptor, write_one, "\t");
|
||||
printer.Print(&os, "<unknown>");
|
||||
if (os.fail())
|
||||
KALDI_ERR << "Stream failure detected writing FST to stream";
|
||||
// Write another newline as a terminating character. The read routine will
|
||||
// detect this [this is a Kaldi mechanism, not something in the original
|
||||
// OpenFst code].
|
||||
os << '\n';
|
||||
ok = os.good();
|
||||
}
|
||||
if (!ok) {
|
||||
KALDI_ERR << "Error writing FST to stream";
|
||||
}
|
||||
}
|
||||
|
||||
// Utility function used in ReadFstKaldi
|
||||
template <class W>
|
||||
inline bool StrToWeight(const std::string &s, bool allow_zero, W *w) {
|
||||
std::istringstream strm(s);
|
||||
strm >> *w;
|
||||
if (strm.fail() || (!allow_zero && *w == W::Zero())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class Arc>
|
||||
void ReadFstKaldi(std::istream &is, bool binary,
|
||||
VectorFst<Arc> *fst) {
|
||||
typedef typename Arc::Weight Weight;
|
||||
typedef typename Arc::StateId StateId;
|
||||
if (binary) {
|
||||
// We don't have access to the filename here, so write [unknown].
|
||||
VectorFst<Arc> *ans =
|
||||
VectorFst<Arc>::Read(is, fst::FstReadOptions(std::string("[unknown]")));
|
||||
if (ans == NULL) {
|
||||
KALDI_ERR << "Error reading FST from stream.";
|
||||
}
|
||||
*fst = *ans; // shallow copy.
|
||||
delete ans;
|
||||
} else {
|
||||
// Consume the \r on Windows, the \n that the text-form FST format starts
|
||||
// with, and any extra spaces that might have got in there somehow.
|
||||
while (std::isspace(is.peek()) && is.peek() != '\n') is.get();
|
||||
if (is.peek() == '\n') is.get(); // consume the newline.
|
||||
else { // saw spaces but no newline.. this is not expected.
|
||||
KALDI_ERR << "Reading FST: unexpected sequence of spaces "
|
||||
<< " at file position " << is.tellg();
|
||||
}
|
||||
using std::string;
|
||||
using std::vector;
|
||||
using kaldi::SplitStringToIntegers;
|
||||
using kaldi::ConvertStringToInteger;
|
||||
fst->DeleteStates();
|
||||
string line;
|
||||
size_t nline = 0;
|
||||
string separator = FLAGS_fst_field_separator + "\r\n";
|
||||
while (std::getline(is, line)) {
|
||||
nline++;
|
||||
vector<string> col;
|
||||
// on Windows we'll write in text and read in binary mode.
|
||||
kaldi::SplitStringToVector(line, separator.c_str(), true, &col);
|
||||
if (col.size() == 0) break; // Empty line is a signal to stop, in our
|
||||
// archive format.
|
||||
if (col.size() > 5) {
|
||||
KALDI_ERR << "Bad line in FST: " << line;
|
||||
}
|
||||
StateId s;
|
||||
if (!ConvertStringToInteger(col[0], &s)) {
|
||||
KALDI_ERR << "Bad line in FST: " << line;
|
||||
}
|
||||
while (s >= fst->NumStates())
|
||||
fst->AddState();
|
||||
if (nline == 1) fst->SetStart(s);
|
||||
|
||||
bool ok = true;
|
||||
Arc arc;
|
||||
Weight w;
|
||||
StateId d = s;
|
||||
switch (col.size()) {
|
||||
case 1:
|
||||
fst->SetFinal(s, Weight::One());
|
||||
break;
|
||||
case 2:
|
||||
if (!StrToWeight(col[1], true, &w)) ok = false;
|
||||
else fst->SetFinal(s, w);
|
||||
break;
|
||||
case 3: // 3 columns not ok for Lattice format; it's not an acceptor.
|
||||
ok = false;
|
||||
break;
|
||||
case 4:
|
||||
ok = ConvertStringToInteger(col[1], &arc.nextstate) &&
|
||||
ConvertStringToInteger(col[2], &arc.ilabel) &&
|
||||
ConvertStringToInteger(col[3], &arc.olabel);
|
||||
if (ok) {
|
||||
d = arc.nextstate;
|
||||
arc.weight = Weight::One();
|
||||
fst->AddArc(s, arc);
|
||||
}
|
||||
break;
|
||||
case 5:
|
||||
ok = ConvertStringToInteger(col[1], &arc.nextstate) &&
|
||||
ConvertStringToInteger(col[2], &arc.ilabel) &&
|
||||
ConvertStringToInteger(col[3], &arc.olabel) &&
|
||||
StrToWeight(col[4], false, &arc.weight);
|
||||
if (ok) {
|
||||
d = arc.nextstate;
|
||||
fst->AddArc(s, arc);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ok = false;
|
||||
}
|
||||
while (d >= fst->NumStates()) fst->AddState();
|
||||
if (!ok)
|
||||
KALDI_ERR << "Bad line in FST: " << line;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
template<class Arc> // static
|
||||
bool VectorFstTplHolder<Arc>::Write(std::ostream &os, bool binary, const T &t) {
|
||||
try {
|
||||
WriteFstKaldi(os, binary, t);
|
||||
return true;
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
template<class Arc> // static
|
||||
bool VectorFstTplHolder<Arc>::Read(std::istream &is) {
|
||||
Clear();
|
||||
int c = is.peek();
|
||||
if (c == -1) {
|
||||
KALDI_WARN << "End of stream detected reading Fst";
|
||||
return false;
|
||||
} else if (isspace(c)) { // The text form of the FST begins
|
||||
// with space (normally, '\n'), so this means it's text (the binary form
|
||||
// cannot begin with space because it starts with the FST Type() which is not
|
||||
// space).
|
||||
try {
|
||||
t_ = new VectorFst<Arc>();
|
||||
ReadFstKaldi(is, false, t_);
|
||||
} catch (...) {
|
||||
Clear();
|
||||
return false;
|
||||
}
|
||||
} else { // reading a binary FST.
|
||||
try {
|
||||
t_ = new VectorFst<Arc>();
|
||||
ReadFstKaldi(is, true, t_);
|
||||
} catch (...) {
|
||||
Clear();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace fst.
|
||||
|
||||
#endif // KALDI_FSTEXT_KALDI_FST_IO_INL_H_
|
||||
@@ -0,0 +1,145 @@
|
||||
// fstext/kaldi-fst-io.cc
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation
|
||||
// 2012-2015 Johns Hopkins University (Author: Daniel Povey)
|
||||
// 2013 Guoguo Chen
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "fstext/kaldi-fst-io.h"
|
||||
#include "base/kaldi-error.h"
|
||||
#include "base/kaldi-math.h"
|
||||
#include "util/kaldi-io.h"
|
||||
|
||||
namespace fst {
|
||||
|
||||
VectorFst<StdArc> *ReadFstKaldi(std::string rxfilename) {
|
||||
if (rxfilename == "") rxfilename = "-"; // interpret "" as stdin,
|
||||
// for compatibility with OpenFst conventions.
|
||||
kaldi::Input ki(rxfilename);
|
||||
fst::FstHeader hdr;
|
||||
if (!hdr.Read(ki.Stream(), rxfilename))
|
||||
KALDI_ERR << "Reading FST: error reading FST header from "
|
||||
<< kaldi::PrintableRxfilename(rxfilename);
|
||||
FstReadOptions ropts("<unspecified>", &hdr);
|
||||
VectorFst<StdArc> *fst = VectorFst<StdArc>::Read(ki.Stream(), ropts);
|
||||
if (!fst)
|
||||
KALDI_ERR << "Could not read fst from "
|
||||
<< kaldi::PrintableRxfilename(rxfilename);
|
||||
return fst;
|
||||
}
|
||||
|
||||
// Register const fst to load it automatically. Other types like
|
||||
// olabel_lookahead or ngram or compact_fst should be registered
|
||||
// through OpenFst registration API.
|
||||
static fst::FstRegisterer<VectorFst<StdArc>> VectorFst_StdArc_registerer;
|
||||
static fst::FstRegisterer<ConstFst<StdArc>> ConstFst_StdArc_registerer;
|
||||
|
||||
Fst<StdArc> *ReadFstKaldiGeneric(std::string rxfilename, bool throw_on_err) {
|
||||
if (rxfilename == "") rxfilename = "-"; // interpret "" as stdin,
|
||||
// for compatibility with OpenFst conventions.
|
||||
kaldi::Input ki(rxfilename);
|
||||
fst::FstHeader hdr;
|
||||
// Read FstHeader which contains the type of FST
|
||||
if (!hdr.Read(ki.Stream(), rxfilename)) {
|
||||
if(throw_on_err) {
|
||||
KALDI_ERR << "Reading FST: error reading FST header from "
|
||||
<< kaldi::PrintableRxfilename(rxfilename);
|
||||
} else {
|
||||
KALDI_WARN << "We fail to read FST header from "
|
||||
<< kaldi::PrintableRxfilename(rxfilename)
|
||||
<< ". A NULL pointer is returned.";
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
// Check the type of Arc
|
||||
if (hdr.ArcType() != fst::StdArc::Type()) {
|
||||
if(throw_on_err) {
|
||||
KALDI_ERR << "FST with arc type " << hdr.ArcType() << " is not supported.";
|
||||
} else {
|
||||
KALDI_WARN << "Fst with arc type" << hdr.ArcType()
|
||||
<< " is not supported. A NULL pointer is returned.";
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
// Read the FST
|
||||
FstReadOptions ropts("<unspecified>", &hdr);
|
||||
Fst<StdArc> *fst = Fst<StdArc>::Read(ki.Stream(), ropts);
|
||||
if (!fst) {
|
||||
if(throw_on_err) {
|
||||
KALDI_ERR << "Could not read fst from "
|
||||
<< kaldi::PrintableRxfilename(rxfilename);
|
||||
} else {
|
||||
KALDI_WARN << "Could not read fst from "
|
||||
<< kaldi::PrintableRxfilename(rxfilename)
|
||||
<< ". A NULL pointer is returned.";
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return fst;
|
||||
}
|
||||
|
||||
VectorFst<StdArc> *CastOrConvertToVectorFst(Fst<StdArc> *fst) {
|
||||
// This version currently supports ConstFst<StdArc> or VectorFst<StdArc>
|
||||
std::string real_type = fst->Type();
|
||||
KALDI_ASSERT(real_type == "vector" || real_type == "const");
|
||||
if (real_type == "vector") {
|
||||
return dynamic_cast<VectorFst<StdArc> *>(fst);
|
||||
} else {
|
||||
// As the 'fst' can't cast to VectorFst, we create a new
|
||||
// VectorFst<StdArc> initialized by 'fst', and delete 'fst'.
|
||||
VectorFst<StdArc> *new_fst = new VectorFst<StdArc>(*fst);
|
||||
delete fst;
|
||||
return new_fst;
|
||||
}
|
||||
}
|
||||
|
||||
void ReadFstKaldi(std::string rxfilename, fst::StdVectorFst *ofst) {
|
||||
fst::StdVectorFst *fst = ReadFstKaldi(rxfilename);
|
||||
*ofst = *fst;
|
||||
delete fst;
|
||||
}
|
||||
|
||||
void WriteFstKaldi(const VectorFst<StdArc> &fst,
|
||||
std::string wxfilename) {
|
||||
if (wxfilename == "") wxfilename = "-"; // interpret "" as stdout,
|
||||
// for compatibility with OpenFst conventions.
|
||||
bool write_binary = true, write_header = false;
|
||||
kaldi::Output ko(wxfilename, write_binary, write_header);
|
||||
FstWriteOptions wopts(kaldi::PrintableWxfilename(wxfilename));
|
||||
fst.Write(ko.Stream(), wopts);
|
||||
}
|
||||
|
||||
fst::VectorFst<fst::StdArc> *ReadAndPrepareLmFst(std::string rxfilename) {
|
||||
// ReadFstKaldi() will die with exception on failure.
|
||||
fst::VectorFst<fst::StdArc> *ans = fst::ReadFstKaldi(rxfilename);
|
||||
if (ans->Properties(fst::kAcceptor, true) == 0) {
|
||||
// If it's not already an acceptor, project on the output, i.e. copy olabels
|
||||
// to ilabels. Generally the G.fst's on disk will have the disambiguation
|
||||
// symbol #0 on the input symbols of the backoff arc, and projection will
|
||||
// replace them with epsilons which is what is on the output symbols of
|
||||
// those arcs.
|
||||
fst::Project(ans, fst::PROJECT_OUTPUT);
|
||||
}
|
||||
if (ans->Properties(fst::kILabelSorted, true) == 0) {
|
||||
// Make sure LM is sorted on ilabel.
|
||||
fst::ILabelCompare<fst::StdArc> ilabel_comp;
|
||||
fst::ArcSort(ans, ilabel_comp);
|
||||
}
|
||||
return ans;
|
||||
}
|
||||
|
||||
} // end namespace fst
|
||||
@@ -0,0 +1,158 @@
|
||||
// fstext/kaldi-fst-io.h
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation
|
||||
// 2012-2015 Johns Hopkins University (Author: Daniel Povey)
|
||||
// 2013 Guoguo Chen
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef KALDI_FSTEXT_KALDI_FST_IO_H_
|
||||
#define KALDI_FSTEXT_KALDI_FST_IO_H_
|
||||
|
||||
#include <fst/fstlib.h>
|
||||
#include <fst/fst-decl.h>
|
||||
#include <fst/script/print-impl.h>
|
||||
#include "base/kaldi-common.h"
|
||||
|
||||
// Some functions for writing Fsts.
|
||||
// I/O for FSTs is a bit of a mess, and not very well integrated with Kaldi's
|
||||
// generic I/O mechanisms, because we want files containing just FSTs to
|
||||
// be readable by OpenFST's native binaries, which is not compatible
|
||||
// with the normal \0B header that identifies Kaldi files as containing
|
||||
// binary data.
|
||||
// So use the functions here with your eyes open, and with caution!
|
||||
namespace fst {
|
||||
|
||||
// Read a binary FST using Kaldi I/O mechanisms (pipes, etc.)
|
||||
// On error returns NULL. Only supports VectorFst and exists
|
||||
// mainly for backward code compabibility.
|
||||
VectorFst<StdArc> *ReadFstKaldi(std::string rxfilename);
|
||||
|
||||
// Read a binary FST using Kaldi I/O mechanisms (pipes, etc.)
|
||||
// If it can't read the FST, if throw_on_err == true it throws using KALDI_ERR;
|
||||
// otherwise it prints a warning and returns. Note:this
|
||||
// doesn't support the text-mode option that we generally like to support.
|
||||
// This version currently supports ConstFst<StdArc> or VectorFst<StdArc>
|
||||
// (const-fst can give better performance for decoding). Other
|
||||
// types could be also loaded if registered inside OpenFst.
|
||||
Fst<StdArc> *ReadFstKaldiGeneric(std::string rxfilename,
|
||||
bool throw_on_err = true);
|
||||
|
||||
// This function attempts to dynamic_cast the pointer 'fst' (which will likely
|
||||
// have been returned by ReadFstGeneric()), to the more derived
|
||||
// type VectorFst<StdArc>. If this succeeds, it returns the same pointer;
|
||||
// if it fails, it converts the FST type (by creating a new VectorFst<stdArc>
|
||||
// initialized by 'fst'), prints a warning, and deletes 'fst'.
|
||||
VectorFst<StdArc> *CastOrConvertToVectorFst(Fst<StdArc> *fst);
|
||||
|
||||
// Version of ReadFstKaldi() that writes to a pointer. Assumes
|
||||
// the FST is binary with no binary marker. Crashes on error.
|
||||
void ReadFstKaldi(std::string rxfilename, VectorFst<StdArc> *ofst);
|
||||
|
||||
// Write an FST using Kaldi I/O mechanisms (pipes, etc.)
|
||||
// On error, throws using KALDI_ERR. For use only in code in fstbin/,
|
||||
// as it doesn't support the text-mode option.
|
||||
void WriteFstKaldi(const VectorFst<StdArc> &fst,
|
||||
std::string wxfilename);
|
||||
|
||||
// This is a more general Kaldi-type-IO mechanism of writing FSTs to
|
||||
// streams, supporting binary or text-mode writing. (note: we just
|
||||
// write the integers, symbol tables are not supported).
|
||||
// On error, throws using KALDI_ERR.
|
||||
template <class Arc>
|
||||
void WriteFstKaldi(std::ostream &os, bool binary,
|
||||
const VectorFst<Arc> &fst);
|
||||
|
||||
// A generic Kaldi-type-IO mechanism of reading FSTs from streams,
|
||||
// supporting binary or text-mode reading/writing.
|
||||
template <class Arc>
|
||||
void ReadFstKaldi(std::istream &is, bool binary,
|
||||
VectorFst<Arc> *fst);
|
||||
|
||||
// Read an FST file for LM (G.fst) and make it an acceptor,
|
||||
// and make sure it is sorted on labels
|
||||
fst::VectorFst<fst::StdArc> *ReadAndPrepareLmFst(std::string rxfilename);
|
||||
|
||||
// This is a Holder class with T = VectorFst<Arc>, that meets the requirements
|
||||
// of a Holder class as described in ../util/kaldi-holder.h. This enables us to
|
||||
// read/write collections of FSTs indexed by strings, using the Table concept (
|
||||
// see ../util/kaldi-table.h).
|
||||
// Originally it was only templated on T = VectorFst<StdArc>, but as the keyword
|
||||
// spotting stuff introduced more types of FSTs, we made it also templated on
|
||||
// the arc.
|
||||
template<class Arc>
|
||||
class VectorFstTplHolder {
|
||||
public:
|
||||
typedef VectorFst<Arc> T;
|
||||
|
||||
VectorFstTplHolder(): t_(NULL) { }
|
||||
|
||||
static bool Write(std::ostream &os, bool binary, const T &t);
|
||||
|
||||
void Copy(const T &t) { // copies it into the holder.
|
||||
Clear();
|
||||
t_ = new T(t);
|
||||
}
|
||||
|
||||
// Reads into the holder.
|
||||
bool Read(std::istream &is);
|
||||
|
||||
// It's potentially a binary format, so must read in binary mode (linefeed
|
||||
// translation will corrupt the file. We don't know till we open the file if
|
||||
// it's really binary, so we need to read in binary mode to be on the safe
|
||||
// side. Extra linefeeds won't matter, the text-mode reading code ignores
|
||||
// them.
|
||||
static bool IsReadInBinary() { return true; }
|
||||
|
||||
T &Value() {
|
||||
// code error if !t_.
|
||||
if (!t_) KALDI_ERR << "VectorFstTplHolder::Value() called wrongly.";
|
||||
return *t_;
|
||||
}
|
||||
|
||||
void Clear() {
|
||||
if (t_) {
|
||||
delete t_;
|
||||
t_ = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void Swap(VectorFstTplHolder<Arc> *other) {
|
||||
std::swap(t_, other->t_);
|
||||
}
|
||||
|
||||
bool ExtractRange(const VectorFstTplHolder<Arc> &other,
|
||||
const std::string &range) {
|
||||
KALDI_ERR << "ExtractRange is not defined for this type of holder.";
|
||||
return false;
|
||||
}
|
||||
|
||||
~VectorFstTplHolder() { Clear(); }
|
||||
// No destructor. Assignment and
|
||||
// copy constructor take their default implementations.
|
||||
private:
|
||||
KALDI_DISALLOW_COPY_AND_ASSIGN(VectorFstTplHolder);
|
||||
T *t_;
|
||||
};
|
||||
|
||||
// Now make the original VectorFstHolder as the typedef of VectorFstHolder<StdArc>.
|
||||
typedef VectorFstTplHolder<StdArc> VectorFstHolder;
|
||||
|
||||
|
||||
} // end namespace fst
|
||||
|
||||
#include "fstext/kaldi-fst-io-inl.h"
|
||||
#endif
|
||||
@@ -0,0 +1,282 @@
|
||||
// fstext/lattice-utils-inl.h
|
||||
|
||||
// Copyright 2009-2012 Microsoft Corporation Johns Hopkins University (Author: Daniel Povey)
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef KALDI_FSTEXT_LATTICE_UTILS_INL_H_
|
||||
#define KALDI_FSTEXT_LATTICE_UTILS_INL_H_
|
||||
// Do not include this file directly. It is included by lattice-utils.h
|
||||
|
||||
|
||||
namespace fst {
|
||||
|
||||
|
||||
/* Convert from FST with arc-type Weight, to one with arc-type
|
||||
CompactLatticeWeight. Uses FactorFst to identify chains
|
||||
of states which can be turned into a single output arc. */
|
||||
|
||||
template<class Weight, class Int>
|
||||
void ConvertLattice(
|
||||
const ExpandedFst<ArcTpl<Weight> > &ifst,
|
||||
MutableFst<ArcTpl<CompactLatticeWeightTpl<Weight, Int> > > *ofst,
|
||||
bool invert) {
|
||||
typedef ArcTpl<Weight> Arc;
|
||||
typedef typename Arc::StateId StateId;
|
||||
typedef CompactLatticeWeightTpl<Weight, Int> CompactWeight;
|
||||
typedef ArcTpl<CompactWeight> CompactArc;
|
||||
|
||||
VectorFst<ArcTpl<Weight> > ffst;
|
||||
std::vector<std::vector<Int> > labels;
|
||||
if (invert) // normal case: want the ilabels as sequences on the arcs of
|
||||
Factor(ifst, &ffst, &labels); // the output... Factor makes seqs of
|
||||
// ilabels.
|
||||
else {
|
||||
VectorFst<ArcTpl<Weight> > invfst(ifst);
|
||||
Invert(&invfst);
|
||||
Factor(invfst, &ffst, &labels);
|
||||
}
|
||||
|
||||
TopSort(&ffst); // Put the states in ffst in topological order, which is
|
||||
// easier on the eye when reading the text-form lattices and corresponds to
|
||||
// what we get when we generate the lattices in the decoder.
|
||||
|
||||
ofst->DeleteStates();
|
||||
|
||||
// The states will be numbered exactly the same as the original FST.
|
||||
// Add the states to the new FST.
|
||||
StateId num_states = ffst.NumStates();
|
||||
for (StateId s = 0; s < num_states; s++) {
|
||||
StateId news = ofst->AddState();
|
||||
assert(news == s);
|
||||
}
|
||||
ofst->SetStart(ffst.Start());
|
||||
for (StateId s = 0; s < num_states; s++) {
|
||||
Weight final_weight = ffst.Final(s);
|
||||
if (final_weight != Weight::Zero()) {
|
||||
CompactWeight final_compact_weight(final_weight, std::vector<Int>());
|
||||
ofst->SetFinal(s, final_compact_weight);
|
||||
}
|
||||
for (ArcIterator<ExpandedFst<Arc> > iter(ffst, s);
|
||||
!iter.Done();
|
||||
iter.Next()) {
|
||||
const Arc &arc = iter.Value();
|
||||
KALDI_PARANOID_ASSERT(arc.weight != Weight::Zero());
|
||||
// note: zero-weight arcs not allowed anyway so weight should not be zero,
|
||||
// but no harm in checking.
|
||||
CompactArc compact_arc(arc.olabel, arc.olabel,
|
||||
CompactWeight(arc.weight, labels[arc.ilabel]),
|
||||
arc.nextstate);
|
||||
ofst->AddArc(s, compact_arc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<class Weight, class Int>
|
||||
void ConvertLattice(
|
||||
const ExpandedFst<ArcTpl<CompactLatticeWeightTpl<Weight, Int> > > &ifst,
|
||||
MutableFst<ArcTpl<Weight> > *ofst,
|
||||
bool invert) {
|
||||
typedef ArcTpl<Weight> Arc;
|
||||
typedef typename Arc::StateId StateId;
|
||||
typedef typename Arc::Label Label;
|
||||
typedef CompactLatticeWeightTpl<Weight, Int> CompactWeight;
|
||||
typedef ArcTpl<CompactWeight> CompactArc;
|
||||
ofst->DeleteStates();
|
||||
// make the states in the new FST have the same numbers as
|
||||
// the original ones, and add chains of states as necessary
|
||||
// to encode the string-valued weights.
|
||||
StateId num_states = ifst.NumStates();
|
||||
for (StateId s = 0; s < num_states; s++) {
|
||||
StateId news = ofst->AddState();
|
||||
assert(news == s);
|
||||
}
|
||||
ofst->SetStart(ifst.Start());
|
||||
for (StateId s = 0; s < num_states; s++) {
|
||||
CompactWeight final_weight = ifst.Final(s);
|
||||
if (final_weight != CompactWeight::Zero()) {
|
||||
StateId cur_state = s;
|
||||
size_t string_length = final_weight.String().size();
|
||||
for (size_t n = 0; n < string_length; n++) {
|
||||
StateId next_state = ofst->AddState();
|
||||
Label ilabel = 0;
|
||||
Arc arc(ilabel, final_weight.String()[n],
|
||||
(n == 0 ? final_weight.Weight() : Weight::One()),
|
||||
next_state);
|
||||
if (invert) std::swap(arc.ilabel, arc.olabel);
|
||||
ofst->AddArc(cur_state, arc);
|
||||
cur_state = next_state;
|
||||
}
|
||||
ofst->SetFinal(cur_state,
|
||||
string_length > 0 ? Weight::One() : final_weight.Weight());
|
||||
}
|
||||
for (ArcIterator<ExpandedFst<CompactArc> > iter(ifst, s);
|
||||
!iter.Done();
|
||||
iter.Next()) {
|
||||
const CompactArc &arc = iter.Value();
|
||||
size_t string_length = arc.weight.String().size();
|
||||
StateId cur_state = s;
|
||||
// for all but the last element in the string--
|
||||
// add a temporary state.
|
||||
for (size_t n = 0 ; n+1 < string_length; n++) {
|
||||
StateId next_state = ofst->AddState();
|
||||
Label ilabel = (n == 0 ? arc.ilabel : 0),
|
||||
olabel = static_cast<Label>(arc.weight.String()[n]);
|
||||
Weight weight = (n == 0 ? arc.weight.Weight() : Weight::One());
|
||||
Arc new_arc(ilabel, olabel, weight, next_state);
|
||||
if (invert) std::swap(new_arc.ilabel, new_arc.olabel);
|
||||
ofst->AddArc(cur_state, new_arc);
|
||||
cur_state = next_state;
|
||||
}
|
||||
Label ilabel = (string_length <= 1 ? arc.ilabel : 0),
|
||||
olabel = (string_length > 0 ? arc.weight.String()[string_length-1] : 0);
|
||||
Weight weight = (string_length <= 1 ? arc.weight.Weight() : Weight::One());
|
||||
Arc new_arc(ilabel, olabel, weight, arc.nextstate);
|
||||
if (invert) std::swap(new_arc.ilabel, new_arc.olabel);
|
||||
ofst->AddArc(cur_state, new_arc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This function converts lattices between float and double;
|
||||
// it works for both CompactLatticeWeight and LatticeWeight.
|
||||
template<class WeightIn, class WeightOut>
|
||||
void ConvertLattice(
|
||||
const ExpandedFst<ArcTpl<WeightIn> > &ifst,
|
||||
MutableFst<ArcTpl<WeightOut> > *ofst) {
|
||||
typedef ArcTpl<WeightIn> ArcIn;
|
||||
typedef ArcTpl<WeightOut> ArcOut;
|
||||
typedef typename ArcIn::StateId StateId;
|
||||
ofst->DeleteStates();
|
||||
// The states will be numbered exactly the same as the original FST.
|
||||
// Add the states to the new FST.
|
||||
StateId num_states = ifst.NumStates();
|
||||
for (StateId s = 0; s < num_states; s++) {
|
||||
StateId news = ofst->AddState();
|
||||
assert(news == s);
|
||||
}
|
||||
ofst->SetStart(ifst.Start());
|
||||
for (StateId s = 0; s < num_states; s++) {
|
||||
WeightIn final_iweight = ifst.Final(s);
|
||||
if (final_iweight != WeightIn::Zero()) {
|
||||
WeightOut final_oweight;
|
||||
ConvertLatticeWeight(final_iweight, &final_oweight);
|
||||
ofst->SetFinal(s, final_oweight);
|
||||
}
|
||||
for (ArcIterator<ExpandedFst<ArcIn> > iter(ifst, s);
|
||||
!iter.Done();
|
||||
iter.Next()) {
|
||||
ArcIn arc = iter.Value();
|
||||
KALDI_PARANOID_ASSERT(arc.weight != WeightIn::Zero());
|
||||
ArcOut oarc;
|
||||
ConvertLatticeWeight(arc.weight, &oarc.weight);
|
||||
oarc.ilabel = arc.ilabel;
|
||||
oarc.olabel = arc.olabel;
|
||||
oarc.nextstate = arc.nextstate;
|
||||
ofst->AddArc(s, oarc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
template<class Weight, class ScaleFloat>
|
||||
void ScaleLattice(
|
||||
const std::vector<std::vector<ScaleFloat> > &scale,
|
||||
MutableFst<ArcTpl<Weight> > *fst) {
|
||||
assert(scale.size() == 2 && scale[0].size() == 2 && scale[1].size() == 2);
|
||||
if (scale == DefaultLatticeScale()) // nothing to do.
|
||||
return;
|
||||
typedef ArcTpl<Weight> Arc;
|
||||
typedef MutableFst<Arc> Fst;
|
||||
typedef typename Arc::StateId StateId;
|
||||
StateId num_states = fst->NumStates();
|
||||
for (StateId s = 0; s < num_states; s++) {
|
||||
for (MutableArcIterator<Fst> aiter(fst, s);
|
||||
!aiter.Done();
|
||||
aiter.Next()) {
|
||||
Arc arc = aiter.Value();
|
||||
arc.weight = Weight(ScaleTupleWeight(arc.weight, scale));
|
||||
aiter.SetValue(arc);
|
||||
}
|
||||
Weight final_weight = fst->Final(s);
|
||||
if (final_weight != Weight::Zero())
|
||||
fst->SetFinal(s, Weight(ScaleTupleWeight(final_weight, scale)));
|
||||
}
|
||||
}
|
||||
|
||||
template<class Weight, class Int>
|
||||
void RemoveAlignmentsFromCompactLattice(
|
||||
MutableFst<ArcTpl<CompactLatticeWeightTpl<Weight, Int> > > *fst) {
|
||||
typedef CompactLatticeWeightTpl<Weight, Int> W;
|
||||
typedef ArcTpl<W> Arc;
|
||||
typedef MutableFst<Arc> Fst;
|
||||
typedef typename Arc::StateId StateId;
|
||||
StateId num_states = fst->NumStates();
|
||||
for (StateId s = 0; s < num_states; s++) {
|
||||
for (MutableArcIterator<Fst> aiter(fst, s);
|
||||
!aiter.Done();
|
||||
aiter.Next()) {
|
||||
Arc arc = aiter.Value();
|
||||
arc.weight = W(arc.weight.Weight(), std::vector<Int>());
|
||||
aiter.SetValue(arc);
|
||||
}
|
||||
W final_weight = fst->Final(s);
|
||||
if (final_weight != W::Zero())
|
||||
fst->SetFinal(s, W(final_weight.Weight(), std::vector<Int>()));
|
||||
}
|
||||
}
|
||||
|
||||
template<class Weight, class Int>
|
||||
bool CompactLatticeHasAlignment(
|
||||
const ExpandedFst<ArcTpl<CompactLatticeWeightTpl<Weight, Int> > > &fst) {
|
||||
typedef CompactLatticeWeightTpl<Weight, Int> W;
|
||||
typedef ArcTpl<W> Arc;
|
||||
typedef ExpandedFst<Arc> Fst;
|
||||
typedef typename Arc::StateId StateId;
|
||||
StateId num_states = fst.NumStates();
|
||||
for (StateId s = 0; s < num_states; s++) {
|
||||
for (ArcIterator<Fst> aiter(fst, s);
|
||||
!aiter.Done();
|
||||
aiter.Next()) {
|
||||
const Arc &arc = aiter.Value();
|
||||
if (!arc.weight.String().empty()) return true;
|
||||
}
|
||||
W final_weight = fst.Final(s);
|
||||
if (!final_weight.String().empty()) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
template <class Real>
|
||||
void ConvertFstToLattice(
|
||||
const ExpandedFst<ArcTpl<TropicalWeight> > &ifst,
|
||||
MutableFst<ArcTpl<LatticeWeightTpl<Real> > > *ofst) {
|
||||
int32 num_states_cache = 50000;
|
||||
fst::CacheOptions cache_opts(true, num_states_cache);
|
||||
fst::MapFstOptions mapfst_opts(cache_opts);
|
||||
StdToLatticeMapper<Real> mapper;
|
||||
MapFst<StdArc, ArcTpl<LatticeWeightTpl<Real> >,
|
||||
StdToLatticeMapper<Real> > map_fst(ifst, mapper, mapfst_opts);
|
||||
*ofst = map_fst;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,331 @@
|
||||
// fstext/lattice-utils-test.cc
|
||||
|
||||
// Copyright 2011 Microsoft Corporation
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "fstext/lattice-utils.h"
|
||||
#include "fstext/fst-test-utils.h"
|
||||
#include "base/kaldi-math.h"
|
||||
|
||||
namespace fst {
|
||||
|
||||
template<class Weight, class Int> void TestConvert(bool invert) {
|
||||
typedef ArcTpl<Weight> Arc;
|
||||
typedef ArcTpl<CompactLatticeWeightTpl<Weight, Int> > CompactArc;
|
||||
for(int i = 0; i < 5; i++) {
|
||||
VectorFst<Arc> *fst = RandFst<Arc>();
|
||||
std::cout << "FST before converting to compact-arc is:\n";
|
||||
{
|
||||
FstPrinter<Arc> fstprinter(*fst, NULL, NULL, NULL, false, true, "\t");
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}
|
||||
VectorFst<CompactArc> ofst;
|
||||
ConvertLattice<Weight, Int>(*fst, &ofst, invert);
|
||||
|
||||
std::cout << "FST after converting is:\n";
|
||||
{
|
||||
FstPrinter<CompactArc> fstprinter(ofst, NULL, NULL, NULL, false, true, "\t");
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}
|
||||
VectorFst<Arc> origfst;
|
||||
ConvertLattice<Weight, Int>(ofst, &origfst, invert);
|
||||
std::cout << "FST after back conversion is:\n";
|
||||
{
|
||||
FstPrinter<Arc> fstprinter(origfst, NULL, NULL, NULL, false, true, "\t");
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}
|
||||
|
||||
assert(RandEquivalent(*fst, origfst, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
|
||||
delete fst;
|
||||
}
|
||||
}
|
||||
|
||||
// This tests the ShortestPath algorithm, and by proxy, tests the
|
||||
// NaturalLess template etc.
|
||||
|
||||
template<class Weight, class Int> void TestShortestPath() {
|
||||
for (int p = 0; p < 10; p++) {
|
||||
typedef ArcTpl<Weight> Arc;
|
||||
typedef ArcTpl<CompactLatticeWeightTpl<Weight, Int> > CompactArc;
|
||||
for(int i = 0; i < 5; i++) {
|
||||
VectorFst<Arc> *fst = RandPairFst<Arc>();
|
||||
std::cout << "Testing shortest path\n";
|
||||
std::cout << "FST before converting to compact-arc is:\n";
|
||||
{
|
||||
FstPrinter<Arc> fstprinter(*fst, NULL, NULL, NULL, false, true, "\t");
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}
|
||||
VectorFst<CompactArc> cfst;
|
||||
ConvertLattice<Weight, Int>(*fst, &cfst, false); // invert == false
|
||||
|
||||
|
||||
{
|
||||
VectorFst<Arc> nbest_fst_1;
|
||||
ShortestPath(*fst, &nbest_fst_1, 1);
|
||||
VectorFst<Arc> nbest_fst_2;
|
||||
ShortestPath(*fst, &nbest_fst_2, 3);
|
||||
VectorFst<Arc> nbest_fst_1b;
|
||||
ShortestPath(nbest_fst_2, &nbest_fst_1b, 1);
|
||||
|
||||
|
||||
assert(ApproxEqual(ShortestDistance(nbest_fst_1),
|
||||
ShortestDistance(nbest_fst_1b)));
|
||||
|
||||
// since semiring is idempotent, this should succeed too.
|
||||
assert(ApproxEqual(ShortestDistance(*fst),
|
||||
ShortestDistance(nbest_fst_1b)));
|
||||
}
|
||||
{
|
||||
VectorFst<CompactArc> nbest_fst_1;
|
||||
ShortestPath(cfst, &nbest_fst_1, 1);
|
||||
VectorFst<CompactArc> nbest_fst_2;
|
||||
ShortestPath(cfst, &nbest_fst_2, 3);
|
||||
VectorFst<CompactArc> nbest_fst_1b;
|
||||
ShortestPath(nbest_fst_2, &nbest_fst_1b, 1);
|
||||
|
||||
assert(ApproxEqual(ShortestDistance(nbest_fst_1),
|
||||
ShortestDistance(nbest_fst_1b)));
|
||||
// since semiring is idempotent, this should succeed too.
|
||||
assert(ApproxEqual(ShortestDistance(cfst),
|
||||
ShortestDistance(nbest_fst_1b)));
|
||||
}
|
||||
|
||||
delete fst;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
template<class Int> void TestConvert2() {
|
||||
typedef ArcTpl<LatticeWeightTpl<float> > ArcF;
|
||||
typedef ArcTpl<LatticeWeightTpl<double> > ArcD;
|
||||
typedef ArcTpl<CompactLatticeWeightTpl<LatticeWeightTpl<float>, Int> > CArcF;
|
||||
typedef ArcTpl<CompactLatticeWeightTpl<LatticeWeightTpl<double>, Int> > CArcD;
|
||||
|
||||
for(int i = 0; i < 2; i++) {
|
||||
{
|
||||
VectorFst<ArcF> *fst1 = RandPairFst<ArcF>();
|
||||
VectorFst<ArcD> fst2;
|
||||
VectorFst<ArcF> fst3;
|
||||
ConvertLattice(*fst1, &fst2);
|
||||
ConvertLattice(fst2, &fst3);
|
||||
|
||||
assert(RandEquivalent(*fst1, fst3, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
|
||||
delete fst1;
|
||||
}
|
||||
|
||||
{
|
||||
VectorFst<ArcF> *fst1 = RandPairFst<ArcF>();
|
||||
VectorFst<CArcF> cfst1, cfst3;
|
||||
ConvertLattice(*fst1, &cfst1);
|
||||
VectorFst<CArcD> cfst2;
|
||||
ConvertLattice(cfst1, &cfst2);
|
||||
ConvertLattice(cfst2, &cfst3);
|
||||
assert(RandEquivalent(cfst1, cfst3, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
|
||||
delete fst1;
|
||||
}
|
||||
|
||||
{
|
||||
VectorFst<ArcF> *fst1 = RandPairFst<ArcF>();
|
||||
VectorFst<CArcD> cfst1, cfst3;
|
||||
ConvertLattice(*fst1, &cfst1);
|
||||
VectorFst<CArcF> cfst2;
|
||||
ConvertLattice(cfst1, &cfst2);
|
||||
ConvertLattice(cfst2, &cfst3);
|
||||
assert(RandEquivalent(cfst1, cfst3, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
|
||||
delete fst1;
|
||||
}
|
||||
|
||||
{
|
||||
VectorFst<ArcD> *fst1 = RandPairFst<ArcD>();
|
||||
VectorFst<CArcD> cfst1, cfst3;
|
||||
ConvertLattice(*fst1, &cfst1);
|
||||
VectorFst<CArcF> cfst2;
|
||||
ConvertLattice(cfst1, &cfst2);
|
||||
ConvertLattice(cfst2, &cfst3);
|
||||
assert(RandEquivalent(cfst1, cfst3, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
|
||||
delete fst1;
|
||||
}
|
||||
|
||||
{
|
||||
VectorFst<ArcD> *fst1 = RandPairFst<ArcD>();
|
||||
VectorFst<CArcF> cfst1;
|
||||
ConvertLattice(*fst1, &cfst1);
|
||||
VectorFst<ArcD> fst2;
|
||||
ConvertLattice(cfst1, &fst2);
|
||||
assert(RandEquivalent(*fst1, fst2, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
|
||||
delete fst1;
|
||||
}
|
||||
|
||||
{
|
||||
VectorFst<ArcF> *fst1 = RandPairFst<ArcF>();
|
||||
VectorFst<CArcD> cfst1;
|
||||
ConvertLattice(*fst1, &cfst1);
|
||||
VectorFst<ArcF> fst2;
|
||||
ConvertLattice(cfst1, &fst2);
|
||||
assert(RandEquivalent(*fst1, fst2, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
|
||||
delete fst1;
|
||||
}
|
||||
|
||||
{
|
||||
VectorFst<ArcD> *fst1 = RandPairFst<ArcD>();
|
||||
VectorFst<CArcF> cfst1;
|
||||
ConvertLattice(*fst1, &cfst1);
|
||||
VectorFst<ArcD> fst2;
|
||||
ConvertLattice(cfst1, &fst2);
|
||||
assert(RandEquivalent(*fst1, fst2, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
|
||||
delete fst1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// use TestConvertPair when the Weight can be constructed from
|
||||
// a pair of floats.
|
||||
template<class Weight, class Int> void TestConvertPair(bool invert) {
|
||||
typedef ArcTpl<Weight> Arc;
|
||||
typedef ArcTpl<CompactLatticeWeightTpl<Weight, Int> > CompactArc;
|
||||
for(int i = 0; i < 2; i++) {
|
||||
VectorFst<Arc> *fst = RandPairFst<Arc>();
|
||||
/*std::cout << "FST before converting to compact-arc is:\n";
|
||||
{
|
||||
FstPrinter<Arc> fstprinter(*fst, NULL, NULL, NULL, false, true);
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}*/
|
||||
VectorFst<CompactArc> ofst;
|
||||
ConvertLattice<Weight, Int>(*fst, &ofst, invert);
|
||||
|
||||
/*std::cout << "FST after converting is:\n";
|
||||
{
|
||||
FstPrinter<CompactArc> fstprinter(ofst, NULL, NULL, NULL, false, true);
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}*/
|
||||
VectorFst<Arc> origfst;
|
||||
ConvertLattice<Weight, Int>(ofst, &origfst, invert);
|
||||
/*std::cout << "FST after back conversion is:\n";
|
||||
{
|
||||
FstPrinter<Arc> fstprinter(origfst, NULL, NULL, NULL, false, true);
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}*/
|
||||
|
||||
assert(RandEquivalent(*fst, origfst, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
|
||||
delete fst;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// use TestConvertPair when the Weight can be constructed from
|
||||
// a pair of floats.
|
||||
template<class Weight, class Int> void TestScalePair(bool invert) {
|
||||
std::vector<std::vector<double> > scale1 = DefaultLatticeScale(),
|
||||
scale2 = DefaultLatticeScale();
|
||||
// important that all these numbers exactly representable as floats..
|
||||
// exact floating-point comparisons are used in LatticeWeight, and
|
||||
// this exactness is being tested here.. this test will fail for
|
||||
// other types of number.
|
||||
if (kaldi::Rand() % 4 == 0) {
|
||||
scale1[0][0] = 2.0;
|
||||
scale2[0][0] = 0.5;
|
||||
scale1[1][1] = 4.0;
|
||||
scale2[1][1] = 0.25;
|
||||
} else if (kaldi::Rand() % 3 == 0) {
|
||||
// use that [1 0.25; 0 1] [ 1 -0.25; 0 1] is the unit matrix.
|
||||
scale1[0][1] = 0.25;
|
||||
scale2[0][1] = -0.25;
|
||||
} else if (kaldi::Rand() % 2 == 0) {
|
||||
scale1[1][0] = 0.25;
|
||||
scale2[1][0] = -0.25;
|
||||
}
|
||||
|
||||
|
||||
typedef ArcTpl<Weight> Arc;
|
||||
typedef ArcTpl<CompactLatticeWeightTpl<Weight, Int> > CompactArc;
|
||||
for(int i = 0; i < 2; i++) {
|
||||
VectorFst<Arc> *fst = RandPairFst<Arc>();
|
||||
/*std::cout << "FST before converting to compact-arc is:\n";
|
||||
{
|
||||
FstPrinter<Arc> fstprinter(*fst, NULL, NULL, NULL, false, true);
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}*/
|
||||
VectorFst<CompactArc> ofst;
|
||||
ConvertLattice<Weight, Int>(*fst, &ofst, invert);
|
||||
ScaleLattice(scale1, &ofst);
|
||||
/*std::cout << "FST after converting and scaling is:\n";
|
||||
{
|
||||
FstPrinter<CompactArc> fstprinter(ofst, NULL, NULL, NULL, false, true);
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}*/
|
||||
VectorFst<Arc> origfst;
|
||||
ConvertLattice<Weight, Int>(ofst, &origfst, invert);
|
||||
ScaleLattice(scale2, &origfst);
|
||||
/*std::cout << "FST after back conversion and scaling is:\n";
|
||||
{
|
||||
FstPrinter<Arc> fstprinter(origfst, NULL, NULL, NULL, false, true);
|
||||
fstprinter.Print(&std::cout, "standard output");
|
||||
}*/
|
||||
// If RandEquivalent doesn't work, it could be due to a nasty issue related to the use
|
||||
// of exact floating-point comparisons in the Plus function of LatticeWeight.
|
||||
if (!RandEquivalent(*fst, origfst, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/)) {
|
||||
std::cerr << "Warn, randequivalent returned false. Checking equivalence another way.\n";
|
||||
assert(Equal(*fst, origfst));
|
||||
}
|
||||
delete fst;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // end namespace fst
|
||||
|
||||
int main() {
|
||||
using namespace fst;
|
||||
|
||||
typedef ::int64 int64;
|
||||
typedef ::uint64 uint64;
|
||||
typedef ::int32 int32;
|
||||
typedef ::uint32 uint32;
|
||||
|
||||
{
|
||||
typedef LatticeWeightTpl<float> LatticeWeight;
|
||||
for(int i = 0; i < 2; i++) {
|
||||
bool invert = (i % 2);
|
||||
TestConvert<TropicalWeight, int32>(invert);
|
||||
TestConvertPair<LatticeWeight, int32>(invert);
|
||||
TestConvertPair<LatticeWeight, size_t>(invert);
|
||||
TestConvertPair<LexicographicWeight<TropicalWeight, TropicalWeight>, size_t>(invert);
|
||||
TestScalePair<LatticeWeight, int32>(invert);
|
||||
TestScalePair<LatticeWeight, size_t>(invert);
|
||||
TestScalePair<LexicographicWeight<TropicalWeight, TropicalWeight>, size_t>(invert);
|
||||
}
|
||||
}
|
||||
{
|
||||
typedef LatticeWeightTpl<double> LatticeWeight;
|
||||
TestShortestPath<LatticeWeight, int32>();
|
||||
TestConvert2<int32>();
|
||||
for(int i = 0; i < 2; i++) {
|
||||
bool invert = (i % 2);
|
||||
TestConvertPair<LatticeWeight, int32>(invert);
|
||||
TestConvertPair<LatticeWeight, size_t>(invert);
|
||||
TestConvertPair<LexicographicWeight<TropicalWeight, TropicalWeight>, size_t>(invert);
|
||||
TestScalePair<LatticeWeight, int32>(invert);
|
||||
TestScalePair<LatticeWeight, size_t>(invert);
|
||||
TestScalePair<LexicographicWeight<TropicalWeight, TropicalWeight>, size_t>(invert);
|
||||
}
|
||||
}
|
||||
std::cout << "Tests succeeded\n";
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
// fstext/lattice-utils.h
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
|
||||
#ifndef KALDI_FSTEXT_LATTICE_UTILS_H_
|
||||
#define KALDI_FSTEXT_LATTICE_UTILS_H_
|
||||
|
||||
#include "fst/fstlib.h"
|
||||
#include "fstext/lattice-weight.h"
|
||||
#include "fstext/factor.h"
|
||||
|
||||
namespace fst {
|
||||
|
||||
// The template ConvertLattice does conversions to and from
|
||||
// LatticeWeight FSTs and CompactLatticeWeight FSTs, and
|
||||
// between float and double, and to convert from LatticeWeight
|
||||
// to TropicalWeight. It's used in the I/O code for lattices,
|
||||
// and for converting lattices to standard FSTs (e.g. for creating
|
||||
// decoding graphs from lattices).
|
||||
|
||||
|
||||
/**
|
||||
Convert lattice from a normal FST to a CompactLattice FST.
|
||||
This is a bit like converting to the Gallic semiring, except
|
||||
the semiring behaves in a different way (designed to take
|
||||
the best path).
|
||||
Note: the ilabels end up as the symbols on the arcs of the
|
||||
output acceptor, and the olabels go to the strings. To make
|
||||
it the other way around (useful for the speech-recognition
|
||||
application), set invert=true [the default].
|
||||
*/
|
||||
template<class Weight, class Int>
|
||||
void ConvertLattice(
|
||||
const ExpandedFst<ArcTpl<Weight> > &ifst,
|
||||
MutableFst<ArcTpl<CompactLatticeWeightTpl<Weight, Int> > > *ofst,
|
||||
bool invert = true);
|
||||
|
||||
/**
|
||||
Convert lattice CompactLattice format to Lattice. This is a bit
|
||||
like converting from the Gallic semiring. As for any CompactLattice, "ifst"
|
||||
must be an acceptor (i.e., ilabels and olabels should be identical). If
|
||||
invert=false, the labels on "ifst" become the ilabels on "ofst" and the
|
||||
strings in the weights of "ifst" becomes the olabels. If invert=true
|
||||
[default], this is reversed (useful for speech recognition lattices; our
|
||||
standard non-compact format has the words on the output side to match HCLG).
|
||||
*/
|
||||
template<class Weight, class Int>
|
||||
void ConvertLattice(
|
||||
const ExpandedFst<ArcTpl<CompactLatticeWeightTpl<Weight, Int> > > &ifst,
|
||||
MutableFst<ArcTpl<Weight> > *ofst,
|
||||
bool invert = true);
|
||||
|
||||
|
||||
/**
|
||||
Convert between CompactLattices and Lattices of different floating point types...
|
||||
this works between any pair of weight types for which ConvertLatticeWeight
|
||||
is defined (c.f. lattice-weight.h), and also includes conversion from
|
||||
LatticeWeight to TropicalWeight.
|
||||
*/
|
||||
template<class WeightIn, class WeightOut>
|
||||
void ConvertLattice(
|
||||
const ExpandedFst<ArcTpl<WeightIn> > &ifst,
|
||||
MutableFst<ArcTpl<WeightOut> > *ofst);
|
||||
|
||||
|
||||
// Now define some ConvertLattice functions that require two phases of conversion (don't
|
||||
// bother coding these separately as they will be used rarely.
|
||||
|
||||
// Lattice with float to CompactLattice with double.
|
||||
template<class Int>
|
||||
void ConvertLattice(const ExpandedFst<ArcTpl<LatticeWeightTpl<float> > > &ifst,
|
||||
MutableFst<ArcTpl<CompactLatticeWeightTpl<LatticeWeightTpl<double>, Int> > > *ofst) {
|
||||
VectorFst<ArcTpl<CompactLatticeWeightTpl<LatticeWeightTpl<float>, Int> > > fst;
|
||||
ConvertLattice(ifst, &fst);
|
||||
ConvertLattice(fst, ofst);
|
||||
}
|
||||
|
||||
// Lattice with double to CompactLattice with float.
|
||||
template<class Int>
|
||||
void ConvertLattice(const ExpandedFst<ArcTpl<LatticeWeightTpl<double> > > &ifst,
|
||||
MutableFst<ArcTpl<CompactLatticeWeightTpl<LatticeWeightTpl<float>, Int> > > *ofst) {
|
||||
VectorFst<ArcTpl<CompactLatticeWeightTpl<LatticeWeightTpl<double>, Int> > > fst;
|
||||
ConvertLattice(ifst, &fst);
|
||||
ConvertLattice(fst, ofst);
|
||||
}
|
||||
|
||||
/// Converts CompactLattice with double to Lattice with float.
|
||||
template<class Int>
|
||||
void ConvertLattice(const ExpandedFst<ArcTpl<CompactLatticeWeightTpl<LatticeWeightTpl<double>, Int> > > &ifst,
|
||||
MutableFst<ArcTpl<LatticeWeightTpl<float> > > *ofst) {
|
||||
VectorFst<ArcTpl<CompactLatticeWeightTpl<LatticeWeightTpl<float>, Int> > > fst;
|
||||
ConvertLattice(ifst, &fst);
|
||||
ConvertLattice(fst, ofst);
|
||||
}
|
||||
|
||||
/// Converts CompactLattice with float to Lattice with double.
|
||||
template<class Int>
|
||||
void ConvertLattice(const ExpandedFst<ArcTpl<CompactLatticeWeightTpl<LatticeWeightTpl<float>, Int> > > &ifst,
|
||||
MutableFst<ArcTpl<LatticeWeightTpl<double> > > *ofst) {
|
||||
VectorFst<ArcTpl<CompactLatticeWeightTpl<LatticeWeightTpl<double>, Int> > > fst;
|
||||
ConvertLattice(ifst, &fst);
|
||||
ConvertLattice(fst, ofst);
|
||||
}
|
||||
|
||||
/// Converts TropicalWeight to LatticeWeight (puts all the weight on
|
||||
/// the first float in the lattice's pair).
|
||||
template <class Real>
|
||||
void ConvertFstToLattice(
|
||||
const ExpandedFst<ArcTpl<TropicalWeight> > &ifst,
|
||||
MutableFst<ArcTpl<LatticeWeightTpl<Real> > > *ofst);
|
||||
|
||||
|
||||
/** Returns a default 2x2 matrix scaling factor for LatticeWeight */
|
||||
inline std::vector<std::vector<double> > DefaultLatticeScale() {
|
||||
std::vector<std::vector<double> > ans(2);
|
||||
ans[0].resize(2, 0.0);
|
||||
ans[1].resize(2, 0.0);
|
||||
ans[0][0] = ans[1][1] = 1.0;
|
||||
return ans;
|
||||
}
|
||||
|
||||
inline std::vector<std::vector<double> > AcousticLatticeScale(double acwt) {
|
||||
std::vector<std::vector<double> > ans(2);
|
||||
ans[0].resize(2, 0.0);
|
||||
ans[1].resize(2, 0.0);
|
||||
ans[0][0] = 1.0;
|
||||
ans[1][1] = acwt;
|
||||
return ans;
|
||||
}
|
||||
|
||||
inline std::vector<std::vector<double> > GraphLatticeScale(double lmwt) {
|
||||
std::vector<std::vector<double> > ans(2);
|
||||
ans[0].resize(2, 0.0);
|
||||
ans[1].resize(2, 0.0);
|
||||
ans[0][0] = lmwt;
|
||||
ans[1][1] = 1.0;
|
||||
return ans;
|
||||
}
|
||||
|
||||
inline std::vector<std::vector<double> > LatticeScale(double lmwt, double acwt) {
|
||||
std::vector<std::vector<double> > ans(2);
|
||||
ans[0].resize(2, 0.0);
|
||||
ans[1].resize(2, 0.0);
|
||||
ans[0][0] = lmwt;
|
||||
ans[1][1] = acwt;
|
||||
return ans;
|
||||
}
|
||||
|
||||
|
||||
/** Scales the pairs of weights in LatticeWeight or CompactLatticeWeight by
|
||||
viewing the pair (a, b) as a 2-vector and pre-multiplying by the 2x2 matrix
|
||||
in "scale". E.g. typically scale would equal
|
||||
[ 1 0;
|
||||
0 acwt ]
|
||||
if we want to scale the acoustics by "acwt".
|
||||
*/
|
||||
template<class Weight, class ScaleFloat>
|
||||
void ScaleLattice(
|
||||
const std::vector<std::vector<ScaleFloat> > &scale,
|
||||
MutableFst<ArcTpl<Weight> > *fst);
|
||||
|
||||
/// Removes state-level alignments (the strings that are
|
||||
/// part of the weights).
|
||||
template<class Weight, class Int>
|
||||
void RemoveAlignmentsFromCompactLattice(
|
||||
MutableFst<ArcTpl<CompactLatticeWeightTpl<Weight, Int> > > *fst);
|
||||
|
||||
/// Returns true if lattice has alignments, i.e. it has
|
||||
/// any nonempty strings inside its weights.
|
||||
template<class Weight, class Int>
|
||||
bool CompactLatticeHasAlignment(
|
||||
const ExpandedFst<ArcTpl<CompactLatticeWeightTpl<Weight, Int> > > &fst);
|
||||
|
||||
|
||||
/// Class StdToLatticeMapper maps a normal arc (StdArc)
|
||||
/// to a LatticeArc by putting the StdArc weight as the first
|
||||
/// element of the LatticeWeight. Useful when doing LM
|
||||
/// rescoring.
|
||||
template<class Real>
|
||||
class StdToLatticeMapper {
|
||||
typedef LatticeWeightTpl<Real> LatticeWeight;
|
||||
typedef ArcTpl<LatticeWeight> LatticeArc;
|
||||
public:
|
||||
LatticeArc operator()(const StdArc &arc) {
|
||||
// Note: we have to check whether the arc's weight is zero below,
|
||||
// and if so return (infinity, infinity) and not (infinity, zero),
|
||||
// because (infinity, zero) is not a valid LatticeWeight, which should
|
||||
// either be both finite, or both infinite (i.e. Zero()).
|
||||
return LatticeArc(arc.ilabel, arc.olabel,
|
||||
LatticeWeight(arc.weight.Value(),
|
||||
arc.weight == StdArc::Weight::Zero() ?
|
||||
arc.weight.Value() : 0.0),
|
||||
arc.nextstate);
|
||||
}
|
||||
MapFinalAction FinalAction() { return MAP_NO_SUPERFINAL; }
|
||||
|
||||
MapSymbolsAction InputSymbolsAction() { return MAP_COPY_SYMBOLS; }
|
||||
|
||||
MapSymbolsAction OutputSymbolsAction() { return MAP_COPY_SYMBOLS; }
|
||||
|
||||
// I believe all properties are preserved.
|
||||
uint64 Properties(uint64 props) { return props; }
|
||||
};
|
||||
|
||||
|
||||
/// Class LatticeToStdMapper maps a LatticeArc to a normal arc (StdArc)
|
||||
/// by adding the elements of the LatticeArc weight.
|
||||
|
||||
template<class Real>
|
||||
class LatticeToStdMapper {
|
||||
typedef LatticeWeightTpl<Real> LatticeWeight;
|
||||
typedef ArcTpl<LatticeWeight> LatticeArc;
|
||||
public:
|
||||
StdArc operator()(const LatticeArc &arc) {
|
||||
return StdArc(arc.ilabel, arc.olabel,
|
||||
StdArc::Weight(arc.weight.Value1() + arc.weight.Value2()),
|
||||
arc.nextstate);
|
||||
}
|
||||
MapFinalAction FinalAction() { return MAP_NO_SUPERFINAL; }
|
||||
|
||||
MapSymbolsAction InputSymbolsAction() { return MAP_COPY_SYMBOLS; }
|
||||
|
||||
MapSymbolsAction OutputSymbolsAction() { return MAP_COPY_SYMBOLS; }
|
||||
|
||||
// I believe all properties are preserved.
|
||||
uint64 Properties(uint64 props) { return props; }
|
||||
};
|
||||
|
||||
|
||||
template<class Weight, class Int>
|
||||
void PruneCompactLattice(
|
||||
Weight beam,
|
||||
MutableFst<ArcTpl<CompactLatticeWeightTpl<Weight, Int> > > *fst);
|
||||
|
||||
|
||||
} // end namespace fst
|
||||
|
||||
#include "fstext/lattice-utils-inl.h"
|
||||
|
||||
#endif // KALDI_FSTEXT_LATTICE_UTILS_H_
|
||||
@@ -0,0 +1,197 @@
|
||||
// fstext/lattice-weight-test.cc
|
||||
|
||||
// Copyright 2009-2011 Microsoft Corporation
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
#include "base/kaldi-math.h"
|
||||
#include "fstext/lattice-weight.h"
|
||||
|
||||
namespace fst {
|
||||
using std::vector;
|
||||
using std::cout;
|
||||
// these typedefs are the same as in ../lat/kaldi-lattice.h, but
|
||||
// just used here for testing (doesn't matter if they get out of
|
||||
// sync).
|
||||
typedef float BaseFloat;
|
||||
|
||||
typedef LatticeWeightTpl<BaseFloat> LatticeWeight;
|
||||
|
||||
typedef CompactLatticeWeightTpl<LatticeWeight, int32> CompactLatticeWeight;
|
||||
|
||||
typedef CompactLatticeWeightCommonDivisorTpl<LatticeWeight, int32>
|
||||
CompactLatticeWeightCommonDivisor;
|
||||
|
||||
|
||||
LatticeWeight RandomLatticeWeight() {
|
||||
int tmp = kaldi::Rand() % 4;
|
||||
if (tmp == 0) {
|
||||
return LatticeWeight::Zero();
|
||||
} else if (tmp == 1) {
|
||||
return LatticeWeight( 1, 2); // sometimes return special values..
|
||||
} else if (tmp == 2) {
|
||||
return LatticeWeight( 2, 1); // this tests more thoroughly certain properties...
|
||||
} else {
|
||||
return LatticeWeight( 100 * kaldi::RandGauss(), 100 * kaldi::RandGauss());
|
||||
}
|
||||
}
|
||||
|
||||
CompactLatticeWeight RandomCompactLatticeWeight() {
|
||||
LatticeWeight w = RandomLatticeWeight();
|
||||
if (w == LatticeWeight::Zero()) {
|
||||
return CompactLatticeWeight(w, vector<int32>());
|
||||
} else {
|
||||
int32 len = kaldi::Rand() % 4;
|
||||
vector<int32> str;
|
||||
for(int32 i = 0; i < len; i++)
|
||||
str.push_back(kaldi::Rand() % 10 + 1);
|
||||
return CompactLatticeWeight(w, str);
|
||||
}
|
||||
}
|
||||
|
||||
void LatticeWeightTest() {
|
||||
for(int32 i = 0; i < 100; i++) {
|
||||
LatticeWeight l1 = RandomLatticeWeight(), l2 = RandomLatticeWeight();
|
||||
LatticeWeight l3 = Plus(l1, l2);
|
||||
LatticeWeight l4 = Times(l1, l2);
|
||||
BaseFloat f1 = l1.Value1() + l1.Value2(), f2 = l2.Value1() + l2.Value2(), f3 = l3.Value1() + l3.Value2(),
|
||||
f4 = l4.Value1() + l4.Value2();
|
||||
kaldi::AssertEqual(std::min(f1, f2), f3);
|
||||
kaldi::AssertEqual(f1 + f2, f4);
|
||||
|
||||
KALDI_ASSERT(Plus(l3, l3) == l3);
|
||||
KALDI_ASSERT(Plus(l1, l2) == Plus(l2, l1)); // commutativity of plus
|
||||
KALDI_ASSERT(Times(l1, l2) == Times(l2, l1)); // commutativity of Times (true for this semiring, not always)
|
||||
KALDI_ASSERT(Plus(l3, LatticeWeight::Zero()) == l3); // x + 0 = x
|
||||
KALDI_ASSERT(Times(l3, LatticeWeight::One()) == l3); // x * 1 = x
|
||||
KALDI_ASSERT(Times(l3, LatticeWeight::Zero()) == LatticeWeight::Zero()); // x * 0 = 0
|
||||
|
||||
KALDI_ASSERT(l3.Reverse().Reverse() == l3);
|
||||
|
||||
NaturalLess<LatticeWeight> nl;
|
||||
bool a = nl(l1, l2);
|
||||
bool b = (Plus(l1, l2) == l1 && l1 != l2);
|
||||
KALDI_ASSERT(a == b);
|
||||
|
||||
KALDI_ASSERT(Compare(l1, Plus(l1, l2)) != 1); // so do not have l1 > l1 + l2
|
||||
LatticeWeight l5 = RandomLatticeWeight(), l6 = RandomLatticeWeight();
|
||||
{
|
||||
LatticeWeight wa = Times(Plus(l1, l2), Plus(l5, l6)),
|
||||
wb = Plus(Times(l1, l5), Plus(Times(l1, l6),
|
||||
Plus(Times(l2, l5), Times(l2, l6))));
|
||||
if (!ApproxEqual(wa, wb)) {
|
||||
std::cout << "l1 = " << l1 << ", l2 = " << l2
|
||||
<< ", l5 = " << l5 << ", l6 = " << l6 << "\n";
|
||||
std::cout << "ERROR: " << wa << " != " << wb << "\n";
|
||||
}
|
||||
// KALDI_ASSERT(Times(Plus(l1, l2), Plus(l5, l6))
|
||||
// == Plus(Times(l1, l5), Plus(Times(l1,l6),
|
||||
// Plus(Times(l2, l5), Times(l2, l6))))); // * distributes over +
|
||||
}
|
||||
KALDI_ASSERT(l1.Member() && l2.Member() && l3.Member() && l4.Member()
|
||||
&& l5.Member() && l6.Member());
|
||||
if (l2 != LatticeWeight::Zero())
|
||||
KALDI_ASSERT(ApproxEqual(Divide(Times(l1, l2), l2), l1)); // (a*b) / b = a if b != 0
|
||||
KALDI_ASSERT(ApproxEqual(l1, l1.Quantize()));
|
||||
|
||||
std::ostringstream s1;
|
||||
s1 << l1;
|
||||
std::istringstream s2(s1.str());
|
||||
s2 >> l2;
|
||||
KALDI_ASSERT(ApproxEqual(l1, l2, 0.001));
|
||||
std::cout << s1.str() << '\n';
|
||||
{
|
||||
std::ostringstream s1b;
|
||||
l1.Write(s1b);
|
||||
std::istringstream s2b(s1b.str());
|
||||
l3.Read(s2b);
|
||||
KALDI_ASSERT(l1 == l3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void CompactLatticeWeightTest() {
|
||||
for(int32 i = 0; i < 100; i++) {
|
||||
CompactLatticeWeight l1 = RandomCompactLatticeWeight(), l2 = RandomCompactLatticeWeight();
|
||||
CompactLatticeWeight l3 = Plus(l1, l2);
|
||||
CompactLatticeWeight l4 = Times(l1, l2);
|
||||
|
||||
KALDI_ASSERT(Plus(l3, l3) == l3);
|
||||
KALDI_ASSERT(Plus(l1, l2) == Plus(l2, l1)); // commutativity of plus
|
||||
KALDI_ASSERT(Plus(l3, CompactLatticeWeight::Zero()) == l3); // x + 0 = x
|
||||
KALDI_ASSERT(Times(l3, CompactLatticeWeight::One()) == l3); // x * 1 = x
|
||||
KALDI_ASSERT(Times(l3, CompactLatticeWeight::Zero()) == CompactLatticeWeight::Zero()); // x * 0 = 0
|
||||
NaturalLess<CompactLatticeWeight> nl;
|
||||
bool a = nl(l1, l2);
|
||||
bool b = (Plus(l1, l2) == l1 && l1 != l2);
|
||||
KALDI_ASSERT(a == b);
|
||||
|
||||
KALDI_ASSERT(Compare(l1, Plus(l1, l2)) != 1); // so do not have l1 > l1 + l2
|
||||
CompactLatticeWeight l5 = RandomCompactLatticeWeight(), l6 = RandomCompactLatticeWeight();
|
||||
KALDI_ASSERT(Times(Plus(l1, l2), Plus(l5, l6)) ==
|
||||
Plus(Times(l1, l5), Plus(Times(l1, l6),
|
||||
Plus(Times(l2, l5), Times(l2, l6))))); // * distributes over +
|
||||
KALDI_ASSERT(l1.Member() && l2.Member() && l3.Member() && l4.Member()
|
||||
&& l5.Member() && l6.Member());
|
||||
if (l2 != CompactLatticeWeight::Zero()) {
|
||||
KALDI_ASSERT(ApproxEqual(Divide(Times(l1, l2), l2, DIVIDE_RIGHT), l1)); // (a*b) / b = a if b != 0
|
||||
KALDI_ASSERT(ApproxEqual(Divide(Times(l2, l1), l2, DIVIDE_LEFT), l1)); // (a*b) / b = a if b != 0
|
||||
}
|
||||
KALDI_ASSERT(ApproxEqual(l1, l1.Quantize()));
|
||||
|
||||
std::ostringstream s1;
|
||||
s1 << l1;
|
||||
std::istringstream s2(s1.str());
|
||||
s2 >> l2;
|
||||
KALDI_ASSERT(ApproxEqual(l1, l2));
|
||||
std::cout << s1.str() << '\n';
|
||||
|
||||
{
|
||||
std::ostringstream s1b;
|
||||
l1.Write(s1b);
|
||||
std::istringstream s2b(s1b.str());
|
||||
l3.Read(s2b);
|
||||
KALDI_ASSERT(l1 == l3);
|
||||
}
|
||||
|
||||
CompactLatticeWeightCommonDivisor divisor;
|
||||
std::cout << "l5 = " << l5 << '\n';
|
||||
std::cout << "l6 = " << l6 << '\n';
|
||||
l1 = divisor(l5, l6);
|
||||
std::cout << "div = " << l1 << '\n';
|
||||
if (l1 != CompactLatticeWeight::Zero()) {
|
||||
l2 = Divide(l5, l1, DIVIDE_LEFT);
|
||||
l3 = Divide(l6, l1, DIVIDE_LEFT);
|
||||
std::cout << "l2 = " << l2 << '\n';
|
||||
std::cout << "l3 = " << l3 << '\n';
|
||||
l4 = divisor(l2, l3); // make sure l2 is now one.
|
||||
std::cout << "l4 = " << l4 << '\n';
|
||||
KALDI_ASSERT(ApproxEqual(l4, CompactLatticeWeight::One()));
|
||||
} else {
|
||||
KALDI_ASSERT(l5 == CompactLatticeWeight::Zero()
|
||||
&& l6 == CompactLatticeWeight::Zero());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
fst::LatticeWeightTest();
|
||||
fst::CompactLatticeWeightTest();
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user