Ajustar los modelos de adaptador MMS para ASR de baja cantidad de recursos

Ajuste de modelos de adaptador MMS para ASR con pocos recursos.

Nuevo (06/2023): Esta publicación del blog está fuertemente inspirada en “Ajuste fino de XLS-R en ASR multilingüe” y se puede considerar como una versión mejorada de la misma.

Wav2Vec2 es un modelo preentrenado para el Reconocimiento Automático del Habla (ASR) y fue lanzado en septiembre de 2020 por Alexei Baevski, Michael Auli y Alex Conneau. Poco después de que se demostrara el rendimiento sólido de Wav2Vec2 en uno de los conjuntos de datos en inglés más populares para ASR, llamado LibriSpeech, Facebook AI presentó dos versiones multilingües de Wav2Vec2, llamadas XLSR y XLM-R, capaces de reconocer el habla en hasta 128 idiomas. XLSR significa representaciones del habla transversal-lingüísticas y se refiere a la capacidad del modelo de aprender representaciones del habla que son útiles en varios idiomas.

El lanzamiento más reciente de Meta AI, Speech Multilingüe Masivo (MMS) de Vineel Pratap, Andros Tjandra, Bowen Shi, et al. lleva las representaciones del habla multilingües a un nuevo nivel. Se pueden identificar, transcribir y generar más de 1,100 idiomas hablados con los diversos puntos de control de identificación de idioma, reconocimiento de voz y conversión de texto a voz lanzados.

En esta publicación del blog, mostramos cómo el entrenamiento de adaptadores de MMS logra tasas de error de palabras sorprendentemente bajas después de solo 10-20 minutos de ajuste fino.

Para idiomas con pocos recursos, recomendamos enérgicamente utilizar el entrenamiento de adaptadores de MMS en lugar de ajustar fino el modelo completo como se hace en “Ajuste fino de XLS-R en ASR multilingüe”.

En nuestros experimentos, el entrenamiento de adaptadores de MMS es más eficiente en términos de memoria, más robusto y ofrece un mejor rendimiento para idiomas con pocos recursos. Sin embargo, para idiomas con muchos recursos, puede ser ventajoso ajustar fino todo el punto de control en lugar de usar capas de adaptadores.

Preservando la diversidad de idiomas del mundo

Según https://www.ethnologue.com/, alrededor de 3000, o el 40% de todos los idiomas “vivos”, están en peligro debido a un número cada vez menor de hablantes nativos. Esta tendencia solo continuará en un mundo cada vez más globalizado.

MMS es capaz de transcribir muchos idiomas en peligro de extinción, como el Ari o Kaivi. En el futuro, MMS puede desempeñar un papel vital en mantener vivos los idiomas ayudando a los hablantes restantes a crear registros escritos y comunicarse en su lengua materna.

Para adaptarse a más de 1000 vocabularios diferentes, MMS utiliza Adaptadores: un método de entrenamiento en el que solo se entrenan una pequeña fracción de los pesos del modelo.

Las capas de adaptadores actúan como puentes lingüísticos, permitiendo que el modelo aproveche el conocimiento de un idioma al descifrar otro.

Ajuste fino de MMS

Los puntos de control no supervisados de MMS fueron preentrenados en más de medio millón de horas de audio en más de 1,400 idiomas, con tamaños de modelo que van desde 300 millones hasta mil millones de parámetros.

Puede encontrar los puntos de control solo preentrenados en el 🤗 Hub para tamaños de modelo de 300 millones de parámetros (300M) y mil millones de parámetros (1B):

  • mms-300m
  • mms-1b

Nota: Si desea ajustar fino los modelos base, puede hacerlo de la misma manera que se muestra en “Ajuste fino de XLS-R en ASR multilingüe”.

Similar al objetivo de modelado de lenguaje enmascarado de BERT, MMS aprende representaciones del habla contextualizadas al enmascarar aleatoriamente vectores de características antes de pasarlos a una red de transformadores durante el preentrenamiento auto-supervisado.

Para ASR, el punto de control preentrenado MMS-1B se ajustó fino aún más de manera supervisada en más de 1000 idiomas con una capa de salida de vocabulario conjunto. Como paso final, se desechó la capa de salida de vocabulario conjunto y se mantuvieron en su lugar capas de adaptadores específicas de cada idioma. Cada capa de adaptador contiene solamente ~2.5M de pesos, que consisten en pequeñas capas de proyección lineal para cada bloque de atención, así como una capa de salida de vocabulario específica de cada idioma.

Tres checkpoints de MMS afinados para el reconocimiento de voz (ASR) han sido lanzados. Incluyen los pesos de adaptador 102, 1107 y 1162 respectivamente (uno para cada idioma):

  • mms-1b-fl102
  • mms-1b-l1107
  • mms-1b-all

Puedes ver que los modelos base se guardan (como de costumbre) como un archivo model.safetensors, pero además estos repositorios tienen muchos pesos de adaptador almacenados en el repositorio, por ejemplo bajo el nombre adapter.fra.safetensors para francés.

La documentación de Hugging Face explica muy bien cómo se pueden usar estos checkpoints para la inferencia, así que en esta publicación del blog nos centraremos en aprender cómo podemos entrenar de manera eficiente modelos de adaptador altamente eficientes basados en cualquiera de los checkpoints ASR lanzados.

Entrenamiento de pesos adaptativos

En el aprendizaje automático, los adaptadores son un método utilizado para afinar modelos pre-entrenados manteniendo los parámetros originales del modelo sin cambios. Hacen esto insertando pequeños módulos entrenables, llamados capas de adaptadores, entre las capas preexistentes del modelo, que luego adaptan el modelo a una tarea específica sin requerir una extensa reentrenamiento.

Los adaptadores tienen una larga historia en el reconocimiento de voz y especialmente en el reconocimiento de hablantes. En el reconocimiento de hablantes, los adaptadores se han utilizado de manera efectiva para ajustar modelos preexistentes y reconocer las idiosincrasias individuales de cada hablante, como se destaca en el trabajo de Gales y Woodland (1996) y Miao et al. (2014). Este enfoque no solo reduce en gran medida los requisitos computacionales en comparación con el entrenamiento del modelo completo, sino que también permite ajustes más flexibles y mejores específicos del hablante.

El trabajo realizado en MMS aprovecha esta idea de adaptadores para el reconocimiento de voz en diferentes idiomas. Se afinan un pequeño número de pesos de adaptador para comprender las características fonéticas y gramaticales únicas de cada idioma objetivo. De esta manera, MMS permite un único modelo base grande (por ejemplo, el checkpoint mms-1b-all) y más de 1000 capas de adaptador pequeñas (2.5M pesos cada una para mms-1b-all) para comprender y transcribir varios idiomas. Esto reduce drásticamente la demanda computacional de desarrollar modelos distintos para cada idioma.

¡Genial! Ahora que entendemos la motivación y la teoría, veamos cómo afinar los pesos del adaptador para mms-1b-all 🔥

Configuración del cuaderno

Como se hizo anteriormente en la publicación del blog “Afinación fina de XLS-R en ASR multilingüe”, afinamos el modelo en el conjunto de datos de ASR de bajo recurso de Common Voice que contiene solo aproximadamente 4 horas de datos de entrenamiento validados.

Al igual que Wav2Vec2 o XLS-R, MMS se afinan utilizando la Clasificación Temporal Conexa (CTC), que es un algoritmo utilizado para entrenar redes neuronales para problemas de secuencia a secuencia, como ASR y reconocimiento de escritura a mano.

Para obtener más detalles sobre el algoritmo CTC, recomiendo encarecidamente leer la publicación del blog bien escrita “Modelado de secuencia con CTC (2017)” de Awni Hannun.

Antes de comenzar, instalemos datasets y transformers. Además, necesitamos torchaudio para cargar archivos de audio y jiwer para evaluar nuestro modelo afinado utilizando la métrica de tasa de error de palabras (WER) 1 {}^1 1.

%%capture
!pip install --upgrade pip 
!pip install datasets
!pip install evaluate
!pip install git+https://github.com/huggingface/transformers.git
!pip install jiwer
!pip install accelerate

Recomendamos encarecidamente cargar tus checkpoints de entrenamiento directamente al 🤗 Hub mientras entrenas. Los repositorios de Hub tienen control de versiones incorporado, por lo que puedes estar seguro de que no se perderá ningún checkpoint del modelo durante el entrenamiento.

Para hacerlo, debes almacenar tu token de autenticación del sitio web de Hugging Face (regístrate aquí si aún no lo has hecho).

from huggingface_hub import notebook_login

notebook_login()

Preparar Datos, Tokenizer, Extractor de características

Los modelos ASR transciben el habla a texto, lo que significa que necesitamos tanto un extractor de características que procese la señal de habla al formato de entrada del modelo, por ejemplo, un vector de características, como un tokenizador que procese el formato de salida del modelo a texto.

En 🤗 Transformers, el modelo MMS está acompañado tanto por un extractor de características, llamado Wav2Vec2FeatureExtractor , como por un tokenizador, llamado Wav2Vec2CTCTokenizer .

Comencemos creando el tokenizador para decodificar las clases de salida predichas a la transcripción de salida.

Crear Wav2Vec2CTCTokenizer

Los modelos MMS ajustados, como mms-1b-all, ya tienen un tokenizador que acompaña al punto de control del modelo. Sin embargo, dado que queremos ajustar el modelo con datos de baja cantidad de recursos de un determinado idioma, se recomienda eliminar completamente el tokenizador y la capa de salida del vocabulario, y simplemente crear nuevos basados en los datos de entrenamiento en sí.

Los modelos similares a Wav2Vec2 ajustados en CTC transcriben un archivo de audio con un solo pase hacia adelante, primero procesando la entrada de audio en una secuencia de representaciones de contexto procesadas y luego utilizando la capa de salida del vocabulario final para clasificar cada representación de contexto a un carácter que representa la transcripción.

El tamaño de salida de esta capa corresponde al número de tokens en el vocabulario, que extraeremos del conjunto de datos etiquetado utilizado para el ajuste fino. Entonces, en el primer paso, echaremos un vistazo al conjunto de datos elegido de Common Voice y definiremos un vocabulario basado en las transcripciones.

Para este cuaderno, utilizaremos el conjunto de datos 6.1 de Common Voice para el idioma turco. El turco corresponde al código de idioma "tr" .

Genial, ahora podemos usar la API simple de 🤗 Datasets para descargar los datos. El nombre del conjunto de datos es "mozilla-foundation/common_voice_6_1" , el nombre de configuración corresponde al código de idioma, que en nuestro caso es "tr" .

Nota : Antes de poder descargar el conjunto de datos, debes acceder a él iniciando sesión en tu cuenta de Hugging Face, yendo a la página del repositorio del conjunto de datos y haciendo clic en “Aceptar y acceder al repositorio”

Common Voice tiene muchos fragmentos diferentes, incluido invalidated , que se refiere a datos que no se calificaron como “suficientemente limpios” como para considerarse útiles. En este cuaderno, solo utilizaremos los fragmentos "train" , "validation" y "test" .

Debido a que el conjunto de datos turco es tan pequeño, fusionaremos tanto los datos de validación como los de entrenamiento en un conjunto de datos de entrenamiento y solo utilizaremos los datos de prueba para la validación.

from datasets import load_dataset, load_metric, Audio

common_voice_train = load_dataset("mozilla-foundation/common_voice_6_1", "tr", split="train+validation", use_auth_token=True)
common_voice_test = load_dataset("mozilla-foundation/common_voice_6_1", "tr", split="test", use_auth_token=True)

Muchos conjuntos de datos ASR solo proporcionan el texto objetivo ( 'sentence' ) para cada matriz de audio ( 'audio' ) y archivo ( 'path' ). Common Voice realmente proporciona mucha más información sobre cada archivo de audio, como el 'accent' , etc. Manteniendo el cuaderno lo más general posible, solo consideramos el texto transcrito para el ajuste fino.

common_voice_train = common_voice_train.remove_columns(["accent", "age", "client_id", "down_votes", "gender", "locale", "segment", "up_votes"])
common_voice_test = common_voice_test.remove_columns(["accent", "age", "client_id", "down_votes", "gender", "locale", "segment", "up_votes"])

Escribamos una función corta para mostrar algunos ejemplos aleatorios del conjunto de datos y ejecutémosla varias veces para tener una idea de las transcripciones.

from datasets import ClassLabel
import random
import pandas as pd
from IPython.display import display, HTML

def show_random_elements(dataset, num_examples=10):
    assert num_examples <= len(dataset), "No se pueden elegir más elementos de los que hay en el conjunto de datos."
    picks = []
    for _ in range(num_examples):
        pick = random.randint(0, len(dataset)-1)
        while pick in picks:
            pick = random.randint(0, len(dataset)-1)
        picks.append(pick)

    df = pd.DataFrame(dataset[picks])
    display(HTML(df.to_html()))

show_random_elements(common_voice_train.remove_columns(["path", "audio"]), num_examples=10)

Oylar teker teker elle sayılacak.
Son olaylar endişe seviyesini yükseltti.
Tek bir kart hepsinin kapılarını açıyor.
Blogcular da tam bundan bahsetmek istiyor.
Bu Aralık iki bin onda oldu.
Fiyatın altmış altı milyon avro olduğu bildirildi.
Ardından da silahlı çatışmalar çıktı.
"Romanya'da kurumlar gelir vergisi oranı yüzde on altı."
Bu konuda neden bu kadar az şey söylendiğini açıklayabilir misiniz?

¡Perfecto! Las transcripciones parecen bastante limpias. Después de haber traducido las frases transcritas, parece que el lenguaje corresponde más a un texto escrito que a un diálogo ruidoso. Esto tiene sentido considerando que Common Voice es un corpus de habla leída de origen colaborativo.

Podemos ver que las transcripciones contienen algunos caracteres especiales, como ,.?!;:. Sin un modelo de lenguaje, es mucho más difícil clasificar fragmentos de habla en función de estos caracteres especiales porque realmente no corresponden a una unidad de sonido característica. Por ejemplo, la letra "s" tiene un sonido más o menos claro, mientras que el carácter especial "." no lo tiene. Además, para comprender el significado de una señal de habla, generalmente no es necesario incluir caracteres especiales en la transcripción.

Simplemente eliminemos todos los caracteres que no contribuyan al significado de una palabra y que realmente no puedan representarse mediante un sonido acústico y normalicemos el texto.

import re
chars_to_remove_regex = '[\,\?\.\!\-\;\:\"\“\%\‘\”\�\']'

def remove_special_characters(batch):
    batch["sentence"] = re.sub(chars_to_remove_regex, '', batch["sentence"]).lower()
    return batch

common_voice_train = common_voice_train.map(remove_special_characters)
common_voice_test = common_voice_test.map(remove_special_characters)

Echemos otro vistazo a las etiquetas de texto procesadas.

mostrar_elementos_aleatorios(common_voice_train.remover_columnas(["path","audio"]))

i̇kinci tur müzakereler eylül ayında başlayacak
jani ve babası bu düşüncelerinde yalnız değil
onurun gözlerindeki büyü
bandiç oyların yüzde kırk sekiz virgül elli dördünü topladı
bu imkansız
bu konu açık değildir
cinayet kamuoyunu şiddetle sarstı
kentin sokakları iki metre su altında kaldı
muhalefet partileri hükümete karşı ciddi bir mücadele ortaya koyabiliyorlar mı
festivale tüm dünyadan elli film katılıyor

¡Bien! Esto se ve mejor. Hemos eliminado la mayoría de los caracteres especiales de las transcripciones y las hemos normalizado a minúsculas solamente.

Antes de finalizar el preprocesamiento, siempre es ventajoso consultar a un hablante nativo del idioma objetivo para ver si el texto se puede simplificar aún más. Para esta entrada de blog, Merve tuvo la amabilidad de echar un vistazo rápido y señaló que los caracteres “con sombrero” – como â – ya no se usan realmente en turco y se pueden reemplazar por su equivalente “sin sombrero”, por ejemplo a.

Esto significa que deberíamos reemplazar una frase como "yargı sistemi hâlâ sağlıksız" por "yargı sistemi hala sağlıksız".

Escribamos otra función de mapeo corta para simplificar aún más las etiquetas de texto. Recuerda: cuanto más simples sean las etiquetas de texto, más fácil será para el modelo aprender a predecir esas etiquetas.

def reemplazar_caracteres_con_sombrero(batch):
    batch["sentence"] = re.sub('[â]', 'a', batch["sentence"])
    batch["sentence"] = re.sub('[î]', 'i', batch["sentence"])
    batch["sentence"] = re.sub('[ô]', 'o', batch["sentence"])
    batch["sentence"] = re.sub('[û]', 'u', batch["sentence"])
    return batch

common_voice_train = common_voice_train.map(reemplazar_caracteres_con_sombrero)
common_voice_test = common_voice_test.map(reemplazar_caracteres_con_sombrero)

En CTC, es común clasificar los fragmentos de habla en letras, así que haremos lo mismo aquí. Extraigamos todas las letras distintas de los datos de entrenamiento y prueba y construyamos nuestro vocabulario a partir de este conjunto de letras.

Escribimos una función de mapeo que concatena todas las transcripciones en una única transcripción larga y luego transforma la cadena en un conjunto de caracteres. Es importante pasar el argumento batched=True a la función map(...) para que la función de mapeo tenga acceso a todas las transcripciones de una sola vez.

def extraer_todos_los_caracteres(batch):
  todo_texto = " ".join(batch["sentence"])
  vocabulario = list(set(todo_texto))
  return {"vocabulario": [vocabulario], "todo_texto": [todo_texto]}

vocabulario_entrenamiento = common_voice_train.map(extraer_todos_los_caracteres, batched=True, batch_size=-1, keep_in_memory=True, remove_columns=common_voice_train.column_names)
vocabulario_prueba = common_voice_test.map(extraer_todos_los_caracteres, batched=True, batch_size=-1, keep_in_memory=True, remove_columns=common_voice_test.column_names)

Ahora creamos la unión de todas las letras distintas en el conjunto de datos de entrenamiento y el conjunto de datos de prueba, y convertimos la lista resultante en un diccionario enumerado.

vocab_list = list(set(vocab_train["vocab"][0]) | set(vocab_test["vocab"][0]))

vocab_dict = {v: k for k, v in enumerate(sorted(vocab_list))}
vocab_dict

    {' ': 0,
     'a': 1,
     'b': 2,
     'c': 3,
     'd': 4,
     'e': 5,
     'f': 6,
     'g': 7,
     'h': 8,
     'i': 9,
     'j': 10,
     'k': 11,
     'l': 12,
     'm': 13,
     'n': 14,
     'o': 15,
     'p': 16,
     'q': 17,
     'r': 18,
     's': 19,
     't': 20,
     'u': 21,
     'v': 22,
     'w': 23,
     'x': 24,
     'y': 25,
     'z': 26,
     'ç': 27,
     'ë': 28,
     'ö': 29,
     'ü': 30,
     'ğ': 31,
     'ı': 32,
     'ş': 33,
     '̇': 34}

Genial, vemos que todas las letras del alfabeto aparecen en el conjunto de datos (lo cual no es realmente sorprendente) y también extraemos los caracteres especiales "" y '. Tenga en cuenta que no excluimos esos caracteres especiales porque el modelo debe aprender a predecir cuando una palabra ha terminado, de lo contrario las predicciones siempre serían una secuencia de letras que harían imposible separar las palabras entre sí.

Siempre se debe tener en cuenta que el preprocesamiento es un paso muy importante antes de entrenar su modelo. Por ejemplo, no queremos que nuestro modelo diferencie entre a y A solo porque olvidamos normalizar los datos. La diferencia entre a y A no depende en absoluto del “sonido” de la letra, sino más bien de las reglas gramaticales, por ejemplo, utilizar una letra en mayúscula al comienzo de la oración. Por lo tanto, tiene sentido eliminar la diferencia entre letras en mayúscula y minúscula para que el modelo tenga más facilidad para aprender a transcribir el habla.

Para dejar en claro que " " tiene su propia clase de token, le damos un carácter más visible |. Además, también agregamos un token “desconocido” para que el modelo pueda lidiar más tarde con caracteres que no se encuentren en el conjunto de datos de entrenamiento de Common Voice.

vocab_dict["|"] = vocab_dict[" "]
del vocab_dict[" "]

Finalmente, también agregamos un token de relleno que corresponde al “token en blanco” de CTC. El “token en blanco” es un componente central del algoritmo CTC. Para obtener más información, consulte la sección “Alineación” aquí .

vocab_dict["[UNK]"] = len(vocab_dict)
vocab_dict["[PAD]"] = len(vocab_dict)
len(vocab_dict)

    37

Genial, ahora nuestro vocabulario está completo y consta de 37 tokens, lo que significa que la capa lineal que agregaremos encima del punto de control MMS preentrenado como parte de los pesos del adaptador tendrá una dimensión de salida de 37.

Dado que un solo punto de control MMS puede proporcionar pesos personalizados para varios idiomas, el tokenizador también puede consistir en múltiples vocabularios. Por lo tanto, debemos anidar nuestro vocab_dict para agregar potencialmente más idiomas al vocabulario en el futuro. El diccionario debe anidarse con el nombre que se utiliza para los pesos del adaptador y que se guarda en la configuración del tokenizador con el nombre target_lang.

Utilicemos los códigos de idioma ISO-639-3 como el punto de control original mms-1b-all.

target_lang = "tur"

Definamos un diccionario vacío al cual podamos agregar el vocabulario recién creado

new_vocab_dict = {target_lang: vocab_dict}

Nota: En caso de que desees usar este cuaderno para agregar una nueva capa de adaptador a un repositorio de modelos existente, asegúrate de no crear un diccionario de vocabulario vacío, sino reutilizar uno que ya exista. Para hacerlo, debes descomentar las siguientes celdas y reemplazar "patrickvonplaten/wav2vec2-large-mms-1b-turkish-colab" con un ID de repositorio de modelos al que desees agregar tus pesos de adaptador.

# from transformers import Wav2Vec2CTCTokenizer

# mms_adapter_repo = "patrickvonplaten/wav2vec2-large-mms-1b-turkish-colab"  # asegúrate de reemplazar esta ruta con un repositorio al que quieras agregar tus nuevos pesos de adaptador

# tokenizer = Wav2Vec2CTCTokenizer.from_pretrained(mms_adapter_repo)
# new_vocab = tokenizer.vocab

# new_vocab[target_lang] = vocab_dict

Ahora guardemos el vocabulario como un archivo json.

import json
with open('vocab.json', 'w') as vocab_file:
    json.dump(new_vocab_dict, vocab_file)

En un paso final, usamos el archivo json para cargar el vocabulario en una instancia de la clase Wav2Vec2CTCTokenizer.

from transformers import Wav2Vec2CTCTokenizer

tokenizer = Wav2Vec2CTCTokenizer.from_pretrained("./", unk_token="[UNK]", pad_token="[PAD]", word_delimiter_token="|", target_lang=target_lang)

Si se desea reutilizar el tokenizer recién creado con el modelo afinado de este cuaderno, se recomienda encarecidamente cargar el tokenizer en el Hub de 🤗. Llamemos al repositorio al que subiremos los archivos "wav2vec2-large-mms-1b-turkish-colab":

repo_name = "wav2vec2-large-mms-1b-turkish-colab"

y carguemos el tokenizer en el Hub de 🤗.

tokenizer.push_to_hub(repo_name)

    CommitInfo(commit_url='https://huggingface.co/patrickvonplaten/wav2vec2-large-mms-1b-turkish-colab/commit/48cccbfd6059aa6ce655e9d94b8358ba39536cb7', commit_message='Subir tokenizer', commit_description='', oid='48cccbfd6059aa6ce655e9d94b8358ba39536cb7', pr_url=None, pr_revision=None, pr_num=None)

Genial, puedes ver el repositorio recién creado en https://huggingface.co/<tu-nombre-de-usuario>/wav2vec2-large-mms-1b-tr-colab

Crear Wav2Vec2FeatureExtractor

El habla es una señal continua y para que las computadoras la traten, primero debe ser discretizada, lo que generalmente se llama muestreo. La tasa de muestreo juega un papel importante en esto, ya que define cuántos puntos de datos de la señal de voz se miden por segundo. Por lo tanto, el muestreo con una tasa de muestreo más alta resulta en una mejor aproximación de la señal de voz real, pero también requiere más valores por segundo.

Un punto de control preentrenado espera que los datos de entrada se hayan muestreado más o menos de la misma distribución que los datos en los que se entrenó. Las mismas señales de voz muestreadas a dos tasas diferentes tienen una distribución muy diferente, por ejemplo, duplicar la tasa de muestreo resulta en el doble de puntos de datos. Por lo tanto, antes de afinar un punto de control preentrenado de un modelo ASR, es crucial verificar que la tasa de muestreo de los datos utilizados para preentrenar el modelo coincida con la tasa de muestreo del conjunto de datos utilizado para afinar el modelo.

Un objeto Wav2Vec2FeatureExtractor requiere los siguientes parámetros para ser instanciado:

  • feature_size: Los modelos de voz toman una secuencia de vectores de características como entrada. Si bien la longitud de esta secuencia obviamente varía, el tamaño de la característica no debería hacerlo. En el caso de Wav2Vec2, el tamaño de la característica es 1 porque el modelo se entrenó en la señal de voz cruda 2 {}^2 2.
  • sampling_rate: La tasa de muestreo en la que se entrenó el modelo.
  • padding_value: Para la inferencia por lotes, las entradas más cortas deben rellenarse con un valor específico.
  • do_normalize: Si la entrada debe normalizarse a cero-media-varianza o no. Por lo general, los modelos de voz funcionan mejor cuando se normaliza la entrada.
  • return_attention_mask: Si el modelo debe utilizar una attention_mask para la inferencia por lotes. En general, los puntos de control de los modelos XLS-R siempre deben usar attention_mask.
from transformers import Wav2Vec2FeatureExtractor

feature_extractor = Wav2Vec2FeatureExtractor(tamaño_característica=1, tasa_muestreo=16000, valor_relleno=0.0, normalizar=True, devolver_máscara_atención=True)

¡Genial, el pipeline de extracción de características de MMS está completamente definido!

Para mejorar la facilidad de uso, el extractor de características y el tokenizador se envuelven en una única clase Wav2Vec2Processor para que solo se necesite un objeto modelo y procesador.

from transformers import Wav2Vec2Processor

procesador = Wav2Vec2Processor(extractor_características=extractor_características, tokenizador=tokenizador)

A continuación, podemos preparar el conjunto de datos.

Preprocesar Datos

Hasta ahora, no hemos examinado los valores reales de la señal de voz, sino solo la transcripción. Además de oración, nuestros conjuntos de datos incluyen otros dos nombres de columna, ruta y audio. ruta indica la ruta absoluta del archivo de audio y audio representa los datos de audio ya cargados. MMS espera que la entrada esté en formato de un array unidimensional de 16 kHz. Esto significa que el archivo de audio debe cargarse y remuestrearse.

Afortunadamente, datasets hace esto automáticamente cuando el nombre de la columna es audio. Vamos a probarlo.

common_voice_train[0]["audio"]

    {'ruta': '/root/.cache/huggingface/datasets/downloads/extracted/71ba9bd154da9d8c769b736301417178729d2b87b9e00cda59f6450f742ed778/cv-corpus-6.1-2020-12-11/tr/clips/common_voice_tr_17346025.mp3',
     'array': array([ 0.00000000e+00, -2.98378618e-13, -1.59835903e-13, ...,
            -2.01663317e-12, -1.87991593e-12, -1.17969588e-12]),
     'tasa_muestreo': 48000}

En el ejemplo anterior podemos ver que los datos de audio se cargan con una tasa de muestreo de 48 kHz, mientras que el modelo espera 16 kHz, como vimos antes. Podemos ajustar la característica de audio a la tasa de muestreo correcta utilizando cast_column:

common_voice_train = common_voice_train.cast_column("audio", Audio(tasa_muestreo=16_000))
common_voice_test = common_voice_test.cast_column("audio", Audio(tasa_muestreo=16_000))

Echemos un vistazo a "audio" nuevamente.

common_voice_train[0]["audio"]

{'ruta': '/root/.cache/huggingface/datasets/downloads/extracted/71ba9bd154da9d8c769b736301417178729d2b87b9e00cda59f6450f742ed778/cv-corpus-6.1-2020-12-11/tr/clips/common_voice_tr_17346025.mp3',
 'array': array([ 9.09494702e-13, -6.13908924e-12, -1.09139364e-11, ...,
         1.81898940e-12,  4.54747351e-13,  3.63797881e-12]),
 'tasa_muestreo': 16000}

¡Esto parece haber funcionado! Hagamos una verificación final de que los datos estén preparados correctamente, imprimiendo la forma de la entrada de voz, su transcripción y la tasa de muestreo correspondiente.

rand_int = random.randint(0, len(common_voice_train)-1)

print("Texto objetivo:", common_voice_train[rand_int]["oración"])
print("Forma del array de entrada:", common_voice_train[rand_int]["audio"]["array"].shape)
print("Tasa de muestreo:", common_voice_train[rand_int]["audio"]["tasa_muestreo"])

    Texto objetivo: bağış anlaşması bir ağustosta imzalandı
    Forma del array de entrada: (70656,)
    Tasa de muestreo: 16000

¡Bien! Todo parece estar bien: los datos son un array unidimensional, la tasa de muestreo siempre corresponde a 16 kHz y el texto objetivo está normalizado.

Finalmente, podemos aprovechar Wav2Vec2Processor para procesar los datos al formato esperado por Wav2Vec2ForCTC para entrenamiento. Para hacer esto, hagamos uso de la función map(...) de Dataset.

Primero, cargamos y remuestreamos los datos de audio, simplemente llamando a batch["audio"]. Segundo, extraemos los input_values del archivo de audio cargado. En nuestro caso, el Wav2Vec2Processor solo normaliza los datos. Sin embargo, para otros modelos de habla, este paso puede incluir una extracción de características más compleja, como la extracción de características Log-Mel. Tercero, codificamos las transcripciones en identificadores de etiquetas.

Nota: Esta función de mapeo es un buen ejemplo de cómo se debe utilizar la clase Wav2Vec2Processor. En un contexto “normal”, llamar a processor(...) se redirige al método de llamada de Wav2Vec2FeatureExtractor. Sin embargo, al envolver el procesador en el contexto as_target_processor, el mismo método se redirige al método de llamada de Wav2Vec2CTCTokenizer. Para obtener más información, consulte la documentación.

def prepare_dataset(batch):
    audio = batch["audio"]

    # la salida agrupada está "sin agrupar"
    batch["input_values"] = processor(audio["array"], sampling_rate=audio["sampling_rate"]).input_values[0]
    batch["input_length"] = len(batch["input_values"])

    batch["labels"] = processor(text=batch["sentence"]).input_ids
    return batch

Apliquemos la función de preparación de datos a todos los ejemplos.

common_voice_train = common_voice_train.map(prepare_dataset, remove_columns=common_voice_train.column_names)
common_voice_test = common_voice_test.map(prepare_dataset, remove_columns=common_voice_test.column_names)

Nota: datasets se encarga automáticamente de la carga y remuestreo de audio. Si desea implementar su propio cargador/remuestreador de datos personalizado, siéntase libre de utilizar la columna "path" y omitir la columna "audio".

¡Genial, ahora estamos listos para comenzar el entrenamiento!

Entrenamiento

Los datos se procesan para que estemos listos para comenzar a configurar el pipeline de entrenamiento. Vamos a utilizar el Trainer de 🤗 para lo cual esencialmente necesitamos hacer lo siguiente:

  • Definir un colector de datos. A diferencia de la mayoría de los modelos de PLN, MMS tiene una longitud de entrada mucho mayor que la longitud de salida. Por ejemplo, una muestra de longitud de entrada 50000 tiene una longitud de salida de no más de 100. Dadas las grandes tamaños de entrada, es mucho más eficiente rellenar los lotes de entrenamiento dinámicamente, lo que significa que todas las muestras de entrenamiento solo deben rellenarse hasta la muestra más larga en su lote y no hasta la muestra más larga en general. Por lo tanto, el ajuste fino de MMS requiere un colector de datos de relleno especial, que definiremos a continuación

  • Métrica de evaluación. Durante el entrenamiento, el modelo debe evaluarse en la tasa de error de palabras. Deberíamos definir una función compute_metrics en consecuencia

  • Cargar un punto de control preentrenado. Necesitamos cargar un punto de control preentrenado y configurarlo correctamente para el entrenamiento.

  • Definir la configuración del entrenamiento.

Después de haber ajustado el modelo, lo evaluaremos correctamente en los datos de prueba y verificaremos que realmente ha aprendido a transcribir el habla correctamente.

Configurar el Trainer

Comencemos definiendo el colector de datos. El código del colector de datos se copió de este ejemplo.

Sin entrar en demasiados detalles, a diferencia de los colectores de datos comunes, este colector de datos trata los input_values y labels de manera diferente y, por lo tanto, aplica dos funciones de relleno separadas en ellos (una vez más, haciendo uso del administrador de contexto del procesador de MMS). Esto es necesario porque, en el reconocimiento de voz, la entrada y la salida son de diferentes modalidades, por lo que no deben ser tratadas por la misma función de relleno. Análogo a los colectores de datos comunes, los tokens de relleno en las etiquetas son -100 para que esos tokens no se tengan en cuenta al calcular la pérdida.

import torch

from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional, Union

@dataclass
class DataCollatorCTCWithPadding:
    """
    Data collator that will dynamically pad the inputs received.
    Args:
        processor (:class:`~transformers.Wav2Vec2Processor`)
            The processor used for proccessing the data.
        padding (:obj:`bool`, :obj:`str` or :class:`~transformers.tokenization_utils_base.PaddingStrategy`, `optional`, defaults to :obj:`True`):
            Select a strategy to pad the returned sequences (according to the model's padding side and padding index)
            among:
            * :obj:`True` or :obj:`'longest'`: Pad to the longest sequence in the batch (or no padding if only a single
              sequence if provided).
            * :obj:`'max_length'`: Pad to a maximum length specified with the argument :obj:`max_length` or to the
              maximum acceptable input length for the model if that argument is not provided.
            * :obj:`False` or :obj:`'do_not_pad'` (default): No padding (i.e., can output a batch with sequences of
              different lengths).
    """

    processor: Wav2Vec2Processor
    padding: Union[bool, str] = True

    def __call__(self, features: List[Dict[str, Union[List[int], torch.Tensor]]]) -> Dict[str, torch.Tensor]:
        # dividir las entradas y las etiquetas ya que deben tener longitudes diferentes y necesitan
        # diferentes métodos de relleno
        input_features = [{"input_values": feature["input_values"]} for feature in features]
        label_features = [{"input_ids": feature["labels"]} for feature in features]

        batch = self.processor.pad(
            input_features,
            padding=self.padding,
            return_tensors="pt",
        )

        labels_batch = self.processor.pad(
            labels=label_features,
            padding=self.padding,
            return_tensors="pt",
        )

        # reemplazar el relleno con -100 para ignorar correctamente la pérdida
        labels = labels_batch["input_ids"].masked_fill(labels_batch.attention_mask.ne(1), -100)

        batch["labels"] = labels

        return batch

data_collator = DataCollatorCTCWithPadding(processor=processor, padding=True)

A continuación, se define la métrica de evaluación. Como se mencionó anteriormente, la métrica predominante en ASR es la tasa de error de palabras (WER), por lo tanto, también la utilizaremos en este cuaderno.

from evaluate import load

wer_metric = load("wer")

El modelo devolverá una secuencia de vectores de logitos: y 1 , … , y m \mathbf{y}_1, \ldots, \mathbf{y}_m y 1 ​ , … , y m ​ con y 1 = f θ ( x 1 , … , x n ) [ 0 ] \mathbf{y}_1 = f_{\theta}(x_1, \ldots, x_n)[0] y 1 ​ = f θ ​ ( x 1 ​ , … , x n ​ ) [ 0 ] y n > > m n >> m n > > m .

Un vector de logitos y 1 \mathbf{y}_1 y 1 ​ contiene las probabilidades logarítmicas para cada palabra en el vocabulario que definimos anteriormente, por lo tanto, len ( y i ) = \text{len}(\mathbf{y}_i) = len ( y i ​ ) = config.vocab_size . Estamos interesados en la predicción más probable del modelo y, por lo tanto, tomamos argmax(...) de los logitos. Además, transformamos las etiquetas codificadas de vuelta a la cadena original reemplazando -100 con el pad_token_id y decodificando los ids asegurándonos de que los tokens consecutivos no se agrupen en el mismo token en el estilo CTC 1 {}^1 1 .

def compute_metrics(pred):
    pred_logits = pred.predictions
    pred_ids = np.argmax(pred_logits, axis=-1)

    pred.label_ids[pred.label_ids == -100] = processor.tokenizer.pad_token_id

    pred_str = processor.batch_decode(pred_ids)
    # no queremos agrupar tokens al calcular las métricas
    label_str = processor.batch_decode(pred.label_ids, group_tokens=False)

    wer = wer_metric.compute(predictions=pred_str, references=label_str)

    return {"wer": wer}

Ahora, podemos cargar el punto de control preentrenado de mms-1b-all . El pad_token_id del tokenizador debe ser definido como el pad_token_id del modelo o en el caso de Wav2Vec2ForCTC también el token en blanco de CTC 2 {}^2 2 .

Dado que solo estamos entrenando un pequeño subconjunto de pesos, el modelo no es propenso al sobreajuste. Por lo tanto, nos aseguramos de desactivar todas las capas de dropout.

Nota: Al utilizar este cuaderno para entrenar MMS en otro idioma de Common Voice, es posible que esos ajustes de hiperparámetros no funcionen muy bien. Siéntase libre de adaptarlos según su caso de uso.

from transformers import Wav2Vec2ForCTC

model = Wav2Vec2ForCTC.from_pretrained(
    "facebook/mms-1b-all",
    attention_dropout=0.0,
    hidden_dropout=0.0,
    feat_proj_dropout=0.0,
    layerdrop=0.0,
    ctc_loss_reduction="mean",
    pad_token_id=processor.tokenizer.pad_token_id,
    vocab_size=len(processor.tokenizer),
    ignore_mismatched_sizes=True,
)

    Algunos pesos de Wav2Vec2ForCTC no se inicializaron a partir del punto de control del modelo en facebook/mms-1b-all y se inicializan de nuevo porque las formas no coinciden:
    - lm_head.bias: se encontró la forma torch.Size([154]) en el punto de control y torch.Size([39]) en el modelo instanciado
    - lm_head.weight: se encontró la forma torch.Size([154, 1280]) en el punto de control y torch.Size([39, 1280]) en el modelo instanciado
    Probablemente deba ENTRENAR este modelo en una tarea secundaria para poder usarlo para predicciones e inferencia.

Nota: Se espera que algunos pesos se inicialicen de nuevo. Esos pesos corresponden a la capa de salida del vocabulario inicializada de nuevo.

Ahora queremos asegurarnos de que solo se entrenen los pesos del adaptador y que el resto del modelo permanezca congelado.

Primero, reinicializamos todos los pesos del adaptador, lo cual se puede hacer con el práctico método init_adapter_layers. También es posible no reinicializar los pesos del adaptador y continuar con el ajuste fino, pero en este caso asegúrese de cargar los pesos del adaptador adecuados a través del método load_adapter(...) antes de entrenar. Sin embargo, es probable que el vocabulario aún no coincida muy bien con los datos de entrenamiento personalizados, por lo que generalmente es más fácil reinicializar todas las capas del adaptador para que se puedan ajustar fácilmente.

model.init_adapter_layers()

A continuación, congelamos todos los pesos, excepto los de las capas del adaptador.

model.freeze_base_model()

adapter_weights = model._get_adapters()
for param in adapter_weights.values():
    param.requires_grad = True

En un paso final, definimos todos los parámetros relacionados con el entrenamiento. Para dar más explicación sobre algunos de los parámetros:

  • group_by_length hace que el entrenamiento sea más eficiente al agrupar muestras de entrenamiento con una longitud de entrada similar en un lote. Esto puede acelerar significativamente el tiempo de entrenamiento al reducir en gran medida el número total de tokens de relleno inútiles que se pasan por el modelo
  • Se eligió learning_rate como 1e-3, que es un valor predeterminado común para el entrenamiento con Adam. Otros valores de learning_rate pueden funcionar igual de bien.

Para obtener más explicaciones sobre otros parámetros, se puede consultar la documentación. Para ahorrar memoria de GPU, habilitamos la verificación de gradiente de PyTorch y también configuramos la reducción de pérdida en “mean”. El ajuste fino del adaptador MMS converge extremadamente rápido a un rendimiento muy bueno, por lo que incluso para un conjunto de datos tan pequeño como 4 horas, solo entrenaremos durante 4 épocas. Durante el entrenamiento, se cargará asincrónicamente un punto de control en el hub cada 200 pasos de entrenamiento. Esto le permite jugar con el widget de demostración incluso mientras su modelo todavía se está entrenando.

Nota: Si no desea cargar los puntos de control del modelo en el hub, simplemente establezca push_to_hub=False.

from transformers import TrainingArguments

training_args = TrainingArguments(
  output_dir=repo_name,
  group_by_length=True,
  per_device_train_batch_size=32,
  evaluation_strategy="steps",
  num_train_epochs=4,
  gradient_checkpointing=True,
  fp16=True,
  save_steps=200,
  eval_steps=100,
  logging_steps=100,
  learning_rate=1e-3,
  warmup_steps=100,
  save_total_limit=2,
  push_to_hub=True,
)

Ahora, todas las instancias se pueden pasar al entrenador y estamos listos para comenzar el entrenamiento.

from transformers import Trainer

trainer = Trainer(
    model=model,
    data_collator=data_collator,
    args=training_args,
    compute_metrics=compute_metrics,
    train_dataset=common_voice_train,
    eval_dataset=common_voice_test,
    tokenizer=processor.feature_extractor,
)

1 {}^1 1 Para permitir que los modelos sean independientes de la velocidad del hablante, en CTC, los tokens consecutivos que son idénticos se agrupan simplemente como un solo token. Sin embargo, las etiquetas codificadas no deben agruparse al decodificar, ya que no corresponden a los tokens predichos por el modelo, por eso el parámetro group_tokens=False debe ser pasado. Si no pasáramos este parámetro, una palabra como "hello" se codificaría incorrectamente y se decodificaría como "helo". 2 {}^2 2 El token en blanco permite que el modelo prediga una palabra, como "hello", forzándolo a insertar el token en blanco entre las dos “l”. Una predicción conforme a CTC de "hello" de nuestro modelo sería [PAD] [PAD] "h" "e" "e" "l" "l" [PAD] "l" "o" "o" [PAD].

Entrenamiento

El entrenamiento debería tomar menos de 30 minutos dependiendo de la GPU utilizada.

trainer.train()

La pérdida de entrenamiento y la WER de validación disminuyen de manera agradable.

Vemos que el ajuste fino de las capas de adaptador de mms-1b-all durante solo 100 pasos supera ampliamente el ajuste fino de todo el punto de control xls-r-300m que se muestra aquí.

A partir del artículo oficial y esta rápida comparación, queda claro que mms-1b-all tiene una capacidad mucho mayor para transferir conocimiento a un idioma de recursos limitados y debería preferirse sobre xls-r-300m. Además, el entrenamiento también es más eficiente en memoria, ya que solo se entrenan un pequeño subconjunto de capas.

Los pesos del adaptador se cargarán como parte del punto de control del modelo, pero también queremos asegurarnos de guardarlos por separado para poder cargarlos y descargarlos fácilmente.

Guardemos todas las capas de adaptador en el directorio de salida de entrenamiento para que se pueda cargar correctamente en el Hub.

from safetensors.torch import save_file as safe_save_file
from transformers.models.wav2vec2.modeling_wav2vec2 import WAV2VEC2_ADAPTER_SAFE_FILE
import os

adapter_file = WAV2VEC2_ADAPTER_SAFE_FILE.format(target_lang)
adapter_file = os.path.join(training_args.output_dir, adapter_file)

safe_save_file(model._get_adapters(), adapter_file, metadata={"format": "pt"})

Finalmente, puedes cargar el resultado del entrenamiento en el Hub de 🤗.

trainer.push_to_hub()

Una de las principales ventajas del entrenamiento de pesos de adaptador es que el modelo “base”, que representa aproximadamente el 99% de los pesos del modelo, se mantiene sin cambios y solo se comparte un pequeño punto de control de adaptador de 2.5M para usar el punto de control entrenado.

Esto hace que sea extremadamente sencillo entrenar capas de adaptador adicionales y agregarlas a tu repositorio.

Puedes hacerlo fácilmente volviendo a ejecutar este script y cambiando el idioma en el que deseas entrenar a otro, por ejemplo, swe para sueco. Además, debes asegurarte de que el vocabulario no se sobrescriba por completo, sino que el nuevo vocabulario del idioma se concatene al existente, como se indica arriba en las celdas comentadas.

Para demostrar cómo se pueden cargar diferentes capas de adaptador, también he entrenado y cargado una capa de adaptador para el sueco bajo el código de idioma iso swe, como se puede ver aquí

Puedes cargar el punto de control ajustado fino como de costumbre usando from_pretrained(...), pero debes asegurarte de agregar target_lang="<your-lang-code>" al método para que se cargue el adaptador correcto. También debes establecer el idioma objetivo correctamente para tu tokenizador.

Veamos cómo podemos cargar primero el punto de control turco.

model_id = "patrickvonplaten/wav2vec2-large-mms-1b-turkish-colab"

model = Wav2Vec2ForCTC.from_pretrained(model_id, target_lang="tur").to("cuda")
processor = Wav2Vec2Processor.from_pretrained(model_id)

processor.tokenizer.set_target_lang("tur")

Verifiquemos que el modelo pueda transcribir correctamente el turco

from datasets import Audio

common_voice_test_tr = load_dataset("mozilla-foundation/common_voice_6_1", "tr", data_dir="./cv-corpus-6.1-2020-12-11", split="test", use_auth_token=True)
common_voice_test_tr = common_voice_test_tr.cast_column("audio", Audio(sampling_rate=16_000))

Procesemos el audio, realicemos un pase hacia adelante y predigamos las ids

input_dict = processor(common_voice_test_tr[0]["audio"]["array"], sampling_rate=16_000, return_tensors="pt", padding=True)

logits = model(input_dict.input_values.to("cuda")).logits

pred_ids = torch.argmax(logits, dim=-1)[0]

Finalmente, podemos decodificar el ejemplo.

print("Predicción:")
print(processor.decode(pred_ids))

print("\nReferencia:")
print(common_voice_test_tr[0]["sentence"].lower())

Salida :

    Predicción:
    pekçoğuda roman toplumundan geliyor

    Referencia:
    pek çoğu da roman toplumundan geliyor.

Esto parece estar casi exactamente correcto, solo se deben agregar dos espacios en blanco en la primera palabra. Ahora es muy simple cambiar el adaptador a sueco llamando a model.load_adapter(...) y cambiando también el tokenizador a sueco.

model.load_adapter("swe")
processor.tokenizer.set_target_lang("swe")

Nuevamente cargamos el conjunto de pruebas en sueco de Common Voice

common_voice_test_swe = load_dataset("mozilla-foundation/common_voice_6_1", "sv-SE", data_dir="./cv-corpus-6.1-2020-12-11", split="test", use_auth_token=True)
common_voice_test_swe = common_voice_test_swe.cast_column("audio", Audio(sampling_rate=16_000))

y transcribimos una muestra:

input_dict = processor(common_voice_test_swe[0]["audio"]["array"], sampling_rate=16_000, return_tensors="pt", padding=True)

logits = model(input_dict.input_values.to("cuda")).logits

pred_ids = torch.argmax(logits, dim=-1)[0]

print("Predicción:")
print(processor.decode(pred_ids))

print("\nReferencia:")
print(common_voice_test_swe[0]["sentence"].lower())

Salida :

    Predicción:
    jag lämnade grovjobbet åt honom

    Referencia:
    jag lämnade grovjobbet åt honom.

¡Genial, esto parece una transcripción perfecta!

Hemos demostrado en esta publicación de blog cómo el ajuste fino de los pesos del adaptador MMS no solo brinda un rendimiento de vanguardia en idiomas con pocos recursos, sino que también acelera significativamente el tiempo de entrenamiento y permite construir fácilmente una colección de pesos de adaptador personalizados.

Las publicaciones relacionadas y los enlaces adicionales se enumeran aquí:

  • Artículo oficial
  • Cobebase original
  • Demostración oficial
  • Documentación de Transformers
  • Publicación de blog relacionada sobre XLS-R
  • Modelos en el Hub

We will continue to update Zepes; if you have any questions or suggestions, please contact us!

Share:

Was this article helpful?

93 out of 132 found this helpful

Discover more

Inteligencia Artificial

Conoce al Omnívoro Diseñador Industrial combina el Arte y el OpenUSD para crear Activos 3D para el Entrenamiento de IA

Nota del editor: esta publicación es parte de nuestra serie Conoce al Omnivore, que presenta a creadores y desarrolla...

Inteligencia Artificial

6 Podcasts de GenAI que deberías estar escuchando

Introducción En el paisaje en constante evolución de la inteligencia artificial, el campo de la IA generativa (GenAI)...

Inteligencia Artificial

La IA está haciendo que la política sea más fácil, más barata y más peligrosa

Los votantes ya están viendo materiales de campaña generados por IA, y probablemente no lo saben.

Investigación

Un sistema robótico de cuatro patas para jugar al fútbol en diversos terrenos.

DribbleBot puede maniobrar un balón de fútbol en terrenos como arena, grava, barro y nieve, utilizando el aprendizaje...

Inteligencia Artificial

La IA generativa imagina nuevas estructuras de proteínas

Investigadores del MIT desarrollan FrameDiff, una herramienta computacional que utiliza inteligencia artificial gener...

Inteligencia Artificial

Las ratas utilizan la imaginación para navegar en realidad virtual

Investigadores del Instituto Howard Hughes Medical Institute probaron si las ratas, al igual que los humanos, pueden ...