Mit fastText Domain-Vorschläge in Echtzeit übersetzen

Wir suchen ständig nach Wegen, großartige Domainnamen zu erzeugen. Fast jedes englische Wort hat bereits eine zugehörige .com-Domain, und viele beliebte Wortpaare wie HelloWorld.com sind ebenfalls vergeben. Eine Möglichkeit ist, ein für Ihr Unternehmen wichtiges Wort in andere Sprachen zu übersetzen. So wird fast auf Französisch zu vite, auf Spanisch zu rápida oder rápido und auf Italienisch zu veloce. Diese Wörter sind zwar nicht englisch, doch die Sprachen haben gemeinsame Wurzeln, und die Namen könnten zu Ihrem Unternehmen passen. Google Translate ist ein hervorragendes Produkt. Über die Oberfläche nachzusehen, wie ein Wort in mehreren Sprachen aussieht, ist jedoch mühsam. Wir wollten ein Tool entwickeln, das ein englisches Wort sofort in mehrere Sprachen übersetzt.

Die Forschung zur maschinellen Übersetzung reicht bis in die 1950er-Jahre zurück und entwickelt sich weiter. Wir möchten sofort vom Englischen in mehrere Sprachen übersetzen, und zwar auf handelsüblicher Hardware ohne GPUs oder andere teure Ausrüstung. Domainnamen sollten kurz sein. Deshalb konzentrieren wir uns auf einzelne Wörter statt auf Wortgruppen. Wir haben bereits Erfahrung mit Wortvektoren bei der Suche nach Domains zum Verkauf. Dabei entdeckten wir einen von Facebook Research veröffentlichten Datensatz, der Wortvektoren verschiedener Sprachen aufeinander ausrichtet.

Moment, was ist ein Wortvektor?

Wortvektoren entstehen mit einem neuronalen Netz, das aus großen Textmengen, etwa einem Webcrawl oder Wikipedia, Beziehungen zwischen Wörtern lernt. Allison Parrish, Assistenzprofessorin an der NYU, hat eine der besten interaktiven Einführungen in Wortvektoren geteilt, die ich kenne. Sehen Sie sie sich an. Allison veranschaulicht anhand von Farben, wie ein Wort Zahlenwerten für Rot, Grün und Blau zugeordnet werden kann. Die Standards PNG und JPG unterstützen beispielsweise jeweils 24-Bit-Farbe, also Werte von 0 bis 255 für R, G und B. Rot wird als 255,0,0 dargestellt, Grün als 0,255,0 und Blau als 0,0,255. Mit der Zeit erkennen Sie etwa 250,0,0 im Code als einen sehr roten Farbton.

fastText

2016 veröffentlichte Facebook Research fastText zusammen mit vortrainierten Modellen, die Millionen Wörter auf eine numerische Darstellung abbilden. Diese Modelle ordnen jedem Wort 300 32-Bit-Gleitkommazahlen zu. Das erfasst viele Details und Nuancen, ist für Menschen aber deutlich schwerer nachzuvollziehen. Der fastText-Wortvektor für das Wort red sieht so aus:

red -0.0331 -0.0465 0.1046 0.0201 0.0665 -0.0394 0.0515 -0.0598 0.0905 0.0738 0.0871 0.0062 0.0002 -0.0135 0.1012 -0.0092 -0.1063 -0.0967 0.0297 0.0790 -0.0429 -0.0470 -0.0926 -0.0227 -0.0240 -0.0768 0.0174 -0.0628 -0.0714 0.0413 0.0072 0.0746 0.0332 0.0780 0.0248 0.0083 -0.0807 -0.0272 0.0805 -0.0736 -0.0323 -0.0140 0.0081 0.0639 -0.0186 -0.0961 0.0240 -0.0159 0.0252 0.0425 0.0403 -0.1151 -0.0582 0.0228 0.0503 0.0262 0.0092 -0.0314 -0.0031 0.0238 0.0023 0.0231 0.1031 0.0147 0.0032 0.0197 -0.0749 0.0452 -0.0060 0.0173 -0.0828 0.0347 0.0330 -0.0970 0.0665 -0.0090 -0.0148 -0.0379 -0.0735 -0.0456 0.0362 -0.0038 -0.0989 0.0229 -0.0710 0.0076 -0.0314 0.0331 0.0470 -0.0968 -0.0182 0.0503 -0.0603 0.0900 0.0617 0.0198 0.0360 0.0885 -0.0665 0.0382 0.0162 -0.0352 -0.0643 0.0298 -0.0647 -0.0815 0.0507 0.0307 -0.0312 -0.0265 -0.0255 -0.0556 0.0302 0.0085 -0.0142 0.0116 0.0497 -0.0091 -0.0327 -0.0533 0.0853 -0.0028 0.0138 0.0235 0.0288 0.0766 -0.0008 0.0410 -0.0574 0.0001 0.0378 0.0842 0.0237 0.0557 -0.0578 -0.0145 -0.0006 -0.1553 -0.0657 0.0826 -0.0335 0.1468 0.0287 -0.0240 -0.0060 0.1243 -0.0685 -0.0024 -0.0419 0.0122 0.0002 -0.1673 -0.1169 -0.0371 -0.0072 -0.0133 -0.0355 0.0781 0.0487 -0.0785 0.1488 0.0351 -0.1184 -0.0185 0.0348 0.0116 -0.0598 -0.0082 0.1296 -0.0158 -0.0234 -0.0796 -0.0322 -0.0004 -0.0170 0.0290 -0.0135 -0.0658 0.0224 0.0262 -0.0747 -0.0174 -0.0673 0.0018 -0.0009 -0.0170 -0.0229 0.0128 0.0414 0.0009 0.0807 -0.0990 0.1185 0.0776 -0.1242 -0.0860 -0.0464 0.0127 -0.0994 0.0284 0.0295 -0.0607 0.0268 0.0738 0.0820 -0.0623 -0.1275 -0.0181 -0.0645 0.0423 -0.0196 0.0610 -0.0459 -0.0614 0.1134 0.0480 -0.0723 -0.0421 -0.0073 -0.0136 -0.0843 -0.0286 -0.0247 0.0456 -0.0090 -0.0546 -0.0464 0.0170 0.0580 -0.0434 0.0340 0.0199 0.0258 -0.0641 0.0110 -0.1129 0.0479 -0.0298 -0.0738 0.0475 -0.0210 0.0199 -0.0134 -0.0297 -0.0400 0.0186 0.0519 0.0505 0.0018 -0.0292 0.0482 0.0071 -0.0222 -0.0302 0.0711 -0.0198 0.0230 -0.0573 0.1053 0.0609 0.0517 0.0693 -0.0668 -0.0047 -0.0557 -0.0430 -0.0130 0.0693 -0.0305 -0.1101 -0.0303 -0.0511 -0.0628 0.0036 0.0101 -0.0206 0.1078 0.0520 -0.0476 0.0408 -0.0027 -0.0753 0.0087 0.0203 0.1821 -0.0566 0.0721 0.0880 -0.0955 -0.1142 -0.0118 -0.0209 0.0230 0.0313 -0.0339 -0.0700 0.0841 -0.0484 -0.0148 -0.0190

Ausgerichtete Wortvektoren

Später veröffentlichte Facebook Research aufeinander abgestimmte Wortvektoren für 44 Sprachen. Sie wirken wie ein Stein von Rosette und ermöglichen, Wörter dieser Sprachen im selben Vektorraum anzuordnen. Zur Veranschaulichung in drei Dimensionen können wir betrachten, wie Rot auf Englisch, Französisch und im Vektorraum dargestellt wird.

Darstellung des RGB-Farbraums als Würfel.

Da wir ein einzelnes Wort ohne Kontext betrachten, kann die Übersetzung in Sprachen mit weiblichen und männlichen Formen mehrdeutig sein. In der Praxis stimmen die Vektoren nicht vollkommen überein. Ihre Darstellungen können leicht abweichen, deshalb müssen wir ungefähre Treffer berücksichtigen. Im Vektorraum lässt sich die euklidische Distanz von beispielsweise red zu einem ähnlichen Wort in einer anderen Sprache messen.

Instant Distance

Computer sind schnell und können Millionen euklidischer Distanzen durch vollständiges Durchrechnen relativ zügig berechnen. Aber nicht sofort. Wir brauchen einen Index dieser Punkte, um die Nachbarn eines Punkts im Raum zu finden. Viele Verfahren des maschinellen Lernens und der KI sind auf effiziente Navigation in Vektorräumen angewiesen. Dafür gibt es unterschiedliche Ansätze und verfügbare Implementierungen. Wir wollten eine reine Rust-Implementierung, fanden aber keine produktionsreife. Deshalb entwickelten und veröffentlichten wir instant-distance, eine schnelle, vollständig in Rust geschriebene Implementierung von Hierarchical Navigable Small World Graphs mit Python-Anbindungen.

Alles zusammengesetzt

Für ein einfaches Übersetzungswerkzeug laden wir zunächst die von fastText veröffentlichten Wortvektordaten herunter. Dann indexieren wir die Wortvektoren mit Instant Distance. Nach Abschluss speichern wir den entstandenen Datensatz im Dateisystem zusammen mit einer Zuordnung von Wort zu Vektor als JSON-Datei.

LANGS = ("en", "fr", "it")
LANG_REPLACE = "$$lang"
WORD_MAP_PATH = f"./data/{'_'.join(LANGS)}.json"
BUILT_IDX_PATH = f"./data/{'_'.join(LANGS)}.idx"
DL_TEMPLATE = f"https://dl.fbaipublicfiles.com/fasttext/vectors-aligned/wiki.{LANG_REPLACE}.align.vec"

points = []
values = []
word_map = {}

async with aiohttp.ClientSession() as session:
  for lang in LANGS:
    # Construct a url for each language
    url = DL_TEMPLATE.replace(LANG_REPLACE, lang)

    # Ensure the directory and files exist
    os.makedirs(os.path.dirname(BUILT_IDX_PATH), exist_ok=True)

    lineno = 0
    async with session.get(url) as resp:
      while True:
        lineno += 1
        line = await resp.content.readline()
        if not line:
          # EOF
          break

        linestr = line.decode("utf-8")
        tokens = linestr.split(" ")

        # The first token is the word and the rest
        # are the embedding
        value = tokens[0]
        embedding = [float(p) for p in tokens[1:]]

        # We only go from english to the other two languages
        if lang == "en":
          word_map[value] = embedding
        else:
          # Don't index words that exist in english
          # to improve the quality of the results.
          if value in word_map:
              continue

          # We track values here to build the instant-distance index
          # Every value is prepended with 2 character language code.
          # This allows us to determine language output later.
          values.append(lang + value)
          points.append(embedding)

# Build the instant-distance index and dump it out to a file with .idx suffix
print("Building index... (this will take a while)")
hnsw = instant_distance.HnswMap.build(points, values, instant_distance.Config())
hnsw.dump(BUILT_IDX_PATH)

# Store the mapping from string to embedding in a .json file
with open(WORD_MAP_PATH, "w") as f:
    json.dump(word_map, f)

Mit diesen Werkzeugen wandeln wir schließlich eine Eingabe, also ein Wort, in ihren Wortvektor um und suchen mit Instant Distance nach den nächsten Nachbarn. Da alle Wortvektoren aufeinander abgestimmt sind, sollten die nächstgelegenen Vektoren verschiedener Sprachen sehr ähnliche Wörter oder direkte Übersetzungen darstellen.

with open(WORD_MAP_PATH, "r") as f:
  word_map = json.load(f)

# Get an embedding for the given word
embedding = word_map.get(word)
if not embedding:
  print(f"Word not recognized: {word}")
  exit(1)

hnsw = instant_distance.HnswMap.load(BUILT_IDX_PATH)
search = instant_distance.Search()
hnsw.search(embedding, search)

# Print the results
for result in list(search)[:10]:
  # We know that the first two characters of the value is the language code
  # from when we built the index.
  print(f"Language: {result.value[:2]}, Translation: {result.value[2:]}")

Hier beispielsweise die Ergebnisse für die Übersetzung des englischen Worts „hello“:

Language: fr, Translation: bonjours
Language: fr, Translation: bonsoir
Language: fr, Translation: salutations
Language: it, Translation: buongiorno
Language: it, Translation: buonanotte
Language: fr, Translation: rebonjour
Language: it, Translation: auguri
Language: fr, Translation: bonjour,
Language: it, Translation: buonasera
Language: it, Translation: chiamatemi

Ausprobieren

Das vollständige Beispiel finden Sie bei Instant Distance auf GitHub. Wenn Sie Fragen haben, eröffnen Sie gern ein Issue auf GitHub!