Table of Contents

Joshua

Toto jsou Danovy poznámky k práci s hierarchickým překladovým dekodérem Joshuou (reimplementace Hiera (David Chiang) v Javě od lidí z JHU (Zhifei Li)).

Zdroje informací:

Instalace

Prerekvizity:

Nastavit důležité proměnné (to by se hodilo přidat do .cshrc nebo nějakého podobného konfiguračního souboru).

setenv JAVA_HOME /opt/jdk1.6
setenv SRILM /home/zeman/nastroje/srilm
setenv JOSHUA /net/work/people/zeman/joshua

Stáhnout aktuální verzi Joshuy:

cd $JOSHUA
svn co https://joshua.svn.sourceforge.net/svnroot/joshua/trunk joshua

Poznámka: Na začátku června 2009 jsem měl problémy se zdrojáky získanými přímo z SVN (třída SuffixArray neobsahovala metodu main()), ale možná to byl jen dočasný výpadek dokumentace, protože šlo o věci, které se vzápětí v nové release verzi dělají jinak. Každopádně mi vždy fungovalo, když jsem si ze

http://joshua.sourceforge.net/

přešel na link Download a stáhnul si aktuální release verzi.

Přeložit Joshuu:

cd $JOSHUA
ant compile

Kdyby bylo potřeba v budoucnosti překompilovat Joshuu načisto, již zkompilované moduly se dají odstranit pomocí

ant clean

Otestujeme, že je Joshua funkční:

ant test
./example/decode_example_javalm.sh
./example/decode_example_srilm.sh

Joshuu jsem překládal na zenu. Teď je ještě potřeba otestovat, že funguje i na clusteru, třeba na počítači sol1. A ejhle, na 64 bitech přestala fungovat spolupráce se SRILM.

21:35 sol1:/ha/work/people/zeman/joshua> ./example/decode_example_srilm.sh
Jun 1, 2009 9:35:43 PM joshua.decoder.JoshuaConfiguration readConfigFile
INFO: you use a LM feature function, so make sure you have a LM grammar
Exception in thread "main" java.lang.UnsatisfiedLinkError: /ha/work/people/zeman/joshua/lib/libsrilm.so: /ha/work/people/zeman/joshua/lib/libsrilm.so: wrong ELF class: ELFCLASS32 (Possible cause: architecture word width mismatch)
        at java.lang.ClassLoader$NativeLibrary.load(Native Method)
        at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1767)
        at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1692)
        at java.lang.Runtime.loadLibrary0(Runtime.java:840)
        at java.lang.System.loadLibrary(System.java:1047)
        at joshua.corpus.vocab.SrilmSymbol.<init>(SrilmSymbol.java:46)
        at joshua.decoder.JoshuaDecoder.initializeSymbolTable(JoshuaDecoder.java:322)
        at joshua.decoder.JoshuaDecoder.initialize(JoshuaDecoder.java:259)
        at joshua.decoder.JoshuaDecoder.<init>(JoshuaDecoder.java:108)
        at joshua.decoder.JoshuaDecoder.main(JoshuaDecoder.java:684)

Přestože SRILM, který nebyl přeložen na 64 bitech, běží jak na 32, tak na 64 bitech, s Joshuou na 64 bitech spolupracovat neumí (na 32 ano). Nepomůže ani když překlad Joshuy pustím až na 64 bitech (při spolupráci s 32bitovým SRILM).

Nicméně se zdá, že pomohlo následující:

Cluster

Ke spuštění Joshuy na clusteru se hodí např. Ondrova obálka (nebo i ta moje vlastní, ale tu bych neměl nutit např. Gauravovi, když jako jeden z mála pořád pracuju s tcsh).

kinit
ssh lrc-two
cd $JOSHUA
~bojar/tools/shell/qsubmit ./example/decode_example_srilm.sh
qstat -u '*'

Extrakce gramatiky

Joshua je nainstalován a funguje. Nyní se musíme naučit, jak ho trénovat a jak ho použít k překladu.

Nejdříve potřebujeme získat paralelní data, to je úkol mimo Joshuu.

Zastaralý návod

Pozor, následující poznámky pocházejí z doby, kdy jsem se učil pracovat s první verzí Joshuy, a jsou zastaralé! Níže je kopie mailu od Lana Schwartze, kde je vysvětlen postup pro Joshuu 1.3.

A takhle pustíme Joshuu, aby z trénovacích dat extrahoval gramatiku. Joshua z nějakého důvodu vyžaduje také testovací soubor se zdrojovým jazykem. Soudě podle příkladu, který dodali, stačí zkopírovat první větu ze zdrojových trénovacích dat. Gramatiku je pak ještě třeba seřadit, vyházet duplicitní pravidla a zagzipovat.

cd /net/work/people/zeman/hindstina
setenv SRC corpus/train.clean.en
setenv TGT corpus/train.clean.hi
setenv ALI model/aligned.grow-diag-final-and
setenv TST corpus/train.clean.en.1
setenv GRM en-hi.grammar
head -1 $SRC > $TST
java -cp $JOSHUA/bin joshua.prefix_tree.ExtractRules \
    --source=$SRC --target=$TGT --alignments=$ALI --test=$TST \
    --output=$GRM.unsorted --maxPhraseLength=5
sort -u $GRM.unsorted > $GRM
gzip $GRM

V příkladu v INSTALL.txt měli navíc ještě volbu –print-rules=false, nevím proč. Výsledná gramatika totiž byla prázdná, a když jsem tuto volbu odstranil, gramatika se vygenerovala.

Pozor, je poměrně snadné vyčerpat paměť. Tomu se dá čelit jednak tím, že se přesuneme na stroj, který má více paměti, jednak že zvolíme postup, který je složitější, ale k paměti šetrnější.

Binarizovat zdrojovou část korpusu.

java -cp $JOSHUA/bin joshua.corpus.suffix_array.SuffixArray \
    $WORK/corpus/train.clean.en \
    $WORK/model/vocab.en.bin \
    $WORK/model/corpus.en.bin \
    $WORK/model/suffixes.en.bin
java -cp $JOSHUA/bin joshua.corpus.suffix_array.SuffixArray \
    $WORK/corpus/train.clean.hi \
    $WORK/model/vocab.hi.bin \
    $WORK/model/corpus.hi.bin \
    $WORK/model/suffixes.hi.bin
java -cp $JOSHUA/bin joshua.corpus.alignment.AlignmentGrids \
    $WORK/model/aligned.grow-diag-final-and \
    $WORK/model/alignments.bin

Takto se extrahuje gramatika pro konkrétní testovací data s pomocí binarizovaného korpusu:

java -Xmx2000m -Xms2000m -cp $JOSHUA/bin joshua.prefix_tree.ExtractRules \
    --binary-source=true --binary-target=true \
    --source=model/corpus.en.bin --target=model/corpus.hi.bin \
    --source-vocab=model/vocab.en.bin --target-vocab=model/vocab.hi.bin \
    --source-suffixes=model/suffixes.en.bin --target-suffixes=model/suffixes.hi.bin \
    --alignmentsType=MemoryMappedAlignmentGrids --alignments=model/alignments.bin \
    --test=corpus/test.lowercased.en \
    --output=model/en-hi.grammar.unsorted \
    --maxPhraseLength=5

Nový návod pro Joshuu 1.3

Následuje výtah z mailu od Lana Schwartze, který vysvětluje, jak zadávat parametry při extrakci gramatiky s Joshuou 1.3. V dokumentaci u Joshuy jsem to nenašel.

The recommended way to extract a grammar is to configure an ant XML file for ExtractRules. All available parameters can be configured using that technique. The main method is meant now to just be a simple version for use if you don't need any custom configuration.

The current version of ExtractRules and its parameters are documented in my and Chris's paper at the most recent MT Marathon:
http://www.mtmarathon2010.info/web/Program_files/art-schwartz-callison-burch.pdf

The ant file should look something like this:

extract.xml:

<project name="JoshuaTasks">
 
	<!-- Define the path to Joshua class files -->
	<property name="classes.dir" 
		value="/path/to/joshua/bin"/>
 
	<!-- Define the ant task to compile a corpus into binary memory-mappable files -->
	<taskdef name="compileJosh" 
		classname="joshua.corpus.suffix_array.Compile" 
		classpath="${classes.dir}"/>
 
	<!-- Define the ant task to extract rules -->
	<taskdef name="extractRules" 
		classname="joshua.prefix_tree.ExtractRules" 
		classpath="${classes.dir}"/>
 
 
	<!-- Declare a target to compile a corpus -->
	<target name="compile_de-en" 
                      description="Compile josh dir for German-English">
		<compileJosh
			sourceCorpus="/path/to/train.de"
			targetCorpus="/path/to/train.en"
			alignments="/path/to/train.de-en.aln"
			outputDir="/path/to/output/de-en.josh"
		/>
	</target>
 
 
	<!-- Declare a target to extract a grammar -->
	<target name="extract_de-en"
                      description="Extract grammar for German-English">
		<extractRules
			joshDir="/path/to/output/de-en.josh"
			outputFile="/path/to/output/de-en.grammar"
			testFile="/path/to/test.de"
		/>
	</target>
 
 
	<!-- Declare a target to extract a grammar with other parameters-->
	<target name="extract_de-en-custom"
                      description="Extract grammar for German-English">
		<extractRules
			joshDir="/path/to/output/de-en.josh"
			outputFile="/path/to/output/de-en.grammar"
			testFile="/path/to/test.de"
			maxPhraseSpan="5"
			maxPhraseLength="5"
			requireTightSpans="true"
			edgeXViolates="false"
			sentenceInitialX="false"
			sentenceFinalX="false"
			ruleSampleSize="200"
			maxNonterminals="2"
		/>
	</target>
 
</project>

You can call this, with any of the targets that you define in extract.xml, using ant:

# Compile the corpus
ant -f extract.xml compile_de-en
 
# Extract rules using defaults
ant -f extract.xml extract_de-en
 
# Extract rules using custom settings
ant -f extract.xml extract_de-en-custom

Decoding

Jakmile máme gramatiku (tj. překladový model), můžeme dekódovat neboli překládat. Bývá sice zvykem ještě nejdříve vyladit váhy jednotlivých komponent pomocí MERTu, ale MERT sám už dekódování používá a teoreticky se můžeme spokojit s dekódováním pomocí odhadnutých, nevyladěných vah.

Parametry dekódování se zadávají prostřednictvím konfiguračního souboru. Vytvoříme si ho třeba tak, že zkopírujeme a upravíme konfigurační soubor, který byl přibalen k Joshuovi v jednom z příkladů (example2). Konfigurační soubor obsahuje následující parametry:

Cesta k souboru s jazykovým modelem. Zatím předpokládám, že to má být soubor ve formátu SRILM, i když SRILM nepoužijeme a místo něj použijeme javovské jazykové modelování, které je součástí Joshuy. Jazykový model může být zagzipován. Cesta k souboru může být relativní.

lm_file=$WORK/lm/train.lowercased.hi.lm

Cesta k souboru s překladovým modelem, tedy s gramatikou vyextrahovanou pro daný testovací soubor. Formát má být stejný, jako produkuje Hiero. Předpokládám, že je to i defaultní formát, který produkuje Joshua, po setřídění s odstraněním duplicit a po zagzipování.

tm_file=$WORK/model/en-hi-dev.grammar.gz
tm_format=hiero

Jakýsi spojovací soubor, glue_file. Vůbec nevím, co to je. Joshua ale jeden obsahuje a vypadá dost obecně, takže možná nezávisí na konkrétních trénovacích datech.

glue_file=$JOSHUA/grammars/hiero.glue
glue_format=hiero

Konfigurace jazykového modelu. Kopíruju ji z example2, akorát měním order na 3, protože jsem trénoval trigramy, nikoli čtyřgramy.

#lm config
use_srilm=false
lm_ceiling_cost=100
use_left_euqivalent_state=false
use_right_euqivalent_state=false
order=3

Konfigurace překladového modelu. Kopíruju ji z example2, aniž bych tušil, co znamená.

#tm config
span_limit=10
phrase_owner=pt
mono_owner=mono
begin_mono_owner=begin_mono
default_non_terminal=X

#pruning config
fuzz1=0.1
fuzz2=0.1
max_n_items=30
relative_threshold=10.0
max_n_rules=50
rule_relative_threshold=10.0

Konfigurace N-best listu (dekodér vrací N překladových hypotéz, které se mu jeví jako nejlepší, seřazených podle skóre, které jim přiřadil). Pro MERT potřebujeme N nejlepších hypotéz, abychom mohli každou z nich porovnat s referenčními překlady, spočítat BLEU skóre a případně upravit váhy, pokud má nejlepší BLEU skóre hypotéza, která se celkovým skóre nedostala na začátek seznamu, ale při jiném vyvážení komponent by se tam mohla dostat. Pro závěrečný překlad testovacích dat obvykle N nejlepších hypotéz nepotřebujeme, chceme znát rovnou tu, kterou systém považuje za úplně nejlepší.

#nbest config
use_unique_nbest=true
use_tree_nbest=false
add_combined_cost=true
top_n=300

Další sekce se týkají vzdáleného serveru pro jazykové modelování a paralelního dekodéru. Tyto sekce vynechávám, protože se nezdá, že bychom je v současné době využili.

Následují váhy jednotlivých komponent (“features”), tedy jazykového modelu, frázového modelu a případně dalších.

###### model weights
#lm order weight
lm 1.000000

#phrasemodel owner column(0-indexed) weight
phrasemodel pt 0 1.066893
phrasemodel pt 1 0.752247
phrasemodel pt 2 0.589793

#arityphrasepenalty owner start_arity end_arity weight
#arityphrasepenalty pt 0 0 1.0
#arityphrasepenalty pt 1 2 -1.0

#phrasemodel mono 0 0.5

#wordpenalty weight
wordpenalty -2.844814

No a nakonec příkaz, kterým pustíme Joshuu s naším konfiguračním souborem (na clusteru):

qsub.csh \
    "java -Xmx1200m -Xms1200m -cp $JOSHUA/bin joshua.decoder.JoshuaDecoder \
     ~zeman/projekty/hindstina/joshua-config-tides-dev.txt \
     $HINDI/corpus/dev.lowercased.en \
     $HINDI/dev.nbest.out"

I když si v konfiguračním souboru řekneme, že chceme N-best výstup pro N=1, dostaneme ho ve formátu, který je připraven na více hypotéz (každý překlad např. obsahuje skóre a váhy). Abychom z toho dostali obyčejný 1-best překlad, můžeme použít Zhifeiův skript, který je k Joshuovi přibalen ve složce example2:

$JOSHUA/example2/get_1best_from_Nbest.pl $HINDI/output/dev.nbest.out $HINDI/output/dev.1best.out

Vyhodnocení úspěšnosti

1-best výstup Joshuy a referenční překlad bychom mohli převést do příslušného XML formátu a pustit na ně oficiální externí perlový skript, který počítá BLEU skóre. Pro výsledná čísla do článku bychom to tak také měli udělat.

Jinak ale Joshua obsahuje svůj vlastní kód pro vyhodnocování. Přehled parametrů jeho volání se dozvíme, když ho zavoláme bez argumentů:

java -cp $JOSHUA/bin joshua.util.JoshuaEval

S využitím většiny defaultů stačí dodat jméno vyhodnocovaného souboru a jméno souboru s referenčním překladem:

java -cp $JOSHUA/bin joshua.util.JoshuaEval \
    -cand $HINDI/output/dev.1best.out \
    -ref $HINDI/corpus/dev.lowercased.hi

Bez MERTu mi zatím vyšlo BLEU = 0.0807.

MERT

Celý proces by nebyl úplný bez MERTu, což je zkratka za Minimum Error Rate Training. V principu jde o to, že se dekodér opakovaně pouští na vývojová data a na základě změřené úspěšnosti se postupně optimalizují váhy překladového modelu, jazykového modelu a případně dalších složek. V Joshuovi je pro tento účel integrován javovský nástroj Z-MERT.

Většina parametrů se Z-MERTu předává prostřednictvím konfiguračního souboru. Přehled volání Z-MERTu se dozvíme takto:

java -cp $JOSHUA/bin joshua.zmert.ZMERT -h

Příklad použití Z-MERTu najdeme ve složce $JOSHUA/ZMERT_example. Můžeme si zkopírovat a upravit tamější konfigurační soubor ZMERT_config_ex2.txt. Některé numerické parametry (počáteční váhy například) jsou v samostatném souboru params.txt, který si také musíme zkopírovat. Pro jistotu si také uděláme kopii vývojových dat.

Dále potřebujeme soubor (vlastně skript: měl by být spustitelný), ve kterém bude napsáno, jak volat dekodér. A, samozřejmě, také soubor s konfigurací dekodéru.

cd $HINDI/mert
cp ../corpus/dev.lowercased.en src.txt
cp ../corpus/dev.lowercased.hi ref.txt
cp $JOSHUA/ZMERT_example/config_ex2.txt decoder-config.txt
vi decoder-config

Změněné parametry:

lm_file=/net/work/people/zeman/hindstina/lm/train.lowercased.hi.lm
tm_file=/net/work/people/zeman/hindstina/model/en-hi-dev.grammar.gz
glue_file=/net/work/people/zeman/joshua/grammars/hiero.glue
order=3
cp $JOSHUA/ZMERT_example/decoder_command_ex2 decoder_command
chmod 755 decoder_command
vi decoder_command

Ve volání dekodéru musíme změnit cesty ke konfiguračnímu, zdrojovému a cílovému souboru.

java -Xmx1200m -Xms1200m -cp $JOSHUA/bin joshua.decoder.JoshuaDecoder \
    $HINDI/mert/decoder-config.txt \
    $HINDI/mert/src.txt \
    $HINDI/mert/nbest.txt

V konfiguraci Z-MERTu zohledníme právě upravené volání a konfiguraci dekodéru.

cp $JOSHUA/ZMERT_example/params.txt .
cp $JOSHUA/ZMERT_example/ZMERT_config_ex2.txt zmert-config.txt
vi zmert-config.txt

Změněné parametry:

-dir /net/work/people/zeman/hindstina/mert # working directory
-s src.txt # source sentences file name
-r ref.txt # target sentences file name
-rps 1     # references per sentence
-maxIt 5   # maximum MERT iterations
-cmd ./decoder_command # file containing commands to run decoder
-decOut nbest.txt # file produced by decoder
-dcfg decoder-config.txt # decoder config file

No a teď už můžeme spustit Z-MERT (případně to celé dát do uvozovek a před to ~zeman/bin/qsub.csh):

java -cp $JOSHUA/bin joshua.zmert.ZMERT -maxMem 500 \
    $HINDI/mert/zmert-config.txt \
    > $HINDI/mert/zmert.out

Troubleshooter

Grammar extraction: Negative array size

If you encounter this exception during corpus binarization or (in older releases of Joshua) during grammar extraction, check your alignment file whether it matches your source and target corpus. Did you switch translation direction accidentially? The alignment file must have the same number of lines as your source and target corpus, one line per sentence (segment) pair. The “tokens” on each line are pairs of numbers, such as “0-0 1-2 2-2 3-5”. The first number in each pair is the index to the source sentence (first token has index 0) and the second number is index to the target sentence. By switching the source and the target, you are likely to cause some indices to point out of the sentence, and you are in trouble.

Another source of this error could be sentences with 100 or more words. This is not a strict limit, often I was able to extract grammars for corpora unchecked for such sentences, but according to Lance Schwartz, long sentences can cause problems. (For me, filtering out such sentences helped with es-en WMT08 training data.) And after all, they are suspicious anyway, and their contribution to the learnt model is doubtful.

ZMERT: corrupted temp file

Hi all,

does the following ZMERT exception look familiar to anyone? My only idea was that the nbest output from the decoder is corrupted somehow. However, I cannot find anything strange in it, such as sequence of more then three “|||” etc.

Thanks,
Dan

zmert.out:


tmpDirPrefix: /ha/work/people/zeman/wmt/experiments/obo-max/mert/ZMERT.
Processed the following args array:
 -dir /ha/work/people/zeman/wmt/experiments/obo-max/mert -s src.txt -r ref.txt -rps 1 -p params.txt -m BLEU 4 closest -maxIt 5 -ipi 20 -cmd ./decoder.pl -decOut nbest.txt -dcfg decoder-config.txt -N 300 -v 1 -seed 12341234

----------------------------------------------------
Initializing...
----------------------------------------------------

Random number generator initialized using seed: 12341234

Number of sentences: 2051
Number of documents: 1
Optimizing BLEU
docSubsetInfo: {0, 1, 1, 1, 1, 0, 0}
Number of features: 5
Feature names: {"lm","phrasemodel pt 0","phrasemodel pt 1","phrasemodel pt 2","wordpenalty"}

c    Default value    Optimizable?    Crit. val. range    Rand. val. range
1     1.0000         Yes         [0.1,Infinity]         [0.5,1.5]
2     1.0669         Yes         [-Infinity,Infinity]         [-1.0,1.0]
3     0.7522         Yes         [-Infinity,Infinity]         [-1.0,1.0]
4     0.5898         Yes         [-Infinity,Infinity]         [-1.0,1.0]
5     -2.8448         Yes         [-Infinity,Infinity]         [-5.0,0.0]

Weight vector normalization method: weights will be scaled so that the "lm" weight has an absolute value of 1.0.

----------------------------------------------------

----------------------------------------------------
Z-MERT run started @ Sat Mar 06 23:52:57 CET 2010
----------------------------------------------------

Initial lambda[]: {1.0, 1.066893, 0.752247, 0.589793, -2.844814}

--- Starting Z-MERT iteration #1 @ Sat Mar 06 23:52:57 CET 2010 ---
Decoding using initial weight vector {1.0, 1.066893, 0.752247, 0.589793, -2.844814}
Running external decoder...
...finished decoding @ Sun Mar 07 00:02:33 CET 2010
Reading candidate translations from iterations 1-1
(and computing BLEU sufficient statistics for previously unseen candidates)
 Progress:
Exception in thread "main" java.lang.NumberFormatException: For input string: "||||||"
   at java.lang.NumberFormatException.forInputString(NumberFormatException.java:48)
   at java.lang.Integer.parseInt(Integer.java:449)
   at java.lang.Integer.parseInt(Integer.java:499)
   at joshua.zmert.MertCore.run_single_iteration(MertCore.java:1071)
   at joshua.zmert.MertCore.main(MertCore.java:3129)
Z-MERT exiting prematurely (MertCore returned 1)...

Omar's response:

Hi Dan,

The “||||||” sequence is in a temp file, not the decoder's output. If
if there are any *temp* (or *tmp*) files in the folder from earlier
runs, make sure you delete them first, then try launching Z-MERT
again. Such files are left over from runs that crash. Z-MERT does
not delete them because they can be used to restart Z-MERT from the
point where it crashed. But that assumes the crash is due to power
loss or an interrupted job, etc. In your case, I think what happened
is that a prior run crashed because of an external problem in the
setup itself, which you fixed and tried to restart Z-MERT. For that
reason, Z-MERT should not be using those temp files in the first
place, but when it sees them there, it assumes it can use them because
the user did not delete them.

Let me know if that's not the case.

O.Z.