openTMS verwendet das Konzept der "Data Source" zum Übersetzen. Eine Data Source ist einfach ausgedrückt eine Klasse + Methoden, die Übersetzungsfunktionalität zur Verfügung stellt. Durch das Implementieren einer solchen Klasse kann openTMS sehr einfach um neue Übersetzungsmöglichkeiten erweitert werden.
In den folgenden Beiträgen zeige ich, wie verschiedene solche Data Sourcen - als erstes am Beispiel des Einsatzes des statistischen maschinellen Übersetzungssystems MOSES - implementiert werden kann.
Ich starte mit einem einfachen Beispiel. Eine Data Source, die nur "übersetzen" kann, aber keine neuen Übersetzungen dauerhaft speichern kann. Wie Speichern von neuen Übersetzungen implementiert wird, werde ich in einem Folgebeitrag zeigen.
Ein typischer Beispiel für eine Data Source, die nur "übersetzen" kann, ist maschinelle Übersetzung. Daher will ich als erstes Beispiel die Implementierung einer MT Data Source darstellen. Als MT System habe ich dafür Moses ausgewählt. Moses ist ein statistisches MT System. Information zu Moses finde sich hier: http://www.statmt.org/moses/
Im folgenden nehme ich an, dass Moses zur Verfügung steht. In meinem Fall stehen die relevanten Dateien hier:
Die ausführbare Anwendung: c:\projekte\MT-Systems\Moses\Release\moses-cmd
Moses ini Datei: c:\projekte\MT-Systems\Moses\sample\sample-models2\phrase-model\moses.ini
Beispieldatei zum Übersetzen: c:/Program Files/OpenTMS/test/tekom2009/moses.xlf
Die Beispieldatei sieht so aus (nur der body Teil des xliff Dokuments):
<body>
<trans-unit approved="no" help-id="0" id="0" reformat="yes" translate="yes" xml:space="preserve">
<source xml:lang="de">das ist ein kleines haus </source>
<target />
<trans-unit>
<trans-unit approved="no" help-id="1" id="1" reformat="yes" translate="yes" xml:space="preserve">
<source xml:lang="de">ein haus ist das</source>
<target />
<trans-unit>
<trans-unit approved="no" help-id="2" id="2" reformat="yes" translate="yes" xml:space="preserve">
<source xml:lang="de">das ist ein kleines auto</source>
<target />
<trans-unit>
<trans-unit approved="no" help-id="3" id="3" reformat="yes" translate="yes" xml:space="preserve">
<source xml:lang="de">Vorwort</source>
<target />
<trans-unit>
<body>
Wie sieht nun die Implementierung einer Data Source für MOSES aus?
Eine Data Source muss das Interface de.folt.models.datamodel.DataSource implementieren. Dazu gibt es bereits eine Klasse, die ein Grundgerüst für dieses Interface zur Verfügung stellt: de.folt.models.datamodel.BasicDataSource.
Daher deklarieren wir uns eine Klasse MTMoses als public class MTMoses extends BasicDataSource.
Im nächsten Schritt müssen wir die notwendigen Methoden zur Verfügung stellen.
Als erstes brauchen wir eine Konstruktor für die Data Source:
/**
* @param dataSourceProperties
*/
public MTMoses(DataSourceProperties dataSourceProperties)
{
super(dataSourceProperties);
mosescommand = (String) dataSourceProperties.get("MosesCommand");
if (mosescommand == null)
mosescommand = OpenTMSProperties.getInstance().getOpenTMSProperty("MTMoses.MosesCommand");
if ((mosescommand == null) || mosescommand.equals(""))
return;
mosesinifile = (String) dataSourceProperties.get("MosesIniFile");
if (mosesinifile == null)
mosesinifile = OpenTMSProperties.getInstance().getOpenTMSProperty("MTMoses.MosesIniFile");
mosesParameterString = (String) dataSourceProperties.get("MosesParameterString");
if (mosesParameterString == null)
mosesParameterString = OpenTMSProperties.getInstance().getOpenTMSProperty("MTMoses.MosesParameterString");
}
Dieser Konstruktor wird z.B. von DataSourceInstance.createInstance verwendet um eine Data Source zu initialisieren. In unserem Fall macht der Konstruktor nicht sehr viel, er läde eine Paramater aus der opentms.properties Datei mittels OpenTMSProperties.getInstance().getOpenTMSProperty.
Wichtig ist die Methode getDataSourceType:
/* (non-Javadoc)
* @see de.folt.models.datamodel.BasicDataSource#getDataSourceType()
*/
@Override
public String getDataSourceType()
{
return MTMoses.class.getName();
}
Sie gibt den Typ einer Data Source zurück. Einfach zu implementieren...
Nun kommt die wichtigste Methode - die translate Methode. Beim Übersetzen eines Xliff Dokuments wird das source Element jeder trans-unit damit übersetzt und kann eine neues alt-trans Element einfügen.
Wie wird mit Moses übersetzt?
Moses - genauer die Anwendung moses-cmd wird u.a.folgendermassen aufgerufen (andere Varianten möglich):
c:/projekte/MT-Systems/Moses/Release/moses-cmd -f moses ini file -input-file inputfile -t -d
Dies lievert dann auf stdout für jede Zeile im inputfile eine Übersetzung. Das Parsen der Ausgabe ist etwa kompliziert, läuft aber folgendermassen ab:
Es wird im Ausgabestrom die Zeichenkette "BEST TRANSLATION:" gesucht. Wenn diese passt, ist in der nächsten Zeile die Übersetzung enthalten. Hier ein Beispiel:
Translating: das ist ein kleines haus Collecting options took 0.000 seconds Search took 0.000 seconds BEST TRANSLATION: this is a small house [11111] [total=-601.833] <<0.000, -5.000, 0.000, -600.000, -1.833>> this is |0-1| a |2-2| small |3-3| house |4-4| Translation took 0.000 seconds 0.000,>
Durch die gewählten Optionen steht bei jedem Wort ein Verweis zu dne Wörtern des Ausgangsatzes. Ich belasse dies in der Übersetzung, eine komplexere Implementierung könnte diese Information benützen, um die Formatinformationen richtig zu zuordnen.
Hier die Implementierung mit entsprechenden Kommentaren versehen. Zuerst der Kommentar aus der Java Dokumentation:
translate translates a trans-unit given the source language, target Language and match similarity
Parameters:
transUnit the trans unit to translate to use
file the file element currently to translate
xliffDocument the basic xliff document
sourceLanguage the source language to use
targetLanguage the target language to use
matchSimilarity the similarity (fuzzy) match quality (0 - 100) to use
translationParameters the hash table contains parameters which control some parameters,
e.g. should header/source/target properties be written to alt-trans
Returns:
the modified trans-unit with new translation
Nun die Methode mit einigen Erläuterungen im Code:
public Element translate(Element transUnit, Element file, XliffDocument xliffDocument, String sourceLanguage, String targetLanguage, int matchSimilarity,
Hashtable translationParameters) throws OpenTMSException
{
Element source = transUnit.getChild("source");
segment enthält den Inhalt des source Elements.
String segment = xliffDocument.elementContentToString(source);
Dieser folgende Teil ist Moses spezifisch.
In einem ersten Schritt (beim ersten Segment) werden alle trans-units gelesen
und die (reinen) Textsegmente in eine Ausgabedatei (Xliff-Datename + ".out") ohne Tags und Zeilenumbrüche geschrieben.
if (bFirstSegment)
{
bFirstSegment = false;
String inFileName = xliffDocument.getXmlDocumentName() + ".in";
boolean bRemoveCRLF = true;
boolean bRemoveTags = true;
boolean bSuccess = xliffDocument.exportToTextFile(file, inFileName, bRemoveCRLF, bRemoveTags);
if (!bSuccess)
{
return transUnit;
}
Hier wird nun Moses aufgerufen und die Übersetzungen ermittelt.
Für jeden Satz der Ausgabedatei wird eine Übersetzung ermittelt und in einem Vektor gespeichert..
translations ist als Klassenvariable definiert!
translations = runMoses(inFileName);
// now get all segments from input file...
if (!bSuccess)
{
return transUnit;
}
}
if (translations.size() == 0)
{
return transUnit;
}
Im nächsten Schritt werden die Übersetzungen zu den jeweiligen trans-units als alt-trans Elemente hinzugefügt.
if (translations.size() > currentTu)
{
Zum Ppeichern eines neuen alt-trans Elementes verwenden wir die Methode
xliffDocument.addAltTrans(transUnit, sourceMono, targetmonos, (int) 0, translationParameters);
Diese Methode fügt zum Element transUnit ein neues alt-trans Element hinzu.
sourceMono ist dabei eine MonoLingualObject mit dem Quellsatz (Ausgangssegment).
targetmonos eine Vector von MonoLingualObject mit Übersetzungen.
In unserem Fall gibt es aber nur eine Übersetzung.
Das MultiLingualObject multi wird hier nur zu Demonstrationsgründen erzeugt, hat hier keine weitere Bedeutung.
Vector targetmonos = new Vector();
MultiLingualObject multi = new MultiLingualObject();
MonoLingualObject sourceMono = new MonoLingualObject();
Speichere Quellsegement und Ausgangssprache
sourceMono.setFormattedSegment(segment);
sourceMono.setLanguage(sourceLanguage);
MonoLingualObject targetMono = new MonoLingualObject();
Speichere Übersetzung mit Zielsprache
targetMono.setFormattedSegment(translations.get(currentTu));
targetMono.setLanguage(targetLanguage);
multi.addMonoLingualObject(sourceMono);
multi.addMonoLingualObject(targetMono);
targetmonos.add(targetMono);
Nun Einfügen der Übersetzung.
Element alttrans = xliffDocument.addAltTrans(transUnit, sourceMono, targetmonos, (int) 0, translationParameters);
Setzen des origin Atributes des neuen alt-trans Elementes.
alttrans.setAttribute("origin", this.getDataSourceType());
Erhöhe Segmentzähler...
currentTu++;
}
return transUnit;
}
Damit ist der wichtigste Teil der Implementierung erledigt. Die Methode runMoses - ohne viel Kommentar - sieht so aus. Sie speichert alle Übersetzungen in einem Vector ab.
public Vector runMoses(String inFile)
{
Vector translations = new Vector();
try
{
ProcessBuilder mosespb = new ProcessBuilder(mosescommand, "-f", mosesinifile, "-input-file", inFile, "-t", "-d");
mosespb.redirectErrorStream(true);
mosesProcess = mosespb.start();
if (mosesProcess == null)
return translations;
InputStream inputstream = mosesProcess.getInputStream();
InputStreamReader mosesInp = new InputStreamReader(inputstream);
BufferedReader mosesReader = new BufferedReader(mosesInp, 10000);
String response = "";
boolean bTranslation = false;
while ((response = mosesReader.readLine()) != null)
{
if (bTranslation)
{
translations.add(response);
bTranslation = false;
}
if (response.startsWith("BEST TRANSLATION:"))
{
bTranslation = true;
}
}
}
catch (Exception e)
{
e.printStackTrace();
return translations;
}
return translations;
}
Damit ist unsere Data Source MTMoses benahe fertig. Optional - damit in den Anwendungen einfacher aufrufbar kann mittels der Methode createDataSource (dient normalerweise zum Erzeugen von Datenbank o.ä.) ein Eintrag in der Datei database/OpenTMSDataSource.config.xml erzeugt werden, der alle erzeugten Data Sources enthält. Bei MT Systemem reicht dazu die Methode einmal aufzurufen, da es ja keine unterschiedlichen Varianten gibt (ok, man könnte verschiedene Varianten für verschiedene moses.ini Dateien anlegen...).
public boolean createDataSource(DataSourceProperties dataModelProperties) throws OpenTMSException
{
String dataSourceName = (String) dataModelProperties.get("dataSourceName");
String dataSourceConfigurationsFile = (String) dataModelProperties.get("dataSourceConfigurationsFile");
dataSourceConfigurationsFile = DataSourceConfigurations.getConfigurationFileName(dataSourceConfigurationsFile);
if (dataSourceConfigurationsFile == null)
{
return false;
}
BasicDataSource sqldatasource = new BasicDataSource();
File fhd = new File(dataSourceConfigurationsFile);
if (!fhd.exists())
{
File fx = new File(sqldatasource.getDefaultDataSourceConfigurationsFileName());
if (!fx.exists())
{
return false;
}
dataSourceConfigurationsFile = sqldatasource.getDefaultDataSourceConfigurationsFileName();
}
dataSourceProperties.put("dataSourceConfigurationsFile", dataSourceConfigurationsFile);
DataSourceConfigurations config = new DataSourceConfigurations(dataSourceConfigurationsFile);
if (config.bDataSourceExistsInConfiguration(dataSourceName))
return true;
DataSourceProperties props = new DataSourceProperties();
props.put("dataSourceConfigurationsFile", dataSourceConfigurationsFile);
props.put("dataSourceName", dataSourceName);
config.addConfiguration(dataSourceName, this.getDataSourceType(), props);
config.saveToXmlFile();
return true;
}
In der database/OpenTMSDataSource.config.xml erhält man dann den Eintrag:
<DataSourceConfiguration name="MT:MOSES" creator="klemens" creationDate="21.11.2009 18:59:36:114" datasourcetype="de.folt.models.datamodel.mtmoses.MTMoses">
<property name="dataSourceName">MT:MOSES</property>
<property name="dataSourceConfigurationsFile">c:/Program Files/OpenTMS/database/OpenTMSDataSource.config.xml</property>
</DataSourceConfiguration>
Damit kann die Data Source in Anwendungen sofort identifiziert werden. Hier einige Beispiel-Aufrufe:
Erzeuge MOSES Data Source in OpenTMSDataSource.config.xml
call datasourcecreate.bat "MT:MOSES" "de.folt.models.datamodel.mtmoses.MTMoses" "c:/Program Files/OpenTMS/database/OpenTMSDataSource.config.xml"
wobei datasourcecreate folgendermassen aussieht:
call env.bat java -cp "%JARS%" -Xmx1000M de.folt.rpc.connect.Interface -method runCreateDB -dataSourceName %1 -dataSourceType %2 -dataSourceConfigurationsFile %3 -sourceLanguage %4 -targetLanguage %5 -encoding %6
Die Funktion de.folt.rpc.connect.Interface stellt dabei ein Interface zu den verschiedenen openTMS Methoden auf Kommandozeilenebene zur Verfügung. Die bat Dateien sind dabei in der Installation zu Demonstrationszwecken enthalten. Hier wird durch -method runCreateDB die Data Source erzeugt.
Übersetzte c:/Program Files/OpenTMS/test/tekom2009/moses.xlf mit MOSES Data Source MT:MOSES
call translate "MT:MOSES" "c:/Program Files/OpenTMS/test/tekom2009/moses.xlf" de en 70 "c:/Program Files/OpenTMS/database/OpenTMSDataSource.config.xml"
wobei hier translate so aussieht:
call env.bat java -cp "%JARS%" -Xmx1000M de.folt.rpc.connect.Interface -method runTranslateDocument -dataSourceName %1 -sourceDocument %2 -sourceLanguage %3 -targetLanguage %4 -similarity %5 -dataSourceConfigurationsFile %6 -inputDocumentType FILE > translate.log 2>&1
-method runTranslateDocument konvertiert und übersetzt dabei ein sourceDoument.
Zum Schluss nun das Übersetzungsergebnis (Auszug und gekürzt):
<body> <trans-unit approved="no" help-id="0" id="0" reformat="yes" translate="yes" xml:space="preserve"> <source xml:lang="de">das ist ein kleines haus </source> <target /> <alt-trans match-quality="0" id="521b1711-8c13-4223-b1a0-e478a08d64d0" xml:space="preserve" origin="de.folt.models.datamodel.mtmoses.MTMoses"> <source xml:lang="de">das ist ein kleines haus </source> <target xml:lang="en">this is |0-1| a |2-2| small |3-3| house |4-4| </target> </alt-trans> </trans-unit> <trans-unit approved="no" help-id="1" id="1" reformat="yes" translate="yes" xml:space="preserve"> <source xml:lang="de">ein haus ist das</source> <target /> <alt-trans match-quality="0" id="6e9c819e-e3a6-4c4a-a24f-23f1ac6300e6" xml:space="preserve" origin="de.folt.models.datamodel.mtmoses.MTMoses"> <source xml:lang="de">ein haus ist das</source> <target xml:lang="en">a |0-0| house |1-1| is |2-2| the |3-3| </target> </alt-trans> </trans-unit> <trans-unit approved="no" help-id="2" id="2" reformat="yes" translate="yes" xml:space="preserve"> <source xml:lang="de">das ist ein kleines auto</source> <target /> <alt-trans match-quality="0" id="9c6a8fd9-1065-4d28-9e93-e2ab6f4df08c" xml:space="preserve" origin="de.folt.models.datamodel.mtmoses.MTMoses"> <source xml:lang="de">das ist ein kleines auto</source> <target xml:lang="en">this is |0-1| a |2-2| small |3-3| car |4-4| </target> </alt-trans> </trans-unit> <trans-unit approved="no" help-id="3" id="3" reformat="yes" translate="yes" xml:space="preserve"> <source xml:lang="de">Vorwort</source> <target /> <alt-trans match-quality="0" id="d07987fa-3aad-41fe-acea-2bbb4d8bb4f7" xml:space="preserve" origin="de.folt.models.datamodel.mtmoses.MTMoses"> <source xml:lang="de">Vorwort</source> <target xml:lang="en">Vorwort |0-0| </target> </alt-trans> </trans-unit> </body>
Mit ein einigen einfachen Schritten haben wir damit eine neue Data Source zu openTMS hinzugefügt.
Im nächsten Teil werde ich zeigen, wie man eine Data Source hinzufügt, die ein bisschen mehr kann, nämlcih unscharfe Suche durchführen und neue Übersetzungen abspeichern kann. Der Quell Code zu MTMoses ist übrigens in der Datei lib/opentms.jar enthalten. Hier können Sie das Beispiel herunterladen: http://www.heartsome.de/arayatest/MTMoses.java sowie meine moses.ini Datei http://www.heartsome.de/arayatest/moses.ini
Noch eine Anmerkung: die Klasse MTMoses enthält noch drei weitere Methoden, die derzeit nicht von der Data Source verwendet werden, aber zum Testen mit Moses und zur Integration in Java Anwendungen nützlich sein können.
start() startet einen Moses Prozess
stop() stoppt den Moses Prozess
translate(String segment) übersetzt ein Segment.
So kann man diese Funktionen einsetzen:
DataSourceProperties model = new DataSourceProperties();
model.put("dataModelClass", "de.folt.models.datamodel.mtmoses.MTMoses");
if (args.length == 0)
{
try
{
MTMoses mtmoses = (MTMoses) DataSourceInstance.createInstance("Moses", model);
mtmoses.start();
String segment = "mein kleines haus";
String result = mtmoses.translate(segment);
System.out.println(segment + " = " + result);
segment = "kleines haus";
result = mtmoses.translate(segment);
System.out.println(segment + " = " + result);
segment = "ich wohne in einem kleines haus";
result = mtmoses.translate(segment);
System.out.println(segment + " = " + result);
mtmoses.stop();
}
catch (Exception e)
{
// TODO: handle exception
}
}
Viel Spaß beim Schreiben eigener Data Sources - bei Fragen (u.a. zu den notwendigen MOSES Dateien) helfe ich gerne weiter! Der nächste Beitrag - Teil 2 - wird sich mit einem der Bausteine von openTMS- MultiLingualObjects und MonoLingulaObjects und ihren Einsatz bei Date Sources beschäftigen.
Dr. Klemens Waldhör klemens.waldhoer @ opentms.de - Heartsome Europe GmbH www.heartsome.de