Traduce cualquier idioma en solo 60 l铆neas de Python.

Traducci贸n a Espa帽ol de un art铆culo de Nicolas Bertagnolli, donde nos explica lo mucho que ha avanzado la Traducci贸n Autom谩tica en los 煤ltimos a帽os, y c贸mo hoy en d铆a, permite traducir 2 idiomas con s贸lo 60 l铆neas de c贸digo Python.

pyhtontraducci贸n autom谩ticatraduccion de c贸digo
1 julio, 2021 Traducci贸n de idiomas con Python, by Nicolas Bertagnolli
1 julio, 2021 Traducci贸n de idiomas con Python, by Nicolas Bertagnolli

Descubre c贸mo usar Python para traducir cualquier idioma en tan solo 60 l铆neas de c贸digo. Como ingeniero de PNL, me voy a quedar sin trabajo pronto 馃槄.

Disclaimer: Esta es una traducci贸n gratuita de un texto original de Nicolas Bertagnolli realizada gratuitamente por un estudiante de traducci贸n para la empresa de traducci贸n Ibidem Group. Si necesitas servicios de traducci贸n profesional, es mejor que contactes con una agencia de traducci贸n en Madrid o alguna oficina de traducci贸n en Barcelona.

Introducci贸n

Recuerdo cuando constru铆 mi primer sistema de traducci贸n seq2seq en 2015. Fue una tonelada de trabajo, desde el procesamiento de los datos hasta el dise帽o y la implementaci贸n de la arquitectura del modelo. Todo eso era para traducir un idioma a otro idioma. Ahora los modelos son mucho mejores y las herramientas en torno a estos modelos tambi茅n son mejores. HuggingFace ha incorporado recientemente m谩s de 1.000 modelos de traducci贸n de la  Universidad deHelsinki a su zoo de modelos de transformaci贸n y son buenos. Casi me siento mal haciendo este tutorial porque construir un sistema de traducci贸n es tan sencillo como copiar la documentaci贸n de la biblioteca de transformadores.

De todos modos, en este tutorial, haremos un transformador que detectar谩 autom谩ticamente el idioma utilizado en el texto y lo traducir谩 al ingl茅s. Esto es 煤til porque a veces estar谩s trabajando en un dominio donde hay datos textuales de muchos idiomas diferentes. Si construyes un modelo s贸lo en ingl茅s tu rendimiento se ver谩 afectado, pero si puedes normalizar todo el texto a un solo idioma probablemente lo har谩s mejor.

馃捑 Datos 馃捑

Para explorar la eficacia de este enfoque, necesitaba un conjunto de datos de peque帽os tramos de texto en muchos idiomas diferentes. El reto JigsawMultilingualToxicCommentClassification de Kaggle es perfecto para esto. Tiene un conjunto de entrenamiento de m谩s de 223.000 comentarios etiquetados como t贸xicos o no en ingl茅s y 8.000 comentarios de otros idiomas en un conjunto de validaci贸n. Podemos entrenar un modelo simple en el conjunto de entrenamiento en ingl茅s. A continuaci贸n, utilizaremos nuestro transformador de traducci贸n para convertir todos los dem谩s textos al ingl茅s y realizaremos nuestras predicciones utilizando el modelo ingl茅s.

Si echamos un vistazo a los datos de entrenamiento, vemos que hay unos 220.000 textos de ejemplo en ingl茅s* etiquetados en cada una de las seis categor铆as.

Una vista de los datos de entrenamiento

Donde las cosas se ponen interesantes es en los datos de validaci贸n. Los datos de validaci贸n no contienen ingl茅s y tienen ejemplos de italiano, espa帽ol y turco.

Tabla  Descripci贸n generada autom谩ticamente

Ejemplo de datos de validaci贸n

馃暤锔忊檧锔 Identificar el idioma 馃暤锔忊檧锔

Naturalmente, el primer paso para normalizar cualquier idioma al ingl茅s es identificar cu谩l es nuestro idioma desconocido. Para ello, recurrimos a la excelente bibliotecaFasttext de Facebook. Esta biblioteca tiene un mont贸n de cosas incre铆bles. La biblioteca hace honor a su nombre. Es realmente r谩pida. Hoy s贸lo vamos a utilizar sus capacidades de predicci贸n del idioma.

As铆 de sencillo es identificar qu茅 idioma es una cadena arbitraria. Lo ejecut茅 sobre el conjunto de validaci贸n para tener una idea de lo bien que funcionaba el modelo. Francamente, me sorprendi贸 su rendimiento inicial. De los 8.000 ejemplos, Fasttext s贸lo clasific贸 mal 43. Adem谩s, s贸lo tard贸 300 ms en ejecutarse en mi MacbookPro. En ambos casos, eso es una barbaridad 馃崒. Si te fijas, ver谩s que en algunos de los errores de clasificaci贸n del espa帽ol predijo el gallego o el occitano. Estas son lenguas que se hablan en Espa帽a y alrededores y que tienen ra铆ces espa帽olas. As铆 que los errores de predicci贸n en algunos casos no son tan graves como podr铆amos pensar.

Calendario  Descripci贸n generada autom谩ticamente
Matriz de confusi贸n de la identificaci贸n ling眉铆stica de Fasttext API en nuestro conjunto de validaci贸n.

馃 Transformers 馃

Ahora que podemos predecir qu茅 idioma es un texto determinado, vamos a ver c贸mo traducirlo. La  transformadores biblioteca de de HuggingFace nunca deja de sorprenderme. Recientemente han a帽adido m谩s de milmodelos detraducci贸n a su zoo de modelos y cada uno de ellos puede utilizarse para realizar una traducci贸n de textos arbitrarios en unas cinco l铆neas de c贸digo. Estoy robando esto casi directamente de la documentaci贸n.

lang = "fr"
target_lang = "enmodel_name = f'Helsinki-NLP/opus-mt-{lang}-{target_lang}'

# Download the model and the tokenizer
model = MarianMTModel.from_pretrained(model_name)
tokenizer = MarianTokenizer.from_pretrained(model_name)

# Tokenize the text
batch = tokenizer([text], return_tensors="pt", padding=True)

# Make sure that the tokenized text does not exceed the maximum
# allowed size of 512
batch["input_ids"] = batch["input_ids"][:, :512]
batch["attention_mask"] = batch["attention_mask"][:, :512]

# Perform the translation and decode the output
translation = model.generate(**batch)
tokenizer.batch_decode(translation, skip_special_tokens=True)

B谩sicamente, para cualquier par de c贸digos de idioma puedes descargar un modelo con el nombre Helsinki-NLP/optus-mt-{lang}-{target_lang} donde lang es el c贸digo del idioma de origen y target_lang es el c贸digo del idioma de destino al que queremos traducir. Si quieres traducir coreano a alem谩n, descarga el modelo Helsinki-NLP/optus-mt-ko-de. Es as铆 de sencillo 馃く!

Hago una ligera modificaci贸n a partir de la documentaci贸n, en la que acoto los input_ids y los attention_mask para que s贸lo tengan 512 tokens. Esto es conveniente porque la mayor铆a de estos modelos de transformadores s贸lo pueden manejar entradas de hasta 512 tokens. Esto evita que se produzcan errores en los textos m谩s largos. Sin embargo, causar谩 problemas si intentas traducir textos muy largos, as铆 que ten en cuenta esta modificaci贸n si utilizas este c贸digo.

Pipelines de SciKit-Learn

Con el modelo descargado vamos a facilitar la incorporaci贸n de esto en un pipeline de sklearn. Si has le铆do alguno de mis posts anteriores probablemente sepas que me encantan los Pipelines de SciKit. Son una buena herramienta para componer la featurizaci贸n y el entrenamiento del modelo. As铆 que con eso en mente vamos a crear un simple transformador que tome cualquier dato textual, prediga su lenguaje y lo traduzca. Nuestro objetivo es ser capaces de construir un modelo que sea agn贸stico en cuanto al idioma al ejecutarlo:

from sklearn import svm
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.pipeline import Pipeline

classifier = svm.LinearSVC(C=1.0, class_weight="balanced")
model = Pipeline([
('translate', EnglishTransformer()),
('tfidf', TfidfVectorizer()),
("classifier", classifier)
])

Este proceso traducir谩 cada punto de datos de cualquier texto al ingl茅s, luego crear谩 caracter铆sticas TF-IDF y despu茅s entrenar谩 un clasificador. Esta soluci贸n mantiene nuestra featurizaci贸n en l铆nea con nuestro modelo y facilita el despliegue. Tambi茅n ayuda a evitar que las caracter铆sticas se desincronicen con el modelo al realizar la featurizaci贸n, el entrenamiento y la predicci贸n en una sola l铆nea.

Ahora que sabemos para qu茅 estamos trabajando, 隆construyamos este EnglishTransformer! La mayor parte de este c贸digo ya lo habr谩s visto arriba s贸lo lo estamos cosiendo. 馃槃

from typing import List, Optional
from sklearn.base import BaseEstimator, TransformerMixin
import fasttext
from transformers import MarianTokenizer, MarianMTModel
import os

class EnglishTransformer(BaseEstimator, TransformerMixin):

def __init__(self,
fasttext_model_path: str="/tmp/lid.176.bin",
allowed_langs: Optional[List[str]]=None,
target_lang: str="en"):

# If the language model doesn't exist download it
if not os.path.isfile(fasttext_model_path):
url = 'https://dl.fbaipublicfiles.com/fasttext/supervised-models/lid.176.bin'
r = requests.get(url, allow_redirects=True)
open('/tmp/lid.176.bin', 'wb').write(r.content)

self.fasttext_model_path = fasttext_model_path
self.lang_model = fasttext.load_model(fasttext_model_path)
self.allowed_langs = allowed_langs
self.target_lang = target_lang
self.romance_langs = {"it", "es", "fr", "pt", "oc", "ca", "rm", "wa",
"lld", "fur", "lij", "lmo", "gl", "lad", "an", "mwl"}

if allowed_langs is None:
self.allowed_langs = self.romance_langs.union({self.target_lang, "tr", "ar", "de", "ru"})
else:
self.allowed_langs = allowed_langs

def get_language(self, texts: List[str]) -> List[str]:

# Predict the language code for each text in texts
langs, _ = self.lang_model.predict([x.replace("\n", " ") for x in texts])

# Extract the two character language code from the predictions.
return [x[0].split("__")[-1] for x in langs]


def transform(self, texts: str) -> List[str]:

# Get the language codes for each text in texts
langs = self.get_language(texts)


translations = []

for text, lang in zip(texts, langs):

# If the language is our target just add it as is without doing any prediciton.
if lang == self.target_lang:
translations.append(text)
else:
# Use the romance model if it is a romance language to avoid
# downloading a model for every language

if lang in self.romance_langs and self.target_lang == "en":

lang = "ROMANCE"
translation_model_name = f'Helsinki-NLP/opus-mt-{lang}-{self.target_lang}'

# Download the model and tokenizer
model = MarianMTModel.from_pretrained(translation_model_name)
tokenizer = MarianTokenizer.from_pretrained(translation_model_name)

# Translate the text
inputs = tokenizer([text], return_tensors="pt", padding=True)
gen = model.generate(**inputs)
translations.append(tokenizer.batch_decode(gen, skip_special_tokens=True)[0])

return translations
  • L铆neas 15-18 – Aseg煤rate de que el modelo fasttext est谩 descargado y listo para usar. Si no lo est谩, lo descarga en la carpeta temporal /tmp/lid.176.bin.
  • L铆nea 24 – Establece los c贸digos de idioma que son traducibles con el modeloROMANCE de Helsinki. Ese modelo maneja un mont贸n de idiomas realmente bien y nos ahorrar谩 un mont贸n de espacio en disco porque no tenemos que descargar un modelo separado para cada uno de esos idiomas.
  • L铆neas 27-30 – Definir qu茅 idiomas vamos a traducir. Queremos crear una lista de idiomas permitidos porque cada uno de estos modelos ocupa unos 300MB, as铆 que si descarg谩ramos cien modelos diferentes 隆terminar铆amos con 30GB de modelos! Esto limita el conjunto de idiomas para que no nos quedemos sin espacio en el disco. Puedes a帽adir c贸digos ISO-639-1 a esta lista si quieres traducirlos.
  • L铆neas 32-38 – Definir una funci贸n para realizar la predicci贸n del lenguaje fasttext como hemos comentado anteriormente. Notar谩 que tambi茅n filtramos el car谩cter \n. Esto se debe a que Fasttext asume autom谩ticamente que se trata de un punto de datos diferente y arrojar谩 un error si est谩 presente.
  • L铆nea 41- Define la transformaci贸n y es donde ocurre la magia. Esta funci贸n convierte una lista de cadenas en cualquier idioma en una lista de cadenas en ingl茅s.
  • L铆neas 48-50 – Comprueba si la cadena actual es de nuestro idioma de destino. Si lo es la a帽adimos a nuestras traducciones tal cual porque ya es el idioma correcto.
  • L铆neas 54-55 – Comprueba si el idioma predicho puede ser manejado por el modelo Romance. Esto nos ayuda a evitar la descarga de un mont贸n de modelos ling眉铆sticos adicionales.
  • L铆neas 56-65 – Deben parecer familiares, son s贸lo el c贸digo de traducci贸n de la documentaci贸n de la cara abrazada. Esta secci贸n descarga el modelo correcto y luego realiza la traducci贸n del texto de entrada.

Eso es todo. Super sencillo y puede manejar cualquier cosa. Algo a tener en cuenta es que este c贸digo fue escrito para ser lo m谩s legible posible y es MUY lento. Al final de este post, incluyo una versi贸n mucho m谩s r谩pida que predice por lotes diferentes idiomas en lugar de descargar un modelo para cada punto de datos.

馃Resultados 馃

Ahora podemos entrenar y probar nuestro modelo utilizando:

from sklearn import svm
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.pipeline import Pipelineclassifier = svm.LinearSVC(C=1.0, class_weight="balanced")
model = Pipeline([
('translate', EnglishTransformer()),
('tfidf', TfidfVectorizer()),
("classifier", classifier)
])
model.fit(train_df["comment_text"].tolist(), train_df["toxic"])
preds = model.predict(val_df["comment_text"])

El entrenamiento de un modelo TF-IDF simple en el conjunto de entrenamiento en ingl茅s y la prueba en el conjunto de validaci贸n nos da una puntuaci贸n F1 para los comentarios t贸xicos de 0,15. Eso es terrible. Al predecir cada clase como t贸xica se obtiene una F1 de 0,26. Utilizando nuestro nuevo sistema de traducci贸n para preprocesar toda la informaci贸n y traducirla al ingl茅s, nuestra F1 es de 0,51. Es una mejora de casi 4 veces.


Comparaci贸n de resultados entre modelos traducidos y no traducidos

Ten en cuenta que el objetivo aqu铆 era la simple traducci贸n y no necesariamente el rendimiento de SOTA en esta tarea. Si realmente quieres entrenar un modelo de clasificaci贸n de comentarios t贸xicos que obtenga un buen rendimiento para afinar un modelo de transformaci贸n profunda como BERT.

Si te ha gustado esta entrada, echa un vistazo a otra de mis entradas sobre el trabajo con texto y SciKit-Learn.

Gracias por leer! : )

Un Transformer m谩s r谩pido

Como se prometi贸, aqu铆 est谩 el c贸digo para una versi贸n m谩s r谩pida del Transformer de Ingl茅s. Aqu铆 ordenamos el corpus por idioma predicho y s贸lo cargamos un modelo una vez para cada idioma. Se podr铆a hacer a煤n m谩s r谩pido mediante el procesamiento por lotes de entrada utilizando el transformador en la parte superior de este.

from typing import List, Optional, Set
from sklearn.base import BaseEstimator, TransformerMixin
import fasttext
from transformers import MarianTokenizer, MarianMTModel
import os
import requests

class LanguageTransformerFast(BaseEstimator, TransformerMixin):
def __init__(
self,
fasttext_model_path: str = "/tmp/lid.176.bin",
allowed_langs: Optional[Set[str]] = None,
target_lang: str = "en",
):

self.fasttext_model_path = fasttext_model_path
self.allowed_langs = allowed_langs
self.target_lang = target_lang
self.romance_langs = {
"it",
"es",
"fr",
"pt",
"oc",
"ca",
"rm",
"wa",
"lld",
"fur",
"lij",
"lmo",
"gl",
"lad",
"an",
"mwl",
}


if allowed_langs is None:
self.allowed_langs = self.romance_langs.union(
{self.target_lang, "tr", "ar", "de"}
)
else:
self.allowed_langs = allowed_langs


def get_language(self, texts: List[str]) -> List[str]:
# If the model doesn't exist download it
if not os.path.isfile(self.fasttext_model_path):
url = (
"https://dl.fbaipublicfiles.com/fasttext/supervised-models/lid.176.bin"
)
r = requests.get(url, allow_redirects=True)
open("/tmp/lid.176.bin", "wb").write(r.content)


lang_model = fasttext.load_model(self.fasttext_model_path)


# Predict the language code for each text in texts
langs, _ = lang_model.predict([x.replace("\n", " ") for x in texts])


# Extract the two character language code from the predictions.
return [x[0].split("__")[-1] for x in langs]


def fit(self, X, y):
return self


def transform(self, texts: str) -> List[str]:


# Get the language codes for each text in texts
langs = self.get_language(texts)


# Zip the texts, languages, and their indecies
# sort on the language so that all languages appear together
text_lang_pairs = sorted(
zip(texts, langs, range(len(langs))), key=lambda x: x[1]
)
model = None


translations = []
prev_lang = text_lang_pairs[0]
for text, lang, idx in text_lang_pairs:
if lang == self.target_lang or lang not in self.allowed_langs:
translations.append((idx, text))
else:
# Use the romance model if it is a romance language to avoid
# downloading a model for every language
if lang in self.romance_langs and self.target_lang == "en":
lang = "ROMANCE"


if model is None or lang != prev_lang:
translation_model_name = (
f"Helsinki-NLP/opus-mt-{lang}-{self.target_lang}"
)


# Download the model and tokenizer
model = MarianMTModel.from_pretrained(translation_model_name)
tokenizer = MarianTokenizer.from_pretrained(translation_model_name)


# Tokenize the text
batch = tokenizer([text], return_tensors="pt", padding=True)


# Make sure that the tokenized text does not exceed the maximum
# allowed size of 512
batch["input_ids"] = batch["input_ids"][:, :512]
batch["attention_mask"] = batch["attention_mask"][:, :512]


gen = model.generate(**batch)
translations.append(
(idx, tokenizer.batch_decode(gen, skip_special_tokens=True)[0])
)
prev_lang = lang


# Reorganize the translations to match the original ordering
return [x[1] for x in sorted(translations, key=lambda x: x[0])]

aqui tambien va otro bloque

Rate this post