diff options
| author | redpony <redpony@ec762483-ff6d-05da-a07a-a48fb63a330f> | 2010-08-11 02:37:10 +0000 | 
|---|---|---|
| committer | redpony <redpony@ec762483-ff6d-05da-a07a-a48fb63a330f> | 2010-08-11 02:37:10 +0000 | 
| commit | 80686d4e567bae579ea39e009826a2de92cd4ace (patch) | |
| tree | c3c35fcba57dde423a248f38aa121ad197c79734 /mteval | |
| parent | 3c85c407c333899f6b4bc26632d312b8e568b638 (diff) | |
major refactor, break bad circular deps
git-svn-id: https://ws10smt.googlecode.com/svn/trunk@509 ec762483-ff6d-05da-a07a-a48fb63a330f
Diffstat (limited to 'mteval')
| -rw-r--r-- | mteval/Makefile.am | 23 | ||||
| -rw-r--r-- | mteval/aer_scorer.cc | 135 | ||||
| -rw-r--r-- | mteval/aer_scorer.h | 23 | ||||
| -rw-r--r-- | mteval/comb_scorer.cc | 97 | ||||
| -rw-r--r-- | mteval/comb_scorer.h | 17 | ||||
| -rw-r--r-- | mteval/fast_score.cc | 72 | ||||
| -rw-r--r-- | mteval/mbr_kbest.cc | 138 | ||||
| -rw-r--r-- | mteval/scorer.cc | 630 | ||||
| -rw-r--r-- | mteval/scorer.h | 110 | ||||
| -rw-r--r-- | mteval/scorer_test.cc | 182 | ||||
| -rw-r--r-- | mteval/ter.cc | 535 | ||||
| -rw-r--r-- | mteval/ter.h | 19 | ||||
| -rw-r--r-- | mteval/test_data/re.txt.0 | 5 | ||||
| -rw-r--r-- | mteval/test_data/re.txt.1 | 5 | ||||
| -rw-r--r-- | mteval/test_data/re.txt.2 | 5 | ||||
| -rw-r--r-- | mteval/test_data/re.txt.3 | 5 | 
16 files changed, 2001 insertions, 0 deletions
| diff --git a/mteval/Makefile.am b/mteval/Makefile.am new file mode 100644 index 00000000..7ae14045 --- /dev/null +++ b/mteval/Makefile.am @@ -0,0 +1,23 @@ +bin_PROGRAMS = \ +  fast_score \ +  mbr_kbest + +if HAVE_GTEST +noinst_PROGRAMS = \ +  scorer_test +endif + +noinst_LIBRARIES = libmteval.a + +libmteval_a_SOURCES = ter.cc comb_scorer.cc aer_scorer.cc scorer.cc + +fast_score_SOURCES = fast_score.cc +fast_score_LDADD = $(top_srcdir)/utils/libutils.a libmteval.a -lz + +mbr_kbest_SOURCES = mbr_kbest.cc +mbr_kbest_LDADD = $(top_srcdir)/utils/libutils.a libmteval.a -lz + +scorer_test_SOURCES = scorer_test.cc +scorer_test_LDADD = $(GTEST_LDFLAGS) $(GTEST_LIBS) $(top_srcdir)/utils/libutils.a libmteval.a -lz + +AM_CPPFLAGS = -W -Wall -Wno-sign-compare $(GTEST_CPPFLAGS) -I$(top_srcdir)/utils diff --git a/mteval/aer_scorer.cc b/mteval/aer_scorer.cc new file mode 100644 index 00000000..edd4390f --- /dev/null +++ b/mteval/aer_scorer.cc @@ -0,0 +1,135 @@ +#include "aer_scorer.h" + +#include <cmath> +#include <cassert> +#include <sstream> + +#include "tdict.h" +#include "alignment_pharaoh.h" + +using namespace std; + +class AERScore : public ScoreBase<AERScore> { +  friend class AERScorer; + public: +  AERScore() : num_matches(), num_predicted(), num_in_ref() {} +  AERScore(int m, int p, int r) : +    num_matches(m), num_predicted(p), num_in_ref(r) {} +  virtual void PlusPartialEquals(const Score& rhs, int oracle_e_cover, int oracle_f_cover, int src_len){} +  virtual void PlusEquals(const Score& delta, const float scale) { +    const AERScore& other = static_cast<const AERScore&>(delta); +    num_matches   += scale*other.num_matches; +    num_predicted += scale*other.num_predicted; +    num_in_ref    += scale*other.num_in_ref; +  } + virtual void PlusEquals(const Score& delta) { +    const AERScore& other = static_cast<const AERScore&>(delta); +    num_matches   += other.num_matches; +    num_predicted += other.num_predicted; +    num_in_ref    += other.num_in_ref; +  } + + +  virtual ScoreP GetZero() const { +    return ScoreP(new AERScore); +  } +  virtual ScoreP GetOne() const { +    return ScoreP(new AERScore); +  } +  virtual void Subtract(const Score& rhs, Score* out) const { +    AERScore* res = static_cast<AERScore*>(out); +    const AERScore& other = static_cast<const AERScore&>(rhs); +    res->num_matches   = num_matches   - other.num_matches; +    res->num_predicted = num_predicted - other.num_predicted; +    res->num_in_ref    = num_in_ref    - other.num_in_ref; +  } +  float Precision() const { +    return static_cast<float>(num_matches) / num_predicted; +  } +  float Recall() const { +    return static_cast<float>(num_matches) / num_in_ref; +  } +  float ComputePartialScore() const { return 0.0;} +  virtual float ComputeScore() const { +    const float prec = Precision(); +    const float rec = Recall(); +    const float f = (2.0 * prec * rec) / (rec + prec); +    if (isnan(f)) return 1.0f; +    return 1.0f - f; +  } +  virtual bool IsAdditiveIdentity() const { +    return (num_matches == 0) && (num_predicted == 0) && (num_in_ref == 0); +  } +  virtual void ScoreDetails(std::string* out) const { +    ostringstream os; +    os << "AER=" << (ComputeScore() * 100.0) +       << " F=" << (100 - ComputeScore() * 100.0) +       << " P=" << (Precision() * 100.0) << " R=" << (Recall() * 100.0) +       << " [" << num_matches << " " << num_predicted << " " << num_in_ref << "]"; +    *out = os.str(); +  } +  virtual void Encode(std::string*out) const { +    out->resize(sizeof(int) * 3); +    *(int *)&(*out)[sizeof(int) * 0] = num_matches; +    *(int *)&(*out)[sizeof(int) * 1] = num_predicted; +    *(int *)&(*out)[sizeof(int) * 2] = num_in_ref; +  } + private: +  int num_matches; +  int num_predicted; +  int num_in_ref; +}; + +AERScorer::AERScorer(const vector<vector<WordID> >& refs, const string& src) : src_(src) { +  if (refs.size() != 1) { +    cerr << "AERScorer can only take a single reference!\n"; +    abort(); +  } +  ref_ = AlignmentPharaoh::ReadPharaohAlignmentGrid(TD::GetString(refs.front())); +} + +static inline bool Safe(const Array2D<bool>& a, int i, int j) { +  if (i >= 0 && j >= 0 && i < a.width() && j < a.height()) +    return a(i,j); +  else +    return false; +} + +ScoreP AERScorer::ScoreCCandidate(const vector<WordID>& shyp) const { +  return ScoreP(); +} + +ScoreP AERScorer::ScoreCandidate(const vector<WordID>& shyp) const { +  boost::shared_ptr<Array2D<bool> > hyp = +    AlignmentPharaoh::ReadPharaohAlignmentGrid(TD::GetString(shyp)); + +  int m = 0; +  int r = 0; +  int p = 0; +  int i_len = ref_->width(); +  int j_len = ref_->height(); +  for (int i = 0; i < i_len; ++i) { +    for (int j = 0; j < j_len; ++j) { +      if ((*ref_)(i,j)) { +        ++r; +        if (Safe(*hyp, i, j)) ++m; +      } +    } +  } +  for (int i = 0; i < hyp->width(); ++i) +    for (int j = 0; j < hyp->height(); ++j) +      if ((*hyp)(i,j)) ++p; + +  return ScoreP(new AERScore(m,p,r)); +} + +ScoreP AERScorer::ScoreFromString(const string& in) { +  AERScore* res = new AERScore; +  res->num_matches   = *(const int *)&in[sizeof(int) * 0]; +  res->num_predicted = *(const int *)&in[sizeof(int) * 1]; +  res->num_in_ref    = *(const int *)&in[sizeof(int) * 2]; +  return ScoreP(res); +} + +const std::string* AERScorer::GetSource() const { return &src_; } + diff --git a/mteval/aer_scorer.h b/mteval/aer_scorer.h new file mode 100644 index 00000000..6d53d359 --- /dev/null +++ b/mteval/aer_scorer.h @@ -0,0 +1,23 @@ +#ifndef _AER_SCORER_ +#define _AER_SCORER_ + +#include <boost/shared_ptr.hpp> + +#include "scorer.h" +#include "array2d.h" + +class AERScorer : public SentenceScorer { + public: +  // when constructing alignment strings from a hypergraph, the source +  // is necessary. +  AERScorer(const std::vector<std::vector<WordID> >& refs, const std::string& src = ""); +  ScoreP ScoreCandidate(const std::vector<WordID>& hyp) const; +  ScoreP ScoreCCandidate(const std::vector<WordID>& hyp) const; +  static ScoreP ScoreFromString(const std::string& in); +  const std::string* GetSource() const; + private: +  std::string src_; +  boost::shared_ptr<Array2D<bool> > ref_; +}; + +#endif diff --git a/mteval/comb_scorer.cc b/mteval/comb_scorer.cc new file mode 100644 index 00000000..9fc37868 --- /dev/null +++ b/mteval/comb_scorer.cc @@ -0,0 +1,97 @@ +#include "comb_scorer.h" + +#include <cstdio> + +using namespace std; + +class BLEUTERCombinationScore : public ScoreBase<BLEUTERCombinationScore> { +  friend class BLEUTERCombinationScorer; + public: +  ~BLEUTERCombinationScore(); +  float ComputePartialScore() const { return 0.0;} +  float ComputeScore() const { +    return (bleu->ComputeScore() - ter->ComputeScore()) / 2.0f; +  } +  void ScoreDetails(string* details) const { +    char buf[160]; +    sprintf(buf, "Combi = %.2f, BLEU = %.2f, TER = %.2f", +      ComputeScore()*100.0f, bleu->ComputeScore()*100.0f, ter->ComputeScore()*100.0f); +    *details = buf; +  } +  void PlusPartialEquals(const Score& rhs, int oracle_e_cover, int oracle_f_cover, int src_len){} + +  void PlusEquals(const Score& delta, const float scale) { +    bleu->PlusEquals(*static_cast<const BLEUTERCombinationScore&>(delta).bleu, scale); +    ter->PlusEquals(*static_cast<const BLEUTERCombinationScore&>(delta).ter, scale); +  } +  void PlusEquals(const Score& delta) { +    bleu->PlusEquals(*static_cast<const BLEUTERCombinationScore&>(delta).bleu); +    ter->PlusEquals(*static_cast<const BLEUTERCombinationScore&>(delta).ter); +  } + + + +  ScoreP GetOne() const { +    BLEUTERCombinationScore* res = new BLEUTERCombinationScore; +    res->bleu = bleu->GetOne(); +    res->ter = ter->GetOne(); +    return ScoreP(res); +  } +  ScoreP GetZero() const { +    BLEUTERCombinationScore* res = new BLEUTERCombinationScore; +    res->bleu = bleu->GetZero(); +    res->ter = ter->GetZero(); +    return ScoreP(res); +  } +  void Subtract(const Score& rhs, Score* res) const { +    bleu->Subtract(*static_cast<const BLEUTERCombinationScore&>(rhs).bleu, +                   static_cast<BLEUTERCombinationScore*>(res)->bleu.get()); +    ter->Subtract(*static_cast<const BLEUTERCombinationScore&>(rhs).ter, +                  static_cast<BLEUTERCombinationScore*>(res)->ter.get()); +  } +  void Encode(std::string* out) const { +    string bs, ts; +    bleu->Encode(&bs); +    ter->Encode(&ts); +    out->clear(); +    (*out) += static_cast<char>(bs.size()); +    (*out) += bs; +    (*out) += ts; +  } +  bool IsAdditiveIdentity() const { +    return bleu->IsAdditiveIdentity() && ter->IsAdditiveIdentity(); +  } + private: +  ScoreP bleu; +  ScoreP ter; +}; + +BLEUTERCombinationScore::~BLEUTERCombinationScore() { +} + +BLEUTERCombinationScorer::BLEUTERCombinationScorer(const vector<vector<WordID> >& refs) { +  bleu_ = SentenceScorer::CreateSentenceScorer(IBM_BLEU, refs); +  ter_ = SentenceScorer::CreateSentenceScorer(TER, refs); +} + +BLEUTERCombinationScorer::~BLEUTERCombinationScorer() { +} + +ScoreP BLEUTERCombinationScorer::ScoreCCandidate(const vector<WordID>& hyp) const { +  return ScoreP(); +} + +ScoreP BLEUTERCombinationScorer::ScoreCandidate(const std::vector<WordID>& hyp) const { +  BLEUTERCombinationScore* res = new BLEUTERCombinationScore; +  res->bleu = bleu_->ScoreCandidate(hyp); +  res->ter = ter_->ScoreCandidate(hyp); +  return ScoreP(res); +} + +ScoreP BLEUTERCombinationScorer::ScoreFromString(const std::string& in) { +  int bss = in[0]; +  BLEUTERCombinationScore* r = new BLEUTERCombinationScore; +  r->bleu = SentenceScorer::CreateScoreFromString(IBM_BLEU, in.substr(1, bss)); +  r->ter = SentenceScorer::CreateScoreFromString(TER, in.substr(1 + bss)); +  return ScoreP(r); +} diff --git a/mteval/comb_scorer.h b/mteval/comb_scorer.h new file mode 100644 index 00000000..346be576 --- /dev/null +++ b/mteval/comb_scorer.h @@ -0,0 +1,17 @@ +#ifndef _COMB_SCORER_ +#define _COMB_SCORER_ + +#include "scorer.h" + +class BLEUTERCombinationScorer : public SentenceScorer { + public: +  BLEUTERCombinationScorer(const std::vector<std::vector<WordID> >& refs); +  ~BLEUTERCombinationScorer(); +  ScoreP ScoreCandidate(const std::vector<WordID>& hyp) const; +  ScoreP ScoreCCandidate(const std::vector<WordID>& hyp) const; +  static ScoreP ScoreFromString(const std::string& in); + private: +  ScorerP bleu_,ter_; +}; + +#endif diff --git a/mteval/fast_score.cc b/mteval/fast_score.cc new file mode 100644 index 00000000..5ee264a6 --- /dev/null +++ b/mteval/fast_score.cc @@ -0,0 +1,72 @@ +#include <iostream> +#include <vector> + +#include <boost/program_options.hpp> +#include <boost/program_options/variables_map.hpp> + +#include "filelib.h" +#include "tdict.h" +#include "scorer.h" + +using namespace std; +namespace po = boost::program_options; + +void InitCommandLine(int argc, char** argv, po::variables_map* conf) { +  po::options_description opts("Configuration options"); +  opts.add_options() +        ("reference,r",po::value<vector<string> >(), "[REQD] Reference translation(s) (tokenized text file)") +        ("loss_function,l",po::value<string>()->default_value("ibm_bleu"), "Scoring metric (ibm_bleu, nist_bleu, koehn_bleu, ter, combi)") +        ("in_file,i", po::value<string>()->default_value("-"), "Input file") +        ("help,h", "Help"); +  po::options_description dcmdline_options; +  dcmdline_options.add(opts); +  po::store(parse_command_line(argc, argv, dcmdline_options), *conf); +  bool flag = false; +  if (!conf->count("reference")) { +    cerr << "Please specify one or more references using -r <REF1.TXT> -r <REF2.TXT> ...\n"; +    flag = true; +  } +  if (flag || conf->count("help")) { +    cerr << dcmdline_options << endl; +    exit(1); +  } +} + +int main(int argc, char** argv) { +  po::variables_map conf; +  InitCommandLine(argc, argv, &conf); +  const string loss_function = conf["loss_function"].as<string>(); +  ScoreType type = ScoreTypeFromString(loss_function); +  DocScorer ds(type, conf["reference"].as<vector<string> >(), ""); +  cerr << "Loaded " << ds.size() << " references for scoring with " << loss_function << endl; + +  ReadFile rf(conf["in_file"].as<string>()); +  ScoreP acc; +  istream& in = *rf.stream(); +  int lc = 0; +  while(in) { +    string line; +    getline(in, line); +    if (line.empty() && !in) break; +    vector<WordID> sent; +    TD::ConvertSentence(line, &sent); +    ScoreP sentscore = ds[lc]->ScoreCandidate(sent); +    if (!acc) { acc = sentscore->GetZero(); } +    acc->PlusEquals(*sentscore); +    ++lc; +  } +  assert(lc > 0); +  if (lc > ds.size()) { +    cerr << "Too many (" << lc << ") translations in input, expected " << ds.size() << endl; +    return 1; +  } +  if (lc != ds.size()) +    cerr << "Fewer sentences in hyp (" << lc << ") than refs (" +         << ds.size() << "): scoring partial set!\n"; +  float score = acc->ComputeScore(); +  string details; +  acc->ScoreDetails(&details); +  cerr << details << endl; +  cout << score << endl; +  return 0; +} diff --git a/mteval/mbr_kbest.cc b/mteval/mbr_kbest.cc new file mode 100644 index 00000000..2867b36b --- /dev/null +++ b/mteval/mbr_kbest.cc @@ -0,0 +1,138 @@ +#include <iostream> +#include <vector> + +#include <boost/program_options.hpp> + +#include "prob.h" +#include "tdict.h" +#include "scorer.h" +#include "filelib.h" +#include "stringlib.h" + +using namespace std; + +namespace po = boost::program_options; + +void InitCommandLine(int argc, char** argv, po::variables_map* conf) { +  po::options_description opts("Configuration options"); +  opts.add_options() +        ("scale,a",po::value<double>()->default_value(1.0), "Posterior scaling factor (alpha)") +        ("loss_function,l",po::value<string>()->default_value("bleu"), "Loss function") +        ("input,i",po::value<string>()->default_value("-"), "File to read k-best lists from") +        ("output_list,L", "Show reranked list as output") +        ("help,h", "Help"); +  po::options_description dcmdline_options; +  dcmdline_options.add(opts); +  po::store(parse_command_line(argc, argv, dcmdline_options), *conf); +  bool flag = false; +  if (flag || conf->count("help")) { +    cerr << dcmdline_options << endl; +    exit(1); +  } +} + +struct LossComparer { +  bool operator()(const pair<vector<WordID>, double>& a, const pair<vector<WordID>, double>& b) const { +    return a.second < b.second; +  } +}; + +bool ReadKBestList(istream* in, string* sent_id, vector<pair<vector<WordID>, prob_t> >* list) { +  static string cache_id; +  static pair<vector<WordID>, prob_t> cache_pair; +  list->clear(); +  string cur_id; +  if (cache_pair.first.size() > 0) { +    list->push_back(cache_pair); +    cur_id = cache_id; +    cache_pair.first.clear(); +  } +  string line; +  string tstr; +  while(*in) { +    getline(*in, line); +    if (line.empty()) continue; +    size_t p1 = line.find(" ||| "); +    if (p1 == string::npos) { cerr << "Bad format: " << line << endl; abort(); } +    size_t p2 = line.find(" ||| ", p1 + 4); +    if (p2 == string::npos) { cerr << "Bad format: " << line << endl; abort(); } +    size_t p3 = line.rfind(" ||| "); +    cache_id = line.substr(0, p1); +    tstr = line.substr(p1 + 5, p2 - p1 - 5); +    double val = strtod(line.substr(p3 + 5).c_str(), NULL); +    TD::ConvertSentence(tstr, &cache_pair.first); +    cache_pair.second.logeq(val); +    if (cur_id.empty()) cur_id = cache_id; +    if (cur_id == cache_id) { +      list->push_back(cache_pair); +      *sent_id = cur_id; +      cache_pair.first.clear(); +    } else { break; } +  } +  return !list->empty(); +} + +int main(int argc, char** argv) { +  po::variables_map conf; +  InitCommandLine(argc, argv, &conf); +  const string metric = conf["loss_function"].as<string>(); +  const bool output_list = conf.count("output_list") > 0; +  const string file = conf["input"].as<string>(); +  const double mbr_scale = conf["scale"].as<double>(); +  cerr << "Posterior scaling factor (alpha) = " << mbr_scale << endl; + +  ScoreType type = ScoreTypeFromString(metric); +  vector<pair<vector<WordID>, prob_t> > list; +  ReadFile rf(file); +  string sent_id; +  while(ReadKBestList(rf.stream(), &sent_id, &list)) { +    vector<prob_t> joints(list.size()); +    const prob_t max_score = pow(list.front().second, mbr_scale); +    prob_t marginal = prob_t::Zero(); +    for (int i = 0 ; i < list.size(); ++i) { +      const prob_t joint = pow(list[i].second, mbr_scale) / max_score; +      joints[i] = joint; +      // cerr << "list[" << i << "] joint=" << log(joint) << endl; +      marginal += joint; +    } +    int mbr_idx = -1; +    vector<double> mbr_scores(output_list ? list.size() : 0); +    double mbr_loss = numeric_limits<double>::max(); +    for (int i = 0 ; i < list.size(); ++i) { +      vector<vector<WordID> > refs(1, list[i].first); +      //cerr << i << ": " << list[i].second <<"\t" << TD::GetString(list[i].first) << endl; +      ScorerP scorer = SentenceScorer::CreateSentenceScorer(type, refs); +      double wl_acc = 0; +      for (int j = 0; j < list.size(); ++j) { +        if (i != j) { +          ScoreP s = scorer->ScoreCandidate(list[j].first); +          double loss = 1.0 - s->ComputeScore(); +          if (type == TER || type == AER) loss = 1.0 - loss; +          double weighted_loss = loss * (joints[j] / marginal); +          wl_acc += weighted_loss; +          if ((!output_list) && wl_acc > mbr_loss) break; +        } +      } +      if (output_list) mbr_scores[i] = wl_acc; +      if (wl_acc < mbr_loss) { +        mbr_loss = wl_acc; +        mbr_idx = i; +      } +    } +    // cerr << "ML translation: " << TD::GetString(list[0].first) << endl; +    cerr << "MBR Best idx: " << mbr_idx << endl; +    if (output_list) { +      for (int i = 0; i < list.size(); ++i) +        list[i].second.logeq(mbr_scores[i]); +      sort(list.begin(), list.end(), LossComparer()); +      for (int i = 0; i < list.size(); ++i) +        cout << sent_id << " ||| " +             << TD::GetString(list[i].first) << " ||| " +             << log(list[i].second) << endl; +    } else { +      cout << TD::GetString(list[mbr_idx].first) << endl; +    } +  } +  return 0; +} + diff --git a/mteval/scorer.cc b/mteval/scorer.cc new file mode 100644 index 00000000..04eeaa93 --- /dev/null +++ b/mteval/scorer.cc @@ -0,0 +1,630 @@ +#include "scorer.h" + +#include <boost/lexical_cast.hpp> +#include <map> +#include <sstream> +#include <iostream> +#include <fstream> +#include <cstdio> +#include <valarray> +#include <algorithm> + +#include <boost/shared_ptr.hpp> + +#include "filelib.h" +#include "ter.h" +#include "aer_scorer.h" +#include "comb_scorer.h" +#include "tdict.h" +#include "stringlib.h" + +using boost::shared_ptr; +using namespace std; + +void Score::TimesEquals(float scale) { +  cerr<<"UNIMPLEMENTED except for BLEU (for MIRA): Score::TimesEquals"<<endl;abort(); +} + +ScoreType ScoreTypeFromString(const string& st) { +  const string sl = LowercaseString(st); +  if (sl == "ser") +    return SER; +  if (sl == "ter") +    return TER; +  if (sl == "aer") +    return AER; +  if (sl == "bleu" || sl == "ibm_bleu") +    return IBM_BLEU; +  if (sl == "ibm_bleu_3") +    return IBM_BLEU_3; +  if (sl == "nist_bleu") +    return NIST_BLEU; +  if (sl == "koehn_bleu") +    return Koehn_BLEU; +  if (sl == "combi") +    return BLEU_minus_TER_over_2; +  cerr << "Don't understand score type '" << st << "', defaulting to ibm_bleu.\n"; +  return IBM_BLEU; +} + +static char const* score_names[]={ +  "IBM_BLEU", "NIST_BLEU", "Koehn_BLEU", "TER", "BLEU_minus_TER_over_2", "SER", "AER", "IBM_BLEU_3" +}; + +std::string StringFromScoreType(ScoreType st) { +  assert(st>=0 && st<sizeof(score_names)/sizeof(score_names[0])); +  return score_names[(int)st]; +} + + +Score::~Score() {} +SentenceScorer::~SentenceScorer() {} + +struct length_accum { +  template <class S> +  float operator()(float sum,S const& ref) const { +    return sum+ref.size(); +  } +}; + +template <class S> +float avg_reflength(vector<S> refs) { +  unsigned n=refs.size(); +  return n?accumulate(refs.begin(),refs.end(),0.,length_accum())/n:0.; +} + + +float SentenceScorer::ComputeRefLength(const Sentence &hyp) const { +  return hyp.size(); // reasonable default? :) +} + +const std::string* SentenceScorer::GetSource() const { return NULL; } + +class SERScore : public ScoreBase<SERScore> { +  friend class SERScorer; + public: +  SERScore() : correct(0), total(0) {} +  float ComputePartialScore() const { return 0.0;} +  float ComputeScore() const { +    return static_cast<float>(correct) / static_cast<float>(total); +  } +  void ScoreDetails(string* details) const { +    ostringstream os; +    os << "SER= " << ComputeScore() << " (" << correct << '/' << total << ')'; +    *details = os.str(); +  } +  void PlusPartialEquals(const Score& /* delta */, int /* oracle_e_cover */, int /* oracle_f_cover */, int /* src_len */){} + +  void PlusEquals(const Score& delta, const float scale) { +    correct += scale*static_cast<const SERScore&>(delta).correct; +    total += scale*static_cast<const SERScore&>(delta).total; +  } +  void PlusEquals(const Score& delta) { +    correct += static_cast<const SERScore&>(delta).correct; +    total += static_cast<const SERScore&>(delta).total; +    } +  ScoreP GetZero() const { return ScoreP(new SERScore); } +  ScoreP GetOne() const { return ScoreP(new SERScore); } +  void Subtract(const Score& rhs, Score* res) const { +    SERScore* r = static_cast<SERScore*>(res); +    r->correct = correct - static_cast<const SERScore&>(rhs).correct; +    r->total = total - static_cast<const SERScore&>(rhs).total; +  } +  void Encode(string* out) const { +    assert(!"not implemented"); +  } +  bool IsAdditiveIdentity() const { +    return (total == 0 && correct == 0);  // correct is always 0 <= n <= total +  } + private: +  int correct, total; +}; + +std::string SentenceScorer::verbose_desc() const { +  return desc+",ref0={ "+TD::GetString(refs[0])+" }"; +} + +class SERScorer : public SentenceScorer { + public: +  SERScorer(const vector<vector<WordID> >& references) : SentenceScorer("SERScorer",references),refs_(references) {} +  ScoreP ScoreCCandidate(const vector<WordID>& /* hyp */) const { +    return ScoreP(); +  } +  ScoreP ScoreCandidate(const vector<WordID>& hyp) const { +    SERScore* res = new SERScore; +    res->total = 1; +    for (int i = 0; i < refs_.size(); ++i) +      if (refs_[i] == hyp) res->correct = 1; +    return ScoreP(res); +  } +  static ScoreP ScoreFromString(const string& data) { +    assert(!"Not implemented"); +  } + private: +  vector<vector<WordID> > refs_; +}; + +class BLEUScore : public ScoreBase<BLEUScore> { +  friend class BLEUScorerBase; + public: +  BLEUScore(int n) : correct_ngram_hit_counts(float(0),n), hyp_ngram_counts(float(0),n) { +    ref_len = 0; +    hyp_len = 0; } +  BLEUScore(int n, int k) :  correct_ngram_hit_counts(float(k),n), hyp_ngram_counts(float(k),n) { +    ref_len = k; +    hyp_len = k; } +  float ComputeScore() const; +  float ComputePartialScore() const; +  void ScoreDetails(string* details) const; +  void TimesEquals(float scale); +  void PlusEquals(const Score& delta); +  void PlusEquals(const Score& delta, const float scale); +  void PlusPartialEquals(const Score& delta, int oracle_e_cover, int oracle_f_cover, int src_len); +  ScoreP GetZero() const; +  ScoreP GetOne() const; +  void Subtract(const Score& rhs, Score* res) const; +  void Encode(string* out) const; +  bool IsAdditiveIdentity() const { +    if (fabs(ref_len) > 0.1f || hyp_len != 0) return false; +    for (int i = 0; i < correct_ngram_hit_counts.size(); ++i) +      if (hyp_ngram_counts[i] != 0 || +        correct_ngram_hit_counts[i] != 0) return false; +    return true; +  } + private: +  int N() const { +    return hyp_ngram_counts.size(); +  } +  float ComputeScore(vector<float>* precs, float* bp) const; +  float ComputePartialScore(vector<float>* prec, float* bp) const; +  valarray<float> correct_ngram_hit_counts; +  valarray<float> hyp_ngram_counts; +  float ref_len; +  float hyp_len; +}; + +class BLEUScorerBase : public SentenceScorer { + public: +  BLEUScorerBase(const vector<vector<WordID> >& references, +                 int n +             ); +  ScoreP ScoreCandidate(const vector<WordID>& hyp) const; +  ScoreP ScoreCCandidate(const vector<WordID>& hyp) const; +  static ScoreP ScoreFromString(const string& in); + +  virtual float ComputeRefLength(const vector<WordID>& hyp) const = 0; + private: +  struct NGramCompare { +    int operator() (const vector<WordID>& a, const vector<WordID>& b) { +      size_t as = a.size(); +      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<vector<WordID>, pair<int,int>, NGramCompare> NGramCountMap; +  void CountRef(const vector<WordID>& ref) { +    NGramCountMap tc; +    vector<WordID> ngram(n_); +    int s = ref.size(); +    for (int j=0; j<s; ++j) { +      int remaining = s-j; +      int k = (n_ < remaining ? n_ : remaining); +      ngram.clear(); +      for (int i=1; i<=k; ++i) { +	ngram.push_back(ref[j + i - 1]); +        tc[ngram].first++; +      } +    } +    for (NGramCountMap::iterator i = tc.begin(); i != tc.end(); ++i) { +      pair<int,int>& p = ngrams_[i->first]; +      if (p.first < i->second.first) +        p = i->second; +    } +  } + +  void ComputeNgramStats(const vector<WordID>& sent, +			 valarray<float>* correct, +			 valarray<float>* hyp, +			 bool clip_counts) +    const { +    assert(correct->size() == n_); +    assert(hyp->size() == n_); +    vector<WordID> ngram(n_); +    (*correct) *= 0; +    (*hyp) *= 0; +    int s = sent.size(); +    for (int j=0; j<s; ++j) { +      int remaining = s-j; +      int k = (n_ < remaining ? n_ : remaining); +      ngram.clear(); +      for (int i=1; i<=k; ++i) { +	ngram.push_back(sent[j + i - 1]); +        pair<int,int>& 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]++; +        } +      } +    } +  } + +  mutable NGramCountMap ngrams_; +  int n_; +  vector<int> lengths_; +}; + +ScoreP BLEUScorerBase::ScoreFromString(const string& in) { +  istringstream is(in); +  int n; +  is >> n; +  BLEUScore* r = new BLEUScore(n); +  is >> r->ref_len >> r->hyp_len; + +  for (int i = 0; i < n; ++i) { +    is >> r->correct_ngram_hit_counts[i]; +    is >> r->hyp_ngram_counts[i]; +  } +  return ScoreP(r); +} + +class IBM_BLEUScorer : public BLEUScorerBase { + public: +    IBM_BLEUScorer(const vector<vector<WordID> >& references, +		   int n=4) : BLEUScorerBase(references, n), lengths_(references.size()) { +   for (int i=0; i < references.size(); ++i) +     lengths_[i] = references[i].size(); + } +  float ComputeRefLength(const vector<WordID>& hyp) const { +    if (lengths_.size() == 1) return lengths_[0]; +    int bestd = 2000000; +    int hl = hyp.size(); +    int bl = -1; +    for (vector<int>::const_iterator ci = lengths_.begin(); ci != lengths_.end(); ++ci) { +      int cl = *ci; +      if (abs(cl - hl) < bestd) { +        bestd = abs(cl - hl); +        bl = cl; +      } +    } +    return bl; +  } + private: +  vector<int> lengths_; +}; + +class NIST_BLEUScorer : public BLEUScorerBase { + public: +    NIST_BLEUScorer(const vector<vector<WordID> >& references, +                    int n=4) : BLEUScorerBase(references, n), +		    shortest_(references[0].size()) { +   for (int i=1; i < references.size(); ++i) +     if (references[i].size() < shortest_) +       shortest_ = references[i].size(); + } +  float ComputeRefLength(const vector<WordID>& /* hyp */) const { +    return shortest_; +  } + private: +  float shortest_; +}; + +class Koehn_BLEUScorer : public BLEUScorerBase { + public: +    Koehn_BLEUScorer(const vector<vector<WordID> >& references, +                     int n=4) : BLEUScorerBase(references, n), +                     avg_(0) { +   for (int i=0; i < references.size(); ++i) +     avg_ += references[i].size(); +   avg_ /= references.size(); + } +  float ComputeRefLength(const vector<WordID>& /* hyp */) const { +    return avg_; +  } + private: +  float avg_; +}; + +ScorerP SentenceScorer::CreateSentenceScorer(const ScoreType type, +      const vector<vector<WordID> >& refs, +      const string& src) +{ +  SentenceScorer *r=0; +  switch (type) { +  case IBM_BLEU: r = new IBM_BLEUScorer(refs, 4);break; +  case IBM_BLEU_3 : r = new IBM_BLEUScorer(refs,3);break; +    case NIST_BLEU: r = new NIST_BLEUScorer(refs, 4);break; +    case Koehn_BLEU: r = new Koehn_BLEUScorer(refs, 4);break; +    case AER: r = new AERScorer(refs, src);break; +    case TER: r = new TERScorer(refs);break; +    case SER: r = new SERScorer(refs);break; +    case BLEU_minus_TER_over_2: r = new BLEUTERCombinationScorer(refs);break; +    default: +      assert(!"Not implemented!"); +  } +  return ScorerP(r); +} + +ScoreP SentenceScorer::GetOne() const { +  Sentence s; +  return ScoreCCandidate(s)->GetOne(); +} + +ScoreP SentenceScorer::GetZero() const { +  Sentence s; +  return ScoreCCandidate(s)->GetZero(); +} + +ScoreP Score::GetOne(ScoreType type) { +  std::vector<SentenceScorer::Sentence > refs; +  return SentenceScorer::CreateSentenceScorer(type,refs)->GetOne(); +} + +ScoreP Score::GetZero(ScoreType type) { +  std::vector<SentenceScorer::Sentence > refs; +  return SentenceScorer::CreateSentenceScorer(type,refs)->GetZero(); +} + + +ScoreP SentenceScorer::CreateScoreFromString(const ScoreType type, const string& in) { +  switch (type) { +    case IBM_BLEU: +  case IBM_BLEU_3: +    case NIST_BLEU: +    case Koehn_BLEU: +      return BLEUScorerBase::ScoreFromString(in); +    case TER: +      return TERScorer::ScoreFromString(in); +    case AER: +      return AERScorer::ScoreFromString(in); +    case SER: +      return SERScorer::ScoreFromString(in); +    case BLEU_minus_TER_over_2: +      return BLEUTERCombinationScorer::ScoreFromString(in); +    default: +      assert(!"Not implemented!"); +  } +} + +void BLEUScore::ScoreDetails(string* details) const { +  char buf[2000]; +  vector<float> precs(max(N(),4)); +  float bp; +  float bleu = ComputeScore(&precs, &bp); +  for (int i=N();i<4;++i) +    precs[i]=0.; +  char *bufn; +  bufn=buf+sprintf(buf, "BLEU = %.2f, %.1f|%.1f|%.1f|%.1f (brev=%.3f)", +       bleu*100.0, +       precs[0]*100.0, +       precs[1]*100.0, +       precs[2]*100.0, +       precs[3]*100.0, +       bp); +  *details = buf; +} + +float BLEUScore::ComputeScore(vector<float>* precs, float* bp) const { +  float log_bleu = 0; +  if (precs) precs->clear(); +  int count = 0; +  for (int i = 0; i < N(); ++i) { +    if (hyp_ngram_counts[i] > 0) { +      float lprec = log(correct_ngram_hit_counts[i]) - log(hyp_ngram_counts[i]); +      if (precs) precs->push_back(exp(lprec)); +      log_bleu += lprec; +      ++count; +    } +  } +  log_bleu /= static_cast<float>(count); +  float lbp = 0.0; +  if (hyp_len < ref_len) +    lbp = (hyp_len - ref_len) / hyp_len; +  log_bleu += lbp; +  if (bp) *bp = exp(lbp); +  return exp(log_bleu); +} + + +//comptue scaled score for oracle retrieval +float BLEUScore::ComputePartialScore(vector<float>* precs, float* bp) const { +  // cerr << "Then here " << endl; +  float log_bleu = 0; +  if (precs) precs->clear(); +  int count = 0; +  for (int i = 0; i < N(); ++i) { +    //  cerr << "In CPS " << hyp_ngram_counts[i] << " " << correct_ngram_hit_counts[i] << endl; +    if (hyp_ngram_counts[i] > 0) { +      float lprec = log(correct_ngram_hit_counts[i]) - log(hyp_ngram_counts[i]); +      if (precs) precs->push_back(exp(lprec)); +      log_bleu += lprec; +      ++count; +    } +  } +  log_bleu /= static_cast<float>(count); +  float lbp = 0.0; +  if (hyp_len < ref_len) +    lbp = (hyp_len - ref_len) / hyp_len; +  log_bleu += lbp; +  if (bp) *bp = exp(lbp); +  return exp(log_bleu); +} + +float BLEUScore::ComputePartialScore() const { +  // cerr << "In here first " << endl; +  return ComputePartialScore(NULL, NULL); +} + +float BLEUScore::ComputeScore() const { +  return ComputeScore(NULL, NULL); +} + +void BLEUScore::Subtract(const Score& rhs, Score* res) const { +  const BLEUScore& d = static_cast<const BLEUScore&>(rhs); +  BLEUScore* o = static_cast<BLEUScore*>(res); +  o->ref_len = ref_len - d.ref_len; +  o->hyp_len = hyp_len - d.hyp_len; +  o->correct_ngram_hit_counts = correct_ngram_hit_counts - d.correct_ngram_hit_counts; +  o->hyp_ngram_counts = hyp_ngram_counts - d.hyp_ngram_counts; +} + +void BLEUScore::PlusEquals(const Score& delta) { +  const BLEUScore& d = static_cast<const BLEUScore&>(delta); +  correct_ngram_hit_counts += d.correct_ngram_hit_counts; +  hyp_ngram_counts += d.hyp_ngram_counts; +  ref_len += d.ref_len; +  hyp_len += d.hyp_len; +} + +void BLEUScore::TimesEquals(float scale) { +  correct_ngram_hit_counts *= scale; +  hyp_ngram_counts *= scale; +  ref_len *= scale; +  hyp_len *= scale; +} + +void BLEUScore::PlusEquals(const Score& delta, const float scale) { +  const BLEUScore& d = static_cast<const BLEUScore&>(delta); +  correct_ngram_hit_counts = correct_ngram_hit_counts + (d.correct_ngram_hit_counts * scale); +  hyp_ngram_counts = hyp_ngram_counts + (d.hyp_ngram_counts * scale); +  ref_len = ref_len + (d.ref_len * scale); +  hyp_len = hyp_len + (d.hyp_len * scale); +} + +void BLEUScore::PlusPartialEquals(const Score& delta, int oracle_e_cover, int oracle_f_cover, int src_len){ +  const BLEUScore& d = static_cast<const BLEUScore&>(delta); +  correct_ngram_hit_counts += d.correct_ngram_hit_counts; +  hyp_ngram_counts += d.hyp_ngram_counts; +  //scale the reference length according to the size of the input sentence covered by this rule + +  ref_len *= (float)oracle_f_cover / src_len; +  ref_len += d.ref_len; + +  hyp_len = oracle_e_cover; +  hyp_len += d.hyp_len; +} + + +ScoreP BLEUScore::GetZero() const { +  return ScoreP(new BLEUScore(N())); +} + +ScoreP BLEUScore::GetOne() const { +  return ScoreP(new BLEUScore(N(),1)); +} + + +void BLEUScore::Encode(string* out) const { +  ostringstream os; +  const int n = correct_ngram_hit_counts.size(); +  os << n << ' ' << ref_len << ' ' << hyp_len; +  for (int i = 0; i < n; ++i) +    os << ' ' << correct_ngram_hit_counts[i] << ' ' << hyp_ngram_counts[i]; +  *out = os.str(); +} + +BLEUScorerBase::BLEUScorerBase(const vector<vector<WordID> >& references, +                               int n) : SentenceScorer("BLEU"+boost::lexical_cast<string>(n),references),n_(n) { +  for (vector<vector<WordID> >::const_iterator ci = references.begin(); +       ci != references.end(); ++ci) { +    lengths_.push_back(ci->size()); +    CountRef(*ci); +  } +} + +ScoreP BLEUScorerBase::ScoreCandidate(const vector<WordID>& hyp) const { +  BLEUScore* bs = new BLEUScore(n_); +  for (NGramCountMap::iterator i=ngrams_.begin(); i != ngrams_.end(); ++i) +    i->second.second = 0; +  ComputeNgramStats(hyp, &bs->correct_ngram_hit_counts, &bs->hyp_ngram_counts, true); +  bs->ref_len = ComputeRefLength(hyp); +  bs->hyp_len = hyp.size(); +  return ScoreP(bs); +} + +ScoreP BLEUScorerBase::ScoreCCandidate(const vector<WordID>& hyp) const { +  BLEUScore* bs = new BLEUScore(n_); +  for (NGramCountMap::iterator i=ngrams_.begin(); i != ngrams_.end(); ++i) +    i->second.second = 0; +  bool clip = false; +  ComputeNgramStats(hyp, &bs->correct_ngram_hit_counts, &bs->hyp_ngram_counts,clip); +  bs->ref_len = ComputeRefLength(hyp); +  bs->hyp_len = hyp.size(); +  return ScoreP(bs); +} + + +DocScorer::~DocScorer() { +} + +void DocScorer::Init( +      const ScoreType type, +      const vector<string>& ref_files, +      const string& src_file, bool verbose) { +  scorers_.clear(); +  // TODO stop using valarray, start using ReadFile +  cerr << "Loading references (" << ref_files.size() << " files)\n"; +  ReadFile srcrf; +  if (type == AER && src_file.size() > 0) { +    cerr << "  (source=" << src_file << ")\n"; +    srcrf.Init(src_file); +  } +  std::vector<ReadFile> ifs(ref_files.begin(),ref_files.end()); +  for (int i=0; i < ref_files.size(); ++i) ifs[i].Init(ref_files[i]); +  char buf[64000]; +  bool expect_eof = false; +  int line=0; +  while (ifs[0].get()) { +    vector<vector<WordID> > refs(ref_files.size()); +    for (int i=0; i < ref_files.size(); ++i) { +      istream &in=ifs[i].get(); +      if (in.eof()) break; +      in.getline(buf, 64000); +      refs[i].clear(); +      if (strlen(buf) == 0) { +        if (in.eof()) { +          if (!expect_eof) { +            assert(i == 0); +            expect_eof = true; +          } +          break; +        } +      } else { +        TD::ConvertSentence(buf, &refs[i]); +        assert(!refs[i].empty()); +      } +      assert(!expect_eof); +    } +    if (!expect_eof) { +      string src_line; +      if (srcrf) { +        getline(srcrf.get(), src_line); +        map<string,string> dummy; +        ProcessAndStripSGML(&src_line, &dummy); +      } +      scorers_.push_back(ScorerP(SentenceScorer::CreateSentenceScorer(type, refs, src_line))); +      if (verbose) +        cerr<<"doc_scorer["<<line<<"] = "<<scorers_.back()->verbose_desc()<<endl; +      ++line; +    } +  } +  cerr << "Loaded reference translations for " << scorers_.size() << " sentences.\n"; +} + diff --git a/mteval/scorer.h b/mteval/scorer.h new file mode 100644 index 00000000..f18c8c7f --- /dev/null +++ b/mteval/scorer.h @@ -0,0 +1,110 @@ +#ifndef SCORER_H_ +#define SCORER_H_ +#include <vector> +#include <string> +#include <boost/shared_ptr.hpp> +//TODO: use intrusive shared_ptr in Score (because there are many of them on ErrorSurfaces) +#include "wordid.h" +#include "intrusive_refcount.hpp" + +class Score; +class SentenceScorer; +typedef boost::intrusive_ptr<Score> ScoreP; +typedef boost::shared_ptr<SentenceScorer> ScorerP; + +class ViterbiEnvelope; +class ErrorSurface; +class Hypergraph;  // needed for alignment + +//TODO: BLEU N (N separate arg, not part of enum)? +enum ScoreType { IBM_BLEU, NIST_BLEU, Koehn_BLEU, TER, BLEU_minus_TER_over_2, SER, AER, IBM_BLEU_3 }; +ScoreType ScoreTypeFromString(const std::string& st); +std::string StringFromScoreType(ScoreType st); + +class Score : public boost::intrusive_refcount<Score> { + public: +  virtual ~Score(); +  virtual float ComputeScore() const = 0; +  virtual float ComputePartialScore() const =0; +  virtual void ScoreDetails(std::string* details) const = 0; +  std::string ScoreDetails() { +    std::string d; +    ScoreDetails(&d); +    return d; +  } +  virtual void TimesEquals(float scale); // only for bleu; for mira oracle +  /// same as rhs.TimesEquals(scale);PlusEquals(rhs) except doesn't modify rhs. +  virtual void PlusEquals(const Score& rhs, const float scale) = 0; +  virtual void PlusEquals(const Score& rhs) = 0; +  virtual void PlusPartialEquals(const Score& rhs, int oracle_e_cover, int oracle_f_cover, int src_len) = 0; +  virtual void Subtract(const Score& rhs, Score *res) const = 0; +  virtual ScoreP GetZero() const = 0; +  virtual ScoreP GetOne() const = 0; +  virtual bool IsAdditiveIdentity() const = 0; // returns true if adding this delta +                                      // to another score results in no score change +				      // under any circumstances +  virtual void Encode(std::string* out) const = 0; +  static ScoreP GetZero(ScoreType type); +  static ScoreP GetOne(ScoreType type); +  virtual ScoreP Clone() const = 0; +protected: +  Score() {  } // we define these explicitly because refcount is noncopyable +  Score(Score const&) {  } +}; + +//TODO: make sure default copy ctors for score types do what we want. +template <class Derived> +struct ScoreBase : public Score { +  ScoreP Clone() const  { +    return ScoreP(new Derived(dynamic_cast<Derived const&>(*this))); +  } +}; + +class SentenceScorer { + public: +  typedef std::vector<WordID> Sentence; +  typedef std::vector<Sentence> Sentences; +  std::string desc; +  Sentences refs; +  SentenceScorer(std::string desc="SentenceScorer_unknown", Sentences const& refs=Sentences()) : desc(desc),refs(refs) {  } +  std::string verbose_desc() const; +  virtual float ComputeRefLength(const Sentence& hyp) const; // default: avg of refs.length +  virtual ~SentenceScorer(); +  virtual ScoreP GetOne() const; +  virtual ScoreP GetZero() const; +  virtual ScoreP ScoreCandidate(const Sentence& hyp) const = 0; +  virtual ScoreP ScoreCCandidate(const Sentence& hyp) const =0; +  virtual const std::string* GetSource() const; +  static ScoreP CreateScoreFromString(const ScoreType type, const std::string& in); +  static ScorerP CreateSentenceScorer(const ScoreType type, +    const std::vector<Sentence >& refs, +    const std::string& src = ""); +}; + +//TODO: should be able to GetOne GetZero without supplying sentence (just type) +class DocScorer { + public: +  ~DocScorer(); +  DocScorer() {  } +  void Init(const ScoreType type, +            const std::vector<std::string>& ref_files, +            const std::string& src_file = "", +            bool verbose=false +    ); +  DocScorer(const ScoreType type, +            const std::vector<std::string>& ref_files, +            const std::string& src_file = "", +            bool verbose=false +    ) +  { +    Init(type,ref_files,src_file,verbose); +  } + +  int size() const { return scorers_.size(); } +  ScorerP operator[](size_t i) const { return scorers_[i]; } + private: +  std::vector<ScorerP> scorers_; +}; + + +#endif diff --git a/mteval/scorer_test.cc b/mteval/scorer_test.cc new file mode 100644 index 00000000..a07a8c4b --- /dev/null +++ b/mteval/scorer_test.cc @@ -0,0 +1,182 @@ +#include <iostream> +#include <fstream> +#include <valarray> +#include <gtest/gtest.h> + +#include "tdict.h" +#include "scorer.h" +#include "aer_scorer.h" + +using namespace std; + +class ScorerTest : public testing::Test { + protected: +   virtual void SetUp() { +     refs0.resize(4); +     refs1.resize(4); +     TD::ConvertSentence("export of high-tech products in guangdong in first two months this year reached 3.76 billion us dollars", &refs0[0]); +     TD::ConvertSentence("guangdong's export of new high technology products amounts to us $ 3.76 billion in first two months of this year", &refs0[1]); +     TD::ConvertSentence("guangdong exports us $ 3.76 billion worth of high technology products in the first two months of this year", &refs0[2]); +     TD::ConvertSentence("in the first 2 months this year , the export volume of new hi-tech products in guangdong province reached 3.76 billion us dollars .", &refs0[3]); +     TD::ConvertSentence("xinhua news agency , guangzhou , march 16 ( reporter chen ji ) the latest statistics show that from january through february this year , the export of high-tech products in guangdong province reached 3.76 billion us dollars , up 34.8 \% over the same period last year and accounted for 25.5 \% of the total export in the province .", &refs1[0]); +     TD::ConvertSentence("xinhua news agency , guangzhou , march 16 ( reporter : chen ji ) -- latest statistic indicates that guangdong's export of new high technology products amounts to us $ 3.76 billion , up 34.8 \% over corresponding period and accounts for 25.5 \% of the total exports of the province .", &refs1[1]); +     TD::ConvertSentence("xinhua news agency report of march 16 from guangzhou ( by staff reporter chen ji ) - latest statistics indicate guangdong province exported us $ 3.76 billion worth of high technology products , up 34.8 percent from the same period last year , which account for 25.5 percent of the total exports of the province .", &refs1[2]); +     TD::ConvertSentence("guangdong , march 16 , ( xinhua ) -- ( chen ji reports ) as the newest statistics shows , in january and feberuary this year , the export volume of new hi-tech products in guangdong province reached 3.76 billion us dollars , up 34.8 \% than last year , making up 25.5 \% of the province's total .", &refs1[3]); +     TD::ConvertSentence("one guangdong province will next export us $ 3.76 high-tech product two months first this year 3.76 billion us dollars", &hyp1); +     TD::ConvertSentence("xinhua news agency , guangzhou , 16th of march ( reporter chen ) -- latest statistics suggest that guangdong exports new advanced technology product totals $ 3.76 million , 34.8 percent last corresponding period and accounts for 25.5 percent of the total export province .", &hyp2); +   } + +   virtual void TearDown() { } + +   vector<vector<WordID> > refs0; +   vector<vector<WordID> > refs1; +   vector<WordID> hyp1; +   vector<WordID> hyp2; +}; + +TEST_F(ScorerTest, TestCreateFromFiles) { +  vector<string> files; +  files.push_back("test_data/re.txt.0"); +  files.push_back("test_data/re.txt.1"); +  files.push_back("test_data/re.txt.2"); +  files.push_back("test_data/re.txt.3"); +  DocScorer ds(IBM_BLEU, files); +} + +TEST_F(ScorerTest, TestBLEUScorer) { +  ScorerP s1 = SentenceScorer::CreateSentenceScorer(IBM_BLEU, refs0); +  ScorerP s2 = SentenceScorer::CreateSentenceScorer(IBM_BLEU, refs1); +  ScoreP b1 = s1->ScoreCandidate(hyp1); +  EXPECT_FLOAT_EQ(0.23185077, b1->ComputeScore()); +  ScoreP b2 = s2->ScoreCandidate(hyp2); +  EXPECT_FLOAT_EQ(0.38101241, b2->ComputeScore()); +  b1->PlusEquals(*b2); +  EXPECT_FLOAT_EQ(0.348854, b1->ComputeScore()); +  EXPECT_FALSE(b1->IsAdditiveIdentity()); +  string details; +  b1->ScoreDetails(&details); +  EXPECT_EQ("BLEU = 34.89, 81.5|50.8|29.5|18.6 (brev=0.898)", details); +  cerr << details << endl; +  string enc; +  b1->Encode(&enc); +  ScoreP b3 = SentenceScorer::CreateScoreFromString(IBM_BLEU, enc); +  details.clear(); +  cerr << "Encoded BLEU score size: " << enc.size() << endl; +  b3->ScoreDetails(&details); +  cerr << details << endl; +  EXPECT_FALSE(b3->IsAdditiveIdentity()); +  EXPECT_EQ("BLEU = 34.89, 81.5|50.8|29.5|18.6 (brev=0.898)", details); +  ScoreP bz = b3->GetZero(); +  EXPECT_TRUE(bz->IsAdditiveIdentity()); +} + +TEST_F(ScorerTest, TestTERScorer) { +  ScorerP s1 = SentenceScorer::CreateSentenceScorer(TER, refs0); +  ScorerP s2 = SentenceScorer::CreateSentenceScorer(TER, refs1); +  string details; +  ScoreP t1 = s1->ScoreCandidate(hyp1); +  t1->ScoreDetails(&details); +  cerr << "DETAILS: " << details << endl; +  cerr << t1->ComputeScore() << endl; +  ScoreP t2 = s2->ScoreCandidate(hyp2); +  t2->ScoreDetails(&details); +  cerr << "DETAILS: " << details << endl; +  cerr << t2->ComputeScore() << endl; +  t1->PlusEquals(*t2); +  cerr << t1->ComputeScore() << endl; +  t1->ScoreDetails(&details); +  cerr << "DETAILS: " << details << endl; +  EXPECT_EQ("TER = 44.16,   4|  8| 16|  6 (len=77)", details); +  string enc; +  t1->Encode(&enc); +  ScoreP t3 = SentenceScorer::CreateScoreFromString(TER, enc); +  details.clear(); +  t3->ScoreDetails(&details); +  EXPECT_EQ("TER = 44.16,   4|  8| 16|  6 (len=77)", details); +  EXPECT_FALSE(t3->IsAdditiveIdentity()); +  ScoreP tz = t3->GetZero(); +  EXPECT_TRUE(tz->IsAdditiveIdentity()); +} + +TEST_F(ScorerTest, TestTERScorerSimple) { +  vector<vector<WordID> > ref(1); +  TD::ConvertSentence("1 2 3 A B", &ref[0]); +  vector<WordID> hyp; +  TD::ConvertSentence("A B 1 2 3", &hyp); +  ScorerP s1 = SentenceScorer::CreateSentenceScorer(TER, ref); +  string details; +  ScoreP t1 = s1->ScoreCandidate(hyp); +  t1->ScoreDetails(&details); +  cerr << "DETAILS: " << details << endl; +} + +TEST_F(ScorerTest, TestSERScorerSimple) { +  vector<vector<WordID> > ref(1); +  TD::ConvertSentence("A B C D", &ref[0]); +  vector<WordID> hyp1; +  TD::ConvertSentence("A B C", &hyp1); +  vector<WordID> hyp2; +  TD::ConvertSentence("A B C D", &hyp2); +  ScorerP s1 = SentenceScorer::CreateSentenceScorer(SER, ref); +  string details; +  ScoreP t1 = s1->ScoreCandidate(hyp1); +  t1->ScoreDetails(&details); +  cerr << "DETAILS: " << details << endl; +  ScoreP t2 = s1->ScoreCandidate(hyp2); +  t2->ScoreDetails(&details); +  cerr << "DETAILS: " << details << endl; +  t2->PlusEquals(*t1); +  t2->ScoreDetails(&details); +  cerr << "DETAILS: " << details << endl; +} + +TEST_F(ScorerTest, TestCombiScorer) { +  ScorerP s1 = SentenceScorer::CreateSentenceScorer(BLEU_minus_TER_over_2, refs0); +  string details; +  ScoreP t1 = s1->ScoreCandidate(hyp1); +  t1->ScoreDetails(&details); +  cerr << "DETAILS: " << details << endl; +  cerr << t1->ComputeScore() << endl; +  string enc; +  t1->Encode(&enc); +  ScoreP t2 = SentenceScorer::CreateScoreFromString(BLEU_minus_TER_over_2, enc); +  details.clear(); +  t2->ScoreDetails(&details); +  cerr << "DETAILS: " << details << endl; +  ScoreP cz = t2->GetZero(); +  EXPECT_FALSE(t2->IsAdditiveIdentity()); +  EXPECT_TRUE(cz->IsAdditiveIdentity()); +  cz->PlusEquals(*t2); +  EXPECT_FALSE(cz->IsAdditiveIdentity()); +  string d2; +  cz->ScoreDetails(&d2); +  EXPECT_EQ(d2, details); +} + +TEST_F(ScorerTest, AERTest) { +  vector<vector<WordID> > refs0(1); +  TD::ConvertSentence("0-0 2-1 1-2 3-3", &refs0[0]); + +  vector<WordID> hyp; +  TD::ConvertSentence("0-0 1-1", &hyp); +  AERScorer* as = new AERScorer(refs0); +  ScoreP x = as->ScoreCandidate(hyp); +  string details; +  x->ScoreDetails(&details); +  cerr << details << endl; +  string enc; +  x->Encode(&enc); +  delete as; +  cerr << "ENC size: " << enc.size() << endl; +  ScoreP y = SentenceScorer::CreateScoreFromString(AER, enc); +  string d2; +  y->ScoreDetails(&d2); +  cerr << d2 << endl; +  EXPECT_EQ(d2, details); +} + +int main(int argc, char **argv) { +  testing::InitGoogleTest(&argc, argv); +  return RUN_ALL_TESTS(); +} + diff --git a/mteval/ter.cc b/mteval/ter.cc new file mode 100644 index 00000000..cacc5b00 --- /dev/null +++ b/mteval/ter.cc @@ -0,0 +1,535 @@ +#include "ter.h" + +#include <cstdio> +#include <cassert> +#include <iostream> +#include <limits> +#include <sstream> +#include <tr1/unordered_map> +#include <set> +#include <valarray> +#include <boost/functional/hash.hpp> +#include <stdexcept> +#include "tdict.h" + +const bool ter_use_average_ref_len = true; +const int ter_short_circuit_long_sentences = -1; + +using namespace std; +using namespace std::tr1; + +struct COSTS { +  static const float substitution; +  static const float deletion; +  static const float insertion; +  static const float shift; +}; +const float COSTS::substitution = 1.0f; +const float COSTS::deletion = 1.0f; +const float COSTS::insertion = 1.0f; +const float COSTS::shift = 1.0f; + +static const int MAX_SHIFT_SIZE = 10; +static const int MAX_SHIFT_DIST = 50; + +struct Shift { +  unsigned int d_; +  Shift() : d_() {} +  Shift(int b, int e, int m) : d_() { +    begin(b); +    end(e); +    moveto(m); +  } +  inline int begin() const { +    return d_ & 0x3ff; +  } +  inline int end() const { +    return (d_ >> 10) & 0x3ff; +  } +  inline int moveto() const { +    int m = (d_ >> 20) & 0x7ff; +    if (m > 1024) { m -= 1024; m *= -1; } +    return m; +  } +  inline void begin(int b) { +    d_ &= 0xfffffc00u; +    d_ |= (b & 0x3ff); +  } +  inline void end(int e) { +    d_ &= 0xfff003ffu; +    d_ |= (e & 0x3ff) << 10; +  } +  inline void moveto(int m) { +    bool neg = (m < 0); +    if (neg) { m *= -1; m += 1024; } +    d_ &= 0xfffff; +    d_ |= (m & 0x7ff) << 20; +  } +}; + +class TERScorerImpl { + + public: +  enum TransType { MATCH, SUBSTITUTION, INSERTION, DELETION }; + +  explicit TERScorerImpl(const vector<WordID>& ref) : ref_(ref) { +    for (int i = 0; i < ref.size(); ++i) +      rwexists_.insert(ref[i]); +  } + +  float Calculate(const vector<WordID>& hyp, int* subs, int* ins, int* dels, int* shifts) const { +    return CalculateAllShifts(hyp, subs, ins, dels, shifts); +  } + +  inline int GetRefLength() const { +    return ref_.size(); +  } + + private: +  vector<WordID> ref_; +  set<WordID> rwexists_; + +  typedef unordered_map<vector<WordID>, set<int>, boost::hash<vector<WordID> > > NgramToIntsMap; +  mutable NgramToIntsMap nmap_; + +  static float MinimumEditDistance( +      const vector<WordID>& hyp, +      const vector<WordID>& ref, +      vector<TransType>* path) { +    vector<vector<TransType> > bmat(hyp.size() + 1, vector<TransType>(ref.size() + 1, MATCH)); +    vector<vector<float> > cmat(hyp.size() + 1, vector<float>(ref.size() + 1, 0)); +    for (int i = 0; i <= hyp.size(); ++i) +      cmat[i][0] = i; +    for (int j = 0; j <= ref.size(); ++j) +      cmat[0][j] = j; +    for (int i = 1; i <= hyp.size(); ++i) { +      const WordID& hw = hyp[i-1]; +      for (int j = 1; j <= ref.size(); ++j) { +        const WordID& rw = ref[j-1]; +	float& cur_c = cmat[i][j]; +	TransType& cur_b = bmat[i][j]; + +        if (rw == hw) { +          cur_c = cmat[i-1][j-1]; +          cur_b = MATCH; +        } else { +          cur_c = cmat[i-1][j-1] + COSTS::substitution; +          cur_b = SUBSTITUTION; +        } +	float cwoi = cmat[i-1][j]; +        if (cur_c > cwoi + COSTS::insertion) { +          cur_c = cwoi + COSTS::insertion; +          cur_b = INSERTION; +        } +        float cwod = cmat[i][j-1]; +        if (cur_c > cwod + COSTS::deletion) { +          cur_c = cwod + COSTS::deletion; +          cur_b = DELETION; +        } +      } +    } + +    // trace back along the best path and record the transition types +    path->clear(); +    int i = hyp.size(); +    int j = ref.size(); +    while (i > 0 || j > 0) { +      if (j == 0) { +        --i; +        path->push_back(INSERTION); +      } else if (i == 0) { +        --j; +        path->push_back(DELETION); +      } else { +        TransType t = bmat[i][j]; +        path->push_back(t); +        switch (t) { +          case SUBSTITUTION: +          case MATCH: +            --i; --j; break; +          case INSERTION: +            --i; break; +          case DELETION: +            --j; break; +        } +      } +    } +    reverse(path->begin(), path->end()); +    return cmat[hyp.size()][ref.size()]; +  } + +  void BuildWordMatches(const vector<WordID>& hyp, NgramToIntsMap* nmap) const { +    nmap->clear(); +    set<WordID> exists_both; +    for (int i = 0; i < hyp.size(); ++i) +      if (rwexists_.find(hyp[i]) != rwexists_.end()) +        exists_both.insert(hyp[i]); +    for (int start=0; start<ref_.size(); ++start) { +      if (exists_both.find(ref_[start]) == exists_both.end()) continue; +      vector<WordID> cp; +      int mlen = min(MAX_SHIFT_SIZE, static_cast<int>(ref_.size() - start)); +      for (int len=0; len<mlen; ++len) { +        if (len && exists_both.find(ref_[start + len]) == exists_both.end()) break; +        cp.push_back(ref_[start + len]); +	(*nmap)[cp].insert(start); +      } +    } +  } + +  static void PerformShift(const vector<WordID>& in, +    int start, int end, int moveto, vector<WordID>* out) { +    // cerr << "ps: " << start << " " << end << " " << moveto << endl; +    out->clear(); +    if (moveto == -1) { +      for (int i = start; i <= end; ++i) +       out->push_back(in[i]); +      for (int i = 0; i < start; ++i) +       out->push_back(in[i]); +      for (int i = end+1; i < in.size(); ++i) +       out->push_back(in[i]); +    } else if (moveto < start) { +      for (int i = 0; i <= moveto; ++i) +       out->push_back(in[i]); +      for (int i = start; i <= end; ++i) +       out->push_back(in[i]); +      for (int i = moveto+1; i < start; ++i) +       out->push_back(in[i]); +      for (int i = end+1; i < in.size(); ++i) +       out->push_back(in[i]); +    } else if (moveto > end) { +      for (int i = 0; i < start; ++i) +       out->push_back(in[i]); +      for (int i = end+1; i <= moveto; ++i) +       out->push_back(in[i]); +      for (int i = start; i <= end; ++i) +       out->push_back(in[i]); +      for (int i = moveto+1; i < in.size(); ++i) +       out->push_back(in[i]); +    } else { +      for (int i = 0; i < start; ++i) +       out->push_back(in[i]); +      for (int i = end+1; (i < in.size()) && (i <= end + (moveto - start)); ++i) +       out->push_back(in[i]); +      for (int i = start; i <= end; ++i) +       out->push_back(in[i]); +      for (int i = (end + (moveto - start))+1; i < in.size(); ++i) +       out->push_back(in[i]); +    } +    if (out->size() != in.size()) { +      cerr << "ps: " << start << " " << end << " " << moveto << endl; +      cerr << "in=" << TD::GetString(in) << endl; +      cerr << "out=" << TD::GetString(*out) << endl; +    } +    assert(out->size() == in.size()); +    // cerr << "ps: " << TD::GetString(*out) << endl; +  } + +  void GetAllPossibleShifts(const vector<WordID>& hyp, +      const vector<int>& ralign, +      const vector<bool>& herr, +      const vector<bool>& rerr, +      const int min_size, +      vector<vector<Shift> >* shifts) const { +    for (int start = 0; start < hyp.size(); ++start) { +      vector<WordID> cp(1, hyp[start]); +      NgramToIntsMap::iterator niter = nmap_.find(cp); +      if (niter == nmap_.end()) continue; +      bool ok = false; +      int moveto; +      for (set<int>::iterator i = niter->second.begin(); i != niter->second.end(); ++i) { +        moveto = *i; +        int rm = ralign[moveto]; +        ok = (start != rm && +              (rm - start) < MAX_SHIFT_DIST && +              (start - rm - 1) < MAX_SHIFT_DIST); +        if (ok) break; +      } +      if (!ok) continue; +      cp.clear(); +      for (int end = start + min_size - 1; +           ok && end < hyp.size() && end < (start + MAX_SHIFT_SIZE); ++end) { +        cp.push_back(hyp[end]); +	vector<Shift>& sshifts = (*shifts)[end - start]; +        ok = false; +        NgramToIntsMap::iterator niter = nmap_.find(cp); +        if (niter == nmap_.end()) break; +        bool any_herr = false; +        for (int i = start; i <= end && !any_herr; ++i) +          any_herr = herr[i]; +        if (!any_herr) { +          ok = true; +          continue; +        } +        for (set<int>::iterator mi = niter->second.begin(); +             mi != niter->second.end(); ++mi) { +          int moveto = *mi; +	  int rm = ralign[moveto]; +	  if (! ((rm != start) && +	        ((rm < start) || (rm > end)) && +		(rm - start <= MAX_SHIFT_DIST) && +		((start - rm - 1) <= MAX_SHIFT_DIST))) continue; +          ok = true; +	  bool any_rerr = false; +	  for (int i = 0; (i <= end - start) && (!any_rerr); ++i) +            any_rerr = rerr[moveto+i]; +	  if (!any_rerr) continue; +	  for (int roff = 0; roff <= (end - start); ++roff) { +	    int rmr = ralign[moveto+roff]; +	    if ((start != rmr) && ((roff == 0) || (rmr != ralign[moveto]))) +	      sshifts.push_back(Shift(start, end, moveto + roff)); +	  } +        } +      } +    } +  } + +  bool CalculateBestShift(const vector<WordID>& cur, +                          const vector<WordID>& hyp, +                          float curerr, +                          const vector<TransType>& path, +                          vector<WordID>* new_hyp, +                          float* newerr, +                          vector<TransType>* new_path) const { +    vector<bool> herr, rerr; +    vector<int> ralign; +    int hpos = -1; +    for (int i = 0; i < path.size(); ++i) { +      switch (path[i]) { +        case MATCH: +	  ++hpos; +	  herr.push_back(false); +	  rerr.push_back(false); +	  ralign.push_back(hpos); +          break; +        case SUBSTITUTION: +	  ++hpos; +	  herr.push_back(true); +	  rerr.push_back(true); +	  ralign.push_back(hpos); +          break; +        case INSERTION: +	  ++hpos; +	  herr.push_back(true); +          break; +	case DELETION: +	  rerr.push_back(true); +	  ralign.push_back(hpos); +          break; +      } +    } +#if 0 +    cerr << "RALIGN: "; +    for (int i = 0; i < rerr.size(); ++i) +      cerr << ralign[i] << " "; +    cerr << endl; +    cerr << "RERR: "; +    for (int i = 0; i < rerr.size(); ++i) +      cerr << (bool)rerr[i] << " "; +    cerr << endl; +    cerr << "HERR: "; +    for (int i = 0; i < herr.size(); ++i) +      cerr << (bool)herr[i] << " "; +    cerr << endl; +#endif + +    vector<vector<Shift> > shifts(MAX_SHIFT_SIZE + 1); +    GetAllPossibleShifts(cur, ralign, herr, rerr, 1, &shifts); +    float cur_best_shift_cost = 0; +    *newerr = curerr; +    vector<TransType> cur_best_path; +    vector<WordID> cur_best_hyp; + +    bool res = false; +    for (int i = shifts.size() - 1; i >=0; --i) { +      float curfix = curerr - (cur_best_shift_cost + *newerr); +      float maxfix = 2.0f * (1 + i) - COSTS::shift; +      if ((curfix > maxfix) || ((cur_best_shift_cost == 0) && (curfix == maxfix))) break; +      for (int j = 0; j < shifts[i].size(); ++j) { +        const Shift& s = shifts[i][j]; +	curfix = curerr - (cur_best_shift_cost + *newerr); +	maxfix = 2.0f * (1 + i) - COSTS::shift;  // TODO remove? +        if ((curfix > maxfix) || ((cur_best_shift_cost == 0) && (curfix == maxfix))) continue; +	vector<WordID> shifted(cur.size()); +	PerformShift(cur, s.begin(), s.end(), ralign[s.moveto()], &shifted); +	vector<TransType> try_path; +	float try_cost = MinimumEditDistance(shifted, ref_, &try_path); +	float gain = (*newerr + cur_best_shift_cost) - (try_cost + COSTS::shift); +	if (gain > 0.0f || ((cur_best_shift_cost == 0.0f) && (gain == 0.0f))) { +	  *newerr = try_cost; +	  cur_best_shift_cost = COSTS::shift; +	  new_path->swap(try_path); +	  new_hyp->swap(shifted); +	  res = true; +	  // cerr << "Found better shift " << s.begin() << "..." << s.end() << " moveto " << s.moveto() << endl; +	} +      } +    } + +    return res; +  } + +  static void GetPathStats(const vector<TransType>& path, int* subs, int* ins, int* dels) { +    *subs = *ins = *dels = 0; +    for (int i = 0; i < path.size(); ++i) { +      switch (path[i]) { +        case SUBSTITUTION: +	  ++(*subs); +        case MATCH: +          break; +        case INSERTION: +          ++(*ins); break; +	case DELETION: +          ++(*dels); break; +      } +    } +  } + +  float CalculateAllShifts(const vector<WordID>& hyp, +      int* subs, int* ins, int* dels, int* shifts) const { +    BuildWordMatches(hyp, &nmap_); +    vector<TransType> path; +    float med_cost = MinimumEditDistance(hyp, ref_, &path); +    float edits = 0; +    vector<WordID> cur = hyp; +    *shifts = 0; +    if (ter_short_circuit_long_sentences < 0 || +        ref_.size() < ter_short_circuit_long_sentences) { +      while (true) { +        vector<WordID> new_hyp; +        vector<TransType> new_path; +        float new_med_cost; +        if (!CalculateBestShift(cur, hyp, med_cost, path, &new_hyp, &new_med_cost, &new_path)) +          break; +        edits += COSTS::shift; +        ++(*shifts); +        med_cost = new_med_cost; +        path.swap(new_path); +        cur.swap(new_hyp); +      } +    } +    GetPathStats(path, subs, ins, dels); +    return med_cost + edits; +  } +}; + +class TERScore : public ScoreBase<TERScore> { +  friend class TERScorer; + + public: +  static const unsigned kINSERTIONS = 0; +  static const unsigned kDELETIONS = 1; +  static const unsigned kSUBSTITUTIONS = 2; +  static const unsigned kSHIFTS = 3; +  static const unsigned kREF_WORDCOUNT = 4; +  static const unsigned kDUMMY_LAST_ENTRY = 5; + + TERScore() : stats(0,kDUMMY_LAST_ENTRY) {} +  float ComputePartialScore() const { return 0.0;} +  float ComputeScore() const { +    float edits = static_cast<float>(stats[kINSERTIONS] + stats[kDELETIONS] + stats[kSUBSTITUTIONS] + stats[kSHIFTS]); +    return edits / static_cast<float>(stats[kREF_WORDCOUNT]); +  } +  void ScoreDetails(string* details) const; +  void PlusPartialEquals(const Score& rhs, int oracle_e_cover, int oracle_f_cover, int src_len){} +  void PlusEquals(const Score& delta, const float scale) { +    if (scale==1) +      stats += static_cast<const TERScore&>(delta).stats; +    if (scale==-1) +      stats -= static_cast<const TERScore&>(delta).stats; +    throw std::runtime_error("TERScore::PlusEquals with scale != +-1"); + } +  void PlusEquals(const Score& delta) { +    stats += static_cast<const TERScore&>(delta).stats; +  } + +  ScoreP GetZero() const { +    return ScoreP(new TERScore); +  } +  ScoreP GetOne() const { +    return ScoreP(new TERScore); +  } +  void Subtract(const Score& rhs, Score* res) const { +    static_cast<TERScore*>(res)->stats = stats - static_cast<const TERScore&>(rhs).stats; +  } +  void Encode(std::string* out) const { +    ostringstream os; +    os << stats[kINSERTIONS] << ' ' +       << stats[kDELETIONS] << ' ' +       << stats[kSUBSTITUTIONS] << ' ' +       << stats[kSHIFTS] << ' ' +       << stats[kREF_WORDCOUNT]; +    *out = os.str(); +  } +  bool IsAdditiveIdentity() const { +    for (int i = 0; i < kDUMMY_LAST_ENTRY; ++i) +      if (stats[i] != 0) return false; +    return true; +  } + private: +  valarray<int> stats; +}; + +ScoreP TERScorer::ScoreFromString(const std::string& data) { +  istringstream is(data); +  TERScore* r = new TERScore; +  is >> r->stats[TERScore::kINSERTIONS] +     >> r->stats[TERScore::kDELETIONS] +     >> r->stats[TERScore::kSUBSTITUTIONS] +     >> r->stats[TERScore::kSHIFTS] +     >> r->stats[TERScore::kREF_WORDCOUNT]; +  return ScoreP(r); +} + +void TERScore::ScoreDetails(std::string* details) const { +  char buf[200]; +  sprintf(buf, "TER = %.2f, %3d|%3d|%3d|%3d (len=%d)", +     ComputeScore() * 100.0f, +     stats[kINSERTIONS], +     stats[kDELETIONS], +     stats[kSUBSTITUTIONS], +     stats[kSHIFTS], +     stats[kREF_WORDCOUNT]); +  *details = buf; +} + +TERScorer::~TERScorer() { +  for (vector<TERScorerImpl*>::iterator i = impl_.begin(); i != impl_.end(); ++i) +    delete *i; +} + +TERScorer::TERScorer(const vector<vector<WordID> >& refs) : impl_(refs.size()) { +  for (int i = 0; i < refs.size(); ++i) +    impl_[i] = new TERScorerImpl(refs[i]); +} + +ScoreP TERScorer::ScoreCCandidate(const vector<WordID>& hyp) const { +  return ScoreP(); +} + +ScoreP TERScorer::ScoreCandidate(const std::vector<WordID>& hyp) const { +  float best_score = numeric_limits<float>::max(); +  TERScore* res = new TERScore; +  int avg_len = 0; +  for (int i = 0; i < impl_.size(); ++i) +    avg_len += impl_[i]->GetRefLength(); +  avg_len /= impl_.size(); +  for (int i = 0; i < impl_.size(); ++i) { +    int subs, ins, dels, shifts; +    float score = impl_[i]->Calculate(hyp, &subs, &ins, &dels, &shifts); +    // cerr << "Component TER cost: " << score << endl; +    if (score < best_score) { +      res->stats[TERScore::kINSERTIONS] = ins; +      res->stats[TERScore::kDELETIONS] = dels; +      res->stats[TERScore::kSUBSTITUTIONS] = subs; +      res->stats[TERScore::kSHIFTS] = shifts; +      if (ter_use_average_ref_len) { +        res->stats[TERScore::kREF_WORDCOUNT] = avg_len; +      } else { +        res->stats[TERScore::kREF_WORDCOUNT] = impl_[i]->GetRefLength(); +      } + +      best_score = score; +    } +  } +  return ScoreP(res); +} diff --git a/mteval/ter.h b/mteval/ter.h new file mode 100644 index 00000000..43314791 --- /dev/null +++ b/mteval/ter.h @@ -0,0 +1,19 @@ +#ifndef _TER_H_ +#define _TER_H_ + +#include "scorer.h" + +class TERScorerImpl; + +class TERScorer : public SentenceScorer { + public: +  TERScorer(const std::vector<std::vector<WordID> >& references); +  ~TERScorer(); +  ScoreP ScoreCandidate(const std::vector<WordID>& hyp) const; +  ScoreP ScoreCCandidate(const std::vector<WordID>& hyp) const; +  static ScoreP ScoreFromString(const std::string& data); + private: +  std::vector<TERScorerImpl*> impl_; +}; + +#endif diff --git a/mteval/test_data/re.txt.0 b/mteval/test_data/re.txt.0 new file mode 100644 index 00000000..86eff087 --- /dev/null +++ b/mteval/test_data/re.txt.0 @@ -0,0 +1,5 @@ +erdogan states turkey to reject any pressures to urge it to recognize cyprus +ankara 12 - 1 ( afp ) - turkish prime minister recep tayyip erdogan announced today , wednesday , that ankara will reject any pressure by the european union to urge it to recognize cyprus . this comes two weeks before the summit of european union state and government heads who will decide whether or nor membership negotiations with ankara should be opened . +erdogan told " ntv " television station that " the european union cannot address us by imposing new conditions on us with regard to cyprus . +we will discuss this dossier in the course of membership negotiations . " +he added " let me be clear , i cannot sidestep turkey , this is something we cannot accept . " diff --git a/mteval/test_data/re.txt.1 b/mteval/test_data/re.txt.1 new file mode 100644 index 00000000..2140f198 --- /dev/null +++ b/mteval/test_data/re.txt.1 @@ -0,0 +1,5 @@ +erdogan confirms turkey will resist any pressure to recognize cyprus +ankara 12 - 1 ( afp ) - the turkish head of government , recep tayyip erdogan , announced today ( wednesday ) that ankara would resist any pressure the european union might exercise in order to force it into recognizing cyprus . this comes two weeks before a summit of european union heads of state and government , who will decide whether or not to open membership negotiations with ankara . +erdogan said to the ntv television channel : " the european union cannot engage with us through imposing new conditions on us with regard to cyprus . +we shall discuss this issue in the course of the membership negotiations . " +he added : " let me be clear - i cannot confine turkey . this is something we do not accept . " diff --git a/mteval/test_data/re.txt.2 b/mteval/test_data/re.txt.2 new file mode 100644 index 00000000..94e46286 --- /dev/null +++ b/mteval/test_data/re.txt.2 @@ -0,0 +1,5 @@ +erdogan confirms that turkey will reject any pressures to encourage it to recognize cyprus +ankara , 12 / 1 ( afp ) - the turkish prime minister recep tayyip erdogan declared today , wednesday , that ankara will reject any pressures that the european union may apply on it to encourage to recognize cyprus . this comes two weeks before a summit of the heads of countries and governments of the european union , who will decide on whether or not to start negotiations on joining with ankara . +erdogan told the ntv television station that " it is not possible for the european union to talk to us by imposing new conditions on us regarding cyprus . +we shall discuss this dossier during the negotiations on joining . " +and he added , " let me be clear . turkey's arm should not be twisted ; this is something we cannot accept . " diff --git a/mteval/test_data/re.txt.3 b/mteval/test_data/re.txt.3 new file mode 100644 index 00000000..f87c3308 --- /dev/null +++ b/mteval/test_data/re.txt.3 @@ -0,0 +1,5 @@ +erdogan stresses that turkey will reject all pressures to force it to recognize cyprus +ankara 12 - 1 ( afp ) - turkish prime minister recep tayyip erdogan announced today , wednesday , that ankara would refuse all pressures applied on it by the european union to force it to recognize cyprus . that came two weeks before the summit of the presidents and prime ministers of the european union , who would decide on whether to open negotiations on joining with ankara or not . +erdogan said to " ntv " tv station that the " european union can not communicate with us by imposing on us new conditions related to cyprus . +we will discuss this file during the negotiations on joining . " +he added , " let me be clear . turkey's arm should not be twisted . this is unacceptable to us . " | 
