Expose k-best lists from incremental search

This commit is contained in:
Kenneth Heafield 2012-11-16 14:46:10 +00:00
parent 6a3fdcd8c0
commit 2df57b6b09
5 changed files with 287 additions and 171 deletions

View File

@ -40,6 +40,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include "moses/StaticData.h"
#include "moses/DummyScoreProducers.h"
#include "moses/InputFileStream.h"
#include "moses/Incremental.h"
#include "moses/PhraseDictionary.h"
#include "moses/ChartTrellisPathList.h"
#include "moses/ChartTrellisPath.h"
@ -366,8 +367,136 @@ void IOWrapper::OutputBestHypo(const ChartHypothesis *hypo, long translationId)
m_singleBestOutputCollector->Write(translationId, out.str());
}
void IOWrapper::OutputNBestList(const ChartTrellisPathList &nBestList, const ChartHypothesis *bestHypo, const TranslationSystem* system, long translationId)
{
void IOWrapper::OutputBestHypo(search::Applied applied, long translationId) {
if (!m_singleBestOutputCollector) return;
std::ostringstream out;
IOWrapper::FixPrecision(out);
if (StaticData::Instance().GetOutputHypoScore()) {
out << applied.GetScore() << ' ';
}
Phrase outPhrase;
Incremental::ToPhrase(applied, outPhrase);
// delete 1st & last
CHECK(outPhrase.GetSize() >= 2);
outPhrase.RemoveWord(0);
outPhrase.RemoveWord(outPhrase.GetSize() - 1);
out << outPhrase.GetStringRep(StaticData::Instance().GetOutputFactorOrder());
out << '\n';
m_singleBestOutputCollector->Write(translationId, out.str());
}
void IOWrapper::OutputBestNone(long translationId) {
if (!m_singleBestOutputCollector) return;
if (StaticData::Instance().GetOutputHypoScore()) {
m_singleBestOutputCollector->Write(translationId, "0 \n");
} else {
m_singleBestOutputCollector->Write(translationId, "\n");
}
}
namespace {
void OutputSparseFeatureScores(std::ostream& out, const ScoreComponentCollection &features, const FeatureFunction *ff, std::string &lastName) {
const StaticData &staticData = StaticData::Instance();
bool labeledOutput = staticData.IsLabeledNBestList();
const FVector scores = features.GetVectorForProducer( ff );
// report weighted aggregate
if (! ff->GetSparseFeatureReporting()) {
const FVector &weights = staticData.GetAllWeights().GetScoresVector();
if (labeledOutput && !boost::contains(ff->GetScoreProducerDescription(), ":"))
out << " " << ff->GetScoreProducerWeightShortName() << ":";
out << " " << scores.inner_product(weights);
}
// report each feature
else {
for(FVector::FNVmap::const_iterator i = scores.cbegin(); i != scores.cend(); i++) {
if (i->second != 0) { // do not report zero-valued features
if (labeledOutput)
out << " " << i->first << ":";
out << " " << i->second;
}
}
}
}
void WriteFeatures(const TranslationSystem &system, const ScoreComponentCollection &features, std::ostream &out) {
bool labeledOutput = StaticData::Instance().IsLabeledNBestList();
// lm
const LMList& lml = system.GetLanguageModels();
if (lml.size() > 0) {
if (labeledOutput)
out << "lm:";
LMList::const_iterator lmi = lml.begin();
for (; lmi != lml.end(); ++lmi) {
out << " " << features.GetScoreForProducer(*lmi);
}
}
std::string lastName = "";
// output stateful sparse features
const vector<const StatefulFeatureFunction*>& sff = system.GetStatefulFeatureFunctions();
for( size_t i=0; i<sff.size(); i++ )
if (sff[i]->GetNumScoreComponents() == ScoreProducer::unlimited)
OutputSparseFeatureScores(out, features, sff[i], lastName);
// translation components
const vector<PhraseDictionaryFeature*>& pds = system.GetPhraseDictionaries();
if (pds.size() > 0) {
for( size_t i=0; i<pds.size(); i++ ) {
size_t pd_numinputscore = pds[i]->GetNumInputScores();
vector<float> scores = features.GetScoresForProducer( pds[i] );
for (size_t j = 0; j<scores.size(); ++j){
if (labeledOutput && (i == 0) ){
if ((j == 0) || (j == pd_numinputscore)){
lastName = pds[i]->GetScoreProducerWeightShortName(j);
out << " " << lastName << ":";
}
}
out << " " << scores[j];
}
}
}
// word penalty
if (labeledOutput)
out << " w:";
out << " " << features.GetScoreForProducer(system.GetWordPenaltyProducer());
// generation
const vector<GenerationDictionary*>& gds = system.GetGenerationDictionaries();
if (gds.size() > 0) {
for( size_t i=0; i<gds.size(); i++ ) {
size_t pd_numinputscore = gds[i]->GetNumInputScores();
vector<float> scores = features.GetScoresForProducer( gds[i] );
for (size_t j = 0; j<scores.size(); ++j){
if (labeledOutput && (i == 0) ){
if ((j == 0) || (j == pd_numinputscore)){
lastName = gds[i]->GetScoreProducerWeightShortName(j);
out << " " << lastName << ":";
}
}
out << " " << scores[j];
}
}
}
// output stateless sparse features
lastName = "";
const vector<const StatelessFeatureFunction*>& slf = system.GetStatelessFeatureFunctions();
for( size_t i=0; i<slf.size(); i++ ) {
if (slf[i]->GetNumScoreComponents() == ScoreProducer::unlimited) {
OutputSparseFeatureScores(out, features, slf[i], lastName);
}
}
}
} // namespace
void IOWrapper::OutputNBestList(const ChartTrellisPathList &nBestList, const TranslationSystem* system, long translationId) {
std::ostringstream out;
// Check if we're writing to std::cout.
@ -376,17 +505,9 @@ void IOWrapper::OutputNBestList(const ChartTrellisPathList &nBestList, const Cha
// preserve existing behaviour, but should probably be done either way.
IOWrapper::FixPrecision(out);
// The output from -output-hypo-score is always written to std::cout.
if (StaticData::Instance().GetOutputHypoScore()) {
if (bestHypo != NULL) {
out << bestHypo->GetTotalScore() << " ";
} else {
out << "0 ";
}
}
// Used to check StaticData's GetOutputHypoScore(), but it makes no sense with nbest output.
}
bool labeledOutput = StaticData::Instance().IsLabeledNBestList();
//bool includeAlignment = StaticData::Instance().NBestIncludesAlignment();
ChartTrellisPathList::const_iterator iter;
@ -410,75 +531,7 @@ void IOWrapper::OutputNBestList(const ChartTrellisPathList &nBestList, const Cha
// before each model type, the corresponding command-line-like name must be emitted
// MERT script relies on this
// lm
const LMList& lml = system->GetLanguageModels();
if (lml.size() > 0) {
if (labeledOutput)
out << "lm:";
LMList::const_iterator lmi = lml.begin();
for (; lmi != lml.end(); ++lmi) {
out << " " << path.GetScoreBreakdown().GetScoreForProducer(*lmi);
}
}
std::string lastName = "";
// output stateful sparse features
const vector<const StatefulFeatureFunction*>& sff = system->GetStatefulFeatureFunctions();
for( size_t i=0; i<sff.size(); i++ )
if (sff[i]->GetNumScoreComponents() == ScoreProducer::unlimited)
OutputSparseFeatureScores( out, path, sff[i], lastName );
// translation components
const vector<PhraseDictionaryFeature*>& pds = system->GetPhraseDictionaries();
if (pds.size() > 0) {
for( size_t i=0; i<pds.size(); i++ ) {
size_t pd_numinputscore = pds[i]->GetNumInputScores();
vector<float> scores = path.GetScoreBreakdown().GetScoresForProducer( pds[i] );
for (size_t j = 0; j<scores.size(); ++j){
if (labeledOutput && (i == 0) ){
if ((j == 0) || (j == pd_numinputscore)){
lastName = pds[i]->GetScoreProducerWeightShortName(j);
out << " " << lastName << ":";
}
}
out << " " << scores[j];
}
}
}
// word penalty
if (labeledOutput)
out << " w:";
out << " " << path.GetScoreBreakdown().GetScoreForProducer(system->GetWordPenaltyProducer());
// generation
const vector<GenerationDictionary*>& gds = system->GetGenerationDictionaries();
if (gds.size() > 0) {
for( size_t i=0; i<gds.size(); i++ ) {
size_t pd_numinputscore = gds[i]->GetNumInputScores();
vector<float> scores = path.GetScoreBreakdown().GetScoresForProducer( gds[i] );
for (size_t j = 0; j<scores.size(); ++j){
if (labeledOutput && (i == 0) ){
if ((j == 0) || (j == pd_numinputscore)){
lastName = gds[i]->GetScoreProducerWeightShortName(j);
out << " " << lastName << ":";
}
}
out << " " << scores[j];
}
}
}
// output stateless sparse features
lastName = "";
const vector<const StatelessFeatureFunction*>& slf = system->GetStatelessFeatureFunctions();
for( size_t i=0; i<slf.size(); i++ ) {
if (slf[i]->GetNumScoreComponents() == ScoreProducer::unlimited) {
OutputSparseFeatureScores( out, path, slf[i], lastName );
}
}
WriteFeatures(*system, path.GetScoreBreakdown(), out);
// total
out << " ||| " << path.GetTotalScore();
@ -508,34 +561,33 @@ void IOWrapper::OutputNBestList(const ChartTrellisPathList &nBestList, const Cha
out <<std::flush;
CHECK(m_nBestOutputCollector);
assert(m_nBestOutputCollector);
m_nBestOutputCollector->Write(translationId, out.str());
}
void IOWrapper::OutputSparseFeatureScores( std::ostream& out, const ChartTrellisPath &path, const FeatureFunction *ff, std::string &lastName )
{
const StaticData &staticData = StaticData::Instance();
bool labeledOutput = staticData.IsLabeledNBestList();
const FVector scores = path.GetScoreBreakdown().GetVectorForProducer( ff );
// report weighted aggregate
if (! ff->GetSparseFeatureReporting()) {
const FVector &weights = staticData.GetAllWeights().GetScoresVector();
if (labeledOutput && !boost::contains(ff->GetScoreProducerDescription(), ":"))
out << " " << ff->GetScoreProducerWeightShortName() << ":";
out << " " << scores.inner_product(weights);
void IOWrapper::OutputNBestList(const std::vector<search::Applied> &nbest, const TranslationSystem &system, long translationId) {
std::ostringstream out;
// wtf? copied from the original OutputNBestList
if (m_nBestOutputCollector->OutputIsCout()) {
IOWrapper::FixPrecision(out);
}
// report each feature
else {
for(FVector::FNVmap::const_iterator i = scores.cbegin(); i != scores.cend(); i++) {
if (i->second != 0) { // do not report zero-valued features
if (labeledOutput)
out << " " << i->first << ":";
out << " " << i->second;
}
}
Phrase outputPhrase;
ScoreComponentCollection features;
for (std::vector<search::Applied>::const_iterator i = nbest.begin(); i != nbest.end(); ++i) {
Incremental::PhraseAndFeatures(system, *i, outputPhrase, features);
// <s> and </s>
CHECK(outputPhrase.GetSize() >= 2);
outputPhrase.RemoveWord(0);
outputPhrase.RemoveWord(outputPhrase.GetSize() - 1);
out << translationId << " ||| ";
OutputSurface(out, outputPhrase, m_outputFactorOrder, false);
out << " ||| ";
WriteFeatures(system, features, out);
out << " ||| " << i->GetScore() << '\n';
}
out << std::flush;
assert(m_nBestOutputCollector);
m_nBestOutputCollector->Write(translationId, out.str());
}
void IOWrapper::FixPrecision(std::ostream &stream, size_t size)

View File

@ -44,6 +44,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include "moses/OutputCollector.h"
#include "moses/ChartHypothesis.h"
#include "moses/ChartTrellisPath.h"
#include "search/applied.hh"
namespace Moses
{
@ -85,14 +86,14 @@ public:
Moses::InputType* GetInput(Moses::InputType *inputType);
void OutputBestHypo(const Moses::ChartHypothesis *hypo, long translationId);
void OutputBestHypo(search::Applied applied, long translationId);
void OutputBestHypo(const std::vector<const Moses::Factor*>& mbrBestHypo, long translationId);
void OutputNBestList(const Moses::ChartTrellisPathList &nBestList, const Moses::ChartHypothesis *bestHypo, const Moses::TranslationSystem* system, long translationId);
void OutputSparseFeatureScores(std::ostream& out, const Moses::ChartTrellisPath &path, const Moses::FeatureFunction *ff, std::string &lastName);
void OutputBestNone(long translationId);
void OutputNBestList(const Moses::ChartTrellisPathList &nBestList, const Moses::TranslationSystem* system, long translationId);
void OutputNBestList(const std::vector<search::Applied> &nbest, const Moses::TranslationSystem &system, long translationId);
void OutputDetailedTranslationReport(const Moses::ChartHypothesis *hypo, const Moses::Sentence &sentence, long translationId);
void Backtrack(const Moses::ChartHypothesis *hypo);
Moses::OutputCollector *ExposeSingleBest() { return m_singleBestOutputCollector; }
void ResetTranslationId();
Moses::OutputCollector *GetSearchGraphOutputCollector() {

View File

@ -91,10 +91,14 @@ public:
if (staticData.GetSearchAlgorithm() == ChartIncremental) {
Incremental::Manager manager(*m_source, system);
manager.ProcessSentence();
if (m_ioWrapper.ExposeSingleBest()) {
m_ioWrapper.ExposeSingleBest()->Write(lineNumber, manager.String() + '\n');
const std::vector<search::Applied> &nbest = manager.ProcessSentence();
if (!nbest.empty()) {
m_ioWrapper.OutputBestHypo(nbest[0], lineNumber);
} else {
m_ioWrapper.OutputBestNone(lineNumber);
}
if (staticData.GetNBestSize() > 0)
m_ioWrapper.OutputNBestList(nbest, system, lineNumber);
return;
}
@ -121,7 +125,7 @@ public:
VERBOSE(2,"WRITING " << nBestSize << " TRANSLATION ALTERNATIVES TO " << staticData.GetNBestFilePath() << endl);
ChartTrellisPathList nBestList;
manager.CalcNBest(nBestSize, nBestList,staticData.GetDistinctNBest());
m_ioWrapper.OutputNBestList(nBestList, bestHypo, &system, lineNumber);
m_ioWrapper.OutputNBestList(nBestList, &system, lineNumber);
IFVERBOSE(2) {
PrintUserTime("N-Best Hypotheses Generation Time:");
}

View File

@ -2,8 +2,10 @@
#include "moses/ChartCell.h"
#include "moses/ChartParserCallback.h"
#include "moses/TranslationSystem.h"
#include "moses/FeatureVector.h"
#include "moses/StaticData.h"
#include "moses/TranslationSystem.h"
#include "moses/Util.h"
#include "lm/model.hh"
#include "search/applied.hh"
@ -78,6 +80,14 @@ template <class Model> class Fill : public ChartParserCallback {
edges_.Search(context_, callback);
}
// Root: everything into one vertex.
template <class Best> search::History RootSearch(Best &best) {
search::Vertex vertex;
search::RootVertexGenerator<Best> gen(vertex, best);
edges_.Search(context_, gen);
return vertex.BestChild();
}
private:
lm::WordIndex Convert(const Word &word) const;
@ -166,80 +176,58 @@ Manager::Manager(const InputType &source, const TranslationSystem &system) :
source_(source),
system_(system),
cells_(source, ChartCellBaseFactory()),
parser_(source, system, cells_) {}
parser_(source, system, cells_),
n_best_(search::NBestConfig(StaticData::Instance().GetNBestSize())) {}
Manager::~Manager() {
system_.CleanUpAfterSentenceProcessing(source_);
}
namespace {
void ConstructString(const search::Applied final, std::ostringstream &stream) {
assert(final.Valid());
const TargetPhrase &phrase = *static_cast<const TargetPhrase*>(final.GetNote().vp);
size_t child = 0;
for (std::size_t i = 0; i < phrase.GetSize(); ++i) {
const Word &word = phrase.GetWord(i);
if (word.IsNonTerminal()) {
assert(child < final.GetArity());
ConstructString(final.Children()[child++], stream);
} else {
stream << word[0]->GetString() << ' ';
}
}
}
void BestString(const ChartCellLabelSet &labels, std::string &out) {
search::Applied best;
for (ChartCellLabelSet::const_iterator i = labels.begin(); i != labels.end(); ++i) {
const search::Applied child(i->second.GetStack().incr->BestChild());
if (child.Valid() && (!best.Valid() || (child.GetScore() > best.GetScore()))) {
best = child;
}
}
if (!best.Valid()) {
out.clear();
return;
}
std::ostringstream stream;
ConstructString(best, stream);
out = stream.str();
CHECK(out.size() > 9);
// <s>
out.erase(0, 4);
// </s>
out.erase(out.size() - 5);
// Hack: include model score
out += " ||| ";
out += boost::lexical_cast<std::string>(best.GetScore());
}
} // namespace
template <class Model> void Manager::LMCallback(const Model &model, const std::vector<lm::WordIndex> &words) {
template <class Model, class Best> search::History Manager::PopulateBest(const Model &model, const std::vector<lm::WordIndex> &words, Best &out) {
const LanguageModel &abstract = **system_.GetLanguageModels().begin();
const float oov_weight = abstract.OOVFeatureEnabled() ? abstract.GetOOVWeight() : 0.0;
const StaticData &data = StaticData::Instance();
search::Config config(abstract.GetWeight(), data.GetCubePruningPopLimit(), search::NBestConfig(data.GetNBestSize()));
search::Context<Model> context(config, model);
search::SingleBest best;
size_t size = source_.GetSize();
boost::object_pool<search::Vertex> vertex_pool(std::max<size_t>(size * size / 2, 32));
for (size_t width = 1; width <= size; ++width) {
for (size_t width = 1; width < size; ++width) {
for (size_t startPos = 0; startPos <= size-width; ++startPos) {
size_t endPos = startPos + width - 1;
WordsRange range(startPos, endPos);
WordsRange range(startPos, startPos + width - 1);
Fill<Model> filler(context, words, oov_weight);
parser_.Create(range, filler);
filler.Search(best, cells_.MutableBase(range).MutableTargetLabelSet(), vertex_pool);
filler.Search(out, cells_.MutableBase(range).MutableTargetLabelSet(), vertex_pool);
}
}
WordsRange range(0, size - 1);
Fill<Model> filler(context, words, oov_weight);
parser_.Create(range, filler);
return filler.RootSearch(out);
}
template <class Model> void Manager::LMCallback(const Model &model, const std::vector<lm::WordIndex> &words) {
std::size_t nbest = StaticData::Instance().GetNBestSize();
if (nbest <= 1) {
search::History ret = PopulateBest(model, words, single_best_);
if (ret) {
backing_for_single_.resize(1);
backing_for_single_[0] = search::Applied(ret);
} else {
backing_for_single_.clear();
}
completed_nbest_ = &backing_for_single_;
} else {
search::History ret = PopulateBest(model, words, n_best_);
if (ret) {
completed_nbest_ = &n_best_.Extract(ret);
} else {
backing_for_single_.clear();
completed_nbest_ = &backing_for_single_;
}
}
BestString(cells_.GetBase(WordsRange(0, source_.GetSize() - 1)).GetTargetLabelSet(), output_);
}
template void Manager::LMCallback<lm::ngram::ProbingModel>(const lm::ngram::ProbingModel &model, const std::vector<lm::WordIndex> &words);
@ -249,10 +237,59 @@ template void Manager::LMCallback<lm::ngram::QuantTrieModel>(const lm::ngram::Qu
template void Manager::LMCallback<lm::ngram::ArrayTrieModel>(const lm::ngram::ArrayTrieModel &model, const std::vector<lm::WordIndex> &words);
template void Manager::LMCallback<lm::ngram::QuantArrayTrieModel>(const lm::ngram::QuantArrayTrieModel &model, const std::vector<lm::WordIndex> &words);
void Manager::ProcessSentence() {
const std::vector<search::Applied> &Manager::ProcessSentence() {
const LMList &lms = system_.GetLanguageModels();
UTIL_THROW_IF(lms.size() != 1, util::Exception, "Incremental search only supports one language model.");
(*lms.begin())->IncrementalCallback(*this);
return *completed_nbest_;
}
namespace {
struct NoOp {
void operator()(const TargetPhrase &) const {}
};
struct AccumScore {
AccumScore(ScoreComponentCollection &out) : out_(&out) {}
void operator()(const TargetPhrase &phrase) {
out_->PlusEquals(phrase.GetScoreBreakdown());
}
ScoreComponentCollection *out_;
};
template <class Action> void AppendToPhrase(const search::Applied final, Phrase &out, Action action) {
assert(final.Valid());
const TargetPhrase &phrase = *static_cast<const TargetPhrase*>(final.GetNote().vp);
action(phrase);
const search::Applied *child = final.Children();
for (std::size_t i = 0; i < phrase.GetSize(); ++i) {
const Word &word = phrase.GetWord(i);
if (word.IsNonTerminal()) {
AppendToPhrase(*child++, out, action);
} else {
out.AddWord(word);
}
}
}
} // namespace
void ToPhrase(const search::Applied final, Phrase &out) {
out.Clear();
AppendToPhrase(final, out, NoOp());
}
void PhraseAndFeatures(const TranslationSystem &system, const search::Applied final, Phrase &phrase, ScoreComponentCollection &features) {
phrase.Clear();
features.ZeroAll();
AppendToPhrase(final, phrase, AccumScore(features));
// If we made it this far, there is only one language model.
float full, ignored_ngram;
std::size_t ignored_oov;
const LanguageModel &model = **system.GetLanguageModels().begin();
model.CalcScore(phrase, full, ignored_ngram, ignored_oov);
// CalcScore transforms, but EvaluateChart doesn't.
features.Assign(&model, UntransformLMScore(full));
}
} // namespace Incremental

View File

@ -1,6 +1,8 @@
#pragma once
#include "lm/word_index.hh"
#include "search/applied.hh"
#include "search/nbest.hh"
#include "moses/ChartCellCollection.h"
#include "moses/ChartParser.h"
@ -9,6 +11,7 @@
#include <string>
namespace Moses {
class ScoreComponentCollection;
class InputType;
class TranslationSystem;
namespace Incremental {
@ -21,18 +24,37 @@ class Manager {
template <class Model> void LMCallback(const Model &model, const std::vector<lm::WordIndex> &words);
void ProcessSentence();
const std::vector<search::Applied> &ProcessSentence();
const std::string &String() const { return output_; }
// Call to get the same value as ProcessSentence returned.
const std::vector<search::Applied> &Completed() const {
return *completed_nbest_;
}
private:
template <class Model, class Best> search::History PopulateBest(const Model &model, const std::vector<lm::WordIndex> &words, Best &out);
const InputType &source_;
const TranslationSystem &system_;
ChartCellCollectionBase cells_;
ChartParser parser_;
std::string output_;
// Only one of single_best_ or n_best_ will be used, but it was easier to do this than a template.
search::SingleBest single_best_;
// ProcessSentence returns a reference to a vector. ProcessSentence
// doesn't have one, so this is populated and returned.
std::vector<search::Applied> backing_for_single_;
search::NBest n_best_;
const std::vector<search::Applied> *completed_nbest_;
};
// Just get the phrase.
void ToPhrase(const search::Applied final, Phrase &out);
// Get the phrase and the features.
void PhraseAndFeatures(const TranslationSystem &system, const search::Applied final, Phrase &phrase, ScoreComponentCollection &features);
} // namespace Incremental
} // namespace Moses