#include "ns.h" #include "ns_ter.h" #include "ns_ext.h" #include "ns_comb.h" #include "ns_cer.h" #include "ns_wer.h" #include "ns_ssk.h" #include #include #include #include #include #include #include "config.h" #include "tdict.h" #include "filelib.h" #include "stringlib.h" using namespace std; map EvaluationMetric::instances_; extern const char* meteor_jar_path; SegmentEvaluator::~SegmentEvaluator() {} EvaluationMetric::~EvaluationMetric() {} bool EvaluationMetric::IsErrorMetric() const { return false; } struct DefaultSegmentEvaluator : public SegmentEvaluator { DefaultSegmentEvaluator(const vector >& refs, const EvaluationMetric* em) : refs_(refs), em_(em) {} void Evaluate(const vector& hyp, SufficientStats* out) const { em_->ComputeSufficientStatistics(hyp, refs_, out); out->id_ = em_->MetricId(); } const vector > refs_; const EvaluationMetric* em_; }; boost::shared_ptr EvaluationMetric::CreateSegmentEvaluator(const vector >& refs) const { return boost::shared_ptr(new DefaultSegmentEvaluator(refs, this)); } #define MAX_SS_VECTOR_SIZE 50 unsigned EvaluationMetric::SufficientStatisticsVectorSize() const { return MAX_SS_VECTOR_SIZE; } void EvaluationMetric::ComputeSufficientStatistics(const vector&, const vector >&, SufficientStats*) const { cerr << "Base class ComputeSufficientStatistics should not be called.\n"; abort(); } string EvaluationMetric::DetailedScore(const SufficientStats& stats) const { ostringstream os; os << MetricId() << "=" << ComputeScore(stats); return os.str(); } enum BleuType { IBM, Koehn, NIST, QCRI }; template struct BleuSegmentEvaluator : public SegmentEvaluator { BleuSegmentEvaluator(const vector >& refs, const EvaluationMetric* em) : evaluation_metric(em) { const vector >& local_refs = (CharBased ? Characterize(refs) : refs); assert(local_refs.size() > 0); float tot = 0; int smallest = 9999999; for (vector >::const_iterator ci = local_refs.begin(); ci != local_refs.end(); ++ci) { lengths_.push_back(ci->size()); tot += lengths_.back(); if (lengths_.back() < smallest) smallest = lengths_.back(); CountRef(*ci); } if (BrevityType == Koehn) lengths_[0] = tot / local_refs.size(); if (BrevityType == NIST) lengths_[0] = smallest; } void Evaluate(const vector& hyp, SufficientStats* out) const { const vector& local_hyp = (CharBased ? Characterize(hyp) : hyp); out->fields.resize(N + N + 2); out->id_ = evaluation_metric->MetricId(); for (unsigned i = 0; i < N+N+2; ++i) out->fields[i] = 0; ComputeNgramStats(local_hyp, &out->fields[0], &out->fields[N], true); float& hyp_len = out->fields[2*N]; float& ref_len = out->fields[2*N + 1]; hyp_len = local_hyp.size(); ref_len = lengths_[0]; if (lengths_.size() > 1 && (BrevityType == IBM || BrevityType == QCRI)) { float bestd = 2000000; float hl = local_hyp.size(); float bl = -1; for (vector::const_iterator ci = lengths_.begin(); ci != lengths_.end(); ++ci) { if (fabs(*ci - hl) < bestd) { bestd = fabs(*ci - hl); bl = *ci; } } ref_len = bl; } } struct NGramCompare { int operator() (const vector& a, const vector& b) const { const size_t as = a.size(); const size_t bs = b.size(); const size_t s = (as < bs ? as : bs); for (size_t i = 0; i < s; ++i) { int d = a[i] - b[i]; if (d < 0) return true; if (d > 0) return false; } return as < bs; } }; typedef map, pair, NGramCompare> NGramCountMap; void CountRef(const vector& ref) { NGramCountMap tc; vector ngram(N); int s = ref.size(); for (int j=0; j& p = ngrams_[i->first]; if (p.first < i->second.first) p = i->second; } } void ComputeNgramStats(const vector& sent, float* correct, // N elements reserved float* hyp, // N elements reserved bool clip_counts = true) const { // clear clipping stats for (typename NGramCountMap::iterator it = ngrams_.begin(); it != ngrams_.end(); ++it) it->second.second = 0; vector ngram(N); *correct *= 0; *hyp *= 0; int s = sent.size(); for (int j=0; j& p = ngrams_[ngram]; if(clip_counts){ if (p.second < p.first) { ++p.second; correct[i-1]++; } } else { ++p.second; correct[i-1]++; } // if the 1 gram isn't found, don't try to match don't need to match any 2- 3- .. grams: if (!p.first) { for (; i<=k; ++i) hyp[i-1]++; } else { hyp[i-1]++; } } } } const EvaluationMetric* evaluation_metric; vector lengths_; mutable NGramCountMap ngrams_; }; template struct BleuMetric : public EvaluationMetric { BleuMetric() : EvaluationMetric(BrevityType == IBM ? "IBM_BLEU" : (BrevityType == Koehn ? "KOEHN_BLEU" : (BrevityType == NIST ? "NIST_BLEU" : "QCRI_BLEU"))) {} unsigned SufficientStatisticsVectorSize() const { return N*2 + 2; } boost::shared_ptr CreateSegmentEvaluator(const vector >& refs) const { return boost::shared_ptr(new BleuSegmentEvaluator(refs, this)); } float ComputeBreakdown(const SufficientStats& stats, float* bp, vector* out) const { if (out) { out->clear(); } float log_bleu = 0; float log_bleu_adj = 0; // for QCRI int count = 0; float alpha = BrevityType == QCRI ? 1 : 0.01; for (int i = 0; i < N; ++i) { if (stats.fields[i+N] > 0) { float cor_count = stats.fields[i]; // correct_ngram_hit_counts[i]; // smooth bleu if (!cor_count) { cor_count = alpha; } float lprec = log(cor_count) - log(stats.fields[i+N]); // log(hyp_ngram_counts[i]); if (out) out->push_back(exp(lprec)); log_bleu += lprec; if (BrevityType == QCRI) log_bleu_adj += log(alpha) - log(stats.fields[i+N] + alpha); ++count; } } log_bleu /= count; log_bleu_adj /= count; float lbp = 0.0; const float& hyp_len = stats.fields[2*N]; const float& ref_len = stats.fields[2*N + 1]; if (hyp_len < ref_len) { if (BrevityType == QCRI) lbp = (hyp_len - ref_len - alpha) / hyp_len; else lbp = (hyp_len - ref_len) / hyp_len; } log_bleu += lbp; if (bp) *bp = exp(lbp); if (BrevityType == QCRI) return exp(log_bleu) - exp(lbp + log_bleu_adj); return exp(log_bleu); } string DetailedScore(const SufficientStats& stats) const { char buf[2000]; vector precs(N); float bp; float bleu = ComputeBreakdown(stats, &bp, &precs); sprintf(buf, "%s = %.2f, %.1f|%.1f|%.1f|%.1f (brev=%.3f)", MetricId().c_str(), bleu*100.0, precs[0]*100.0, precs[1]*100.0, precs[2]*100.0, precs[3]*100.0, bp); return buf; } float ComputeScore(const SufficientStats& stats) const { return ComputeBreakdown(stats, NULL, NULL); } }; EvaluationMetric* EvaluationMetric::Instance(const string& imetric_id) { static bool is_first = true; if (is_first) { instances_["NULL"] = NULL; is_first = false; } const string metric_id = UppercaseString(imetric_id); map::iterator it = instances_.find(metric_id); if (it == instances_.end()) { EvaluationMetric* m = NULL; if (metric_id == "IBM_BLEU") { m = new BleuMetric<4, IBM>; } else if (metric_id == "NIST_BLEU") { m = new BleuMetric<4, NIST>; } else if (metric_id == "KOEHN_BLEU") { m = new BleuMetric<4, Koehn>; } else if (metric_id == "QCRI_BLEU") { m = new BleuMetric<4, QCRI>; } else if (metric_id == "SSK") { m = new SSKMetric; } else if (metric_id == "TER") { m = new TERMetric; } else if (metric_id == "METEOR") { #ifdef METEOR_JAR if (!FileExists(meteor_jar_path)) { cerr << meteor_jar_path << " not found!\n"; abort(); } m = new ExternalMetric("METEOR", string("java -Xmx1536m -jar ") + meteor_jar_path + " - - -mira -lower -t tune -l en"); #else cerr << "cdec was not built with the -DMETEOR_JAR=/path/to/meteor.jar option." << endl; abort(); #endif } else if (metric_id.find("COMB:") == 0) { m = new CombinationMetric(metric_id); } else if (metric_id == "CER") { m = new CERMetric; } else if (metric_id == "WER") { m = new WERMetric; } else if (metric_id == "CBLEU") { return new BleuMetric<5, IBM, true>; } else { cerr << "Implement please: " << metric_id << endl; abort(); } if (m->MetricId() != metric_id) { cerr << "Registry error: " << metric_id << " vs. " << m->MetricId() << endl; abort(); } return instances_[metric_id] = m; } else { return it->second; } } SufficientStats::SufficientStats(const string& encoded) { istringstream is(encoded); is >> id_; float val; while(is >> val) fields.push_back(val); } void SufficientStats::Encode(string* out) const { ostringstream os; if (id_.size() > 0) os << id_; else os << "NULL"; for (unsigned i = 0; i < fields.size(); ++i) os << ' ' << fields[i]; *out = os.str(); }