Initial commit: FunASR Speech Recognition Toolkit
Update API Documentation / build-api-docs (push) Has been cancelled

Add complete FunASR codebase including models, runtime, and documentation.
This commit is contained in:
freedakgmail
2026-07-09 22:38:58 +08:00
commit 6116b1f3c6
3683 changed files with 990984 additions and 0 deletions
+71
View File
@@ -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()
+27
View File
@@ -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
View File
@@ -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;
}
+218
View File
@@ -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
+245
View File
@@ -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
+231
View File
@@ -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>();
}
+162
View File
@@ -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
+363
View File
@@ -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
+162
View File
@@ -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();
}
+85
View File
@@ -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
View File
@@ -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_
+20
View File
@@ -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
@@ -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
@@ -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.
@@ -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
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
@@ -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.
@@ -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
+31
View File
@@ -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", &central_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;
}
}
+78
View File
@@ -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", &central_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;
}
}
+67
View File
@@ -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;
}
}
+30
View File
@@ -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>();
}
}
+158
View File
@@ -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