diff options
Diffstat (limited to 'scripts/training/mert-moses.pl')
-rwxr-xr-x | scripts/training/mert-moses.pl | 956 |
1 files changed, 0 insertions, 956 deletions
diff --git a/scripts/training/mert-moses.pl b/scripts/training/mert-moses.pl deleted file mode 100755 index 58f7c4f01..000000000 --- a/scripts/training/mert-moses.pl +++ /dev/null @@ -1,956 +0,0 @@ -#!/usr/bin/perl -w -# Usage: -# mert-moses.pl <foreign> <english> <decoder-executable> <decoder-config> -# For other options see below or run 'mert-moses.pl --help' - -# Notes: -# <foreign> and <english> should be raw text files, one sentence per line -# <english> can be a prefix, in which case the files are <english>0, <english>1, etc. are used - -# Revision history - -# 31 Jl 1006 move gzip run*.out to avoid failure wit restartings -# adding default paths -# 29 Jul 2006 run-filter, score-nbest and mert run on the queue (Nicola; Ondrej had to type it in again) -# 28 Jul 2006 attempt at foolproof usage, strong checking of input validity, merged the parallel and nonparallel version (Ondrej Bojar) -# 27 Jul 2006 adding the safesystem() function to handle with process failure -# 22 Jul 2006 fixed a bug about handling relative path of configuration file (Nicola Bertoldi) -# 21 Jul 2006 adapted for Moses-in-parallel (Nicola Bertoldi) -# 18 Jul 2006 adapted for Moses and cleaned up (PK) -# 21 Jan 2005 unified various versions, thorough cleanup (DWC) -# now indexing accumulated n-best list solely by feature vectors -# 14 Dec 2004 reimplemented find_threshold_points in C (NMD) -# 25 Oct 2004 Use either average or shortest (default) reference -# length as effective reference length (DWC) -# 13 Oct 2004 Use alternative decoders (DWC) -# Original version by Philipp Koehn - -# defaults for initial values and ranges are: -my $default_triples = { - # for each _d_istortion, _l_anguage _m_odel, _t_ranslation _m_odel and _w_ord penalty, there is a list - # of [ default value, lower bound, upper bound ]-triples. In most cases, only one triple is used, - # but the translation model has currently 5 features - "d" => [ [ 1.0, 0.0, 2.0 ] ], - "lm" => [ [ 1.0, 0.0, 2.0 ] ], - "tm" => [ - [ 0.3, 0.0, 0.5 ], - [ 0.2, 0.0, 0.5 ], - [ 0.3, 0.0, 0.5 ], - [ 0.2, 0.0, 0.5 ], - [ 0.0, -1.0, 1.0 ], - ], - "g" => [ - [ 1.0, 0.0, 2.0 ], - [ 1.0, 0.0, 2.0 ], - ], - "w" => [ [ 0.0, -1.0, 1.0 ] ], -}; - -# moses.ini file uses FULL names for lambdas, while this training script internally (and on the command line) -# uses ABBR names. -my $ABBR_FULL_MAP = "d=weight-d lm=weight-l tm=weight-t w=weight-w g=weight-generation"; -my %ABBR2FULL = map {split/=/,$_,2} split /\s+/, $ABBR_FULL_MAP; -my %FULL2ABBR = map {my ($a, $b) = split/=/,$_,2; ($b, $a);} split /\s+/, $ABBR_FULL_MAP; - -# We parse moses.ini to figure out how many weights do we need to optimize. -# For this, we must know the correspondence between options defining files -# for models and options assigning weights to these models. -my $TABLECONFIG_ABBR_MAP = "ttable-file=tm lmodel-file=lm distortion-file=d generation-file=g"; -my %TABLECONFIG2ABBR = map {split(/=/,$_,2)} split /\s+/, $TABLECONFIG_ABBR_MAP; - -# There are weights that do not correspond to any input file, they just increase the total number of lambdas we optimize -my $extra_lambdas_for_model = { - "w" => 1, # word penalty - "d" => 1, # basic distortion -}; - - - - -my $minimum_required_change_in_weights = 0.00001; - # stop if no lambda changes more than this - -my $verbose = 0; -my $usage = 0; # request for --help -my $___WORKING_DIR = "mert-work"; -my $___DEV_F = undef; # required, input text to decode -my $___DEV_E = undef; # required, basename of files with references -my $___DECODER = undef; # required, pathname to the decoder executable -my $___CONFIG = undef; # required, pathname to startup ini file -my $___N_BEST_LIST_SIZE = 100; -my $queue_flags = "-l ws06ossmt=true -l mem_free=0.5G -hard"; # extra parameters for parallelizer - # the -l ws0ssmt is relevant only to JHU workshop -my $___JOBS = undef; # if parallel, number of jobs to use (undef -> serial) -my $___DECODER_FLAGS = ""; # additional parametrs to pass to the decoder -my $___LAMBDA = undef; # string specifying the seed weights and boundaries of all lambdas -my $continue = 0; # should we try to continue from the last saved step? -my $skip_decoder = 0; # and should we skip the first decoder run (assuming we got interrupted during mert) - -# Parameter for effective reference length when computing BLEU score -# This is used by score-nbest-bleu.py -# Default is to use shortest reference -# Use "--average" to use average reference length -my $___AVERAGE = 0; - -my $allow_unknown_lambdas = 0; -my $allow_skipping_lambdas = 0; - - -my $SCRIPTS_ROOTDIR = undef; # path to all tools (overriden by specific options) -my $cmertdir = undef; # path to cmert directory -my $pythonpath = undef; # path to python libraries needed by cmert -my $filtercmd = undef; # path to filter-model-given-input.pl -my $SCORENBESTCMD = undef; -my $qsubwrapper = undef; -my $moses_parallel_cmd = undef; - - -use strict; -use Getopt::Long; -GetOptions( - "working-dir=s" => \$___WORKING_DIR, - "input=s" => \$___DEV_F, - "refs=s" => \$___DEV_E, - "decoder=s" => \$___DECODER, - "config=s" => \$___CONFIG, - "nbest=i" => \$___N_BEST_LIST_SIZE, - "queue-flags=s" => \$queue_flags, - "jobs=i" => \$___JOBS, - "decoder-flags=s" => \$___DECODER_FLAGS, - "lambdas=s" => \$___LAMBDA, - "continue" => \$continue, - "skip-decoder" => \$skip_decoder, - "average" => \$___AVERAGE, - "help" => \$usage, - "allow-unknown-lambdas" => \$allow_unknown_lambdas, - "allow-skipping-lambdas" => \$allow_skipping_lambdas, - "verbose" => \$verbose, - "roodir=s" => \$SCRIPTS_ROOTDIR, - "cmertdir=s" => \$cmertdir, - "pythonpath=s" => \$pythonpath, - "filtercmd=s" => \$filtercmd, # allow to override the default location - "scorenbestcmd=s" => \$SCORENBESTCMD, # path to score-nbest.py - "qsubwrapper=s" => \$qsubwrapper, # allow to override the default location - "mosesparallelcmd=s" => \$moses_parallel_cmd, # allow to override the default location -) or exit(1); - -# the 4 required parameters can be supplied on the command line directly -# or using the --options -if (scalar @ARGV == 4) { - # required parameters: input_file references_basename decoder_executable - $___DEV_F = shift; - $___DEV_E = shift; - $___DECODER = shift; - $___CONFIG = shift; -} - - -print STDERR "After default: $queue_flags\n"; - -if ($usage || !defined $___DEV_F || !defined$___DEV_E || !defined$___DECODER || !defined $___CONFIG) { - print STDERR "usage: mert-moses.pl input-text references decoder-executable decoder.ini -Options: - --working-dir=mert-dir ... where all the files are created - --nbest=100 ... how big nbestlist to generate - --jobs=N ... set this to anything to run moses in parallel - --mosesparallelcmd=STRING ... use a different script instead of moses-parallel - --queue-flags=STRING ... anything you with to pass to - qsub, eg. '-l ws06osssmt=true' - The default is to submit the jobs to the ws06ossmt queue, which - makes sense only at JHU. To reset the default JHU queue - parameters, please use \"--queue-flags=' '\" (i.e. a space between - the quotes). - --decoder-flags=STRING ... extra parameters for the decoder - --lambdas=STRING ... default values and ranges for lambdas, a complex string - such as 'd:1,0.5-1.5 lm:1,0.5-1.5 tm:0.3,0.25-0.75;0.2,0.25-0.75;0.2,0.25-0.75;0.3,0.25-0.75;0,-0.5-0.5 w:0,-0.5-0.5' - --allow-unknown-lambdas ... keep going even if someone supplies a new lambda - in the lambdas option (such as 'superbmodel:1,0-1'); optimize it, too - --continue ... continue from the last achieved state - --skip-decoder ... skip the decoder run for the first time, assuming that - we got interrupted during optimization - --average ... Use either average or shortest (default) reference - length as effective reference length - --filtercmd=STRING ... path to filter-model-given-input.pl - --roodir=STRING ... where do helpers reside (if not given explicitly) - --cmertdir=STRING ... where is cmert installed - --pythonpath=STRING ... where is python executable - --scorenbestcmd=STRING ... path to score-nbest.py -"; - exit 1; -} - -# Check validity of input parameters and set defaults if needed - - - - -if (!defined $SCRIPTS_ROOTDIR) { - $SCRIPTS_ROOTDIR = $ENV{"SCRIPTS_ROOTDIR"}; - die "Please set SCRIPTS_ROOTDIR or specify --rootdir" if !defined $SCRIPTS_ROOTDIR; -} - -print STDERR "Using SCRIPTS_ROOTDIR: $SCRIPTS_ROOTDIR\n"; - - - - -# path of script for filtering phrase tables and running the decoder -$filtercmd="$SCRIPTS_ROOTDIR/training/filter-model-given-input.pl" if !defined $filtercmd; - -$qsubwrapper="$SCRIPTS_ROOTDIR/generic/qsub-wrapper.pl" if !defined $qsubwrapper; - -$moses_parallel_cmd = "$SCRIPTS_ROOTDIR/generic/moses-parallel.pl" - if !defined $moses_parallel_cmd; - -$cmertdir = "$SCRIPTS_ROOTDIR/training/cmert-0.5" if !defined $cmertdir; -my $cmertcmd="$cmertdir/mert"; - -$SCORENBESTCMD = "$cmertdir/score-nbest.py" if ! defined $SCORENBESTCMD; - -$pythonpath = "$cmertdir/python" if !defined $pythonpath; - -$ENV{PYTHONPATH} = $pythonpath; # other scripts need to know - - -die "Not executable: $filtercmd" if ! -x $filtercmd; -die "Not executable: $cmertcmd" if ! -x $cmertcmd; -die "Not executable: $moses_parallel_cmd" if defined $___JOBS && ! -x $moses_parallel_cmd; -die "Not executable: $qsubwrapper" if defined $___JOBS && ! -x $qsubwrapper; -die "Not a dir: $pythonpath" if ! -d $pythonpath; -die "Not executable: $___DECODER" if ! -x $___DECODER; - -my $input_abs = ensure_full_path($___DEV_F); -die "File not found: $___DEV_F (interpreted as $input_abs)." - if ! -e $input_abs; -$___DEV_F = $input_abs; - - -my $decoder_abs = ensure_full_path($___DECODER); -die "File not found: $___DECODER (interpreted as $decoder_abs)." - if ! -x $decoder_abs; -$___DECODER = $decoder_abs; - - -my $ref_abs = ensure_full_path($___DEV_E); -# check if English dev set (reference translations) exist and store a list of all references -my @references; -if (-e $ref_abs) { - push @references, $ref_abs; -} -else { - # if multiple file, get a full list of the files - my $part = 0; - while (-e $ref_abs.$part) { - push @references, $ref_abs.$part; - $part++; - } - die("Reference translations not found: $___DEV_E (interpreted as $ref_abs)") unless $part; -} - -my $config_abs = ensure_full_path($___CONFIG); -die "File not found: $___CONFIG (interpreted as $config_abs)." - if ! -e $config_abs; -$___CONFIG = $config_abs; - - - -# check validity of moses.ini and collect number of models and lambdas per model -# need to make a copy of $extra_lambdas_for_model, scan_config spoils it -my %copy_of_extra_lambdas_for_model = %$extra_lambdas_for_model; -my ($lambdas_per_model, $models_used) = scan_config($___CONFIG, \%copy_of_extra_lambdas_for_model); - - -# Parse the lambda config string and convert it to a nice structure in the same format as $default_triples -my $use_triples = undef; -if (defined $___LAMBDA) { - # interpreting lambdas from command line - foreach (split(/\s+/,$___LAMBDA)) { - my ($name,$values) = split(/:/); - die "Malformed setting: '$_', expected name:values\n" if !defined $name || !defined $values; - foreach my $startminmax (split/;/,$values) { - if ($startminmax =~ /^(-?[\.\d]+),(-?[\.\d]+)-(-?[\.\d]+)$/) { - my $start = $1; - my $min = $1; - my $max = $1; - push @{$use_triples->{$name}}, [$start, $min, $max]; - } - else { - die "Malformed feature range definition: $name => $startminmax\n"; - } - } - } -} else { - # no lambdas supplied, use the default ones, but do not forget to repeat them accordingly - # first for or inherent models - foreach my $name (keys %$extra_lambdas_for_model) { - foreach (1..$extra_lambdas_for_model->{$name}) { - die "No default weights defined for -$name" - if !defined $default_triples->{$name}; - # XXX here was a deadly bug: we need a deep copy of the default values - my @copy = (); - foreach my $triple (@{$default_triples->{$name}}) { - my @copy_triple = @$triple; - push @copy, [ @copy_triple ]; - } - push @{$use_triples->{$name}}, @copy; - } - } - # and then for all models used - foreach my $name (keys %$models_used) { - foreach (1..$models_used->{$name}) { - die "No default weights defined for -$name" - if !defined $default_triples->{$name}; - # XXX here was a deadly bug: we need a deep copy of the default values - my @copy = (); - foreach my $triple (@{$default_triples->{$name}}) { - my @copy_triple = @$triple; - push @copy, [ @copy_triple ]; - } - push @{$use_triples->{$name}}, @copy; - } - } -} - -# moses should use our config -if ($___DECODER_FLAGS =~ /(^|\s)-(config|f) / -|| $___DECODER_FLAGS =~ /(^|\s)-(ttable-file|t) / -|| $___DECODER_FLAGS =~ /(^|\s)-(distortion-file) / -|| $___DECODER_FLAGS =~ /(^|\s)-(generation-file) / -|| $___DECODER_FLAGS =~ /(^|\s)-(lmodel-file) / -) { - die "It is forbidden to supply any of -config, -ttable-file, -distortion-file, -generation-file or -lmodel-file in the --decoder-flags.\nPlease use only the --config option to give the config file that lists all the supplementary files."; -} - -# walk through all lambdas the user wishes to optimize and check -# if the number of lambdas matches -foreach my $name (keys %$use_triples) { - my $expected_lambdas = $lambdas_per_model->{$name}; - $expected_lambdas = 0 if !defined $expected_lambdas; - my $got_lambdas = defined $use_triples->{$name} ? scalar @{$use_triples->{$name}} : 0; - if ($got_lambdas != $expected_lambdas) { - if ($allow_unknown_lambdas && $expected_lambdas == 0) { - print STDERR "Allowing to optimize $name, although I have no idea what it is.\n"; - } else { - print STDERR "Wrong number of lambdas for $name. Expected (given the config file): $expected_lambdas, got: $got_lambdas. -Use --allow-unknown-lambdas to optimize lambdas that you are just introducing -and I cannot validate against the models mentioned in moses.ini.\n"; - exit 1; - } - } -} - -# as weights are normalized in the next steps (by cmert) -# normalize initial LAMBDAs, too -my $need_to_normalize = 1; - - - -my @order_of_lambdas_from_decoder = (); -# this will store the labels of scores coming out of the decoder (and hence the order of lambdas coming out of mert) -# we will use the array to interpret the lambdas -# the array gets filled with labels only after first nbestlist was generated - - - - -#store current directory and create the working directory (if needed) -my $cwd = `pwd`; chop($cwd); -safesystem("mkdir -p $___WORKING_DIR") or die "Can't mkdir $___WORKING_DIR"; - -{ -# open local scope - -#chdir to the working directory -chdir($___WORKING_DIR) or die "Can't chdir to $___WORKING_DIR"; - - - - -# set start run -my $start_run = 1; - -if ($continue) { - # need to load last best values - print STDERR "Trying to continue an interrupted optimization.\n"; - open IN, "finished_step.txt" or die "Failed to find the step number, failed to read finished_step.txt"; - my $step = <IN>; - chomp $step; - $step++; - close IN; - $start_run = $step +1; - - die "Can't start from step $step, because run$step.best$___N_BEST_LIST_SIZE.out.gz was not found!" - if ! -e "run$step.best$___N_BEST_LIST_SIZE.out.gz"; - - print STDERR "Reading last cached lambda values (result from step $step)\n"; - @order_of_lambdas_from_decoder = get_order_of_scores_from_nbestlist("gunzip -c < run$step.best$___N_BEST_LIST_SIZE.out.gz |"); - - open IN, "weights.txt" or die "Can't read weights.txt"; - my $newweights = <IN>; - chomp $newweights; - close IN; - my @newweights = split /\s+/, $newweights; - - # dump_triples($use_triples); - $use_triples = store_new_lambda_values($use_triples, \@order_of_lambdas_from_decoder, \@newweights); - # dump_triples($use_triples); -} - - - -# filter the phrase tables, use --decoder-flags -print "filtering the phrase tables... ".`date`; -my $cmd = "$filtercmd ./filtered $___CONFIG $___DEV_F"; -if (defined $___JOBS) { - safesystem("$qsubwrapper -command='$cmd' -queue-parameter=\"$queue_flags\"" ) or die "Failed to submit filtering of tables to the queue (via $qsubwrapper)"; -} else { - safesystem($cmd) or die "Failed to filter the tables."; -} - - -# the decoder should now use the filtered model -my $PARAMETERS; -$PARAMETERS = $___DECODER_FLAGS . " -config filtered/moses.ini"; - -my $devbleu = undef; -my $bestpoint = undef; -my $run=$start_run-1; -my $prev_size = -1; -while(1) { - $run++; - # run beamdecoder with option to output nbestlists - # the end result should be (1) @NBEST_LIST, a list of lists; (2) @SCORE, a list of lists of lists - - print "run $run start at ".`date`; - - # In case something dies later, we might wish to have a copy - create_config($___CONFIG, "./run$run.moses.ini", $use_triples, $run, (defined$devbleu?$devbleu:"--not-estimated--")); - - - # skip if the user wanted - if (!$skip_decoder) { - print "($run) run decoder to produce n-best lists\n"; - @order_of_lambdas_from_decoder = run_decoder($use_triples, $PARAMETERS, $run, \@order_of_lambdas_from_decoder, $need_to_normalize); - $need_to_normalize = 0; - safesystem("gzip -f run*out") or die "Failed to gzip run*out"; - } - else { - print "skipped decoder run\n"; - if (0 == scalar @order_of_lambdas_from_decoder) { - @order_of_lambdas_from_decoder = get_order_of_scores_from_nbestlist("gunzip -dc run*.best*.out.gz | head -1 |"); - } - $skip_decoder = 0; - $need_to_normalize = 0; - } - - my $EFF_REF_LEN = ""; - if ($___AVERAGE) { - $EFF_REF_LEN = "-a"; - } - - # To be sure that scoring script produses these fresh: - safesystem("rm -f cands.opt feats.opt") or die; - - # convert n-best list into a numberized format with error scores - - print STDERR "Scoring the nbestlist.\n"; - my $cmd = "export PYTHONPATH=$pythonpath ; gunzip -dc run*.best*.out.gz | sort -n -t \"|\" -k 1,1 | $SCORENBESTCMD $EFF_REF_LEN ".join(" ", @references)." ./"; - if (defined $___JOBS) { - safesystem("$qsubwrapper -command='$cmd' -queue-parameter=\"$queue_flags\"") or die "Failed to submit scoring nbestlist to queue (via $qsubwrapper)"; - } else { - safesystem($cmd) or die "Failed to score nbestlist"; - } - - - print STDERR "Hoping that scoring succeeded. We'll see if we can read the output files now.\n"; - - - # keep a count of lines in nbests lists (alltogether) - # if it did not increase since last iteration, we are DONE - open(IN,"cands.opt") or die "Can't read cands.opt"; - my $size=0; - while (<IN>) { - chomp; - my @flds = split / /; - $size += $flds[1]; - } - close(IN); - print "$size accumulated translations\n"; - print "prev accumulated translations was : $prev_size\n"; - if ($size <= $prev_size){ - print STDERR "No new hypotheses in nbest list. Stopping.\n"; - last; - } - $prev_size = $size; - - - # run cmert - # cmert reads in the file init.opt containing three lines: - # minimum values - # maximum values - # current values - # We need to prepare the files and **the order of the lambdas must - # correspond to the order @order_of_lambdas_from_decoder - - my @MIN = (); # lower bounds - my @MAX = (); # upper bounds - my @CURR = (); # the starting values - my @NAME = (); # to which model does the lambda belong - - # walk in order of @order_of_lambdas_from_decoder and collect the min,max,val - my %visited = (); - foreach my $name (@order_of_lambdas_from_decoder) { - next if $visited{$name}; - $visited{$name} = 1; - die "The decoder produced also some '$name' scores, but we do not know the ranges for them, no way to optimize them\n" - if !defined $use_triples->{$name}; - foreach my $feature (@{$use_triples->{$name}}) { - my ($val, $min, $max) = @$feature; - push @CURR, $val; - push @MIN, $min; - push @MAX, $max; - push @NAME, $name; - } - } - - open(OUT,"> init.opt") or die "Can't write init.opt (WD now $___WORKING_DIR)"; - print OUT join(" ", @MIN)."\n"; - print OUT join(" ", @MAX)."\n"; - print OUT join(" ", @CURR)."\n"; - close(OUT); - - #just for brevity - open(OUT,"> names.txt") or die "Can't write names.txt (WD now $___WORKING_DIR)"; - print OUT join(" ", @NAME)."\n"; - close(OUT); - - # make a backup copy labelled with this run number - safesystem("cp init.opt run$run.init.opt") or die; - - my $DIM = scalar(@CURR); # number of lambdas - $cmd="$cmertcmd -d $DIM"; - - print STDERR "Starting cmert.\n"; - if (defined $___JOBS) { - safesystem("$qsubwrapper -command='$cmd' -stderr=cmert.log -queue-parameter=\"$queue_flags\"") or die "Failed to start cmert (via qsubwrapper $qsubwrapper)"; - } else { - safesystem("$cmd 2> cmert.log") or die "Failed to run cmert"; - } - die "Optimization failed, file weights.txt does not exist or is empty" - if ! -s "weights.txt"; - # backup copies - safesystem ("cp cmert.log run$run.cmert.log") or die; - safesystem ("cp weights.txt run$run.weights.txt") or die; # this one is needed for restarts, too - print "run $run end at ".`date`; - - $bestpoint = undef; - $devbleu = undef; - open(IN,"cmert.log") or die "Can't open cmert.log"; - while (<IN>) { - if (/Best point:\s*([\s\d\.\-]+?)\s*=> ([\d\.]+)/) { - $bestpoint = $1; - $devbleu = $2; - last; - } - } - close IN; - die "Failed to parse cmert.log, missed Best point there." - if !defined $bestpoint || !defined $devbleu; - print "($run) BEST at $run: $bestpoint => $devbleu at ".`date`; - - my @newweights = split /\s+/, $bestpoint; - - # update my cache of lambda values - $use_triples = store_new_lambda_values($use_triples, \@order_of_lambdas_from_decoder, \@newweights); - - ## additional stopping criterion: weights have not changed - my $shouldstop = 1; - for(my $i=0; $i<@CURR; $i++) { - die "Lost weight! cmert reported fewer weights (@newweights) than we gave it (@CURR)" - if !defined $newweights[$i]; - if (abs($CURR[$i] - $newweights[$i]) >= $minimum_required_change_in_weights) { - $shouldstop = 0; - last; - } - } - - open F, "> finished_step.txt" or die "Can't mark finished step"; - print F $run."\n"; - close F; - - - if ($shouldstop) { - print STDERR "None of the weights changed more than $minimum_required_change_in_weights. Stopping.\n"; - last; - } - -} -print "Training finished at ".`date`; - -safesystem("cp init.opt run$run.init.opt") or die; -safesystem ("cp cmert.log run$run.cmert.log") or die; - -create_config($___CONFIG, "./moses.ini", $use_triples, $run, $devbleu); - -# just to be sure that we have the really last finished step marked -open F, "> finished_step.txt" or die "Can't mark finished step"; -print F $run."\n"; -close F; - - -#chdir back to the original directory # useless, just to remind we were not there -chdir($cwd); - -} # end of local scope - - -sub store_new_lambda_values { - # given new lambda values (in given order), replace the 'val' element in our triples - my $triples = shift; - my $names = shift; - my $values = shift; - - my %idx = (); - foreach my $i (0..scalar(@$values)-1) { - my $name = $names->[$i]; - die "Missed name for lambda $values->[$i] (in @$values; names: @$names)" - if !defined $name; - if (!defined $idx{$name}) { - $idx{$name} = 0; - } else { - $idx{$name}++; - } - die "We did not optimize '$name', but moses returned it back to us" - if !defined $triples->{$name}; - die "Moses gave us too many lambdas for '$name', we had ".scalar(@{$triples->{$name}}) - ." but we got at least ".$idx{$name}+1 - if !defined $triples->{$name}->[$idx{$name}]; - - # set the corresponding field in triples - # print STDERR "Storing $i-th score as $name: $idx{$name}: $values->[$i]\n"; - $triples->{$name}->[$idx{$name}]->[0] = $values->[$i]; - } - return $triples; -} - -sub dump_triples { - my $triples = shift; - - foreach my $name (keys %$triples) { - foreach my $triple (@{$triples->{$name}}) { - my ($val, $min, $max) = @$triple; - print STDERR "Triples: $name\t$val\t$min\t$max ($triple)\n"; - } - } -} - - -sub run_decoder { - my ($triples, $parameters, $run, $output_order_of_lambdas, $need_to_normalize) = @_; - my $filename_template = "run%d.best$___N_BEST_LIST_SIZE.out"; - my $filename = sprintf($filename_template, $run); - - print "params = $parameters\n"; - # prepare the decoder config: - my $decoder_config = ""; - my @vals = (); - foreach my $name (keys %$triples) { - $decoder_config .= "-$name "; - foreach my $triple (@{$triples->{$name}}) { - my ($val, $min, $max) = @$triple; - $decoder_config .= "%.6f "; - push @vals, $val; - } - } - if ($need_to_normalize) { - print STDERR "Normalizing lambdas: @vals\n"; - my $totlambda=0; - grep($totlambda+=abs($_),@vals); - grep($_/=$totlambda,@vals); - } - print STDERR "DECODER_CFG = $decoder_config\n"; - print STDERR " values = @vals\n"; - $decoder_config = sprintf($decoder_config, @vals); - print "decoder_config = $decoder_config\n"; - - # run the decoder - my $decoder_cmd; - if (defined $___JOBS) { - $decoder_cmd = "$moses_parallel_cmd -qsub-prefix mert$run -queue-parameters \"$queue_flags\" $parameters $decoder_config -n-best-file $filename -n-best-size $___N_BEST_LIST_SIZE -input-file $___DEV_F -jobs $___JOBS -decoder $___DECODER > run$run.out"; - } else { - $decoder_cmd = "$___DECODER $parameters $decoder_config -n-best-list $filename $___N_BEST_LIST_SIZE -i $___DEV_F > run$run.out"; - } - - safesystem($decoder_cmd) or die "The decoder died."; - - if (0 == scalar @$output_order_of_lambdas) { - # we have to peek at the nbestlist - return get_order_of_scores_from_nbestlist($filename); - } else { - # we have checked the nbestlist already, we trust the order of output scores does not change - return @$output_order_of_lambdas; - } -} - -sub get_order_of_scores_from_nbestlist { - # read the first line and interpret the ||| label: num num num label2: num ||| column in nbestlist - # return the score labels in order - my $fname_or_source = shift; - print STDERR "Peeking at the beginning of nbestlist to get order of scores: $fname_or_source\n"; - open IN, $fname_or_source or die "Failed to get order of scores from nbestlist '$fname_or_source'"; - my $line = <IN>; - close IN; - die "Line empty in nbestlist '$fname_or_source'" if !defined $line; - my ($sent, $hypo, $scores, $total) = split /\|\|\|/, $line; - $scores =~ s/^\s*|\s*$//g; - die "No scores in line: $line" if $scores eq ""; - - my @order = (); - my $label = undef; - foreach my $tok (split /\s+/, $scores) { - if ($tok =~ /^([a-z][0-9a-z]*):/i) { - $label = $1; - } elsif ($tok =~ /^-?[-0-9.e]+$/) { - # a score found, remember it - die "Found a score but no label before it! Bad nbestlist '$fname_or_source'!" - if !defined $label; - push @order, $label; - } else { - die "Not a label, not a score '$tok'. Failed to parse the scores string: '$scores' of nbestlist '$fname_or_source'"; - } - } - print STDERR "The decoder returns the scores in this order: @order\n"; - return @order; -} - -sub create_config { - my $infn = shift; # source config - my $outfn = shift; # where to save the config - my $triples = shift; # the lambdas we should write - my $iteration = shift; # just for verbosity - my $bleu_achieved = shift; # just for verbosity - - my %P; # the hash of all parameters we wish to override - - # first convert the command line parameters to the hash - { # ensure local scope of vars - my $parameter=undef; - print "Parsing --decoder-flags: |$___DECODER_FLAGS|\n"; - $___DECODER_FLAGS =~ s/^\s*|\s*$//; - $___DECODER_FLAGS =~ s/\s+/ /; - foreach (split(/ /,$___DECODER_FLAGS)) { - if (/^\-([^\d].*)$/) { - $parameter = $1; - $parameter = $ABBR2FULL{$parameter} if defined($ABBR2FULL{$parameter}); - } - else { - die "Found value with no -paramname before it: $_" - if !defined $parameter; - push @{$P{$parameter}},$_; - } - } - } - - # Convert weights to elements in P - foreach my $abbr (keys %$triples) { - # First delete all weights params from the input, in short or long-named version - delete($P{$abbr}); - delete($P{$ABBR2FULL{$abbr}}); - # Then feed P with the current values - foreach my $feature (@{$use_triples->{$abbr}}) { - my ($val, $min, $max) = @$feature; - my $name = defined $ABBR2FULL{$abbr} ? $ABBR2FULL{$abbr} : $abbr; - push @{$P{$name}}, $val; - } - } - - # create new moses.ini decoder config file by cloning and overriding the original one - open(INI,$infn) or die "Can't read $infn"; - delete($P{"config"}); # never output - print "Saving new config to: $outfn\n"; - open(OUT,"> $outfn") or die "Can't write $outfn"; - print OUT "# MERT optimized configuration\n"; - print OUT "# decoder $___DECODER\n"; - print OUT "# BLEU $bleu_achieved on dev $___DEV_F\n"; - print OUT "# We were before running iteration $iteration\n"; - print OUT "# finished ".`date`; - my $line = <INI>; - while(1) { - last unless $line; - - # skip until hit [parameter] - if ($line !~ /^\[(.+)\]\s*$/) { - $line = <INI>; - print OUT $line if $line =~ /^\#/ || $line =~ /^\s+$/; - next; - } - - # parameter name - my $parameter = $1; - $parameter = $ABBR2FULL{$parameter} if defined($ABBR2FULL{$parameter}); - print OUT "[$parameter]\n"; - - # change parameter, if new values - if (defined($P{$parameter})) { - # write new values - foreach (@{$P{$parameter}}) { - print OUT $_."\n"; - } - delete($P{$parameter}); - # skip until new parameter, only write comments - while($line = <INI>) { - print OUT $line if $line =~ /^\#/ || $line =~ /^\s+$/; - last if $line =~ /^\[/; - last unless $line; - } - next; - } - - # unchanged parameter, write old - while($line = <INI>) { - last if $line =~ /^\[/; - print OUT $line; - } - } - - # write all additional parameters - foreach my $parameter (keys %P) { - print OUT "\n[$parameter]\n"; - foreach (@{$P{$parameter}}) { - print OUT $_."\n"; - } - } - - close(INI); - close(OUT); - print STDERR "Saved: $outfn\n"; -} - -sub safesystem { - print STDERR "Executing: @_\n"; - system(@_); - if ($? == -1) { - print STDERR "Failed to execute: @_\n $!\n"; - exit(1); - } - elsif ($? & 127) { - printf STDERR "Execution of: @_\n died with signal %d, %s coredump\n", - ($? & 127), ($? & 128) ? 'with' : 'without'; - exit(1); - } - else { - my $exitcode = $? >> 8; - print STDERR "Exit code: $exitcode\n" if $exitcode; - return ! $exitcode; - } -} -sub ensure_full_path { - my $PATH = shift; - return $PATH if $PATH =~ /^\//; - $PATH = `pwd`."/".$PATH; - $PATH =~ s/[\r\n]//g; - $PATH =~ s/\/\.\//\//g; - $PATH =~ s/\/+/\//g; - my $sanity = 0; - while($PATH =~ /\/\.\.\// && $sanity++<10) { - $PATH =~ s/\/+/\//g; - $PATH =~ s/\/[^\/]+\/\.\.\//\//g; - } - $PATH =~ s/\/[^\/]+\/\.\.$//; - $PATH =~ s/\/+$//; - return $PATH; -} - - - - -sub scan_config { - my $ini = shift; - my $inishortname = $ini; $inishortname =~ s/^.*\///; # for error reporting - my $lambda_counts = shift; - # we get a pre-filled counts, because some lambdas are always needed (word penalty, for instance) - # as we walk though the ini file, we record how many extra lambdas do we need - # and finally, we report it - - # in which field (counting from zero) is the filename to check? - my %where_is_filename = ( - "ttable-file" => 3, - "generation-file" => 3, - "lmodel-file" => 3, - "distortion-file" => 0, - ); - # by default, each line of each section means one lambda, but some sections - # explicitly state a custom number of lambdas - my %where_is_lambda_count = ( - "ttable-file" => 2, - "generation-file" => 2, - ); - - open INI, $ini or die "Can't read $ini"; - my $section = undef; # name of the section we are reading - my $shortname = undef; # the corresponding short name - my $nr = 0; - my $error = 0; - my %defined_files; - my %defined_steps; # check the ini file for compatible mapping steps and actually defined files - while (<INI>) { - $nr++; - next if /^\s*#/; # skip comments - if (/^\[([^\]]*)\]\s*$/) { - $section = $1; - $shortname = $TABLECONFIG2ABBR{$section}; - next; - } - if (defined $section && $section eq "mapping") { - # keep track of mapping steps used - $defined_steps{$1}++ if /^([TG])/; - } - if (defined $section && defined $where_is_filename{$section}) { - # this ini section is relevant to lambdas - chomp; - my @flds = split / +/; - my $fn = $flds[$where_is_filename{$section}]; - if (defined $fn && $fn !~ /^\s+$/) { - # this is a filename! check it - if ($fn !~ /^\//) { - $error = 1; - print STDERR "$inishortname:$nr:Filename not absolute: $fn\n"; - } - if (! -s $fn) { - $error = 1; - print STDERR "$inishortname:$nr:File does not exist or empty: $fn\n"; - } - # remember the number of files used, to know how many lambdas do we need - die "No short name was defined for section $section!" - if ! defined $shortname; - - # how many lambdas does this model need? - # either specified explicitly, or the default, i.e. one - my $needlambdas = defined $where_is_lambda_count{$section} ? $flds[$where_is_lambda_count{$section}] : 1; - - print STDERR "Config needs $needlambdas lambdas for $section (i.e. $shortname)\n" if $verbose; - $lambda_counts->{$shortname}+=$needlambdas; - if (!defined $___LAMBDA && (!defined $default_triples->{$shortname} || scalar(@{$default_triples->{$shortname}}) != $needlambdas)) { - print STDERR "$inishortname:$nr:Your model $shortname needs $needlambdas weights but we define the default ranges for " - .scalar(@{$default_triples->{$shortname}})." weights. Cannot use the default, you must supply lambdas by hand.\n"; - $error = 1; - } - $defined_files{$shortname}++; - } - } - } - die "$inishortname: File was empty!" if !$nr; - close INI; - for my $pair (qw/T=tm=translation G=g=generation/) { - my ($tg, $shortname, $label) = split /=/, $pair; - $defined_files{$shortname} = 0 if ! defined $defined_files{$shortname}; - $defined_steps{$tg} = 0 if ! defined $defined_steps{$tg}; - if ($defined_files{$shortname} != $defined_steps{$tg}) { - print STDERR "$inishortname: You defined $defined_files{$shortname} files for $label but use $defined_steps{$tg} in [mapping]!\n"; - $error = 1; - } - } - exit(1) if $error; - return ($lambda_counts, \%defined_files); -} - |