#include #include #include "moses/Incremental.h" #include "moses/ChartCell.h" #include "moses/ChartParserCallback.h" #include "moses/FeatureVector.h" #include "moses/StaticData.h" #include "moses/Util.h" #include "moses/LM/Base.h" #include "moses/OutputCollector.h" #include "lm/model.hh" #include "search/applied.hh" #include "search/config.hh" #include "search/context.hh" #include "search/edge_generator.hh" #include "search/rule.hh" #include "search/vertex_generator.hh" #include namespace Moses { namespace Incremental { namespace { // This is called by EdgeGenerator. Route hypotheses to separate vertices for // each left hand side label, populating ChartCellLabelSet out. template class HypothesisCallback { private: typedef search::VertexGenerator Gen; public: HypothesisCallback(search::ContextBase &context, Best &best, ChartCellLabelSet &out, boost::object_pool &vertex_pool) : context_(context), best_(best), out_(out), vertex_pool_(vertex_pool) {} void NewHypothesis(search::PartialEdge partial) { // Get the LHS, look it up in the output ChartCellLabel, and upcast it. // It's not part of the union because it would have been ugly to expose template types in ChartCellLabel. ChartCellLabel::Stack &stack = out_.FindOrInsert(static_cast(partial.GetNote().vp)->GetTargetLHS()); Gen *entry = static_cast(stack.incr_generator); if (!entry) { entry = generator_pool_.construct(boost::ref(context_), boost::ref(*vertex_pool_.construct()), boost::ref(best_)); stack.incr_generator = entry; } entry->NewHypothesis(partial); } void FinishedSearch() { for (ChartCellLabelSet::iterator i(out_.mutable_begin()); i != out_.mutable_end(); ++i) { if ((*i) == NULL) { continue; } ChartCellLabel::Stack &stack = (*i)->MutableStack(); Gen *gen = static_cast(stack.incr_generator); gen->FinishedSearch(); stack.incr = &gen->Generating(); } } private: search::ContextBase &context_; Best &best_; ChartCellLabelSet &out_; boost::object_pool &vertex_pool_; boost::object_pool generator_pool_; }; // This is called by the moses parser to collect hypotheses. It converts to my // edges (search::PartialEdge). template class Fill : public ChartParserCallback { public: Fill(search::Context &context, const std::vector &vocab_mapping, search::Score oov_weight) : context_(context), vocab_mapping_(vocab_mapping), oov_weight_(oov_weight) {} void Add(const TargetPhraseCollection &targets, const StackVec &nts, const Range &ignored); void AddPhraseOOV(TargetPhrase &phrase, std::list &waste_memory, const Range &range); float GetBestScore(const ChartCellLabel *chartCell) const; bool Empty() const { return edges_.Empty(); } template void Search(Best &best, ChartCellLabelSet &out, boost::object_pool &vertex_pool) { HypothesisCallback callback(context_, best, out, vertex_pool); edges_.Search(context_, callback); } // Root: everything into one vertex. template search::History RootSearch(Best &best) { search::Vertex vertex; search::RootVertexGenerator gen(vertex, best); edges_.Search(context_, gen); return vertex.BestChild(); } void EvaluateWithSourceContext(const InputType &input, const InputPath &inputPath) { // TODO for input lattice } private: lm::WordIndex Convert(const Word &word) const; search::Context &context_; const std::vector &vocab_mapping_; search::EdgeGenerator edges_; const search::Score oov_weight_; }; template void Fill::Add(const TargetPhraseCollection &targets, const StackVec &nts, const Range &range) { std::vector vertices; vertices.reserve(nts.size()); float below_score = 0.0; for (StackVec::const_iterator i(nts.begin()); i != nts.end(); ++i) { vertices.push_back((*i)->GetStack().incr->RootAlternate()); below_score += (*i)->GetBestScore(this); } std::vector words; for (TargetPhraseCollection::const_iterator p(targets.begin()); p != targets.end(); ++p) { words.clear(); const TargetPhrase &phrase = **p; const AlignmentInfo::NonTermIndexMap &align = phrase.GetAlignNonTerm().GetNonTermIndexMap(); search::PartialEdge edge(edges_.AllocateEdge(nts.size())); search::PartialVertex *nt = edge.NT(); for (size_t i = 0; i < phrase.GetSize(); ++i) { const Word &word = phrase.GetWord(i); if (word.IsNonTerminal()) { *(nt++) = vertices[align[i]]; words.push_back(search::kNonTerminal); } else { words.push_back(Convert(word)); } } edge.SetScore(phrase.GetFutureScore() + below_score); // prob and oov were already accounted for. search::ScoreRule(context_.LanguageModel(), words, edge.Between()); search::Note note; note.vp = &phrase; edge.SetNote(note); edge.SetRange(range); edges_.AddEdge(edge); } } template void Fill::AddPhraseOOV(TargetPhrase &phrase, std::list &, const Range &range) { std::vector words; UTIL_THROW_IF2(phrase.GetSize() > 1, "OOV target phrase should be 0 or 1 word in length"); if (phrase.GetSize()) words.push_back(Convert(phrase.GetWord(0))); search::PartialEdge edge(edges_.AllocateEdge(0)); // Appears to be a bug that FutureScore does not already include language model. search::ScoreRuleRet scored(search::ScoreRule(context_.LanguageModel(), words, edge.Between())); edge.SetScore(phrase.GetFutureScore() + scored.prob * context_.LMWeight() + static_cast(scored.oov) * oov_weight_); search::Note note; note.vp = &phrase; edge.SetNote(note); edge.SetRange(range); edges_.AddEdge(edge); } // for pruning template float Fill::GetBestScore(const ChartCellLabel *chartCell) const { search::PartialVertex vertex = chartCell->GetStack().incr->RootAlternate(); UTIL_THROW_IF2(vertex.Empty(), "hypothesis with empty stack"); return vertex.Bound(); } // TODO: factors (but chart doesn't seem to support factors anyway). template lm::WordIndex Fill::Convert(const Word &word) const { std::size_t factor = word.GetFactor(0)->GetId(); return (factor >= vocab_mapping_.size() ? 0 : vocab_mapping_[factor]); } struct ChartCellBaseFactory { ChartCellBase *operator()(size_t startPos, size_t endPos) const { return new ChartCellBase(startPos, endPos); } }; } // namespace Manager::Manager(ttasksptr const& ttask) : BaseManager(ttask) , cells_(m_source, ChartCellBaseFactory(), parser_) , parser_(ttask, cells_) , n_best_(search::NBestConfig(StaticData::Instance().options()->nbest.nbest_size)) { } Manager::~Manager() { } namespace { // Natural logarithm of 10. // // Some implementations of define M_LM10, but not all. const float log_10 = logf(10); } template search::History Manager:: PopulateBest(const Model &model, const std::vector &words, Best &out) { const LanguageModel &abstract = LanguageModel::GetFirstLM(); const StaticData &data = StaticData::Instance(); const float lm_weight = data.GetWeights(&abstract)[0]; const float oov_weight = abstract.OOVFeatureEnabled() ? data.GetWeights(&abstract)[1] : 0.0; size_t cpl = data.options()->cube.pop_limit; size_t nbs = data.options()->nbest.nbest_size; search::Config config(lm_weight * log_10, cpl, search::NBestConfig(nbs)); search::Context context(config, model); size_t size = m_source.GetSize(); boost::object_pool vertex_pool(std::max(size * size / 2, 32)); for (int startPos = size-1; startPos >= 0; --startPos) { for (size_t width = 1; width <= size-startPos; ++width) { // full range uses RootSearch if (startPos == 0 && startPos + width == size) { break; } Range range(startPos, startPos + width - 1); Fill filler(context, words, oov_weight); parser_.Create(range, filler); filler.Search(out, cells_.MutableBase(range).MutableTargetLabelSet(), vertex_pool); } } Range range(0, size - 1); Fill filler(context, words, oov_weight); parser_.Create(range, filler); return filler.RootSearch(out); } template void Manager::LMCallback(const Model &model, const std::vector &words) { std::size_t nbest = StaticData::Instance().options()->nbest.nbest_size; 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_; } } } template void Manager::LMCallback(const lm::ngram::ProbingModel &model, const std::vector &words); template void Manager::LMCallback(const lm::ngram::RestProbingModel &model, const std::vector &words); template void Manager::LMCallback(const lm::ngram::TrieModel &model, const std::vector &words); template void Manager::LMCallback(const lm::ngram::QuantTrieModel &model, const std::vector &words); template void Manager::LMCallback(const lm::ngram::ArrayTrieModel &model, const std::vector &words); template void Manager::LMCallback(const lm::ngram::QuantArrayTrieModel &model, const std::vector &words); void Manager::Decode() { LanguageModel::GetFirstLM().IncrementalCallback(*this); } const std::vector &Manager::GetNBest() const { return *completed_nbest_; } void Manager::OutputBest(OutputCollector *collector) const { const long translationId = m_source.GetTranslationId(); const std::vector &nbest = GetNBest(); if (!nbest.empty()) { OutputBestHypo(collector, nbest[0], translationId); } else { OutputBestNone(collector, translationId); } } void Manager::OutputNBest(OutputCollector *collector) const { if (collector == NULL) { return; } OutputNBestList(collector, *completed_nbest_, m_source.GetTranslationId()); } void Manager:: OutputNBestList(OutputCollector *collector, std::vector const& nbest, long translationId) const { const std::vector &outputFactorOrder = options()->output.factor_order; std::ostringstream out; // wtf? copied from the original OutputNBestList if (collector->OutputIsCout()) { FixPrecision(out); } Phrase outputPhrase; ScoreComponentCollection features; for (std::vector::const_iterator i = nbest.begin(); i != nbest.end(); ++i) { Incremental::PhraseAndFeatures(*i, outputPhrase, features); // and UTIL_THROW_IF2(outputPhrase.GetSize() < 2, "Output phrase should have contained at least 2 words " << "(beginning and end-of-sentence)"); outputPhrase.RemoveWord(0); outputPhrase.RemoveWord(outputPhrase.GetSize() - 1); out << translationId << " ||| "; OutputSurface(out, outputPhrase); // , outputFactorOrder, false); out << " ||| "; bool with_labels = options()->nbest.include_feature_labels; features.OutputAllFeatureScores(out, with_labels); out << " ||| " << i->GetScore() << '\n'; } out << std::flush; assert(collector); collector->Write(translationId, out.str()); } void Manager:: OutputDetailedTranslationReport(OutputCollector *collector) const { if (collector && !completed_nbest_->empty()) { const search::Applied &applied = completed_nbest_->at(0); OutputDetailedTranslationReport(collector, &applied, static_cast(m_source), m_source.GetTranslationId()); } } void Manager::OutputDetailedTranslationReport( OutputCollector *collector, const search::Applied *applied, const Sentence &sentence, long translationId) const { if (applied == NULL) { return; } std::ostringstream out; ApplicationContext applicationContext; OutputTranslationOptions(out, applicationContext, applied, sentence, translationId); collector->Write(translationId, out.str()); } void Manager::OutputTranslationOptions(std::ostream &out, ApplicationContext &applicationContext, const search::Applied *applied, const Sentence &sentence, long translationId) const { if (applied != NULL) { OutputTranslationOption(out, applicationContext, applied, sentence, translationId); out << std::endl; } // recursive const search::Applied *child = applied->Children(); for (size_t i = 0; i < applied->GetArity(); i++) { OutputTranslationOptions(out, applicationContext, child++, sentence, translationId); } } void Manager::OutputTranslationOption(std::ostream &out, ApplicationContext &applicationContext, const search::Applied *applied, const Sentence &sentence, long translationId) const { ReconstructApplicationContext(applied, sentence, applicationContext); const TargetPhrase &phrase = *static_cast(applied->GetNote().vp); out << "Trans Opt " << translationId << " " << applied->GetRange() << ": "; WriteApplicationContext(out, applicationContext); out << ": " << phrase.GetTargetLHS() << "->" << phrase << " " << applied->GetScore(); // << hypo->GetScoreBreakdown() TODO: missing in incremental search hypothesis } // Given a hypothesis and sentence, reconstructs the 'application context' -- // the source RHS symbols of the SCFG rule that was applied, plus their spans. void Manager::ReconstructApplicationContext(const search::Applied *applied, const Sentence &sentence, ApplicationContext &context) const { context.clear(); const Range &span = applied->GetRange(); const search::Applied *child = applied->Children(); size_t i = span.GetStartPos(); size_t j = 0; while (i <= span.GetEndPos()) { if (j == applied->GetArity() || i < child->GetRange().GetStartPos()) { // Symbol is a terminal. const Word &symbol = sentence.GetWord(i); context.push_back(std::make_pair(symbol, Range(i, i))); ++i; } else { // Symbol is a non-terminal. const Word &symbol = static_cast(child->GetNote().vp)->GetTargetLHS(); const Range &range = child->GetRange(); context.push_back(std::make_pair(symbol, range)); i = range.GetEndPos()+1; ++child; ++j; } } } void Manager::OutputDetailedTreeFragmentsTranslationReport(OutputCollector *collector) const { if (collector == NULL || Completed().empty()) { return; } const search::Applied *applied = &Completed()[0]; const Sentence &sentence = static_cast(m_source); const size_t translationId = m_source.GetTranslationId(); std::ostringstream out; ApplicationContext applicationContext; OutputTreeFragmentsTranslationOptions(out, applicationContext, applied, sentence, translationId); //Tree of full sentence //TODO: incremental search doesn't support stateful features collector->Write(translationId, out.str()); } void Manager::OutputTreeFragmentsTranslationOptions(std::ostream &out, ApplicationContext &applicationContext, const search::Applied *applied, const Sentence &sentence, long translationId) const { if (applied != NULL) { OutputTranslationOption(out, applicationContext, applied, sentence, translationId); const TargetPhrase &currTarPhr = *static_cast(applied->GetNote().vp); out << " ||| "; if (const PhraseProperty *property = currTarPhr.GetProperty("Tree")) { out << " " << *property->GetValueString(); } else { out << " " << "noTreeInfo"; } out << std::endl; } // recursive const search::Applied *child = applied->Children(); for (size_t i = 0; i < applied->GetArity(); i++) { OutputTreeFragmentsTranslationOptions(out, applicationContext, child++, sentence, translationId); } } void Manager::OutputBestHypo(OutputCollector *collector, search::Applied applied, long translationId) const { if (collector == NULL) return; std::ostringstream out; FixPrecision(out); if (options()->output.ReportHypoScore) { out << applied.GetScore() << ' '; } Phrase outPhrase; Incremental::ToPhrase(applied, outPhrase); // delete 1st & last UTIL_THROW_IF2(outPhrase.GetSize() < 2, "Output phrase should have contained at least 2 words (beginning and end-of-sentence)"); outPhrase.RemoveWord(0); outPhrase.RemoveWord(outPhrase.GetSize() - 1); out << outPhrase.GetStringRep(options()->output.factor_order); out << '\n'; collector->Write(translationId, out.str()); VERBOSE(1,"BEST TRANSLATION: " << outPhrase << "[total=" << applied.GetScore() << "]" << std::endl); } void Manager:: OutputBestNone(OutputCollector *collector, long translationId) const { if (collector == NULL) return; if (options()->output.ReportHypoScore) { collector->Write(translationId, "0 \n"); } else { collector->Write(translationId, "\n"); } } 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 void AppendToPhrase(const search::Applied final, Phrase &out, Action action) { assert(final.Valid()); const TargetPhrase &phrase = *static_cast(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 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 = LanguageModel::GetFirstLM(); model.CalcScore(phrase, full, ignored_ngram, ignored_oov); // CalcScore transforms, but EvaluateWhenApplied doesn't. features.Assign(&model, full); } } // namespace Incremental } // namespace Moses