Sie sind auf Seite 1von 12

Erstellen Sie Ihren eigenen maschinellen

Übersetzungsdienst mit Transformatoren


26-31 minutes

Verwenden Sie die neuesten Helsinki NLP-Modelle, die in


der Transformers-Bibliothek verfügbar sind, um einen
standardisierten maschinellen Übersetzungsdienst zu
erstellen
Maschinelle Übersetzung ist in der Unternehmensumgebung gefragt. Es ist wichtig, dass globale
Unternehmen Dokumente, Notizen, E-Mails und andere Texte in einer Vielzahl von Sprachen mit
Menschen auf der ganzen Welt teilen können.

Noch wichtiger ist wohl die Notwendigkeit, neben der Globalisierung auch Informationen zu
demokratisieren. Unabhängig von Ihrer gesprochenen Sprache sollten Sie Zugriff auf dieselben
Open-Source-Veröffentlichungen, Gesundheitsinformationen, Tools und Kenntnisse haben wie
diejenigen, deren Hauptsprache weit verbreitet ist, wie z. B. Englisch.
Ich habe das in Powerpoint gemacht. Es ist schrecklich.

Glücklicherweise gibt es maschinelle Übersetzungen schon seit einiger Zeit - viele von uns haben
Google Übersetzer verwendet, um den Inhalt von etwas online zu bestätigen oder einfach nur
intelligent und weltlich zu klingen. Wenn Sie Übersetzungsanforderungen haben, die eine
Skalierung erfordern, verfügen Azure, AWS und GCP über Verbrauchs-APIs für nur 10 US-
Dollar pro Million Zeichen, die Tausende von Sprachpaaren unterstützen (ein Paar besteht aus
einer Ausgangssprache und einer Zielsprache).

Im Open-Source-Bereich wurden unzählige maschinelle Lernarbeiten in mehreren Sprachen


durchgeführt, und die Menschen konnten ihre eigenen maschinellen Übersetzungsmodelle längst
trainieren. Die meisten Ansätze scheinen jedoch hinsichtlich des Modellierungsansatzes und der
Anzahl der unterstützten Sprachen unterschiedlich zu sein (nach meiner eigenen naiven
Einschätzung).

In jüngerer Zeit veröffentlichte Huggingface über 1.000 vorgefertigte Sprachmodelle von der
Universität Helsinki . Für alle, die ihre eigene AWS- oder Google Übersetzungs-API erstellen
möchten, war es nie einfacher. Also dachte ich mir, ich würde von der harten Arbeit anderer
profitieren. Dies ist das funktionale Äquivalent von "Lassen Sie uns maschinelles Lernen in eine
Kolben-API und ein Docker-Image einwickeln", aber was auch immer, es macht Spaß. Machen
wir das.

Maschinelle Übersetzung mit Transformatoren


Huggingface hat unglaubliche Arbeit geleistet und SOTA-Modelle (State of the Art) in einer
einfachen Python-API für Copy + Paste-Codierer wie mich verfügbar gemacht. Um Text lokal zu
übersetzen, müssen Sie nur pip install transformersdas folgende Snippet aus den
Transformers-Dokumenten verwenden .

from transformers import MarianTokenizer, MarianMTModel


from typing import List
src = 'fr' # source language
trg = 'en' # target language
sample_text = "où est l'arrêt de bus ?"
mname = f'Helsinki-NLP/opus-mt-{src}-{trg}'
model = MarianMTModel.from_pretrained(mname)
tok = MarianTokenizer.from_pretrained(mname)
batch = tok.prepare_translation_batch(src_texts=[sample_text]) # don't need tgt_text for
inference
gen = model.generate(**batch) # for forward pass: model(**batch)
words: List[str] = tok.batch_decode(gen, skip_special_tokens=True) # returns "Where is the the
bus stop ?"

Dadurch wird das erforderliche Modell heruntergeladen und der Quelltext -> Zieltext übersetzt.
Mit der schieren Menge an unterstützten Sprachen, die in diesem Format verfügbar sind, ist es
super einfach, dies in eine Docker-Flask-App zu werfen und es einen Tag zu nennen, aber wir
müssen zuerst ein bisschen zusätzliche Arbeit erledigen ...

Fühlen Sie sich frei, im Git Repo mitzumachen .

Herunterladen der Modelle


Wenn Sie das obige Modell initialisieren, laden Transformatoren die erforderlichen Dateien
herunter, wenn Sie sie nicht lokal haben. Dies ist fantastisch für den lokalen Gebrauch, aber nicht
für Docker-Container. Da die Lagerung von Containern kurzlebig ist, verlieren wir jedes Mal,
wenn wir den Container verlassen, alle Modelle und müssen sie beim nächsten Mal erneut
herunterladen.

Um dies zu umgehen, können wir den Modelldownload separat behandeln und die Modelle als
Volume bereitstellen. Dies gibt uns auch mehr Kontrolle über die Sprachen, die unser Service
von Anfang an unterstützen soll. Huggingface hostet alle erforderlichen Dateien in S3, sodass wir
dies einfach standardisieren können…
HUGGINGFACE_S3_BASE_URL="https://s3.amazonaws.com/models.huggingface.co/bert/He
lsinki-NLP"
FILENAMES =
["config.json","pytorch_model.bin","source.spm","target.spm","tokenizer_config.json","vocab.j
son"]
MODEL_PATH = "data"

Erstellen Sie anschließend ein einfaches Befehlszeilenprogramm zum Herunterladen:

import os
import argparse
import urllib
from urllib.request import urlretrieve
from config import *
parser = argparse.ArgumentParser()
parser.add_argument('--source', type=str, help='source language code')
parser.add_argument('--target', type=str, help='sum the integers (default: find the max)')
def download_language_model(source,target):
model = f"opus-mt-{source}-{target}"
print(">>>Downloading data for %s to %s model..." % (source, target))
os.makedirs(os.path.join("data",model))
for f in FILENAMES:
try:
print(os.path.join(HUGGINGFACE_S3_BASE_URL,model,f))
urlretrieve(os.path.join(HUGGINGFACE_S3_BASE_URL,model,f),
os.path.join(MODEL_PATH,model,f))
print("Download complete!")
except urllib.error.HTTPError:
print("Error retrieving model from url. Please confirm model exists.")
os.rmdir(os.path.join("data",model))
break
if __name__ == "__main__":
args = parser.parse_args()
download_language_model(args.source, args.target)

Cool. Jetzt können wir die benötigten Modelle mit einem einzigen Befehl herunterladen. Nehmen
Sie das folgende Beispiel für Japanisch -> Englisch.

python download_models.py --source ja --target en

Dadurch werden sie in ein Verzeichnis heruntergeladen, das datastandardmäßig aufgerufen wird.
Überprüfen Sie daher, ob sie vorhanden sind.
Dynamische Sprachübersetzung in Python
Nachdem wir nun eine bessere Möglichkeit haben, die von uns unterstützten Sprachen zu
verwalten, kommen wir zum Kern des Problems, dann zu einer Klasse und den dazugehörigen
Methoden zur Verwaltung unserer Übersetzungen.

Wir brauchen hier ein paar Funktionen:

 Übersetzen Sie Text in einer Quell- und Zielsprache (duh).


 Laden und Verwalten von Modellen, die wir nicht im Speicher haben (ich verwende ein
einfaches Diktat)
 Unterstützte Sprachen erhalten (Welche Sprachen haben wir für diese App
heruntergeladen?)

from transformers import MarianTokenizer, MarianMTModel
import os
from typing import List
class Translator():
def __init__(self, models_dir):
self.models = {}
self.models_dir = models_dir
def get_supported_langs(self):
routes = [x.split('-')[-2:] for x in os.listdir(self.models_dir)]
return routes
def load_model(self, route):
model = f'opus-mt-{route}'
path = os.path.join(self.models_dir,model)
try:
model = MarianMTModel.from_pretrained(path)
tok = MarianTokenizer.from_pretrained(path)
except:
return 0,f"Make sure you have downloaded model for {route} translation"
self.models[route] = (model,tok)
return 1,f"Successfully loaded model for {route} transation"
def translate(self, source, target, text):
route = f'{source}-{target}'
if not self.models.get(route):
success_code, message = self.load_model(route)
if not success_code:
return message
batch = self.models[route][1].prepare_translation_batch(src_texts=[text])
gen = self.models[route][0].generate(**batch)
words: List[str] = self.models[route][1].batch_decode(gen, skip_special_tokens=True)
return words

Diese Klasse wird mit dem Pfad initialisiert, den wir zum Speichern von Modellen verwenden,
und behandelt den Rest selbst. Wenn wir eine Übersetzungsanforderung erhalten und dieses
Modell nicht im Speicher haben, load_modelswird es aufgerufen, um es in das
self.modelsWörterbuch zu laden . Überprüfen Sie unser dataVerzeichnis, um festzustellen, ob
wir dieses Modell haben, und geben Sie eine Nachricht zurück, um uns mitzuteilen, ob wir dies
tun oder nicht.

Eine API daraus machen


Alles, was wir jetzt tun müssen, ist, dies in eine Flasche zu wickeln, damit wir HTTP-Aufrufe an
sie tätigen können.

import os
from flask import Flask, request, jsonify
from translate import Translator
from config import *
app = Flask(__name__)
translator = Translator(MODEL_PATH)
app.config["DEBUG"] = True # turn off in prod
@app.route('/', methods=["GET"])
def health_check():
"""Confirms service is running"""
return "Machine translation service is up and running."
@app.route('/lang_routes', methods = ["GET"])
def get_lang_route():
lang = request.args['lang']
all_langs = translator.get_supported_langs()
lang_routes = [l for l in all_langs if l[0] == lang]
return jsonify({"output":lang_routes})
@app.route('/supported_languages', methods=["GET"])
def get_supported_languages():
langs = translator.get_supported_langs()
return jsonify({"output":langs})
@app.route('/translate', methods=["POST"])
def get_prediction():
source = request.json['source']
target = request.json['target']
text = request.json['text']
translation = translator.translate(source, target, text)
return jsonify({"output":translation})
app.run(host="0.0.0.0")
Um es zu verwenden, führen Sie es einfach aus python app.pyund rufen Sie den Dienst an. Um
zu überprüfen, ob es funktioniert, können Sie curl localhost:5000etwas Anspruchsvolleres
wie Postman verwenden oder verwenden. Ich verwende hier den Flask-Server, aber Sie möchten,
dass ein Produktions-Webserver diesen überall real verwendet.

Wickeln Sie es in ein Docker-Image


Jetzt, da unsere App mit Basis-Python funktioniert, möchten wir, dass sie mit Docker oder
Docker-Compose funktioniert, damit wir sie so weit wie nötig vergrößern und verkleinern
können. Das, was Dockerfilewir dafür verwenden, ist ziemlich einfach. Wir müssen nur
sicherstellen, dass wir den Dienst mit dem angeschlossenen Volume ausführen, damit er bereits
Zugriff auf die Daten hat.

Normalerweise würde ich ein kleineres Basis-Image verwenden, aber ehrlich gesagt hatte ich
keine Lust zum Debuggen:

FROM python:3.6
WORKDIR /app
# install requirements first for caching
COPY requirements.txt /app
RUN pip install -r requirements.txt
COPY . /app
CMD python app.py

Befehle erstellen und ausführen:

docker build -t machine-translation-service .


docker run -p 5000:5000 -v $(pwd)/data:/app/data -it machine-translation-
service
curl --location --request POST 'http://localhost:5000/translate' \
--header 'Content-Type: application/json' \
--data-raw '{
"text":"hello",
"source":"en",
"target":"fr"
}'

Ich habe meinen Service in eine kleine Kolben-API verpackt und ihn sogar „richtig“ zu einem
Docker-Container gemacht, der reproduzierbar skaliert werden kann. Ich möchte jedoch einige
Bedenken zu diesem Ansatz äußern.

Diese Übersetzungsmodelle sind ziemlich groß (wie in jedem ist wie 300 MB). Selbst wenn die
Daten / Modelle separat heruntergeladen werden, müssen wir jedes einzelne Sprachpaar, das wir
unterstützen möchten, in den Speicher laden - und das wird für einen einzelnen Container schnell
außer Kontrolle geraten.
Warum also nicht ein konfigurierbares Image erstellen, mit dem wir mithilfe von Docker-
Compose einen Container für jeden Dienst hochfahren können? Auf diese Weise gibt es einen
Dienst pro Sprachpaar, und jedes Paar kann bei steigender Nachfrage individuell skaliert werden.
Anschließend können wir eine einzelne, exponierte API schreiben, die Anforderungen an alle
Container im Netzwerk weiterleitet.

Haftungsausschluss: Zu diesem Zeitpunkt habe ich gerade erst angefangen, es zu erfinden, und
ich bin keineswegs davon überzeugt, dass dies der optimale Ansatz war - aber ich wollte sehen,
wie weit ich es bringen kann.

Zum Auftakt habe ich das Verzeichnis etwas überarbeitet. Um es herauszufinden, kannst du den
Zweig auf Git erkunden, den ich gemacht habe:

.
├── LICENSE
├── README.md
├── docker-compose.yaml
├── proxy
│   ├── Dockerfile
│   ├── app.py
│   └── requirements.txt
└── translation_base
├── Dockerfile
├── app.py
├── config.py
├── download_models.py
├── requirements.txt
└── translate.py

Der Zweck von proxybesteht darin, Anforderungen an jeden maschinellen Übersetzungsdienst


umzuleiten, der hochgefahren wird. Dies ist der einzige exponierte Endpunkt. Ich habe eine kurze
docker-composeDatei für Dienste erstellt, die die Übersetzung unterstützen.

version: '3.3'
services:
translation_proxy:
build:
context: ./proxy
environment:
- MODEL_PATH=data
command: python app.py
ports:
- "5000:5000"
en_fr:
image: translation_base
environment:
- MODEL_PATH=data
- SOURCE_LANG=en
- TARGET_LANG=fr
command: python app.py
volumes:
- ./data:/app/data
fr_en:
image: translation_base
environment:
- MODEL_PATH=data
- SOURCE_LANG=fr
- TARGET_LANG=en
command: python app.py
volumes:
- ./data:/app/data
volumes:
data

Jeder Übersetzungsdienst hat dieselben env-Variablen (ich sollte wahrscheinlich nur eine
.envDatei erstellen), denselben Befehl und dasselbe Volume, das unsere Modelle enthält. Es gibt
wahrscheinlich einen besseren Weg, dies mit yamlAutomatisierung oder ähnlichem zu skalieren,
aber ich bin noch nicht dort angekommen.

Danach mache ich nur noch einige hässliche Arbeiten am /translateEndpunkt meines Proxys,
um den angeforderten Service-Endpunkt bei Bedarf zu erstellen. Wenn Benutzer Anforderungen
an diesen exponierten Dienst stellen, wird eine weitere Anforderung an andere Container
gesendet, die nur innerhalb dieses Netzwerks erreichbar sind.

@app.route('/translate', methods=["POST"])
def get_prediction():
source = request.json['source']
target = request.json['target']
text = str(request.json['text'])
route = f'{source}_{target}'
headers = {
'Content-Type': 'application/json'
}
r = requests.post(f"http://machine-translation-service_{route}_1:5000/translate",
headers = headers,
json={"text":text})
translation = str(r.content)
return jsonify({"output":translation})

Alles was wir tun müssen, ist das Basis-Image zu erstellen.

cd translation_base
docker build -t translation_base
docker-compose up

Endlich Kubernetes
Ich werde hier nicht näher darauf eingehen, aber der logische nächste Schritt wäre, dies zu
Kubernetes zu bringen, um eine echte Skalierung zu erreichen. Ein einfacher Ausgangspunkt ist
die Verwendung der komposeCLI, um Docker-Compose in Kubernetes YAML umzuwandeln.
Unter macOS:
$brew install kompose
$kompose convert
INFO Kubernetes file "translation-proxy-service.yaml" created
INFO Kubernetes file "en-fr-deployment.yaml" created
INFO Kubernetes file "en-fr-claim0-persistentvolumeclaim.yaml" created
INFO Kubernetes file "fr-en-deployment.yaml" created
INFO Kubernetes file "fr-en-claim0-persistentvolumeclaim.yaml" created
INFO Kubernetes file "translation-proxy-deployment.yaml" created

Hier ist die Beispielbereitstellung für den fr-en-Dienst:

apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
kompose.cmd: kompose convert
kompose.version: 1.21.0 ()
creationTimestamp: null
labels:
io.kompose.service: fr-en
name: fr-en
spec:
replicas: 1
selector:
matchLabels:
io.kompose.service: fr-en
strategy:
type: Recreate
template:
metadata:
annotations:
kompose.cmd: kompose convert
kompose.version: 1.21.0 ()
creationTimestamp: null
labels:
io.kompose.service: fr-en
spec:
containers:
- args:
- python
- app.py
env:
- name: MODEL_PATH
value: data
- name: SOURCE_LANG
value: fr
- name: TARGET_LANG
value: en
image: translation_base
imagePullPolicy: ""
name: fr-en
resources: {}
volumeMounts:
- mountPath: /app/data
name: fr-en-claim0
restartPolicy: Always
serviceAccountName: ""
volumes:
- name: fr-en-claim0
persistentVolumeClaim:
claimName: fr-en-claim0
status: {}

Sobald Sie einen konfigurierten Kubernetes-Cluster haben, können Sie kubectl apply -f
$FILENAMEIhre Dienste, Bereitstellungen und dauerhaften Volumenansprüche erstellen.

Natürlich gibt es mehr zu tun und noch mehr Kubernetes-Objekte zu erstellen, aber ich wollte
hier einen einfachen Starter für alle bereitstellen, die tatsächlich etwas Ähnliches skalieren
möchten.

Fazit
Ich hoffe, dass die Tools, die Huggingface weiter entwickelt (zusammen mit den Modellen, die
engagierte Forscher trainieren), weiterhin einen gerechten Zugang zu intelligentem maschinellem
Lernen bieten. Die Bemühungen um eine offene maschinelle Übersetzung tragen nicht nur zum
Forschungsbereich bei, sondern ermöglichen den weltweiten Zugang zu äußerst wichtigen
Ressourcen, die in einer einzigen Sprache verfasst sind.

Ich habe keine Ahnung, ob das Hosten dieser massiven Modelle selbst halb so billig ist wie die
Verwendung einer AWS- oder Google Translate-API, und ich habe auch die Qualität nicht
untersucht. Aber es hat großen Spaß gemacht, sich darauf einzulassen, und bietet hoffentlich
einen Einblick in das, was Sie mit den Tausenden von SOTA-Modellen für maschinelles Lernen
erstellen können, die Ihnen zur Verfügung stehen.