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
+25
View File
@@ -0,0 +1,25 @@
EXTRA_CXXFLAGS = -Wno-sign-compare
all:
include ../kaldi.mk
TESTFILES = arpa-file-parser-test arpa-lm-compiler-test
OBJFILES = arpa-file-parser.o arpa-lm-compiler.o const-arpa-lm.o \
kaldi-rnnlm.o mikolov-rnnlm-lib.o
ifdef KENLM_ROOT
TESTFILES += kenlm-test
OBJFILES += kenlm.o
EXTRA_CXXFLAGS += $(KENLM_CXXFLAGS)
EXTRA_LDFLAGS += $(KENLM_LDFLAGS)
EXTRA_LDLIBS += $(KENLM_LDLIBS)
endif
LIBNAME = kaldi-lm
ADDLIBS = ../fstext/kaldi-fstext.a ../util/kaldi-util.a \
../matrix/kaldi-matrix.a ../base/kaldi-base.a
include ../makefiles/default_rules.mk
+108
View File
@@ -0,0 +1,108 @@
#
# README
#
# Language model & lexicon examples
# using command-line executables in lm/
# To print and display FSTs,
# make sure you have OpenFst binaries in your PATH, for example:
# export PATH=$PATH:~/Sources/UBM-ASR/branches/clean/openfst-1.2/bin
# If you have X installed in your local machine, you can display
# FSTs from merlin by ssh'ing with X forwarding, for example:
# ssh -X qboulianne@merlin
# The following commands and examples assume that your are
# in working directory UBM-ASR/branches/clean/src/lm
#-------------------------------------------
# Language model FST (G)
# The command-line utility for
# creating a language model FST from an arpa file is
# "arpa2fst".
# A summary of options and usage can be displayed with:
./arpa2fst --help
# Read an arpa file to produce an FST with symbol tables:
./arpa2fst < input.arpa > grammar.fst
# Print it or display it:
fstprint grammar.fst
fstdraw grammar.fst | dotty -
# Note that arpa2fst will create a word symbol table
# from all the words are in the ARPA file.
# You can save this symbol table in text format
# for examination or later reuse.
fstprint --save_isymbols=grammar.syms grammar.fst > /dev/null
#----------------------------------------------
# Lexicon (L)
# The command-line utility for
# creating a lexicon FST from a text file is
# "lex2fst".
# A summary of options and usage can be displayed with:
./lex2fst --help
# Read a lexicon file (containing pronunciation probabilities)
# and produce an FST with symbol tables.
# By default it will have disambiguation markers,
# optional silence between words,
# and FST weights will be -log(prob).
./lex2fst < prob_input.lex > lexicon.fst
# Print it or display it
fstprint lexicon.fst
fstdraw lexicon.fst | dotty -
# To produce one without markers (and also smaller):
./lex2fst --nodisamb < prob_input.lex > lexicon_nomarkers.fst
#---------------------------------
# Combining lexicon and grammar
# lgrecipe.cc is an example recipe for building det(LoG)
# in C++ using calls to functions in lm/kaldi-lm.a
# Here we use a large lexicon and language model from last year's.
# First get input files:
export MDIR=/homes/eva/q/qgoel/englishModels
cp $MDIR/lm_callhome_gigaword_switchboard_web.hd.dct largelexicon.dct
gunzip -c $MDIR/lm_callhome_gigaword_switchboard_web.3gram.arpa.gz > largelm.arpa
# Set memory limits (by default limited to 400 MB). Make it 4 GB.
ulimit -m 4000000 -v 8000000
./lgrecipe largelexicon.dct largelm.arpa detlg.fst
# Check its size: should be close to 13 M arcs and 8 M nodes
fstinfo detlg.fst | head -15
#---------------------------------------
# Expanding nested grammars
# replace-example is a use-case example that creates
# small grammars from text files. These grammars
# refer to other grammars using non-terminal symbols.
# The result is the fully expanded grammar FST,
# where each non-terminal has been expanded entirely
# to non-terminals.
cd examples
# Example 1 : create the expanded grammar
../replace-example input3.txt CREATURE.txt > input3.fst
# and generate a random sentence from the expanded grammar
fstrandgen input3.fst | fstrmepsilon | fstprint | cut -f 4
# Example 2 : create the expanded grammar
../replace-example input4.txt DAYOFMONTH.txt MONTH.txt YEAR.txt YEARDATE.txt > input4.fst
# and generate random sentence
fstrandgen input4.fst | fstproject --project_output | fstrmepsilon | fstprint | cut -f 4
#--------------------------------------------
# TODO: Adapt README.testfiles as an example of how to create
# an arpa language model and evaluate its score / perplexity.
@@ -0,0 +1,373 @@
// lm/arpa-file-parser-test.cc
// Copyright 2016 Smart Action Company LLC (kkm)
// 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.
/**
* @file arpa-file-parser-test.cc
* @brief Unit tests for language model code.
*/
#include <iomanip>
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include "base/kaldi-common.h"
#include "fst/fstlib.h"
#include "lm/arpa-file-parser.h"
namespace kaldi {
namespace {
const int kMaxOrder = 3;
struct NGramTestData {
int32 line_number;
float logprob;
int32 words[kMaxOrder];
float backoff;
};
std::ostream& operator<<(std::ostream &os, const NGramTestData &data) {
std::ios::fmtflags saved_state(os.flags());
os << std::fixed << std::setprecision(6);
os << data.logprob << ' ';
for (int i = 0; i < kMaxOrder; ++i) os << data.words[i] << ' ';
os << data.backoff << " // Line " << data.line_number;
os.flags(saved_state);
return os;
}
// This does not own the array pointer, and uset to simplify passing expected
// result to TestableArpaFileParser::Verify.
template <class T>
struct CountedArray {
template <size_t N>
CountedArray(T(&array)[N]) : array(array), count(N) { }
const T *array;
const size_t count;
};
template <class T, size_t N>
inline CountedArray<T> MakeCountedArray(T(&array)[N]) {
return CountedArray<T>(array);
}
class TestableArpaFileParser : public ArpaFileParser {
public:
TestableArpaFileParser(const ArpaParseOptions &options,
fst::SymbolTable *symbols)
: ArpaFileParser(options, symbols),
header_available_(false),
read_complete_(false),
last_order_(0) { }
void Validate(CountedArray<int32> counts, CountedArray<NGramTestData> ngrams);
private:
// ArpaFileParser overrides.
virtual void HeaderAvailable();
virtual void ConsumeNGram(const NGram& ngram);
virtual void ReadComplete();
bool header_available_;
bool read_complete_;
int32 last_order_;
std::vector<NGramTestData> ngrams_;
};
void TestableArpaFileParser::HeaderAvailable() {
KALDI_ASSERT(!header_available_);
KALDI_ASSERT(!read_complete_);
header_available_ = true;
KALDI_ASSERT(NgramCounts().size() <= kMaxOrder);
}
void TestableArpaFileParser::ConsumeNGram(const NGram& ngram) {
KALDI_ASSERT(header_available_);
KALDI_ASSERT(!read_complete_);
KALDI_ASSERT(ngram.words.size() <= NgramCounts().size());
KALDI_ASSERT(ngram.words.size() >= last_order_);
last_order_ = ngram.words.size();
NGramTestData entry = { 0 };
entry.line_number = LineNumber();
entry.logprob = ngram.logprob;
entry.backoff = ngram.backoff;
std::copy(ngram.words.begin(), ngram.words.end(), entry.words);
ngrams_.push_back(entry);
}
void TestableArpaFileParser::ReadComplete() {
KALDI_ASSERT(header_available_);
KALDI_ASSERT(!read_complete_);
read_complete_ = true;
}
bool CompareNgrams(const NGramTestData &actual,
NGramTestData expected) {
expected.logprob *= Log(10.0);
expected.backoff *= Log(10.0);
if (actual.line_number != expected.line_number
|| !std::equal(actual.words, actual.words + kMaxOrder,
expected.words)
|| !ApproxEqual(actual.logprob, expected.logprob)
|| !ApproxEqual(actual.backoff, expected.backoff)) {
KALDI_WARN << "Actual n-gram [" << actual
<< "] differs from expected [" << expected << "]";
return false;
}
return true;
}
void TestableArpaFileParser::Validate(
CountedArray<int32> expect_counts,
CountedArray<NGramTestData> expect_ngrams) {
// This needs better disagnostics probably.
KALDI_ASSERT(NgramCounts().size() == expect_counts.count);
KALDI_ASSERT(std::equal(NgramCounts().begin(), NgramCounts().end(),
expect_counts.array));
KALDI_ASSERT(ngrams_.size() == expect_ngrams.count);
// auto mpos = std::mismatch(ngrams_.begin(), ngrams_.end(),
// expect_ngrams.array, CompareNgrams);
// if (mpos.first != ngrams_.end())
// KALDI_ERR << "Maismatch at index " << mpos.first - ngrams_.begin();
// TODO: auto above requres C++11, and I cannot spell out the type!!!
KALDI_ASSERT(std::equal(ngrams_.begin(), ngrams_.end(),
expect_ngrams.array, CompareNgrams));
}
// Read integer LM (no symbols) with log base conversion.
void ReadIntegerLmLogconvExpectSuccess() {
KALDI_LOG << "ReadIntegerLmLogconvExpectSuccess()";
static std::string integer_lm = "\
\\data\\\n\
ngram 1=4\n\
ngram 2=2\n\
ngram 3=2\n\
\n\
\\1-grams:\n\
-5.2\t4\t-3.3\n\
-3.4\t5\n\
0\t1\t-2.5\n\
-4.3\t2\n\
\n\
\\2-grams:\n\
-1.4\t4 5\t-3.2\n\
-1.3\t1 4\t-4.2\n\
\n\
\\3-grams:\n\
-0.3\t1 4 5\n\
-0.2\t4 5 2\n\
\n\
\\end\\";
int32 expect_counts[] = { 4, 2, 2 };
NGramTestData expect_ngrams[] = {
{ 7, -5.2, { 4, 0, 0 }, -3.3 },
{ 8, -3.4, { 5, 0, 0 }, 0.0 },
{ 9, 0.0, { 1, 0, 0 }, -2.5 },
{ 10, -4.3, { 2, 0, 0 }, 0.0 },
{ 13, -1.4, { 4, 5, 0 }, -3.2 },
{ 14, -1.3, { 1, 4, 0 }, -4.2 },
{ 17, -0.3, { 1, 4, 5 }, 0.0 },
{ 18, -0.2, { 4, 5, 2 }, 0.0 } };
ArpaParseOptions options;
options.bos_symbol = 1;
options.eos_symbol = 2;
TestableArpaFileParser parser(options, NULL);
std::istringstream stm(integer_lm, std::ios_base::in);
parser.Read(stm);
parser.Validate(MakeCountedArray(expect_counts),
MakeCountedArray(expect_ngrams));
}
// \xCE\xB2 = UTF-8 for Greek beta, to churn some UTF-8 cranks.
static std::string symbolic_lm = "\
We also allow random text coming before the \\data\\\n\
section marker. Even this is ok:\n\
\n\
\\1-grams:\n\
\n\
and should be ignored before the \\data\\ marker\n\
is seen alone by itself on a line.\n\
\n\
\\data\\\n\
ngram 1=4\n\
ngram 2=2\n\
ngram 3=2\n\
\n\
\\1-grams: \n\
-5.2\ta\t-3.3\n\
-3.4\t\xCE\xB2\n\
0.0\t<s>\t-2.5\n\
-4.3\t</s>\n\
\n\
\\2-grams:\t\n\
-1.5\ta \xCE\xB2\t-3.2\n\
-1.3\t<s> a\t-4.2\n\
\n\
\\3-grams:\n\
-0.3\t<s> a \xCE\xB2\n\
-0.2\t<s> a </s>\n\
\\end\\";
// Symbol table that is created with predefined test symbols, "a" but no "b".
class TestSymbolTable : public fst::SymbolTable {
public:
TestSymbolTable() {
AddSymbol("<eps>", 0);
AddSymbol("<s>", 1);
AddSymbol("</s>", 2);
AddSymbol("<unk>", 3);
AddSymbol("a", 4);
}
};
// Full expected result shared between ReadSymbolicLmNoOovImpl and
// ReadSymbolicLmWithOovAddToSymbols().
NGramTestData expect_symbolic_full[] = {
{ 15, -5.2, { 4, 0, 0 }, -3.3 },
{ 16, -3.4, { 5, 0, 0 }, 0.0 },
{ 17, 0.0, { 1, 0, 0 }, -2.5 },
{ 18, -4.3, { 2, 0, 0 }, 0.0 },
{ 21, -1.5, { 4, 5, 0 }, -3.2 },
{ 22, -1.3, { 1, 4, 0 }, -4.2 },
{ 25, -0.3, { 1, 4, 5 }, 0.0 },
{ 26, -0.2, { 1, 4, 2 }, 0.0 } };
// This is run with all possible oov setting and yields same result.
void ReadSymbolicLmNoOovImpl(ArpaParseOptions::OovHandling oov) {
int32 expect_counts[] = { 4, 2, 2 };
TestSymbolTable symbols;
symbols.AddSymbol("\xCE\xB2", 5);
ArpaParseOptions options;
options.bos_symbol = 1;
options.eos_symbol = 2;
options.unk_symbol = 3;
options.oov_handling = oov;
TestableArpaFileParser parser(options, &symbols);
std::istringstream stm(symbolic_lm, std::ios_base::in);
parser.Read(stm);
parser.Validate(MakeCountedArray(expect_counts),
MakeCountedArray(expect_symbolic_full));
KALDI_ASSERT(symbols.NumSymbols() == 6);
}
void ReadSymbolicLmNoOovTests() {
KALDI_LOG << "ReadSymbolicLmNoOovImpl(kRaiseError)";
ReadSymbolicLmNoOovImpl(ArpaParseOptions::kRaiseError);
KALDI_LOG << "ReadSymbolicLmNoOovImpl(kAddToSymbols)";
ReadSymbolicLmNoOovImpl(ArpaParseOptions::kAddToSymbols);
KALDI_LOG << "ReadSymbolicLmNoOovImpl(kReplaceWithUnk)";
ReadSymbolicLmNoOovImpl(ArpaParseOptions::kReplaceWithUnk);
KALDI_LOG << "ReadSymbolicLmNoOovImpl(kSkipNGram)";
ReadSymbolicLmNoOovImpl(ArpaParseOptions::kSkipNGram);
}
// This is run with all possible oov setting and yields same result.
void ReadSymbolicLmWithOovImpl(
ArpaParseOptions::OovHandling oov,
CountedArray<NGramTestData> expect_ngrams,
fst::SymbolTable* symbols) {
int32 expect_counts[] = { 4, 2, 2 };
ArpaParseOptions options;
options.bos_symbol = 1;
options.eos_symbol = 2;
options.unk_symbol = 3;
options.oov_handling = oov;
TestableArpaFileParser parser(options, symbols);
std::istringstream stm(symbolic_lm, std::ios_base::in);
parser.Read(stm);
parser.Validate(MakeCountedArray(expect_counts), expect_ngrams);
}
void ReadSymbolicLmWithOovAddToSymbols() {
TestSymbolTable symbols;
ReadSymbolicLmWithOovImpl(ArpaParseOptions::kAddToSymbols,
MakeCountedArray(expect_symbolic_full),
&symbols);
KALDI_ASSERT(symbols.NumSymbols() == 6);
KALDI_ASSERT(symbols.Find("\xCE\xB2") == 5);
}
void ReadSymbolicLmWithOovReplaceWithUnk() {
NGramTestData expect_symbolic_unk_b[] = {
{ 15, -5.2, { 4, 0, 0 }, -3.3 },
{ 16, -3.4, { 3, 0, 0 }, 0.0 },
{ 17, 0.0, { 1, 0, 0 }, -2.5 },
{ 18, -4.3, { 2, 0, 0 }, 0.0 },
{ 21, -1.5, { 4, 3, 0 }, -3.2 },
{ 22, -1.3, { 1, 4, 0 }, -4.2 },
{ 25, -0.3, { 1, 4, 3 }, 0.0 },
{ 26, -0.2, { 1, 4, 2 }, 0.0 } };
TestSymbolTable symbols;
ReadSymbolicLmWithOovImpl(ArpaParseOptions::kReplaceWithUnk,
MakeCountedArray(expect_symbolic_unk_b),
&symbols);
KALDI_ASSERT(symbols.NumSymbols() == 5);
}
void ReadSymbolicLmWithOovSkipNGram() {
NGramTestData expect_symbolic_no_b[] = {
{ 15, -5.2, { 4, 0, 0 }, -3.3 },
{ 17, 0.0, { 1, 0, 0 }, -2.5 },
{ 18, -4.3, { 2, 0, 0 }, 0.0 },
{ 22, -1.3, { 1, 4, 0 }, -4.2 },
{ 26, -0.2, { 1, 4, 2 }, 0.0 } };
TestSymbolTable symbols;
ReadSymbolicLmWithOovImpl(ArpaParseOptions::kSkipNGram,
MakeCountedArray(expect_symbolic_no_b),
&symbols);
KALDI_ASSERT(symbols.NumSymbols() == 5);
}
void ReadSymbolicLmWithOovTests() {
KALDI_LOG << "ReadSymbolicLmWithOovAddToSymbols()";
ReadSymbolicLmWithOovAddToSymbols();
KALDI_LOG << "ReadSymbolicLmWithOovReplaceWithUnk()";
ReadSymbolicLmWithOovReplaceWithUnk();
KALDI_LOG << "ReadSymbolicLmWithOovSkipNGram()";
ReadSymbolicLmWithOovSkipNGram();
}
} // namespace
} // namespace kaldi
int main(int argc, char *argv[]) {
kaldi::ReadIntegerLmLogconvExpectSuccess();
kaldi::ReadSymbolicLmNoOovTests();
kaldi::ReadSymbolicLmWithOovTests();
}
@@ -0,0 +1,281 @@
// lm/arpa-file-parser.cc
// Copyright 2014 Guoguo Chen
// Copyright 2016 Smart Action Company LLC (kkm)
// 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 <fst/fstlib.h>
#include <sstream>
#include "base/kaldi-error.h"
#include "base/kaldi-math.h"
#include "lm/arpa-file-parser.h"
#include "util/text-utils.h"
namespace kaldi {
ArpaFileParser::ArpaFileParser(const ArpaParseOptions& options,
fst::SymbolTable* symbols)
: options_(options), symbols_(symbols),
line_number_(0), warning_count_(0) {
}
ArpaFileParser::~ArpaFileParser() {
}
void TrimTrailingWhitespace(std::string *str) {
str->erase(str->find_last_not_of(" \n\r\t") + 1);
}
void ArpaFileParser::Read(std::istream &is) {
// Argument sanity checks.
if (options_.bos_symbol <= 0 || options_.eos_symbol <= 0 ||
options_.bos_symbol == options_.eos_symbol)
KALDI_ERR << "BOS and EOS symbols are required, must not be epsilons, and "
<< "differ from each other. Given:"
<< " BOS=" << options_.bos_symbol
<< " EOS=" << options_.eos_symbol;
if (symbols_ != NULL &&
options_.oov_handling == ArpaParseOptions::kReplaceWithUnk &&
(options_.unk_symbol <= 0 ||
options_.unk_symbol == options_.bos_symbol ||
options_.unk_symbol == options_.eos_symbol))
KALDI_ERR << "When symbol table is given and OOV mode is kReplaceWithUnk, "
<< "UNK symbol is required, must not be epsilon, and "
<< "differ from both BOS and EOS symbols. Given:"
<< " UNK=" << options_.unk_symbol
<< " BOS=" << options_.bos_symbol
<< " EOS=" << options_.eos_symbol;
if (symbols_ != NULL && symbols_->Find(options_.bos_symbol).empty())
KALDI_ERR << "BOS symbol must exist in symbol table";
if (symbols_ != NULL && symbols_->Find(options_.eos_symbol).empty())
KALDI_ERR << "EOS symbol must exist in symbol table";
if (symbols_ != NULL && options_.unk_symbol > 0 &&
symbols_->Find(options_.unk_symbol).empty())
KALDI_ERR << "UNK symbol must exist in symbol table";
ngram_counts_.clear();
line_number_ = 0;
warning_count_ = 0;
current_line_.clear();
#define PARSE_ERR KALDI_ERR << LineReference() << ": "
// Give derived class an opportunity to prepare its state.
ReadStarted();
// Processes "\data\" section.
bool keyword_found = false;
while (++line_number_, getline(is, current_line_) && !is.eof()) {
if (current_line_.find_first_not_of(" \t\n\r") == std::string::npos) {
continue;
}
TrimTrailingWhitespace(&current_line_);
// Continue skipping lines until the \data\ marker alone on a line is found.
if (!keyword_found) {
if (current_line_ == "\\data\\") {
KALDI_LOG << "Reading \\data\\ section.";
keyword_found = true;
}
continue;
}
if (current_line_[0] == '\\') break;
// Enters "\data\" section, and looks for patterns like "ngram 1=1000",
// which means there are 1000 unigrams.
std::size_t equal_symbol_pos = current_line_.find("=");
if (equal_symbol_pos != std::string::npos)
// Guaranteed spaces around the "=".
current_line_.replace(equal_symbol_pos, 1, " = ");
std::vector<std::string> col;
SplitStringToVector(current_line_, " \t", true, &col);
if (col.size() == 4 && col[0] == "ngram" && col[2] == "=") {
int32 order, ngram_count = 0;
if (!ConvertStringToInteger(col[1], &order) ||
!ConvertStringToInteger(col[3], &ngram_count)) {
PARSE_ERR << "cannot parse ngram count";
}
if (ngram_counts_.size() <= order) {
ngram_counts_.resize(order);
}
ngram_counts_[order - 1] = ngram_count;
} else {
KALDI_WARN << LineReference()
<< ": uninterpretable line in \\data\\ section";
}
}
if (ngram_counts_.size() == 0)
PARSE_ERR << "\\data\\ section missing or empty.";
// Signal that grammar order and n-gram counts are known.
HeaderAvailable();
NGram ngram;
ngram.words.reserve(ngram_counts_.size());
// Processes "\N-grams:" section.
for (int32 cur_order = 1; cur_order <= ngram_counts_.size(); ++cur_order) {
// Skips n-grams with zero count.
if (ngram_counts_[cur_order - 1] == 0)
KALDI_WARN << "Zero ngram count in ngram order " << cur_order
<< "(look for 'ngram " << cur_order << "=0' in the \\data\\ "
<< " section). There is possibly a problem with the file.";
// Must be looking at a \k-grams: directive at this point.
std::ostringstream keyword;
keyword << "\\" << cur_order << "-grams:";
if (current_line_ != keyword.str()) {
PARSE_ERR << "invalid directive, expecting '" << keyword.str() << "'";
}
KALDI_LOG << "Reading " << current_line_ << " section.";
int32 ngram_count = 0;
while (++line_number_, getline(is, current_line_) && !is.eof()) {
if (current_line_.find_first_not_of(" \n\t\r") == std::string::npos) {
continue;
}
if (current_line_[0] == '\\') {
TrimTrailingWhitespace(&current_line_);
std::ostringstream next_keyword;
next_keyword << "\\" << cur_order + 1 << "-grams:";
if ((current_line_ != next_keyword.str()) &&
(current_line_ != "\\end\\")) {
if (ShouldWarn()) {
KALDI_WARN << "ignoring possible directive '" << current_line_
<< "' expecting '" << next_keyword.str() << "'";
if (warning_count_ > 0 &&
warning_count_ > static_cast<uint32>(options_.max_warnings)) {
KALDI_WARN << "Of " << warning_count_ << " parse warnings, "
<< options_.max_warnings << " were reported. "
<< "Run program with --max-arpa-warnings=-1 "
<< "to see all warnings";
}
}
} else {
break;
}
}
std::vector<std::string> col;
SplitStringToVector(current_line_, " \t", true, &col);
if (col.size() < 1 + cur_order ||
col.size() > 2 + cur_order ||
(cur_order == ngram_counts_.size() && col.size() != 1 + cur_order)) {
PARSE_ERR << "Invalid n-gram data line";
}
++ngram_count;
// Parse out n-gram logprob and, if present, backoff weight.
if (!ConvertStringToReal(col[0], &ngram.logprob)) {
PARSE_ERR << "invalid n-gram logprob '" << col[0] << "'";
}
ngram.backoff = 0.0;
if (col.size() > cur_order + 1) {
if (!ConvertStringToReal(col[cur_order + 1], &ngram.backoff))
PARSE_ERR << "invalid backoff weight '" << col[cur_order + 1] << "'";
}
// Convert to natural log.
ngram.logprob *= M_LN10;
ngram.backoff *= M_LN10;
ngram.words.resize(cur_order);
bool skip_ngram = false;
for (int32 index = 0; !skip_ngram && index < cur_order; ++index) {
int32 word;
if (symbols_) {
// Symbol table provided, so symbol labels are expected.
if (options_.oov_handling == ArpaParseOptions::kAddToSymbols) {
word = symbols_->AddSymbol(col[1 + index]);
} else {
word = symbols_->Find(col[1 + index]);
if (word == -1) { // fst::kNoSymbol
switch (options_.oov_handling) {
case ArpaParseOptions::kReplaceWithUnk:
word = options_.unk_symbol;
break;
case ArpaParseOptions::kSkipNGram:
if (ShouldWarn())
KALDI_WARN << LineReference() << " skipped: word '"
<< col[1 + index] << "' not in symbol table";
skip_ngram = true;
break;
default:
PARSE_ERR << "word '" << col[1 + index]
<< "' not in symbol table";
}
}
}
} else {
// Symbols not provided, LM file should contain integers.
if (!ConvertStringToInteger(col[1 + index], &word) || word < 0) {
PARSE_ERR << "invalid symbol '" << col[1 + index] << "'";
}
}
// Whichever way we got it, an epsilon is invalid.
if (word == 0) {
PARSE_ERR << "epsilon symbol '" << col[1 + index]
<< "' is illegal in ARPA LM";
}
ngram.words[index] = word;
}
if (!skip_ngram) {
ConsumeNGram(ngram);
}
}
if (ngram_count > ngram_counts_[cur_order - 1]) {
PARSE_ERR << "header said there would be " << ngram_counts_[cur_order - 1]
<< " n-grams of order " << cur_order
<< ", but we saw more already.";
}
}
if (current_line_ != "\\end\\") {
PARSE_ERR << "invalid or unexpected directive line, expecting \\end\\";
}
if (warning_count_ > 0 &&
warning_count_ > static_cast<uint32>(options_.max_warnings)) {
KALDI_WARN << "Of " << warning_count_ << " parse warnings, "
<< options_.max_warnings << " were reported. Run program with "
<< "--max-arpa-warnings=-1 to see all warnings";
}
current_line_.clear();
ReadComplete();
#undef PARSE_ERR
}
std::string ArpaFileParser::LineReference() const {
std::ostringstream ss;
ss << "line " << line_number_ << " [" << current_line_ << "]";
return ss.str();
}
bool ArpaFileParser::ShouldWarn() {
return (warning_count_ != -1) &&
(++warning_count_ <= static_cast<uint32>(options_.max_warnings));
}
} // namespace kaldi
@@ -0,0 +1,146 @@
// lm/arpa-file-parser.h
// Copyright 2014 Guoguo Chen
// Copyright 2016 Smart Action Company LLC (kkm)
// 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_LM_ARPA_FILE_PARSER_H_
#define KALDI_LM_ARPA_FILE_PARSER_H_
#include <fst/fst-decl.h>
#include <string>
#include <vector>
#include "base/kaldi-types.h"
#include "itf/options-itf.h"
namespace kaldi {
/**
Options that control ArpaFileParser
*/
struct ArpaParseOptions {
enum OovHandling {
kRaiseError, ///< Abort on OOV words
kAddToSymbols, ///< Add novel words to the symbol table.
kReplaceWithUnk, ///< Replace OOV words with <unk>.
kSkipNGram ///< Skip n-gram with OOV word and continue.
};
ArpaParseOptions():
bos_symbol(-1), eos_symbol(-1), unk_symbol(-1),
oov_handling(kRaiseError), max_warnings(30) { }
void Register(OptionsItf *opts) {
// Registering only the max_warnings count, since other options are
// treated differently by client programs: some want integer symbols,
// while other are passed words in their command line.
opts->Register("max-arpa-warnings", &max_warnings,
"Maximum warnings to report on ARPA parsing, "
"0 to disable, -1 to show all");
}
int32 bos_symbol; ///< Symbol for <s>, Required non-epsilon.
int32 eos_symbol; ///< Symbol for </s>, Required non-epsilon.
int32 unk_symbol; ///< Symbol for <unk>, Required for kReplaceWithUnk.
OovHandling oov_handling; ///< How to handle OOV words in the file.
int32 max_warnings; ///< Maximum warnings to report, <0 unlimited.
};
/**
A parsed n-gram from ARPA LM file.
*/
struct NGram {
NGram() : logprob(0.0), backoff(0.0) { }
std::vector<int32> words; ///< Symbols in left to right order.
float logprob; ///< Log-prob of the n-gram.
float backoff; ///< log-backoff weight of the n-gram.
///< Defaults to zero if not specified.
};
/**
ArpaFileParser is an abstract base class for ARPA LM file conversion.
See ConstArpaLmBuilder and ArpaLmCompiler for usage examples.
*/
class ArpaFileParser {
public:
/// Constructs the parser with the given options and optional symbol table.
/// If symbol table is provided, then the file should contain text n-grams,
/// and the words are mapped to symbols through it. bos_symbol and
/// eos_symbol in the options structure must be valid symbols in the table,
/// and so must be unk_symbol if provided. The table is not owned by the
/// parser, but may be augmented, if oov_handling is set to kAddToSymbols.
/// If symbol table is a null pointer, the file should contain integer
/// symbol values, and oov_handling has no effect. bos_symbol and eos_symbol
/// must be valid symbols still.
ArpaFileParser(const ArpaParseOptions& options, fst::SymbolTable* symbols);
virtual ~ArpaFileParser();
/// Read ARPA LM file from a stream.
void Read(std::istream &is);
/// Parser options.
const ArpaParseOptions& Options() const { return options_; }
protected:
/// Override called before reading starts. This is the point to prepare
/// any state in the derived class.
virtual void ReadStarted() { }
/// Override function called to signal that ARPA header with the expected
/// number of n-grams has been read, and ngram_counts() is now valid.
virtual void HeaderAvailable() { }
/// Pure override that must be implemented to process current n-gram. The
/// n-grams are sent in the file order, which guarantees that all
/// (k-1)-grams are processed before the first k-gram is.
virtual void ConsumeNGram(const NGram&) = 0;
/// Override function called after the last n-gram has been consumed.
virtual void ReadComplete() { }
/// Read-only access to symbol table. Not owned, do not make public.
const fst::SymbolTable* Symbols() const { return symbols_; }
/// Inside ConsumeNGram(), provides the current line number.
int32 LineNumber() const { return line_number_; }
/// Inside ConsumeNGram(), returns a formatted reference to the line being
/// compiled, to print out as part of diagnostics.
std::string LineReference() const;
/// Increments warning count, and returns true if a warning should be
/// printed or false if the count has exceeded the set maximum.
bool ShouldWarn();
/// N-gram counts. Valid from the point when HeaderAvailable() is called.
const std::vector<int32>& NgramCounts() const { return ngram_counts_; }
private:
ArpaParseOptions options_;
fst::SymbolTable* symbols_; // the pointer is not owned here.
int32 line_number_;
uint32 warning_count_;
std::string current_line_;
std::vector<int32> ngram_counts_;
};
} // namespace kaldi
#endif // KALDI_LM_ARPA_FILE_PARSER_H_
@@ -0,0 +1,250 @@
// lm/arpa-lm-compiler-test.cc
// Copyright 2009-2011 Gilles Boulianne
// Copyright 2016 Smart Action LLC (kkm)
// 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 <iostream>
#include <string>
#include <sstream>
#include "base/kaldi-error.h"
#include "base/kaldi-math.h"
#include "lm/arpa-lm-compiler.h"
#include "util/kaldi-io.h"
namespace kaldi {
// Predefine some symbol values, because any integer is as good than any other.
enum {
kEps = 0,
kDisambig,
kBos, kEos,
};
// Number of random sentences for coverage test.
static const int kRandomSentences = 50;
// Creates an FST that generates any sequence of symbols taken from given
// symbol table. The FST is then associated with the symbol table.
static fst::StdVectorFst* CreateGenFst(bool seps, const fst::SymbolTable* pst) {
fst::StdVectorFst* genFst = new fst::StdVectorFst;
genFst->SetInputSymbols(pst);
genFst->SetOutputSymbols(pst);
fst::StdArc::StateId midId = genFst->AddState();
if (!seps) {
fst::StdArc::StateId initId = genFst->AddState();
fst::StdArc::StateId finalId = genFst->AddState();
genFst->SetStart(initId);
genFst->SetFinal(finalId, fst::StdArc::Weight::One());
genFst->AddArc(initId, fst::StdArc(kBos, kBos, 0, midId));
genFst->AddArc(midId, fst::StdArc(kEos, kEos, 0, finalId));
} else {
genFst->SetStart(midId);
genFst->SetFinal(midId, fst::StdArc::Weight::One());
}
// Add a loop for each symbol in the table except the four special ones.
fst::SymbolTableIterator si(*pst);
for (si.Reset(); !si.Done(); si.Next()) {
if (si.Value() == kBos || si.Value() == kEos ||
si.Value() == kEps || si.Value() == kDisambig)
continue;
genFst->AddArc(midId, fst::StdArc(si.Value(), si.Value(),
fst::StdArc::Weight::One(), midId));
}
return genFst;
}
// Compile given ARPA file.
ArpaLmCompiler* Compile(bool seps, const std::string &infile) {
ArpaParseOptions options;
fst::SymbolTable symbols;
// Use spaces on special symbols, so we rather fail than read them by mistake.
symbols.AddSymbol(" <eps>", kEps);
symbols.AddSymbol(" #0", kDisambig);
options.bos_symbol = symbols.AddSymbol("<s>", kBos);
options.eos_symbol = symbols.AddSymbol("</s>", kEos);
options.oov_handling = ArpaParseOptions::kAddToSymbols;
// Tests in this form cannot be run with epsilon substitution, unless every
// random path is also fitted with a #0-transducing self-loop.
ArpaLmCompiler* lm_compiler =
new ArpaLmCompiler(options,
seps ? kDisambig : 0,
&symbols);
{
Input ki(infile);
lm_compiler->Read(ki.Stream());
}
return lm_compiler;
}
// Add a state to an FSA after last_state, add a form last_state to the new
// state, and return the new state.
fst::StdArc::StateId AddToChainFsa(fst::StdMutableFst* fst,
fst::StdArc::StateId last_state,
int64 symbol) {
fst::StdArc::StateId next_state = fst->AddState();
fst->AddArc(last_state, fst::StdArc(symbol, symbol, 0, next_state));
return next_state;
}
// Add a disambiguator-generating self loop to every state of an FST.
void AddSelfLoops(fst::StdMutableFst* fst) {
for (fst::StateIterator<fst::StdMutableFst> siter(*fst);
!siter.Done(); siter.Next()) {
fst->AddArc(siter.Value(),
fst::StdArc(kEps, kDisambig, 0, siter.Value()));
}
}
// Compiles infile and then runs kRandomSentences random coverage tests on the
// compiled FST.
bool CoverageTest(bool seps, const std::string &infile) {
// Compile ARPA model.
ArpaLmCompiler* lm_compiler = Compile(seps, infile);
// Create an FST that generates any sequence of symbols taken from the model
// output.
fst::StdVectorFst* genFst =
CreateGenFst(seps, lm_compiler->Fst().OutputSymbols());
int num_successes = 0;
for (int32 i = 0; i < kRandomSentences; ++i) {
// Generate a random sentence FST.
fst::StdVectorFst sentence;
RandGen(*genFst, &sentence);
if (seps)
AddSelfLoops(&sentence);
fst::ArcSort(lm_compiler->MutableFst(), fst::StdOLabelCompare());
// The past must successfully compose with the LM FST.
fst::StdVectorFst composition;
Compose(sentence, lm_compiler->Fst(), &composition);
if (composition.Start() != fst::kNoStateId)
++num_successes;
}
delete genFst;
delete lm_compiler;
bool ok = num_successes == kRandomSentences;
if (!ok) {
KALDI_WARN << "Coverage test failed on " << infile << ": composed "
<< num_successes << "/" << kRandomSentences;
}
return ok;
}
bool ScoringTest(bool seps, const std::string &infile, const std::string& sentence,
float expected) {
ArpaLmCompiler* lm_compiler = Compile(seps, infile);
const fst::SymbolTable* symbols = lm_compiler->Fst().InputSymbols();
// Create a sentence FST for scoring.
fst::StdVectorFst sentFst;
fst::StdArc::StateId state = sentFst.AddState();
sentFst.SetStart(state);
if (!seps) {
state = AddToChainFsa(&sentFst, state, kBos);
}
std::stringstream ss(sentence);
std::string word;
while (ss >> word) {
int64 word_sym = symbols->Find(word);
KALDI_ASSERT(word_sym != -1);
state = AddToChainFsa(&sentFst, state, word_sym);
}
if (!seps) {
state = AddToChainFsa(&sentFst, state, kEos);
}
if (seps) {
AddSelfLoops(&sentFst);
}
sentFst.SetFinal(state, 0);
sentFst.SetOutputSymbols(symbols);
// Do the composition and extract final weight.
fst::StdVectorFst composed;
fst::Compose(sentFst, lm_compiler->Fst(), &composed);
delete lm_compiler;
if (composed.Start() == fst::kNoStateId) {
KALDI_WARN << "Test sentence " << sentence << " did not compose "
<< "with the language model FST\n";
return false;
}
std::vector<fst::StdArc::Weight> shortest;
fst::ShortestDistance(composed, &shortest, true);
float actual = shortest[composed.Start()].Value();
bool ok = ApproxEqual(expected, actual);
if (!ok) {
KALDI_WARN << "Scored " << sentence << " in " << infile
<< ": Expected=" << expected << " actual=" << actual;
}
return ok;
}
bool ThrowsExceptionTest(bool seps, const std::string &infile) {
try {
// Make memory cleanup easy in both cases of try-catch block.
std::unique_ptr<ArpaLmCompiler> compiler(Compile(seps, infile));
return false;
} catch (const KaldiFatalError&) {
return true;
}
}
} // namespace kaldi
bool RunAllTests(bool seps) {
bool ok = true;
ok &= kaldi::CoverageTest(seps, "test_data/missing_backoffs.arpa");
ok &= kaldi::CoverageTest(seps, "test_data/unused_backoffs.arpa");
ok &= kaldi::CoverageTest(seps, "test_data/input.arpa");
ok &= kaldi::ScoringTest(seps, "test_data/input.arpa", "b b b a", 59.2649);
ok &= kaldi::ScoringTest(seps, "test_data/input.arpa", "a b", 4.36082);
ok &= kaldi::ThrowsExceptionTest(seps, "test_data/missing_bos.arpa");
if (!ok) {
KALDI_WARN << "Tests " << (seps ? "with" : "without")
<< " epsilon substitution FAILED";
}
return ok;
}
int main(int argc, char *argv[]) {
bool ok = true;
ok &= RunAllTests(false); // Without disambiguators (old behavior).
ok &= RunAllTests(true); // With epsilon substitution (new behavior).
if (ok) {
KALDI_LOG << "All tests passed";
return 0;
} else {
KALDI_WARN << "Test FAILED";
return 1;
}
}
@@ -0,0 +1,377 @@
// lm/arpa-lm-compiler.cc
// Copyright 2009-2011 Gilles Boulianne
// Copyright 2016 Smart Action LLC (kkm)
// Copyright 2017 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.
#include <algorithm>
#include <limits>
#include <sstream>
#include <utility>
#include "base/kaldi-math.h"
#include "lm/arpa-lm-compiler.h"
#include "util/stl-utils.h"
#include "util/text-utils.h"
#include "fstext/remove-eps-local.h"
namespace kaldi {
class ArpaLmCompilerImplInterface {
public:
virtual ~ArpaLmCompilerImplInterface() { }
virtual void ConsumeNGram(const NGram& ngram, bool is_highest) = 0;
};
namespace {
typedef int32 StateId;
typedef int32 Symbol;
// GeneralHistKey can represent state history in an arbitrarily large n
// n-gram model with symbol ids fitting int32.
class GeneralHistKey {
public:
// Construct key from being and end iterators.
template<class InputIt>
GeneralHistKey(InputIt begin, InputIt end) : vector_(begin, end) { }
// Construct empty history key.
GeneralHistKey() : vector_() { }
// Return tails of the key as a GeneralHistKey. The tails of an n-gram
// w[1..n] is the sequence w[2..n] (and the heads is w[1..n-1], but the
// key class does not need this operartion).
GeneralHistKey Tails() const {
return GeneralHistKey(vector_.begin() + 1, vector_.end());
}
// Keys are equal if represent same state.
friend bool operator==(const GeneralHistKey& a, const GeneralHistKey& b) {
return a.vector_ == b.vector_;
}
// Public typename HashType for hashing.
struct HashType {
size_t operator()(const GeneralHistKey& key) const {
return VectorHasher<Symbol>().operator()(key.vector_);
}
};
private:
std::vector<Symbol> vector_;
};
// OptimizedHistKey combines 3 21-bit symbol ID values into one 64-bit
// machine word. allowing significant memory reduction and some runtime
// benefit over GeneralHistKey. Since 3 symbols are enough to track history
// in a 4-gram model, this optimized key is used for smaller models with up
// to 4-gram and symbol values up to 2^21-1.
//
// See GeneralHistKey for interface requirements of a key class.
class OptimizedHistKey {
public:
enum {
kShift = 21, // 21 * 3 = 63 bits for data.
kMaxData = (1 << kShift) - 1
};
template<class InputIt>
OptimizedHistKey(InputIt begin, InputIt end) : data_(0) {
for (uint32 shift = 0; begin != end; ++begin, shift += kShift) {
data_ |= static_cast<uint64>(*begin) << shift;
}
}
OptimizedHistKey() : data_(0) { }
OptimizedHistKey Tails() const {
return OptimizedHistKey(data_ >> kShift);
}
friend bool operator==(const OptimizedHistKey& a, const OptimizedHistKey& b) {
return a.data_ == b.data_;
}
struct HashType {
size_t operator()(const OptimizedHistKey& key) const { return key.data_; }
};
private:
explicit OptimizedHistKey(uint64 data) : data_(data) { }
uint64 data_;
};
} // namespace
template <class HistKey>
class ArpaLmCompilerImpl : public ArpaLmCompilerImplInterface {
public:
ArpaLmCompilerImpl(ArpaLmCompiler* parent, fst::StdVectorFst* fst,
Symbol sub_eps);
virtual void ConsumeNGram(const NGram &ngram, bool is_highest);
private:
StateId AddStateWithBackoff(HistKey key, float backoff);
void CreateBackoff(HistKey key, StateId state, float weight);
ArpaLmCompiler *parent_; // Not owned.
fst::StdVectorFst* fst_; // Not owned.
Symbol bos_symbol_;
Symbol eos_symbol_;
Symbol sub_eps_;
StateId eos_state_;
typedef unordered_map<HistKey, StateId,
typename HistKey::HashType> HistoryMap;
HistoryMap history_;
};
template <class HistKey>
ArpaLmCompilerImpl<HistKey>::ArpaLmCompilerImpl(
ArpaLmCompiler* parent, fst::StdVectorFst* fst, Symbol sub_eps)
: parent_(parent), fst_(fst), bos_symbol_(parent->Options().bos_symbol),
eos_symbol_(parent->Options().eos_symbol), sub_eps_(sub_eps) {
// The algorithm maintains state per history. The 0-gram is a special state
// for empty history. All unigrams (including BOS) backoff into this state.
StateId zerogram = fst_->AddState();
history_[HistKey()] = zerogram;
// Also, if </s> is not treated as epsilon, create a common end state for
// all transitions accepting the </s>, since they do not back off. This small
// optimization saves about 2% states in an average grammar.
if (sub_eps_ == 0) {
eos_state_ = fst_->AddState();
fst_->SetFinal(eos_state_, 0);
}
}
template <class HistKey>
void ArpaLmCompilerImpl<HistKey>::ConsumeNGram(const NGram &ngram,
bool is_highest) {
// Generally, we do the following. Suppose we are adding an n-gram "A B
// C". Then find the node for "A B", add a new node for "A B C", and connect
// them with the arc accepting "C" with the specified weight. Also, add a
// backoff arc from the new "A B C" node to its backoff state "B C".
//
// Two notable exceptions are the highest order n-grams, and final n-grams.
//
// When adding a highest order n-gram (e. g., our "A B C" is in a 3-gram LM),
// the following optimization is performed. There is no point adding a node
// for "A B C" with a "C" arc from "A B", since there will be no other
// arcs ingoing to this node, and an epsilon backoff arc into the backoff
// model "B C", with the weight of \bar{1}. To save a node, create an arc
// accepting "C" directly from "A B" to "B C". This saves as many nodes
// as there are the highest order n-grams, which is typically about half
// the size of a large 3-gram model.
//
// Indeed, this does not apply to n-grams ending in EOS, since they do not
// back off. These are special, as they do not have a back-off state, and
// the node for "(..anything..) </s>" is always final. These are handled
// in one of the two possible ways, If symbols <s> and </s> are being
// replaced by epsilons, neither node nor arc is created, and the logprob
// of the n-gram is applied to its source node as final weight. If <s> and
// </s> are preserved, then a special final node for </s> is allocated and
// used as the destination of the "</s>" acceptor arc.
HistKey heads(ngram.words.begin(), ngram.words.end() - 1);
typename HistoryMap::iterator source_it = history_.find(heads);
if (source_it == history_.end()) {
// There was no "A B", therefore the probability of "A B C" is zero.
// Print a warning and discard current n-gram.
if (parent_->ShouldWarn())
KALDI_WARN << parent_->LineReference()
<< " skipped: no parent (n-1)-gram exists";
return;
}
StateId source = source_it->second;
StateId dest;
Symbol sym = ngram.words.back();
float weight = -ngram.logprob;
if (sym == sub_eps_ || sym == 0) {
KALDI_ERR << " <eps> or disambiguation symbol " << sym << "found in the ARPA file. ";
}
if (sym == eos_symbol_) {
if (sub_eps_ == 0) {
// Keep </s> as a real symbol when not substituting.
dest = eos_state_;
} else {
// Treat </s> as if it was epsilon: mark source final, with the weight
// of the n-gram.
fst_->SetFinal(source, weight);
return;
}
} else {
// For the highest order n-gram, this may find an existing state, for
// non-highest, will create one (unless there are duplicate n-grams
// in the grammar, which cannot be reliably detected if highest order,
// so we better do not do that at all).
dest = AddStateWithBackoff(
HistKey(ngram.words.begin() + (is_highest ? 1 : 0),
ngram.words.end()),
-ngram.backoff);
}
if (sym == bos_symbol_) {
weight = 0; // Accepting <s> is always free.
if (sub_eps_ == 0) {
// <s> is as a real symbol, only accepted in the start state.
source = fst_->AddState();
fst_->SetStart(source);
} else {
// The new state for <s> unigram history *is* the start state.
fst_->SetStart(dest);
return;
}
}
// Add arc from source to dest, whichever way it was found.
fst_->AddArc(source, fst::StdArc(sym, sym, weight, dest));
return;
}
// Find or create a new state for n-gram defined by key, and ensure it has a
// backoff transition. The key is either the current n-gram for all but
// highest orders, or the tails of the n-gram for the highest order. The
// latter arises from the chain-collapsing optimization described above.
template <class HistKey>
StateId ArpaLmCompilerImpl<HistKey>::AddStateWithBackoff(HistKey key,
float backoff) {
typename HistoryMap::iterator dest_it = history_.find(key);
if (dest_it != history_.end()) {
// Found an existing state in the history map. Invariant: if the state in
// the map, then its backoff arc is in the FST. We are done.
return dest_it->second;
}
// Otherwise create a new state and its backoff arc, and register in the map.
StateId dest = fst_->AddState();
history_[key] = dest;
CreateBackoff(key.Tails(), dest, backoff);
return dest;
}
// Create a backoff arc for a state. Key is a backoff destination that may or
// may not exist. When the destination is not found, naturally fall back to
// the lower order model, and all the way down until one is found (since the
// 0-gram model is always present, the search is guaranteed to terminate).
template <class HistKey>
inline void ArpaLmCompilerImpl<HistKey>::CreateBackoff(
HistKey key, StateId state, float weight) {
typename HistoryMap::iterator dest_it = history_.find(key);
while (dest_it == history_.end()) {
key = key.Tails();
dest_it = history_.find(key);
}
// The arc should transduce either <eos> or #0 to <eps>, depending on the
// epsilon substitution mode. This is the only case when input and output
// label may differ.
fst_->AddArc(state, fst::StdArc(sub_eps_, 0, weight, dest_it->second));
}
ArpaLmCompiler::~ArpaLmCompiler() {
if (impl_ != NULL)
delete impl_;
}
void ArpaLmCompiler::HeaderAvailable() {
KALDI_ASSERT(impl_ == NULL);
// Use optimized implementation if the grammar is 4-gram or less, and the
// maximum attained symbol id will fit into the optimized range.
int64 max_symbol = 0;
if (Symbols() != NULL)
max_symbol = Symbols()->AvailableKey() - 1;
// If augmenting the symbol table, assume the worst case when all words in
// the model being read are novel.
if (Options().oov_handling == ArpaParseOptions::kAddToSymbols)
max_symbol += NgramCounts()[0];
if (NgramCounts().size() <= 4 && max_symbol < OptimizedHistKey::kMaxData) {
impl_ = new ArpaLmCompilerImpl<OptimizedHistKey>(this, &fst_, sub_eps_);
} else {
impl_ = new ArpaLmCompilerImpl<GeneralHistKey>(this, &fst_, sub_eps_);
KALDI_LOG << "Reverting to slower state tracking because model is large: "
<< NgramCounts().size() << "-gram with symbols up to "
<< max_symbol;
}
}
void ArpaLmCompiler::ConsumeNGram(const NGram &ngram) {
// <s> is invalid in tails, </s> in heads of an n-gram.
for (int i = 0; i < ngram.words.size(); ++i) {
if ((i > 0 && ngram.words[i] == Options().bos_symbol) ||
(i + 1 < ngram.words.size()
&& ngram.words[i] == Options().eos_symbol)) {
if (ShouldWarn())
KALDI_WARN << LineReference()
<< " skipped: n-gram has invalid BOS/EOS placement";
return;
}
}
bool is_highest = ngram.words.size() == NgramCounts().size();
impl_->ConsumeNGram(ngram, is_highest);
}
void ArpaLmCompiler::RemoveRedundantStates() {
fst::StdArc::Label backoff_symbol = sub_eps_;
if (backoff_symbol == 0) {
// The method of removing redundant states implemented in this function
// leads to slow determinization of L o G when people use the older style of
// usage of arpa2fst where the --disambig-symbol option was not specified.
// The issue seems to be that it creates a non-deterministic FST, while G is
// supposed to be deterministic. By 'return'ing below, we just disable this
// method if people were using an older script. This method isn't really
// that consequential anyway, and people will move to the newer-style
// scripts (see current utils/format_lm.sh), so this isn't much of a
// problem.
return;
}
fst::StdArc::StateId num_states = fst_.NumStates();
// replace the #0 symbols on the input of arcs out of redundant states (states
// that are not final and have only a backoff arc leaving them), with <eps>.
for (fst::StdArc::StateId state = 0; state < num_states; state++) {
if (fst_.NumArcs(state) == 1 && fst_.Final(state) == fst::TropicalWeight::Zero()) {
fst::MutableArcIterator<fst::StdVectorFst> iter(&fst_, state);
fst::StdArc arc = iter.Value();
if (arc.ilabel == backoff_symbol) {
arc.ilabel = 0;
iter.SetValue(arc);
}
}
}
// we could call fst::RemoveEps, and it would have the same effect in normal
// cases, where backoff_symbol != 0 and there are no epsilons in unexpected
// places, but RemoveEpsLocal is a bit safer in case something weird is going
// on; it guarantees not to blow up the FST.
fst::RemoveEpsLocal(&fst_);
KALDI_LOG << "Reduced num-states from " << num_states << " to "
<< fst_.NumStates();
}
void ArpaLmCompiler::Check() const {
if (fst_.Start() == fst::kNoStateId) {
KALDI_ERR << "Arpa file did not contain the beginning-of-sentence symbol "
<< Symbols()->Find(Options().bos_symbol) << ".";
}
}
void ArpaLmCompiler::ReadComplete() {
fst_.SetInputSymbols(Symbols());
fst_.SetOutputSymbols(Symbols());
RemoveRedundantStates();
Check();
}
} // namespace kaldi
@@ -0,0 +1,65 @@
// lm/arpa-lm-compiler.h
// Copyright 2009-2011 Gilles Boulianne
// Copyright 2016 Smart Action LLC (kkm)
// 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_LM_ARPA_LM_COMPILER_H_
#define KALDI_LM_ARPA_LM_COMPILER_H_
#include <fst/fstlib.h>
#include "lm/arpa-file-parser.h"
namespace kaldi {
class ArpaLmCompilerImplInterface;
class ArpaLmCompiler : public ArpaFileParser {
public:
ArpaLmCompiler(const ArpaParseOptions& options, int sub_eps,
fst::SymbolTable* symbols)
: ArpaFileParser(options, symbols),
sub_eps_(sub_eps), impl_(NULL) {
}
~ArpaLmCompiler();
const fst::StdVectorFst& Fst() const { return fst_; }
fst::StdVectorFst* MutableFst() { return &fst_; }
protected:
// ArpaFileParser overrides.
virtual void HeaderAvailable();
virtual void ConsumeNGram(const NGram& ngram);
virtual void ReadComplete();
private:
// this function removes states that only have a backoff arc coming
// out of them.
void RemoveRedundantStates();
void Check() const;
int sub_eps_;
ArpaLmCompilerImplInterface* impl_; // Owned.
fst::StdVectorFst fst_;
template <class HistKey> friend class ArpaLmCompilerImpl;
};
} // namespace kaldi
#endif // KALDI_LM_ARPA_LM_COMPILER_H_
File diff suppressed because it is too large Load Diff
+430
View File
@@ -0,0 +1,430 @@
// lm/const-arpa-lm.h
// Copyright 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_LM_CONST_ARPA_LM_H_
#define KALDI_LM_CONST_ARPA_LM_H_
#include <string>
#include <vector>
#include "base/kaldi-common.h"
#include "fstext/deterministic-fst.h"
#include "lm/arpa-file-parser.h"
#include "util/common-utils.h"
namespace kaldi {
/**
The following explains how the const arpa LM works. We will start from a toy
example, and gradually get to the existing framework. Related classes are:
LmState, ConstArpaLmBuilder and ConstArpaLm.
First, let's explain how we can compute LM scores from an Arpa file. Suppose
we want to get the N-gram prob for "A B C". We can code the lookup something
very roughly like this:
float GetNgramLogprob(hist, word) { // hist = "A B", word = "C"
backoff_logprob = 0.0;
if ((state = GetLmState(hist)) != NULL) {
// "A B" exists as a prefix in the LM
if (state->HasWord(word)) {
return state->Logprob(word);
} else {
// We'll need to backoff to "B C", but include the backoff penalty.
backoff_logprob = state->BackoffLogprob();
}
}
return backoff_logprob + GetNgramLogprob(hist_minus_first_word, word);
}
In terms of data-structures, in the most abstract form of it would be
something like the following (note, we assume words in the lexicon can be
represented as int32, and that these indexes are nonnegative):
class LmState { // e.g., LmState for "A B"
// This is the actual LM-prob of this sequence, e.g. if this state is
// "A B" then it would be the logprob of "A -> B".
float logprob_;
// Backoff probability for LM-state "A B" -> "X" backing off to "B" -> "X"
// if "A B X" is not present in the language model.
float backoff_logprob_;
// e.g. "C" -> LmState of "A B C".
std::unordered_map<int32, LmState*> children_;
};
The above design is very memory inefficient for two reasons:
1. Suppose "A B" has no children, i.e. no C such that "A B C" is an n-gram.
In this case the backoff_logprob will be zero and the 'children' vector
will be empty. So all we need is the "float logprob_". Let's call "A B" a
leaf in this case.
2. The map std::unordered_map uses a lot of memory.
A first iteration of making this efficient is to get rid of the map as
follows:
class LmState {
float logprob_;
float backoff_logprob_;
std::vector<std::pair<int32, int32> > children_;
};
Here, the 'children_' vector contains pairs (child_word, child_info), sorted
by 'child_word' so we can use binary search to locate the entry. We have to
do some fancy bit-work to avoid having to allocate an LmState if a given
N-gram is a leaf. We design the child_info in the children_ vector as
follows:
1. If it's an even number, then it represents a float (i.e. we
reinterpret_cast to float), and the associated N-gram is a leaf. This
requires losing the least significant bit of information in the float.
2. If it's an odd number, then it will be used to represent a pointer to the
LmState of the child. In order to use a 32-bit number to represent a
possibly 64-bit pointer, we store the LmState structures in memory in
a way that's sorted lexicographically by the vector of words, so that
following "A B" will be the LmStates for "A B A", "A B B", "A B C" and so
on (note, we actually deal with integers instead of letters). So if we
make the pointers relative to the current LmState, most of them will be
quite small (and all will be positive, due to the lexicographic sorting).
As for the pointers that are too large, if any, we can have an "overflow
buffer" indexed by a 30-bit index that stores, directly as pointers, the
child LmStates. We use the first bit to distinguish the relative pointer
case and the overflow pointer case, i.e.,
a. If (child_info / 2) is positive, then (current_lmstate_pointer +
child_info / 2) is the address of the child LmState.
b. If (child_info / 2) is negative, then -1 * (child_info / 2) is the
index into the overflow buffer which gives the address of the child
LmState.
Note that unigram LM-states are usually frequently accessed, so it makes
sense to assign one LmState to each single word even if it would otherwise
be "leaf" as defined above. We then can have an array of those unigram
LM-states for efficient lookup.
Also, we define the class LmState just to set up data structure for Arpa
LM. In the end, we have a class like the following:
class ConstArpaLm {
public:
// Some public functions.
private:
// Index of largest word-id, plus one; defines end of "unigram_states_"
// array.
int32 num_words_;
// Loopup table for pointers of unigrams. The pointer could be NULL, for
// example for those words that are in words.txt, but not in the language
// model.
int32 **unigram_states_;
// Number of entries in the overflow buffer for pointers that couldn't be
// represented as a 30-bit relative index
int32 overflow_buffer_size_;
// Technically a 32-bit number cannot represent a possibly 64-bit pointer.
// We therefore use "relative" address instead of "absolute" address,
// which will be a small number most of the time. This buffer is for the
// case where the relative address has more than 30-bits.
int32 **overflow_buffer_;
// Size of the array lm_states_. This is required only for I/O.
int64 lm_states_size_;
// Data block for LmState.
int32 *lm_states_;
};
Note, when we do I/O, we don't write out the arrays of pointers
"overflow_buffer_" and "unigram_states_" directly. Instead we subtract
"lm_states_" from each one before writing them out, so we are writing out
indexes. Then, when we read them back in, after we allocate "lm_states_"
we can convert them back to pointers. When we create these temporary arrays
of indexes while reading and writing, we use int64, even if the pointer type
of the machine is int32. This way the I/O is independent of the pointer size
of the machine.
Now it is time to put things together.
ConstArpaLmBuilder takes charge of reading in the Arpa LM and building the
ConstArpaLm.
ConstArpaLM holds the Arpa LM in memory, and provides interfaces for LM
operations, such as GetNgramLogprob().
LmState is an auxiliary class that computes the relative pointers for
ConstArpaLmBuilder and ConstArpaLm. It will only be called once during the
building process, so it doesn't have to be very efficient.
In summary, the general building process is as follows:
1. In ConstArpaLmBuilder, read in the Arpa format LM. While reading, we keep
in memory something like this:
std::unordered_map<std::vector<int32>,
LmState*, VectorHasher<int32> > seq_to_state_;
The map helps us to convert n-gram entries into LmState (including
setting up the parent-children relationship, see above about LmState).
Note that at this stage, we don't work on the relative pointers yet.
2. In ConstArpaLmBuilder, create a sorted vector from <seq_to_state_>
std::vector<std::pair<std::vector<int32>*, LmState*> > sorted_vec;
Note, only LmState with non-zero MemSize() should be put into the sorted
vector, and we sort it lexicographically according to the word.
3. In ConstArpaLmBuilder, update the address for each LmState, relative to
the first LmState in the sorted vector (i.e. assume the first LmState has
address 0, and work out the rest LmState address using the MemSize() of
each LmState).
4. In ConstArpaLmBuilder, create a memory block for all the LmStates (after
sorting and updating the address). This includes <lm_state_> that stores
all the LmStates in an int32 array, <unigram_states_> that keeps the
address of unigram LmStates, <overflow_buffer_> that keeps the address
of LmState whose address differs too much from the parent address. See
above how we handle the leaf case.
5. With the information in step 4, create the class ConstArpaLm.
*/
// Forward declaration of Auxiliary struct ArpaLine.
struct ArpaLine;
union Int32AndFloat {
int32 i;
float f;
Int32AndFloat() {}
Int32AndFloat(int32 input_i) : i(input_i) {}
Int32AndFloat(float input_f) : f(input_f) {}
};
class ConstArpaLm {
public:
// Default constructor, will be used if you are going to load the ConstArpaLm
// format language model from disk.
ConstArpaLm() {
lm_states_ = NULL;
unigram_states_ = NULL;
overflow_buffer_ = NULL;
memory_assigned_ = false;
initialized_ = false;
ngram_order_ = 0;
}
// Special constructor, will be used when you initialize ConstArpaLm from
// scratch through this constructor.
ConstArpaLm(const int32 bos_symbol, const int32 eos_symbol,
const int32 unk_symbol, const int32 ngram_order,
const int32 num_words, const int32 overflow_buffer_size,
const int64 lm_states_size, int32** unigram_states,
int32** overflow_buffer, int32* lm_states) :
bos_symbol_(bos_symbol), eos_symbol_(eos_symbol),
unk_symbol_(unk_symbol), ngram_order_(ngram_order),
num_words_(num_words), overflow_buffer_size_(overflow_buffer_size),
lm_states_size_(lm_states_size), unigram_states_(unigram_states),
overflow_buffer_(overflow_buffer), lm_states_(lm_states) {
KALDI_ASSERT(unigram_states_ != NULL);
KALDI_ASSERT(overflow_buffer_ != NULL);
KALDI_ASSERT(lm_states_ != NULL);
KALDI_ASSERT(ngram_order_ > 0);
KALDI_ASSERT(bos_symbol_ < num_words_ && bos_symbol_ > 0);
KALDI_ASSERT(eos_symbol_ < num_words_ && eos_symbol_ > 0);
KALDI_ASSERT(unk_symbol_ < num_words_ &&
(unk_symbol_ > 0 || unk_symbol_ == -1));
lm_states_end_ = lm_states_ + lm_states_size_ - 1;
memory_assigned_ = false;
initialized_ = true;
}
~ConstArpaLm() {
if (memory_assigned_) {
delete[] lm_states_;
delete[] unigram_states_;
delete[] overflow_buffer_;
}
}
// Reads the ConstArpaLm format language model. It calls ReadInternal() or
// ReadInternalOldFormat() to do the actual reading.
void Read(std::istream &is, bool binary);
// Writes the language model in ConstArpaLm format.
void Write(std::ostream &os, bool binary) const;
// Creates Arpa format language model from ConstArpaLm format, and writes it
// to output stream. This will be useful in testing.
void WriteArpa(std::ostream &os) const;
// Wrapper of GetNgramLogprobRecurse. It first maps possible out-of-vocabulary
// words to <unk>, if <unk> is defined, and then calls GetNgramLogprobRecurse.
float GetNgramLogprob(const int32 word, const std::vector<int32>& hist) const;
// Returns true if the history word sequence <hist> has successor, which means
// <hist> will be a state in the FST format language model.
bool HistoryStateExists(const std::vector<int32>& hist) const;
int32 BosSymbol() const { return bos_symbol_; }
int32 EosSymbol() const { return eos_symbol_; }
int32 UnkSymbol() const { return unk_symbol_; }
int32 NgramOrder() const { return ngram_order_; }
bool Initialized() const { return initialized_; }
private:
// Function that loads data from stream to the class.
void ReadInternal(std::istream &is, bool binary);
// Function that loads data from stream to the class. This is a deprecated one
// that handles the old on-disk format. We keep this for back-compatibility
// purpose. We have modified the Write() function so for all the new on-disk
// format, ReadInternal() will be called.
void ReadInternalOldFormat(std::istream &is, bool binary);
// Loops up n-gram probability for given word sequence. Backoff is handled by
// recursively calling this function.
float GetNgramLogprobRecurse(const int32 word,
const std::vector<int32>& hist) const;
// Given a word sequence, find the address of the corresponding LmState.
// Returns NULL if no corresponding LmState is found.
//
// If the word sequence exists in n-gram language model, but it is a leaf and
// is not an unigram, we still return NULL, since there is no LmState struct
// reserved for this sequence.
int32* GetLmState(const std::vector<int32>& seq) const;
// Given a pointer to the parent, find the child_info that corresponds to
// given word. The parent has the following structure:
// struct LmState {
// float logprob;
// float backoff_logprob;
// int32 num_children;
// std::pair<int32, int32> [] children;
// }
// It returns false if the child is not found.
bool GetChildInfo(const int32 word, int32* parent, int32* child_info) const;
// Decodes <child_info> to get log probability and child LmState. In the leaf
// case, only <logprob> will be returned, and <child_address> will be NULL.
void DecodeChildInfo(const int32 child_info, int32* parent,
int32** child_lm_state, float* logprob) const;
void WriteArpaRecurse(int32* lm_state,
const std::vector<int32>& seq,
std::vector<ArpaLine> *output) const;
// We assign memory in Read(). If it is called, we have to release memory in
// the destructor.
bool memory_assigned_;
// Makes sure that the language model has been loaded before using it.
bool initialized_;
// Integer corresponds to <s>.
int32 bos_symbol_;
// Integer corresponds to </s>.
int32 eos_symbol_;
// Integer corresponds to unknown-word. -1 if no unknown-word symbol is
// provided.
int32 unk_symbol_;
// N-gram order of the language model.
int32 ngram_order_;
// Index of largest word-id plus one. It defines the end of <unigram_states_>
// array.
int32 num_words_;
// Number of entries in the overflow buffer for pointers that couldn't be
// represented as a 30-bit relative index.
int32 overflow_buffer_size_;
// Size of the <lm_states_> array, which will be needed by I/O.
int64 lm_states_size_;
// Points to the end of <lm_states_>. We use this information to check if
// there is any illegal visit to the un-reserved memory.
int32* lm_states_end_;
// Loopup table for pointers of unigrams. The pointer could be NULL, for
// example for those words that are in words.txt, but not in the language
// model.
int32** unigram_states_;
// Technically a 32-bit number cannot represent a possibly 64-bit pointer. We
// therefore use "relative" address instead of "absolute" address, which will
// be a small number most of the time. This buffer is for the case where the
// relative address has more than 30-bits.
int32** overflow_buffer_;
// Memory chunk that contains the actual LmStates. One LmState has the
// following structure:
//
// struct LmState {
// float logprob;
// float backoff_logprob;
// int32 num_children;
// std::pair<int32, int32> [] children;
// }
//
// Note that the floating point representation has 4 bytes, int32 also has 4
// bytes, therefore one LmState will occupy the following number of bytes:
//
// x = 1 + 1 + 1 + 2 * children.size() = 3 + 2 * children.size()
int32* lm_states_;
};
/**
This class wraps a ConstArpaLm format language model with the interface defined
in DeterministicOnDemandFst.
*/
class ConstArpaLmDeterministicFst
: public fst::DeterministicOnDemandFst<fst::StdArc> {
public:
typedef fst::StdArc::Weight Weight;
typedef fst::StdArc::StateId StateId;
typedef fst::StdArc::Label Label;
explicit ConstArpaLmDeterministicFst(const ConstArpaLm& lm);
// We cannot use "const" because the pure virtual function in the interface is
// not const.
virtual StateId Start() { return start_state_; }
// We cannot use "const" because the pure virtual function in the interface is
// not const.
virtual Weight Final(StateId s);
virtual bool GetArc(StateId s, Label ilabel, fst::StdArc* oarc);
private:
typedef unordered_map<std::vector<Label>,
StateId, VectorHasher<Label> > MapType;
StateId start_state_;
MapType wseq_to_state_;
std::vector<std::vector<Label> > state_to_wseq_;
const ConstArpaLm& lm_;
};
// Reads in an Arpa format language model and converts it into ConstArpaLm
// format. We assume that the words in the input Arpa format language model have
// been converted into integers.
bool BuildConstArpaLm(const ArpaParseOptions& options,
const std::string& arpa_rxfilename,
const std::string& const_arpa_wxfilename);
} // namespace kaldi
#endif // KALDI_LM_CONST_ARPA_LM_H_
@@ -0,0 +1,17 @@
boy
boy
boy
boy
boy
boy
boy
girl
girl
pretty girl
cat
dog
dog
dog
rat
red faced clown
green caterpillar
@@ -0,0 +1,5 @@
first
second
third
fourth
fifth
@@ -0,0 +1,5 @@
January
February
March
April
May
@@ -0,0 +1,4 @@
two thousand and ten
two zero one zero
two thousand and nine
two zero zero nine
@@ -0,0 +1 @@
%#MONTH#% %#DAYOFMONTH#% %#YEAR#%
@@ -0,0 +1 @@
<s> the %#CREATURE#% crossed the street </s>
@@ -0,0 +1 @@
<s> He came here on %#YEARDATE#% </s>
+7
View File
@@ -0,0 +1,7 @@
# Additionnal definitions needed to make with IRSTLM toolkit
# Assumes IRSTLM includes and libraries have been installed under
# $(SRCDIR)/../lmtoolkit/include/irstlm and $(SRCDIR)/../lmtoolkit/lib/irstlm
EXTRA_CXXFLAGS = -DHAVE_IRSTLM -I$(SRCDIR)/../lmtoolkit/include -Wno-sign-compare
EXTRA_LDLIBS = $(SRCDIR)/../lmtoolkit/lib/irstlm/x86_64-apple-darwin10.0/libirstlm.a -lz
+140
View File
@@ -0,0 +1,140 @@
// lm/kaldi-rnnlm.cc
// Copyright 2015 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 <utility>
#include "lm/kaldi-rnnlm.h"
#include "util/stl-utils.h"
#include "util/text-utils.h"
namespace kaldi {
KaldiRnnlmWrapper::KaldiRnnlmWrapper(
const KaldiRnnlmWrapperOpts &opts,
const std::string &unk_prob_rspecifier,
const std::string &word_symbol_table_rxfilename,
const std::string &rnnlm_rxfilename) {
rnnlm_.setRnnLMFile(rnnlm_rxfilename);
rnnlm_.setRandSeed(1);
rnnlm_.setUnkSym(opts.unk_symbol);
rnnlm_.setUnkPenalty(unk_prob_rspecifier);
rnnlm_.restoreNet();
// Reads symbol table.
fst::SymbolTable *word_symbols = NULL;
if (!(word_symbols =
fst::SymbolTable::ReadText(word_symbol_table_rxfilename))) {
KALDI_ERR << "Could not read symbol table from file "
<< word_symbol_table_rxfilename;
}
label_to_word_.resize(word_symbols->NumSymbols() + 1);
for (int32 i = 0; i < label_to_word_.size() - 1; ++i) {
label_to_word_[i] = word_symbols->Find(i);
if (label_to_word_[i] == "") {
KALDI_ERR << "Could not find word for integer " << i << "in the word "
<< "symbol table, mismatched symbol table or you have discontinuous "
<< "integers in your symbol table?";
}
}
label_to_word_[label_to_word_.size() - 1] = opts.eos_symbol;
eos_ = label_to_word_.size() - 1;
}
BaseFloat KaldiRnnlmWrapper::GetLogProb(
int32 word, const std::vector<int32> &wseq,
const std::vector<float> &context_in,
std::vector<float> *context_out) {
std::vector<std::string> wseq_symbols(wseq.size());
for (int32 i = 0; i < wseq_symbols.size(); ++i) {
KALDI_ASSERT(wseq[i] < label_to_word_.size());
wseq_symbols[i] = label_to_word_[wseq[i]];
}
return rnnlm_.computeConditionalLogprob(label_to_word_[word], wseq_symbols,
context_in, context_out);
}
RnnlmDeterministicFst::RnnlmDeterministicFst(int32 max_ngram_order,
KaldiRnnlmWrapper *rnnlm) {
KALDI_ASSERT(rnnlm != NULL);
max_ngram_order_ = max_ngram_order;
rnnlm_ = rnnlm;
// Uses empty history for <s>.
std::vector<Label> bos;
std::vector<float> bos_context(rnnlm->GetHiddenLayerSize(), 1.0);
state_to_wseq_.push_back(bos);
state_to_context_.push_back(bos_context);
wseq_to_state_[bos] = 0;
start_state_ = 0;
}
fst::StdArc::Weight RnnlmDeterministicFst::Final(StateId s) {
// At this point, we should have created the state.
KALDI_ASSERT(static_cast<size_t>(s) < state_to_wseq_.size());
std::vector<Label> wseq = state_to_wseq_[s];
BaseFloat logprob = rnnlm_->GetLogProb(rnnlm_->GetEos(), wseq,
state_to_context_[s], NULL);
return Weight(-logprob);
}
bool RnnlmDeterministicFst::GetArc(StateId s, Label ilabel, fst::StdArc *oarc) {
// At this point, we should have created the state.
KALDI_ASSERT(static_cast<size_t>(s) < state_to_wseq_.size());
std::vector<Label> wseq = state_to_wseq_[s];
std::vector<float> new_context(rnnlm_->GetHiddenLayerSize());
BaseFloat logprob = rnnlm_->GetLogProb(ilabel, wseq,
state_to_context_[s], &new_context);
wseq.push_back(ilabel);
if (max_ngram_order_ > 0) {
while (wseq.size() >= max_ngram_order_) {
// History state has at most <max_ngram_order_> - 1 words in the state.
wseq.erase(wseq.begin(), wseq.begin() + 1);
}
}
std::pair<const std::vector<Label>, StateId> wseq_state_pair(
wseq, static_cast<Label>(state_to_wseq_.size()));
// Attemps to insert the current <lseq_state_pair>. If the pair already exists
// then it returns false.
typedef MapType::iterator IterType;
std::pair<IterType, bool> result = wseq_to_state_.insert(wseq_state_pair);
// If the pair was just inserted, then also add it to <state_to_wseq_> and
// <state_to_context_>.
if (result.second == true) {
state_to_wseq_.push_back(wseq);
state_to_context_.push_back(new_context);
}
// Creates the arc.
oarc->ilabel = ilabel;
oarc->olabel = ilabel;
oarc->nextstate = result.first->second;
oarc->weight = Weight(-logprob);
return true;
}
} // namespace kaldi
+104
View File
@@ -0,0 +1,104 @@
// lm/kaldi-rnnlm.h
// Copyright 2015 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_LM_KALDI_RNNLM_H_
#define KALDI_LM_KALDI_RNNLM_H_
#include <string>
#include <vector>
#include "base/kaldi-common.h"
#include "fstext/deterministic-fst.h"
#include "lm/mikolov-rnnlm-lib.h"
#include "util/common-utils.h"
namespace kaldi {
struct KaldiRnnlmWrapperOpts {
std::string unk_symbol;
std::string eos_symbol;
KaldiRnnlmWrapperOpts() : unk_symbol("<RNN_UNK>"), eos_symbol("</s>") {}
void Register(OptionsItf *opts) {
opts->Register("unk-symbol", &unk_symbol, "Symbol for out-of-vocabulary "
"words in rnnlm.");
opts->Register("eos-symbol", &eos_symbol, "End of sentence symbol in "
"rnnlm.");
}
};
class KaldiRnnlmWrapper {
public:
KaldiRnnlmWrapper(const KaldiRnnlmWrapperOpts &opts,
const std::string &unk_prob_rspecifier,
const std::string &word_symbol_table_rxfilename,
const std::string &rnnlm_rxfilename);
int32 GetHiddenLayerSize() const { return rnnlm_.getHiddenLayerSize(); }
int32 GetEos() const { return eos_; }
BaseFloat GetLogProb(int32 word, const std::vector<int32> &wseq,
const std::vector<float> &context_in,
std::vector<float> *context_out);
private:
rnnlm::CRnnLM rnnlm_;
std::vector<std::string> label_to_word_;
int32 eos_;
KALDI_DISALLOW_COPY_AND_ASSIGN(KaldiRnnlmWrapper);
};
class RnnlmDeterministicFst
: public fst::DeterministicOnDemandFst<fst::StdArc> {
public:
typedef fst::StdArc::Weight Weight;
typedef fst::StdArc::StateId StateId;
typedef fst::StdArc::Label Label;
// Does not take ownership.
RnnlmDeterministicFst(int32 max_ngram_order, KaldiRnnlmWrapper *rnnlm);
// We cannot use "const" because the pure virtual function in the interface is
// not const.
virtual StateId Start() { return start_state_; }
// We cannot use "const" because the pure virtual function in the interface is
// not const.
virtual Weight Final(StateId s);
virtual bool GetArc(StateId s, Label ilabel, fst::StdArc* oarc);
private:
typedef unordered_map<std::vector<Label>,
StateId, VectorHasher<Label> > MapType;
StateId start_state_;
MapType wseq_to_state_;
std::vector<std::vector<Label> > state_to_wseq_;
KaldiRnnlmWrapper *rnnlm_;
int32 max_ngram_order_;
std::vector<std::vector<float> > state_to_context_;
};
} // namespace kaldi
#endif // KALDI_LM_KALDI_RNNLM_H_
+84
View File
@@ -0,0 +1,84 @@
#ifdef HAVE_KENLM
#include "util/text-utils.h"
#include "util/kaldi-io.h"
#include "lm/kenlm.h"
namespace kaldi {
void UnitTestKenLm() {
// construct symbol_to_symbol_id to map word string to kaldi symbol index,
// in practice, hypothesis is already intergerized, no need for this mapping.
std::unordered_map<std::string, int32> symbol_to_symbol_id;
std::ifstream symbol_table_stream("test_data/words.txt");
std::string line;
while (std::getline(symbol_table_stream, line)) {
std::vector<std::string> fields;
SplitStringToVector(line, " ", true, &fields);
if (fields.size() == 2) {
std::string symbol = fields[0];
uint32 symbol_id = -1;
ConvertStringToInteger(fields[1], &symbol_id);
symbol_to_symbol_id[symbol] = symbol_id;
}
}
// open testing stream, one sentence per line, in raw text form
std::ifstream is("test_data/sentences.txt");
KenLm lm;
lm.Load("test_data/lm.kenlm", "test_data/words.txt");
KenLmDeterministicOnDemandFst<fst::StdArc> lm_fst(&lm);
std::string sentence;
while(std::getline(is, sentence)) {
std::vector<std::string> words;
SplitStringToVector(sentence, " ", true, &words);
words.push_back("</s>");
// 1. test KenLm interface: this is only for test purpose,
// you should not use kenlm this way in Kaldi.
std::string sentence_log = "[KENLM]";
KenLm::State state[2];
KenLm::State* istate = &state[0];
KenLm::State* ostate = &state[1];
lm.SetStateToBeginOfSentence(istate);
for (int i = 0; i < words.size(); i++) {
std::string word = words[i];
BaseFloat log10_word_score = lm.Score(istate, lm.GetWordIndex(word), ostate);
sentence_log += " " + word +
"[" + std::to_string(lm.GetWordIndex(word)) + "]=" +
std::to_string(-log10_word_score * M_LN10); //convert to -ln()
std::swap(istate, ostate);
}
KALDI_LOG << sentence_log;
// 2. test Fst wrapper interface (KenLmDeterministicFst),
// this is the recommanded way to interact with Kaldi's Fst framework.
sentence_log = "(KALDI)";
KenLmDeterministicOnDemandFst<fst::StdArc>::StateId s = lm_fst.Start();
for (int i = 0; i < words.size(); i++) {
int32 symbol_id = symbol_to_symbol_id[words[i]];
sentence_log += " " + words[i] + "(" + std::to_string(symbol_id) + ")=";
if (words[i] == "</s>") {
sentence_log += std::to_string(lm_fst.Final(s).Value());
} else {
fst::StdArc arc;
lm_fst.GetArc(s, symbol_id, &arc);
s = arc.nextstate;
sentence_log += std::to_string(arc.weight.Value());
}
}
KALDI_LOG << sentence_log;
}
}
} // namespace kaldi
int main(int argc, char *argv[]) {
using namespace kaldi;
UnitTestKenLm();
return 0;
}
#endif
+99
View File
@@ -0,0 +1,99 @@
// Copyright 2020 Jiayu DU
// 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_KENLM
#include "lm/kenlm.h"
namespace kaldi {
void KenLm::ComputeSymbolToWordIndexMapping(std::string symbol_table_filename) {
// count symbol table size
int num_syms = 0;
std::string line;
std::ifstream is(symbol_table_filename);
while(std::getline(is, line)) {
if (!line.empty()) num_syms++;
}
symid_to_wid_.clear();
symid_to_wid_.resize(num_syms, 0);
is.clear();
is.seekg(0);
int num_mapped = 0;
while (std::getline(is, line)) {
std::vector<std::string> fields;
SplitStringToVector(line, " ", true, &fields);
if (fields.size() == 2) {
std::string sym = fields[0];
int32 symid = 0; ConvertStringToInteger(fields[1], &symid);
// mark special LM word
if (sym == bos_sym_) {
bos_symid_ = symid;
} else if (sym == eos_sym_) {
eos_symid_ = symid;
} else if (sym == "<unk>" || sym == "<UNK>") {
unk_sym_ = sym;
unk_symid_ = symid;
}
// check vocabulary consistency between kaldi and kenlm.
// note we always handle <unk> & <UNK> as a pair,
// so don't worry about the literal mismatch
// between Kaldi and kenlm arpa (<UNK> vs <unk>)
WordIndex wid = vocab_->Index(sym.c_str());
if ((wid == vocab_->Index("<unk>") || wid == vocab_->Index("<UNK>"))
&& sym != "<unk>" && sym != "<UNK>"
&& sym != "<eps>"
&& sym != "#0") {
KALDI_ERR << "found mismatched symbol: " << sym
<< ", this symbol is in Kaldi, but is unseen in KenLm"
<< ", they should have strictly consistent vocabulary.";
} else {
symid_to_wid_[symid] = wid;
num_mapped += 1;
}
}
}
KALDI_ASSERT(num_mapped == symid_to_wid_.size());
KALDI_LOG << "Successfully mapped " << num_mapped
<< " Kaldi symbols to KenLm words";
}
int KenLm::Load(std::string kenlm_filename,
std::string symbol_table_filename,
util::LoadMethod load_method) {
if (model_ != nullptr) { delete model_; }
model_ = nullptr;
vocab_ = nullptr;
// load KenLm model
lm::ngram::Config config;
config.load_method = load_method;
model_ = lm::ngram::LoadVirtual(kenlm_filename.c_str(), config);
if (model_ == nullptr) { KALDI_ERR << "Failed to load KenLm model"; }
// KenLm holds vocabulary internally with ownership,
// vocab_ here is just for concise reference
vocab_ = &model_->BaseVocabulary();
if (vocab_ == nullptr) { KALDI_ERR << "Failed to get vocabulary from KenLm model"; }
// compute the index mapping from Kaldi symbol to KenLm word
ComputeSymbolToWordIndexMapping(symbol_table_filename);
return 0;
}
} // namespace kaldi
#endif
+207
View File
@@ -0,0 +1,207 @@
// Copyright 2020 Jiayu DU
// 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_KENLM
#ifndef KALDI_LM_KENLM_H
#define KALDI_LM_KENLM_H
#include <base/kaldi-common.h>
#include <fst/fstlib.h>
#include <fst/fst-decl.h>
#include <fstext/deterministic-fst.h>
#include "lm/model.hh"
#include "util/murmur_hash.hh"
namespace kaldi {
// KenLm class wraps kenlm model(supporting both "trie" or "probing" models):
// 1. provides interface for loading binary LM, and holds it with ownership
// 2. provides interface for ngram score query at runtime
// 3. handles the index mapping between kaldi's symbols & kenlm's words
// KenLm object is heavy, stateless and thread-safe,
// can be shared by Fst wrapper class(i.e. KenLmDeterministicOnDemandFst)
class KenLm {
public:
typedef lm::WordIndex WordIndex;
typedef lm::ngram::State State;
public:
KenLm() :
model_(nullptr), vocab_(nullptr),
bos_sym_("<s>"), eos_sym_("</s>"), unk_sym_("<unk>"),
bos_symid_(0), eos_symid_(0), unk_symid_(0)
{ }
~KenLm() {
if (model_ != nullptr) {
delete model_;
}
model_ = nullptr;
vocab_ = nullptr;
symid_to_wid_.clear();
}
// If you have big LM on SSD hard-drive,
// you can set load_method to util::LoadMethod::LAZY,
// which enables "on-demand" model reading(via POSIX mmap) at runtime.
// Refer to tools/kenlm/util/mmap.hh for more load methods.
int Load(std::string kenlm_filename,
std::string kaldi_symbol_table_filename,
util::LoadMethod load_method = util::LoadMethod::POPULATE_OR_READ);
inline WordIndex GetWordIndex(std::string word) const {
return vocab_->Index(word.c_str());
}
inline WordIndex GetWordIndex(int32 symbol_id) const {
return symid_to_wid_[symbol_id];
}
void SetStateToBeginOfSentence(State *s) const { model_->BeginSentenceWrite(s); }
void SetStateToNull(State *s) const { model_->NullContextWrite(s); }
int32 BosSymbolIndex() const { return bos_symid_; }
int32 EosSymbolIndex() const { return eos_symid_; }
int32 UnkSymbolIndex() const { return unk_symid_; }
inline BaseFloat Score(const State *in_state,
WordIndex word,
State *out_state) const {
return model_->BaseScore(in_state, word, out_state);
}
// This provides a fast state hashing,
// KenLmDeterministicOnDemandFst needs this for Fst states managing.
struct StateHasher {
inline size_t operator()(const State &s) const noexcept {
return util::MurmurHashNative(s.words, sizeof(WordIndex) * s.Length());
}
};
private:
void ComputeSymbolToWordIndexMapping(std::string symbol_table);
private:
lm::base::Model *model_; // with ownership
// without ownership, points to internal vocabulary of model_
const lm::base::Vocabulary* vocab_;
// There are two integerized indexing systems here:
// 1. Kaldi's fst output *symbol index*(defined in words.txt),
// 2. KenLm's *word index*(defined by word string hashing).
// In order to rescore kaldi hypotheses with kenlm ngrams,
// we need to know the index mapping from symbol to word.
// KenLm class precomputes (during model loading) and stores this mapping,
// and apply the mapping at runtime.
// This is slower, but at least we don't need
// to modify/convert runtime resources.(e.g. HCLG/lattices or kenlm models)
//
// In the mapping, <eps> and #0 symbols are special:
// They do not correspond to any word in KenLm,
// so the mapping of these two symbols are logically undefined,
// we just map them to KenLm's <unk> to avoid random invalid mapping.
// symid_to_wid_[kaldi_symbol_index] -> kenlm word index
std::vector<WordIndex> symid_to_wid_;
// special lm symbols
std::string bos_sym_;
std::string eos_sym_;
std::string unk_sym_;
int32 bos_symid_;
int32 eos_symid_;
int32 unk_symid_;
}; // class KenLm
// DeterministicOnDemandFst wraps a KenLm object as a deteministic Fst.
// Internally, it manages dynamically expanded Fst states(so not thread-safe),
// different threads should create their own instances of this class.
// They are lightweight and can share the same KenLm object.
template<class Arc>
class KenLmDeterministicOnDemandFst : public fst::DeterministicOnDemandFst<Arc> {
public:
typedef typename Arc::Weight Weight;
typedef typename Arc::StateId StateId;
typedef typename Arc::Label Label;
typedef typename KenLm::State State;
typedef typename KenLm::WordIndex WordIndex;
explicit KenLmDeterministicOnDemandFst(const KenLm *lm)
: lm_(lm), num_states_(0), bos_state_id_(0)
{
// create bos to be FST start state
MapElem e;
lm->SetStateToBeginOfSentence(&e.first);
e.second = bos_state_id_;
std::pair<IterType, bool> r = state_map_.insert(e);
KALDI_ASSERT(r.second == true); // bos successfully inserted into state map
state_vec_.push_back(&r.first->first);
num_states_++;
eos_symbol_id_ = lm_->EosSymbolIndex();
}
virtual ~KenLmDeterministicOnDemandFst() { }
virtual StateId Start() {
return bos_state_id_;
}
virtual bool GetArc(StateId s, Label label, Arc *oarc) {
KALDI_ASSERT(s < static_cast<StateId>(state_vec_.size()));
const State* istate = state_vec_[s];
MapElem e;
WordIndex word = lm_->GetWordIndex(label);
BaseFloat log_10_prob = lm_->Score(istate, word, &e.first);
e.second = num_states_;
std::pair<IterType, bool> r = state_map_.insert(e);
if (r.second == true) { // new state
state_vec_.push_back(&(r.first->first));
num_states_++;
}
oarc->ilabel = label;
oarc->olabel = oarc->ilabel;
oarc->nextstate = r.first->second;
oarc->weight = Weight(-log_10_prob * M_LN10); // KenLm log10() -> Kaldi ln()
return true;
}
virtual Weight Final(StateId s) {
Arc oarc;
GetArc(s, eos_symbol_id_, &oarc);
return oarc.weight;
}
private:
typedef std::pair<State, StateId> MapElem;
typedef unordered_map<State, StateId, KenLm::StateHasher> MapType;
typedef typename MapType::iterator IterType;
const KenLm *lm_; // no ownership
MapType state_map_;
std::vector<const State*> state_vec_;
StateId num_states_; // state vector index range, [0, num_states_)
StateId bos_state_id_; // fst start state id
Label eos_symbol_id_;
}; // class KenLmDeterministicOnDemandFst
} // namespace kaldi
#endif
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,239 @@
// lm/mikolov-rnnlm-lib.h
// Copyright 2015 Guoguo Chen Hainan Xu
// 2010-2012 Tomas Mikolov
// See ../../COPYING for clarification regarding multiple authors
//
// This file is based on version 0.3e of the RNNLM language modeling
// toolkit by Tomas Mikolov. Changes made by authors other than
// Tomas Mikolov are licensed under the Apache License, the short form
// os which is below. The original code by Tomas Mikolov is licensed
// under the BSD 3-clause license, whose text is further below.
//
// 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.
//
//
// Original BSD 3-clause license text:
// Copyright (c) 2010-2012 Tomas Mikolov
//
// All rights reserved. Redistribution and use in source and binary forms, with
// or without modification, are permitted provided that the following conditions
// are met: 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following
// disclaimer. 2. Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the
// distribution. 3. Neither name of copyright holders nor the names of its
// contributors may be used to endorse or promote products derived from this
// software without specific prior written permission. THIS SOFTWARE IS PROVIDED
// BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
// EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef KALDI_LM_MIKOLOV_RNNLM_LIB_H_
#define KALDI_LM_MIKOLOV_RNNLM_LIB_H_
#include <string>
#include <vector>
#include "util/stl-utils.h"
namespace rnnlm {
#define MAX_STRING 100
#define MAX_FILENAME_STRING 300
typedef double real; // doubles for NN weights
typedef double direct_t; // doubles for ME weights;
struct neuron {
real ac; // actual value stored in neuron
real er; // error value in neuron, used by learning algorithm
};
struct synapse {
real weight; // weight of synapse
};
struct vocab_word {
int cn;
char word[MAX_STRING];
real prob;
int class_index;
};
const unsigned int PRIMES[] = {108641969, 116049371, 125925907, 133333309,
145678979, 175308587, 197530793, 234567803, 251851741, 264197411,
330864029, 399999781,
407407183, 459258997, 479012069, 545678687, 560493491, 607407037, 629629243,
656789717, 716048933, 718518067, 725925469, 733332871, 753085943, 755555077,
782715551, 790122953, 812345159, 814814293, 893826581, 923456189, 940740127,
953085797, 985184539, 990122807};
const unsigned int PRIMES_SIZE = sizeof(PRIMES) / sizeof(PRIMES[0]);
const int MAX_NGRAM_ORDER = 20;
enum FileTypeEnum {TEXT, BINARY, COMPRESSED}; // COMPRESSED not yet implemented
class CRnnLM {
protected:
char train_file[MAX_FILENAME_STRING];
char valid_file[MAX_FILENAME_STRING];
char test_file[MAX_FILENAME_STRING];
char rnnlm_file[MAX_FILENAME_STRING];
char lmprob_file[MAX_FILENAME_STRING];
int rand_seed;
int version;
int filetype;
int use_lmprob;
real gradient_cutoff;
real dynamic;
real alpha;
real starting_alpha;
int alpha_divide;
double logp, llogp;
float min_improvement;
int iter;
int vocab_max_size;
int vocab_size;
int train_words;
int train_cur_pos;
int counter;
int anti_k;
real beta;
int class_size;
int **class_words;
int *class_cn;
int *class_max_cn;
int old_classes;
struct vocab_word *vocab;
void sortVocab();
int *vocab_hash;
int vocab_hash_size;
int layer0_size;
int layer1_size;
int layerc_size;
int layer2_size;
long long direct_size;
int direct_order;
int history[MAX_NGRAM_ORDER];
int bptt;
int bptt_block;
int *bptt_history;
neuron *bptt_hidden;
struct synapse *bptt_syn0;
int gen;
int independent;
struct neuron *neu0; // neurons in input layer
struct neuron *neu1; // neurons in hidden layer
struct neuron *neuc; // neurons in hidden layer
struct neuron *neu2; // neurons in output layer
struct synapse *syn0; // weights between input and hidden layer
struct synapse *syn1; // weights between hidden and output layer
// (or hidden and compression if compression>0)
struct synapse *sync; // weights between hidden and compression layer
direct_t *syn_d; // direct parameters between input and output layer
// (similar to Maximum Entropy model parameters)
// backup used in training:
struct neuron *neu0b;
struct neuron *neu1b;
struct neuron *neucb;
struct neuron *neu2b;
struct synapse *syn0b;
struct synapse *syn1b;
struct synapse *syncb;
direct_t *syn_db;
// backup used in n-bset rescoring:
struct neuron *neu1b2;
unordered_map<std::string, float> unk_penalty;
std::string unk_sym;
public:
int alpha_set, train_file_set;
CRnnLM();
~CRnnLM();
real random(real min, real max);
void setRnnLMFile(const std::string &str);
int getHiddenLayerSize() const { return layer1_size; }
void setRandSeed(int newSeed);
int getWordHash(const char *word);
void readWord(char *word, FILE *fin);
int searchVocab(const char *word);
void saveWeights(); // saves current weights and unit activations
void initNet();
void goToDelimiter(int delim, FILE *fi);
void restoreNet();
void netReset(); // will erase just hidden layer state + bptt history
// + maxent history (called at end of sentences in
// the independent mode)
void computeNet(int last_word, int word);
void copyHiddenLayerToInput();
void matrixXvector(struct neuron *dest, struct neuron *srcvec,
struct synapse *srcmatrix, int matrix_width,
int from, int to, int from2, int to2, int type);
void restoreContextFromVector(const std::vector<float> &context_in);
void saveContextToVector(std::vector<float> *context_out);
float computeConditionalLogprob(
std::string current_word,
const std::vector<std::string> &history_words,
const std::vector<float> &context_in,
std::vector<float> *context_out);
void setUnkSym(const std::string &unk);
void setUnkPenalty(const std::string &filename);
float getUnkPenalty(const std::string &word);
bool isUnk(const std::string &word);
};
} // namespace rnnlm
#endif // KALDI_LM_MIKOLOV_RNNLM_LIB_H_
@@ -0,0 +1,21 @@
\data\
ngram 1=4
ngram 2=2
ngram 3=2
\1-grams:
-5.234679 a -3.3
-3.456783 b
0.0000000 <s> -2.5
-4.333333 </s>
\2-grams:
-1.45678 a b -3.23
-1.30490 <s> a -4.2
\3-grams:
-0.34958 <s> a b
-0.23940 a b </s>
\end\
@@ -0,0 +1,20 @@
\data\
ngram 1=4
ngram 2=1
ngram 3=2
\1-grams:
-5.234679 a -3.3
-3.456783 b
0.0000000 <s> -2.5
-4.333333 </s>
\2-grams:
-1.30490 <s> a -4.2
\3-grams:
-0.34958 <s> a b
-0.23940 a b </s>
\end\
@@ -0,0 +1,18 @@
\data\
ngram 1=3
ngram 2=1
ngram 3=1
\1-grams:
-5.234679 a -3.3
-3.456783 b -3.0
-4.333333 </s>
\2-grams:
-1.45678 a b -3.23
\3-grams:
-0.23940 a b </s>
\end\
@@ -0,0 +1,26 @@
\data\
ngram 1=4
ngram 2=2
ngram 3=2
ngram 4=2
\1-grams:
-5.234679 a -3.3
-3.456783 b
0.0000000 <s> -2.5
-4.333333 </s>
\2-grams:
-1.45678 a b -3.23
-1.30490 <s> a -4.2
\3-grams:
-0.34958 <s> a b
-0.23940 a b </s>
\4-grams:
-0.01888 <s> a b b
-0.03333 <s> b b b
\end\