Hugging Face transformers
La librería transformers
de Hugging Face es una de las librerías más populares para trabajar con modelos de lenguaje. Su facilidad de uso hizo que se democratizara el uso de la arquitectura Transformer
y que se pudiera trabajar con modelos de lenguaje de última generación sin necesidad de tener un gran conocimiento en el área.
Entre la librería transformers
, el hub de modelos y su facilidad de uso, los spaces y la facilidad de desplegar demos, y nuevas librerías como datasets
, accelerate
, PEFT
y otras más, han hecho que Hugging Face sea uno de los actores más importantes de la escena de inteligencia artificial del momento. Ellos mismos se auto-denominan como "el GitHub de la IA" y ciertamente lo son.
Instalación
Para instalar transformers se puede hacer con pip
pip install transformers
o con conda
conda install conda-forge::transformers
Además de la librería es necesario tener un backend de PyTorch o TensorFlow instalado. Es decir, necesitas tener instalar torch
o tensorflow
para poder usar transformers
.
Inferencia con pipeline
Con los pipeline
s de transformers
se puede hacer inferencia con modelos de lenguaje de una manera muy sencilla. Esto tiene la ventaja de que el desarrollo se realiza de manera mucho más rápida y se puede hacer prototipado de manera muy sencilla. Además permite a personas que no tienen mucho conocimiento poder usar los modelos
Con pipeline
se puede hacer inferencia en un montón de tareas diferentes. Cada tarea tiene su propio pipeline
(pipeline
de NLP, pipeline
de vision, etc), pero se puede hacer una abstracción general usando la clase pipeline
que se encarga de seleccionar el pipeline
adecuado para la tarea que se le pase.
Tareas
Al día de escribir este post, las tareas que se pueden hacer con pipeline
son:
Audio:
- Clasificación de audio
- clasificación de escena acústica: etiquetar audio con una etiqueta de escena (“oficina”, “playa”, “estadio”)
- detección de eventos acústicos: etiquetar audio con una etiqueta de evento de sonido (“bocina de automóvil”, “llamada de ballena”, “cristal rompiéndose”)
- etiquetado: etiquetar audio que contiene varios sonidos (canto de pájaros, identificación de altavoces en una reunión)
- clasificación de música: etiquetar música con una etiqueta de género (“metal”, “hip-hop”, “country”)
- Clasificación de audio
Reconocimiento automático del habla (ASR, audio speech recognition):
Visión por computadora
- Clasificación de imágenes
- Detección de objetos
- Segmentación de imágenes
- Estimación de profundidad
Procesamiento del lenguaje natural (NLP, natural language processing)
- Clasificación de texto
- análisis de sentimientos
- clasificación de contenido
- Clasificación de tokens
- reconocimiento de entidades nombradas (NER, por sus siglas en inglés): etiquetar un token según una categoría de entidad como organización, persona, ubicación o fecha.
- etiquetado de partes del discurso (POS, por sus siglas en inglés): etiquetar un token según su parte del discurso, como sustantivo, verbo o adjetivo. POS es útil para ayudar a los sistemas de traducción a comprender cómo dos palabras idénticas son gramaticalmente diferentes (por ejemplo, “corte” como sustantivo versus “corte” como verbo)
- Respuestas a preguntas
- extractivas: dada una pregunta y algún contexto, la respuesta es un fragmento de texto del contexto que el modelo debe extraer
- abstractivas: dada una pregunta y algún contexto, la respuesta se genera a partir del contexto; este enfoque lo maneja la Text2TextGenerationPipeline en lugar del QuestionAnsweringPipeline que se muestra a continuación
- Resumir
- extractiva: identifica y extrae las oraciones más importantes del texto original
- abstractiva: genera el resumen objetivo (que puede incluir nuevas palabras no presentes en el documento de entrada) a partir del texto original
- Traducción
- Modelado de lenguaje
- causal: el objetivo del modelo es predecir el próximo token en una secuencia, y los tokens futuros están enmascarados
- enmascarado: el objetivo del modelo es predecir un token enmascarado en una secuencia con acceso completo a los tokens en la secuencia
- Clasificación de texto
Multimodal
- Respuestas a preguntas de documentos
Uso de pipeline
La forma más sencilla de crear un pipeline
es simplemente indicarle la tarea que queremos que resuelva mediante el parámetro task
. Y la librería se encargará de seleccionar el mejor modelo para esa tarea, descargarlo y guardarlo en la caché para futuros usos.
from transformers import pipelinegenerator = pipeline(task="text-generation")
No model was supplied, defaulted to openai-community/gpt2 and revision 6c0e608 (https://huggingface.co/openai-community/gpt2).Using a pipeline without specifying a model name and revision in production is not recommended.
generator("Me encanta aprender de")
Como se puede ver el texto generado está en francés, mientras que yo se lo he introducido en español, por lo que es importante elegir bien el modelo. SI te fijas la librería ha cogido el modelo openai-community/gpt2
que es un modelo entrenado en su mayoría en inglés, y que al meterle texto en español se ha liado y ha generado una respuesta en francés.
Vamos a usar un modelo reentrenado en español mediante el parámetro model
.
generator("Me encanta aprender de")from transformers import pipelinegenerator = pipeline(task="text-generation", model="flax-community/gpt-2-spanish")
generator("Me encanta aprender de")
Ahora el texto generado tiene mucha mejor pinta
La clase pipeline
tiene muchos posibles parámetros, por lo que para verlos todos y aprender más sobre la clase te recomiendo leer su documentación, pero vamos a hablar de una, ya que para el deep learning es muy importante y es device
. Define el dispositivo (por ejemplo, cpu
, cuda:1
, mps
o un rango ordinal de GPU como 1
) en el que se asignará el pipeline
.
En mi caso, como tengo una GPU pongo 0
from transformers import pipeline
generator = pipeline(task="text-generation", model="flax-community/gpt-2-spanish", device=0)
generation = generator("Me encanta aprender de")
print(generation[0]['generated_text'])
Cómo funciona pipeline
Cuando hacemos uso de pipeline
por debajo lo que está pasando es esto
Automáticamente se está tokenizando el texto, se pasa por el modelo y después por un postprocesado
Inferencia con AutoClass
y pipeline
Hemos visto que pipeline
nos abstrae mucho de lo que pasa, pero nosotros podemos seleccionar qué tokenizador, qué modelo y qué postprocesado queremos usar.
Tokenización con AutoTokenizer
Antes usamos el modelo flax-community/gpt-2-spanish
para generar texto, podemos usar su tokenizador
generator("Me encanta aprender de")from transformers import pipelinegenerator = pipeline(task="text-generation", model="flax-community/gpt-2-spanish")generator("Me encanta aprender de")from transformers import pipelinegenerator = pipeline(task="text-generation", model="flax-community/gpt-2-spanish", device=0)generation = generator("Me encanta aprender de")print(generation[0]['generated_text'])from transformers import AutoTokenizercheckpoint = "flax-community/gpt-2-spanish"tokenizer = AutoTokenizer.from_pretrained(checkpoint)text = "Me encanta lo que estoy aprendiendo"tokens = tokenizer(text, return_tensors="pt")print(tokens)
Setting `pad_token_id` to `eos_token_id`:50256 for open-end generation.Setting `pad_token_id` to `eos_token_id`:50256 for open-end generation.Setting `pad_token_id` to `eos_token_id`:50256 for open-end generation.{'input_ids': tensor([[ 2879, 4835, 382, 288, 2383, 15257]]), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1]])}
Modelo AutoModel
Ahora podemos crear el modelo y pasarle los tokens
from transformers import AutoModelmodel = AutoModel.from_pretrained("flax-community/gpt-2-spanish")output = model(**tokens)type(output), output.keys()
(transformers.modeling_outputs.BaseModelOutputWithPastAndCrossAttentions,odict_keys(['last_hidden_state', 'past_key_values']))
Si ahora lo intentamos usar en un pipeline
nos dará un error
from transformers import pipeline
pipeline("text-generation", model=model, tokenizer=tokenizer)("Me encanta aprender de")
Hesto es porque cuando funcionaba usábamos
pipeline(task="text-generation", model="flax-community/gpt-2-spanish")
Pero ahora hemos hecho
tokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish")
model = AutoModel.from_pretrained("flax-community/gpt-2-spanish")
pipeline("text-generation", model=model, tokenizer=tokenizer)
En el primer caso solo usábamos pipeline
y el nombre del modelo, por debajo buscaba la mejor manera de implementar el modelo y el tokenizador. Pero en el segundo caso hemos creado el tokenizador y el modelo y se lo hemos pasado a pipeline
, pero no los hemos creado bien para lo que el pipeline
necesita
Para arreglar esto usamos AutoModelFor
Modelo AutoModelFor
La librería transformers nos da la oportunidad de crear un modelo para una tarea determinada como
AutoModelForCausalLM
que sirve para continuar textosAutoModelForMaskedLM
que se usa para rellenar huecosAutoModelForMaskGeneration
que sirve para generar máscarasAutoModelForSeq2SeqLM
que se usa par convertir de secuencias a secuencias, como por ejemplo en traducciónAutoModelForSequenceClassification
para clasificación de textoAutoModelForMultipleChoice
para elección múltipleAutoModelForNextSentencePrediction
para predecir si dos frases son consecutivasAutoModelForTokenClassification
para clasificación de tokensAutoModelForQuestionAnswering
para preguntas y respuestasAutoModelForTextEncoding
para codificación de texto
Vamos a usar el modelo anterior para generar texto
from transformers import AutoTokenizer, AutoModelForCausalLM
from transformers import pipeline
tokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish")
model = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish")
pipeline("text-generation", model=model, tokenizer=tokenizer)("Me encanta aprender de")[0]['generated_text']
Ahora si funciona, porque hemos creado el modelo de una manera que pipeline
puede entender
Inferencia solo con AutoClass
Antes hemos creado el modelo y el tokenizador y se lo hemos dado a pipeline
para que por debajo haga lo necesario, pero podemos usar nosotros los métodos para la inferencia.
Generación de texto casual
Creamos el modelo y el tokenizador
from transformers import pipelinepipeline("text-generation", model=model, tokenizer=tokenizer)("Me encanta aprender de")from transformers import AutoTokenizer, AutoModelForCausalLMfrom transformers import pipelinetokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish")model = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish")pipeline("text-generation", model=model, tokenizer=tokenizer)("Me encanta aprender de")[0]['generated_text']from transformers import AutoTokenizer, AutoModelForCausalLMtokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish")model = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish", device_map=0)
Con device_map
, hemos cargado el modelo en la GPU 0
Ahora tenemos que hacer nosotros lo que antes hacía pipeline
Primero generamos los tokens
from transformers import pipelinepipeline("text-generation", model=model, tokenizer=tokenizer)("Me encanta aprender de")from transformers import AutoTokenizer, AutoModelForCausalLMfrom transformers import pipelinetokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish")model = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish")pipeline("text-generation", model=model, tokenizer=tokenizer)("Me encanta aprender de")[0]['generated_text']from transformers import AutoTokenizer, AutoModelForCausalLMtokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish")model = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish", device_map=0)tokens_input = tokenizer(["Me encanta aprender de"], return_tensors="pt", padding=True).to("cuda")
The model 'GPT2Model' is not supported for text-generation. Supported models are ['BartForCausalLM', 'BertLMHeadModel', 'BertGenerationDecoder', 'BigBirdForCausalLM', 'BigBirdPegasusForCausalLM', 'BioGptForCausalLM', 'BlenderbotForCausalLM', 'BlenderbotSmallForCausalLM', 'BloomForCausalLM', 'CamembertForCausalLM', 'LlamaForCausalLM', 'CodeGenForCausalLM', 'CpmAntForCausalLM', 'CTRLLMHeadModel', 'Data2VecTextForCausalLM', 'ElectraForCausalLM', 'ErnieForCausalLM', 'FalconForCausalLM', 'FuyuForCausalLM', 'GemmaForCausalLM', 'GitForCausalLM', 'GPT2LMHeadModel', 'GPT2LMHeadModel', 'GPTBigCodeForCausalLM', 'GPTNeoForCausalLM', 'GPTNeoXForCausalLM', 'GPTNeoXJapaneseForCausalLM', 'GPTJForCausalLM', 'LlamaForCausalLM', 'MarianForCausalLM', 'MBartForCausalLM', 'MegaForCausalLM', 'MegatronBertForCausalLM', 'MistralForCausalLM', 'MixtralForCausalLM', 'MptForCausalLM', 'MusicgenForCausalLM', 'MvpForCausalLM', 'OpenLlamaForCausalLM', 'OpenAIGPTLMHeadModel', 'OPTForCausalLM', 'PegasusForCausalLM', 'PersimmonForCausalLM', 'PhiForCausalLM', 'PLBartForCausalLM', 'ProphetNetForCausalLM', 'QDQBertLMHeadModel', 'Qwen2ForCausalLM', 'ReformerModelWithLMHead', 'RemBertForCausalLM', 'RobertaForCausalLM', 'RobertaPreLayerNormForCausalLM', 'RoCBertForCausalLM', 'RoFormerForCausalLM', 'RwkvForCausalLM', 'Speech2Text2ForCausalLM', 'StableLmForCausalLM', 'TransfoXLLMHeadModel', 'TrOCRForCausalLM', 'WhisperForCausalLM', 'XGLMForCausalLM', 'XLMWithLMHeadModel', 'XLMProphetNetForCausalLM', 'XLMRobertaForCausalLM', 'XLMRobertaXLForCausalLM', 'XLNetLMHeadModel', 'XmodForCausalLM'].Setting `pad_token_id` to `eos_token_id`:50256 for open-end generation.---------------------------------------------------------------------------ValueError Traceback (most recent call last)Cell In[2], line 1----> 1 tokens_input = tokenizer(["Me encanta aprender de"], return_tensors="pt", padding=True).to("cuda")File ~/miniconda3/envs/nlp/lib/python3.11/site-packages/transformers/tokenization_utils_base.py:2829, in PreTrainedTokenizerBase.__call__(self, text, text_pair, text_target, text_pair_target, add_special_tokens, padding, truncation, max_length, stride, is_split_into_words, pad_to_multiple_of, return_tensors, return_token_type_ids, return_attention_mask, return_overflowing_tokens, return_special_tokens_mask, return_offsets_mapping, return_length, verbose, **kwargs)2827 if not self._in_target_context_manager:2828 self._switch_to_input_mode()-> 2829 encodings = self._call_one(text=text, text_pair=text_pair, **all_kwargs)2830 if text_target is not None:2831 self._switch_to_target_mode()File ~/miniconda3/envs/nlp/lib/python3.11/site-packages/transformers/tokenization_utils_base.py:2915, in PreTrainedTokenizerBase._call_one(self, text, text_pair, add_special_tokens, padding, truncation, max_length, stride, is_split_into_words, pad_to_multiple_of, return_tensors, return_token_type_ids, return_attention_mask, return_overflowing_tokens, return_special_tokens_mask, return_offsets_mapping, return_length, verbose, **kwargs)2910 raise ValueError(2911 f"batch length of `text`: {len(text)} does not match batch length of `text_pair`:"2912 f" {len(text_pair)}."2913 )2914 batch_text_or_text_pairs = list(zip(text, text_pair)) if text_pair is not None else text-> 2915 return self.batch_encode_plus(2916 batch_text_or_text_pairs=batch_text_or_text_pairs,2917 add_special_tokens=add_special_tokens,2918 padding=padding,2919 truncation=truncation,2920 max_length=max_length,2921 stride=stride,2922 is_split_into_words=is_split_into_words,2923 pad_to_multiple_of=pad_to_multiple_of,2924 return_tensors=return_tensors,2925 return_token_type_ids=return_token_type_ids,2926 return_attention_mask=return_attention_mask,2927 return_overflowing_tokens=return_overflowing_tokens,2928 return_special_tokens_mask=return_special_tokens_mask,2929 return_offsets_mapping=return_offsets_mapping,2930 return_length=return_length,2931 verbose=verbose,2932 **kwargs,2933 )2934 else:2935 return self.encode_plus(2936 text=text,2937 text_pair=text_pair,(...)2953 **kwargs,2954 )File ~/miniconda3/envs/nlp/lib/python3.11/site-packages/transformers/tokenization_utils_base.py:3097, in PreTrainedTokenizerBase.batch_encode_plus(self, batch_text_or_text_pairs, add_special_tokens, padding, truncation, max_length, stride, is_split_into_words, pad_to_multiple_of, return_tensors, return_token_type_ids, return_attention_mask, return_overflowing_tokens, return_special_tokens_mask, return_offsets_mapping, return_length, verbose, **kwargs)3080 """3081 Tokenize and prepare for the model a list of sequences or a list of pairs of sequences.3082(...)3093 details in `encode_plus`).3094 """3096 # Backward compatibility for 'truncation_strategy', 'pad_to_max_length'-> 3097 padding_strategy, truncation_strategy, max_length, kwargs = self._get_padding_truncation_strategies(3098 padding=padding,3099 truncation=truncation,3100 max_length=max_length,3101 pad_to_multiple_of=pad_to_multiple_of,3102 verbose=verbose,3103 **kwargs,3104 )3106 return self._batch_encode_plus(3107 batch_text_or_text_pairs=batch_text_or_text_pairs,3108 add_special_tokens=add_special_tokens,(...)3123 **kwargs,3124 )File ~/miniconda3/envs/nlp/lib/python3.11/site-packages/transformers/tokenization_utils_base.py:2734, in PreTrainedTokenizerBase._get_padding_truncation_strategies(self, padding, truncation, max_length, pad_to_multiple_of, verbose, **kwargs)2732 # Test if we have a padding token2733 if padding_strategy != PaddingStrategy.DO_NOT_PAD and (self.pad_token is None or self.pad_token_id < 0):-> 2734 raise ValueError(2735 "Asking to pad but the tokenizer does not have a padding token. "2736 "Please select a token to use as `pad_token` `(tokenizer.pad_token = tokenizer.eos_token e.g.)` "2737 "or add a new pad token via `tokenizer.add_special_tokens({'pad_token': '[PAD]'})`."2738 )2740 # Check that we will truncate to a multiple of pad_to_multiple_of if both are provided2741 if (2742 truncation_strategy != TruncationStrategy.DO_NOT_TRUNCATE2743 and padding_strategy != PaddingStrategy.DO_NOT_PAD(...)2746 and (max_length % pad_to_multiple_of != 0)2747 ):ValueError: Asking to pad but the tokenizer does not have a padding token. Please select a token to use as `pad_token` `(tokenizer.pad_token = tokenizer.eos_token e.g.)` or add a new pad token via `tokenizer.add_special_tokens({'pad_token': '[PAD]'})`.
Vemos que nos ha dado un error, nos dice que el tokenizador no tiene token de padding. La mayoría de LLMs no tienen un token de padding, pero para usar la librería transformers
es necesario un token de padding, por lo que lo que se suele hacer es asignar el token de fin de sentencia al token de padding
tokenizer.pad_token = tokenizer.eos_token
Ahora ya podemos generar los tokens
tokenizer.pad_token = tokenizer.eos_tokentokens_input = tokenizer(["Me encanta aprender de"], return_tensors="pt", padding=True).to("cuda")tokens_input.input_ids
tensor([[2879, 4835, 3760, 225, 72, 73]], device='cuda:0')
Ahora se los pasamos al modelo que generará nuevos tokens, para eso usamos el método generate
tokens_output = model.generate(**tokens_input, max_length=50)
print(f"input tokens: {tokens_input.input_ids}")
print(f"output tokens: {tokens_output}")
Podemos ver que los primeros tokens de token_inputs
son los mismos que los de token_outputs
, los que vienen a continuación son los que ha generado el modelo
Ahora tenemos que convertir esos tokens a una sentencia mediante el decoder del tokenizador
tokens_output = model.generate(**tokens_input, max_length=50)print(f"input tokens: {tokens_input.input_ids}")print(f"output tokens: {tokens_output}")sentence_output = tokenizer.decode(tokens_output[0], skip_special_tokens=True)sentence_output
Setting `pad_token_id` to `eos_token_id`:50256 for open-end generation.'Me encanta aprender de los demás, y en este caso de los que me rodean, y es que en el fondo, es una forma de aprender de los demás. '
Ya tenemos el texto generado
Clasificación de texto
Creamos el modelo y el tokenizador
import torchfrom transformers import AutoTokenizer, AutoModelForSequenceClassificationtokenizer = AutoTokenizer.from_pretrained("stevhliu/my_awesome_model")model = AutoModelForSequenceClassification.from_pretrained("stevhliu/my_awesome_model", device_map=0)
Generamos los tokens
import torchfrom transformers import AutoTokenizer, AutoModelForSequenceClassificationtokenizer = AutoTokenizer.from_pretrained("stevhliu/my_awesome_model")model = AutoModelForSequenceClassification.from_pretrained("stevhliu/my_awesome_model", device_map=0)text = "This was a masterpiece. Not completely faithful to the books, but enthralling from beginning to end. Might be my favorite of the three."inputs = tokenizer(text, return_tensors="pt").to("cuda")
Una vez tenemos los tokens, clasificamos
import torchfrom transformers import AutoTokenizer, AutoModelForSequenceClassificationtokenizer = AutoTokenizer.from_pretrained("stevhliu/my_awesome_model")model = AutoModelForSequenceClassification.from_pretrained("stevhliu/my_awesome_model", device_map=0)text = "This was a masterpiece. Not completely faithful to the books, but enthralling from beginning to end. Might be my favorite of the three."inputs = tokenizer(text, return_tensors="pt").to("cuda")with torch.no_grad():logits = model(**inputs).logitspredicted_class_id = logits.argmax().item()prediction = model.config.id2label[predicted_class_id]prediction
'LABEL_1'
Vamos a ver las clases
clases = model.config.id2labelclases
{0: 'LABEL_0', 1: 'LABEL_1'}
Así no hay quien se entere, así que lo modificamos
model.config.id2label = {0: "NEGATIVE", 1: "POSITIVE"}
Y ahora volvemos a clasificar
model.config.id2label = {0: "NEGATIVE", 1: "POSITIVE"}with torch.no_grad():logits = model(**inputs).logitspredicted_class_id = logits.argmax().item()prediction = model.config.id2label[predicted_class_id]prediction
'POSITIVE'
Clasificación de tokens
Creamos el modelo y el tokenizador
import torchfrom transformers import AutoTokenizer, AutoModelForTokenClassificationtokenizer = AutoTokenizer.from_pretrained("stevhliu/my_awesome_wnut_model")model = AutoModelForTokenClassification.from_pretrained("stevhliu/my_awesome_wnut_model", device_map=0)
Generamos los tokens
import torchfrom transformers import AutoTokenizer, AutoModelForTokenClassificationtokenizer = AutoTokenizer.from_pretrained("stevhliu/my_awesome_wnut_model")model = AutoModelForTokenClassification.from_pretrained("stevhliu/my_awesome_wnut_model", device_map=0)text = "The Golden State Warriors are an American professional basketball team based in San Francisco."inputs = tokenizer(text, return_tensors="pt").to("cuda")
Una vez tenemos los tokens, clasificamos
import torchfrom transformers import AutoTokenizer, AutoModelForTokenClassificationtokenizer = AutoTokenizer.from_pretrained("stevhliu/my_awesome_wnut_model")model = AutoModelForTokenClassification.from_pretrained("stevhliu/my_awesome_wnut_model", device_map=0)text = "The Golden State Warriors are an American professional basketball team based in San Francisco."inputs = tokenizer(text, return_tensors="pt").to("cuda")with torch.no_grad():logits = model(**inputs).logitspredictions = torch.argmax(logits, dim=2)predicted_token_class = [model.config.id2label[t.item()] for t in predictions[0]]for i in range(len(inputs.input_ids[0])):print(f"{inputs.input_ids[0][i]} ({tokenizer.decode([inputs.input_ids[0][i]])}) -> {predicted_token_class[i]}")
101 ([CLS]) -> O1996 (the) -> O3585 (golden) -> B-location2110 (state) -> I-location6424 (warriors) -> B-group2024 (are) -> O2019 (an) -> O2137 (american) -> O2658 (professional) -> O3455 (basketball) -> O2136 (team) -> O2241 (based) -> O1999 (in) -> O2624 (san) -> B-location3799 (francisco) -> B-location1012 (.) -> O102 ([SEP]) -> O
Como se puede ver los tokens correspondientes a golden
, state
, warriors
, san
y francisco
los ha clasificado como tokens de localizacióm
Respuesta a preguntas (question answering)
Creamos el modelo y el tokenizador
import torchfrom transformers import AutoTokenizer, AutoModelForQuestionAnsweringtokenizer = AutoTokenizer.from_pretrained("mrm8488/roberta-base-1B-1-finetuned-squadv1")model = AutoModelForQuestionAnswering.from_pretrained("mrm8488/roberta-base-1B-1-finetuned-squadv1", device_map=0)
Some weights of the model checkpoint at mrm8488/roberta-base-1B-1-finetuned-squadv1 were not used when initializing RobertaForQuestionAnswering: ['roberta.pooler.dense.bias', 'roberta.pooler.dense.weight']- This IS expected if you are initializing RobertaForQuestionAnswering from the checkpoint of a model trained on another task or with another architecture (e.g. initializing a BertForSequenceClassification model from a BertForPreTraining model).- This IS NOT expected if you are initializing RobertaForQuestionAnswering from the checkpoint of a model that you expect to be exactly identical (initializing a BertForSequenceClassification model from a BertForSequenceClassification model).
Generamos los tokens
question = "How many programming languages does BLOOM support?"context = "BLOOM has 176 billion parameters and can generate text in 46 languages natural languages and 13 programming languages."inputs = tokenizer(question, context, return_tensors="pt").to("cuda")
Una vez tenemos los tokens, clasificamos
question = "How many programming languages does BLOOM support?"context = "BLOOM has 176 billion parameters and can generate text in 46 languages natural languages and 13 programming languages."inputs = tokenizer(question, context, return_tensors="pt").to("cuda")with torch.no_grad():outputs = model(**inputs)answer_start_index = outputs.start_logits.argmax()answer_end_index = outputs.end_logits.argmax()predict_answer_tokens = inputs.input_ids[0, answer_start_index : answer_end_index + 1]tokenizer.decode(predict_answer_tokens)
' 13'
Modelización del lenguaje enmascarado (Masked language modeling)
Creamos el modelo y el tokenizador
import torchfrom transformers import AutoTokenizer, AutoModelForMaskedLMtokenizer = AutoTokenizer.from_pretrained("nyu-mll/roberta-base-1B-1")model = AutoModelForMaskedLM.from_pretrained("nyu-mll/roberta-base-1B-1", device_map=0)
Some weights of the model checkpoint at nyu-mll/roberta-base-1B-1 were not used when initializing RobertaForMaskedLM: ['roberta.pooler.dense.bias', 'roberta.pooler.dense.weight']- This IS expected if you are initializing RobertaForMaskedLM from the checkpoint of a model trained on another task or with another architecture (e.g. initializing a BertForSequenceClassification model from a BertForPreTraining model).- This IS NOT expected if you are initializing RobertaForMaskedLM from the checkpoint of a model that you expect to be exactly identical (initializing a BertForSequenceClassification model from a BertForSequenceClassification model).
Generamos los tokens
text = "The Milky Way is a <mask> galaxy."inputs = tokenizer(text, return_tensors="pt").to("cuda")mask_token_index = torch.where(inputs["input_ids"] == tokenizer.mask_token_id)[1]
Una vez tenemos los tokens, clasificamos
text = "The Milky Way is a <mask> galaxy."inputs = tokenizer(text, return_tensors="pt").to("cuda")mask_token_index = torch.where(inputs["input_ids"] == tokenizer.mask_token_id)[1]with torch.no_grad():logits = model(**inputs).logitsmask_token_logits = logits[0, mask_token_index, :]top_3_tokens = torch.topk(mask_token_logits, 3, dim=1).indices[0].tolist()for token in top_3_tokens:print(text.replace(tokenizer.mask_token, tokenizer.decode([token])))
The Milky Way is a spiral galaxy.The Milky Way is a closed galaxy.The Milky Way is a distant galaxy.
Personalización del modelo
Antes hemos hecho la inferencia con AutoClass
, pero lo hemos hecho con las cofiguraciones por defecto del modelo. Pero podemos configurar el modelo todo lo que queramos
Vamos a instanciar un modelo y a ver su configuración
from transformers import AutoTokenizer, AutoModelForCausalLM, AutoConfigtokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish")model = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish", device_map=0)config = AutoConfig.from_pretrained("flax-community/gpt-2-spanish")config
GPT2Config {"_name_or_path": "flax-community/gpt-2-spanish","activation_function": "gelu_new","architectures": ["GPT2LMHeadModel"],"attn_pdrop": 0.0,"bos_token_id": 50256,"embd_pdrop": 0.0,"eos_token_id": 50256,"gradient_checkpointing": false,"initializer_range": 0.02,"layer_norm_epsilon": 1e-05,"model_type": "gpt2","n_ctx": 1024,"n_embd": 768,"n_head": 12,"n_inner": null,"n_layer": 12,"n_positions": 1024,"reorder_and_upcast_attn": false,"resid_pdrop": 0.0,"scale_attn_by_inverse_layer_idx": false,"scale_attn_weights": true,"summary_activation": null,"summary_first_dropout": 0.1,"summary_proj_to_labels": true,"summary_type": "cls_index","summary_use_proj": true,"task_specific_params": {"text-generation": {"do_sample": true,"max_length": 50}},"transformers_version": "4.38.1","use_cache": true,"vocab_size": 50257}
Podemos ver la configuración del modelo, por ejemplo la función de activación es gelu_new
, tiene 12 head
s, el tamaño del vocabulario es 50257 palabras, etc.
Pero podemos modificar esta configuración
config = AutoConfig.from_pretrained("flax-community/gpt-2-spanish", activation_function="relu")config.activation_function
'relu'
Creamos ahora el modelo con esta configuración
model = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish", config=config, device_map=0)
Y generamos texto
tokenizer.pad_token = tokenizer.eos_token
tokens_input = tokenizer(["Me encanta aprender de"], return_tensors="pt", padding=True).to("cuda")
tokens_output = model.generate(**tokens_input, max_length=50)
sentence_output = tokenizer.decode(tokens_output[0], skip_special_tokens=True)
sentence_output
Vemos que esta modificación hace que no genere tan buen texto
Tokenización
Hasta ahora hemos visto las diferentes manera que hay de hacer inferencia con la librería transformers
. Ahora nos vamos a meter en las tripas de la librería. Para ello primero vamos a ver cosas a tener en cuenta a la hora de tokenizar.
No vamos a explicar lo que es tokenizar a fondo, ya que eso ya lo explicamos en el post de la librería tokenizers
Padding
Cuando se tiene un batch de secuencias, a veces es necesario que después de tokenizar, todas las secuencias tengan la misma longitud, así que para ello usamos el parámetro padding=True
model = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish", config=config, device_map=0)tokenizer.pad_token = tokenizer.eos_tokentokens_input = tokenizer(["Me encanta aprender de"], return_tensors="pt", padding=True).to("cuda")tokens_output = model.generate(**tokens_input, max_length=50)sentence_output = tokenizer.decode(tokens_output[0], skip_special_tokens=True)sentence_outputfrom transformers import AutoTokenizerbatch_sentences = ["Pero, ¿qué pasa con el segundo desayuno?","No creo que sepa lo del segundo desayuno, Pedro","¿Qué hay de los elevensies?",]tokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish", pad_token="PAD")encoded_input = tokenizer(batch_sentences, padding=True)for encoded in encoded_input["input_ids"]:print(encoded)print(f"Padding token id: {tokenizer.pad_token_id}")
Setting `pad_token_id` to `eos_token_id`:50256 for open-end generation.[2959, 16, 875, 3736, 3028, 303, 291, 2200, 8080, 35, 50257, 50257][1489, 2275, 288, 12052, 382, 325, 2200, 8080, 16, 4319, 50257, 50257][1699, 2899, 707, 225, 72, 73, 314, 34630, 474, 515, 1259, 35]Padding token id: 50257
Como vemos a las dos primeras secuencias les ha añadido un paddings al final
Truncado
A parte de añadir padding, a veces es necesario truncar las secuencias para que no ocupen más de un número determinado de tokens. Para ello establecemos truncation=True
y max_length
con el número de tokens que queremos que tenga la secuencia
from transformers import AutoTokenizerbatch_sentences = ["Pero, ¿qué pasa con el segundo desayuno?","No creo que sepa lo del segundo desayuno, Pedro","¿Qué hay de los elevensies?",]tokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish")encoded_input = tokenizer(batch_sentences, truncation=True, max_length=5)for encoded in encoded_input["input_ids"]:print(encoded)
[2959, 16, 875, 3736, 3028][1489, 2275, 288, 12052, 382][1699, 2899, 707, 225, 72]
Las mismas sentencias de antes, ahora generan menos tokens
Tensores
Hasta ahora estábamos recibiendo listas de tokens, pero seguramente nos interese recibir tensores de PyTorch o TensorFlow. Para ello usamos el parámetro return_tensors
y le especificamos de qué framework queremos recibir el tensor, en nuestro caso elegiremos PyTorch con pt
Vemos primero sin especificar que nos devuelva tensores
from transformers import AutoTokenizerbatch_sentences = ["Pero, ¿qué pasa con el segundo desayuno?","No creo que sepa lo del segundo desayuno, Pedro","¿Qué hay de los elevensies?",]tokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish", pad_token="PAD")encoded_input = tokenizer(batch_sentences, padding=True)for encoded in encoded_input["input_ids"]:print(type(encoded))
<class 'list'><class 'list'><class 'list'>
Recibimos listas, si queremos recibir tensores de PyTorch usamos return_tensors="pt"
from transformers import AutoTokenizerbatch_sentences = ["Pero, ¿qué pasa con el segundo desayuno?","No creo que sepa lo del segundo desayuno, Pedro","¿Qué hay de los elevensies?",]tokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish", pad_token="PAD")encoded_input = tokenizer(batch_sentences, padding=True, return_tensors="pt")for encoded in encoded_input["input_ids"]:print(type(encoded), encoded.shape)print(type(encoded_input["input_ids"]), encoded_input["input_ids"].shape)
<class 'torch.Tensor'> torch.Size([12])<class 'torch.Tensor'> torch.Size([12])<class 'torch.Tensor'> torch.Size([12])<class 'torch.Tensor'> torch.Size([3, 12])
Máscaras
Cuando tokenizamos una sentencia no solo obtenemos los input_ids
, sino que también obtenemos la máscara de atención. La máscara de atención es un tensor que tiene el mismo tamaño que input_ids
y tiene un 1
en las posiciones que son tokens y un 0
en las posiciones que son padding
from transformers import AutoTokenizerbatch_sentences = ["Pero, ¿qué pasa con el segundo desayuno?","No creo que sepa lo del segundo desayuno, Pedro","¿Qué hay de los elevensies?",]tokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish", pad_token="PAD")encoded_input = tokenizer(batch_sentences, padding=True)print(f"padding token id: {tokenizer.pad_token_id}")print(f" encoded_input[0] inputs_ids: {encoded_input['input_ids'][0]}")print(f"encoded_input[0] attention_mask: {encoded_input['attention_mask'][0]}")print(f" encoded_input[1] inputs_ids: {encoded_input['input_ids'][1]}")print(f"encoded_input[1] attention_mask: {encoded_input['attention_mask'][1]}")print(f" encoded_input[2] inputs_ids: {encoded_input['input_ids'][2]}")print(f"encoded_input[2] attention_mask: {encoded_input['attention_mask'][2]}")
padding token id: 50257encoded_input[0] inputs_ids: [2959, 16, 875, 3736, 3028, 303, 291, 2200, 8080, 35, 50257, 50257]encoded_input[0] attention_mask: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0]encoded_input[1] inputs_ids: [1489, 2275, 288, 12052, 382, 325, 2200, 8080, 16, 4319, 50257, 50257]encoded_input[1] attention_mask: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0]encoded_input[2] inputs_ids: [1699, 2899, 707, 225, 72, 73, 314, 34630, 474, 515, 1259, 35]encoded_input[2] attention_mask: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
Como se puede ver, en las dos primeras sentencias, tenemos un 1 en las primeras posiciones y un 0 en las dos últimas posiciones. En esas mismas posiciones tenemos el token 50257
, que corresponde al token de padding.
Con estas máscaras de atención le estamos diciendo al modelo a qué tokens tiene que prestar atención y a cuáles no.
La generación de texto se podría hacer igualmente si no pasáramos estas máscaras de atención, el método generate
haría su mayur esfuerzo por inferir esta máscara, pero si se la pasamos ayudamos a generar mejor texto
Fast Tokenizers
Algunos tokenizadores preentrenados tienen una versión fast
, tienen los mismos métodos que los normales, solo que están desarrollados en Rust. Para usarlos debemos usar la clase PreTrainedTokenizerFast
de la librería transformers
Veamos primero el timepo de tokenización con un tokenizador normal
%%timefrom transformers import BertTokenizertokenizer = BertTokenizer.from_pretrained("google-bert/bert-base-uncased")sentence = ("The Permaculture Design Principles are a set of universal design principles ""that can be applied to any location, climate and culture, and they allow us to design ""the most efficient and sustainable human habitation and food production systems. ""Permaculture is a design system that encompasses a wide variety of disciplines, such ""as ecology, landscape design, environmental science and energy conservation, and the ""Permaculture design principles are drawn from these various disciplines. Each individual ""design principle itself embodies a complete conceptual framework based on sound ""scientific principles. When we bring all these separate principles together, we can ""create a design system that both looks at whole systems, the parts that these systems ""consist of, and how those parts interact with each other to create a complex, dynamic, ""living system. Each design principle serves as a tool that allows us to integrate all ""the separate parts of a design, referred to as elements, into a functional, synergistic, ""whole system, where the elements harmoniously interact and work together in the most ""efficient way possible.")tokens = tokenizer([sentence], padding=True, return_tensors="pt")
CPU times: user 55.3 ms, sys: 8.58 ms, total: 63.9 msWall time: 226 ms
Y ahora con uno rápido
%%timefrom transformers import BertTokenizerFasttokenizer = BertTokenizerFast.from_pretrained("google-bert/bert-base-uncased")sentence = ("The Permaculture Design Principles are a set of universal design principles ""that can be applied to any location, climate and culture, and they allow us to design ""the most efficient and sustainable human habitation and food production systems. ""Permaculture is a design system that encompasses a wide variety of disciplines, such ""as ecology, landscape design, environmental science and energy conservation, and the ""Permaculture design principles are drawn from these various disciplines. Each individual ""design principle itself embodies a complete conceptual framework based on sound ""scientific principles. When we bring all these separate principles together, we can ""create a design system that both looks at whole systems, the parts that these systems ""consist of, and how those parts interact with each other to create a complex, dynamic, ""living system. Each design principle serves as a tool that allows us to integrate all ""the separate parts of a design, referred to as elements, into a functional, synergistic, ""whole system, where the elements harmoniously interact and work together in the most ""efficient way possible.")tokens = tokenizer([sentence], padding=True, return_tensors="pt")
CPU times: user 42.6 ms, sys: 3.26 ms, total: 45.8 msWall time: 179 ms
Se puede ver como el BertTokenizerFast
es unos 40 ms más rápido
Formas de generación de texto
Seguimos con las tripas de la librería transformers
, ahora vamos a ver las maneras de generar texto.
La arquitectura transformer genera el siguiente token más probable, esta es la manera más sencilla de generar texto, pero no es la única, así que vamos a verlas.
A la hora de generar textno no hay una forma mejor y dependerá de nuestro modelo y del propósito de uso
Greedy Search
Es la manera más sencilla de generación de texto. Busca el token más probable en cada iteracción
Para generar téxto de esta manera con transformers
no hay que hacer nada especial, ya que es la manera por defecto
from transformers import AutoTokenizer, AutoModelForCausalLM
tokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish")
tokenizer.pad_token = tokenizer.eos_token
model = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish", device_map=0)
tokens_input = tokenizer(["Me encanta aprender de"], return_tensors="pt", padding=True).to("cuda")
tokens_output = model.generate(**tokens_input, max_new_tokens=500)
sentence_output = tokenizer.decode(tokens_output[0], skip_special_tokens=True)
print(sentence_output)
Se puede ver que el texto generado está bien, pero se empieza a repetir. Esto es porque en la búsqueda codiciosa (greedy search), palabras con una alta probabilidad se pueden esconder detrás de palabras con una probabilidad más baja, por lo que se pueden perder
Aquí, la palabra has
tiene una alta probabilidad, pero queda escondida detrás de dog
, que tiene menor probabilidad que nice
Contrastive Search
El método Contrastive Search optimiza la generación de texto seleccionando las opciones de palabras o frases que maximizan un criterio de calidad sobre otras menos deseables. En la práctica, esto significa que durante la generación de texto, en cada paso, el modelo no solo busca la siguiente palabra que tiene mayor probabilidad de seguir según lo aprendido durante su entrenamiento, sino que también compara diferentes candidatos para esa próxima palabra y evalúa cuál de ellos contribuiría a formar el texto más coherente, relevante y de alta calidad en el contexto dado. Por lo tanto, Contrastive Search reduce la posibilidad de generar respuestas irrelevantes o de baja calidad, enfocándose en aquellas opciones que mejor se ajustan al objetivo de la generación de texto, basándose en una comparación directa entre posibles continuaciones en cada paso del proceso.
Para generar texto con contrastive search en transformers
hay que usar los parámetros penalty_alpha
y top_k
a la hora de generar texto
from transformers import AutoTokenizer, AutoModelForCausalLM
tokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish")
tokenizer.pad_token = tokenizer.eos_token
model = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish", device_map=0)
tokens_input = tokenizer(["Me encanta aprender de"], return_tensors="pt", padding=True).to("cuda")
tokens_output = model.generate(**tokens_input, max_new_tokens=500, penalty_alpha=0.6, top_k=4)
sentence_output = tokenizer.decode(tokens_output[0], skip_special_tokens=True)
print(sentence_output)
Aquí el modelo tarda más en empezar a repetirse
Multinomial sampling
A diferencia de la búsqueda codiciosa que siempre elige un token con la mayor probabilidad como el siguiente token, el muestreo multinomial (también llamado muestreo ancestral) selecciona aleatoriamente el siguiente token en función de la distribución de probabilidad de todo el vocabulario proporcionado por el modelo. Cada token con una probabilidad distinta de cero tiene posibilidades de ser seleccionado, lo que reduce el riesgo de repetición.
Para habilitar el Multinomial sampling
hay que poner do_sample=True
y num_beams=1
.
from transformers import AutoTokenizer, AutoModelForCausalLM
tokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish")
tokenizer.pad_token = tokenizer.eos_token
model = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish", device_map=0)
tokens_input = tokenizer(["Me encanta aprender de"], return_tensors="pt", padding=True).to("cuda")
tokens_output = model.generate(**tokens_input, max_new_tokens=500, do_sample=True, num_beams=1)
sentence_output = tokenizer.decode(tokens_output[0], skip_special_tokens=True)
print(sentence_output)
La verdad es que el modelo no se repite nada, pero siento que estoy hablando con un niño pequeño, que habla de un tema y empieza a hilar con otros que no tienen nada que ver
Beam search
La búsqueda por haz reduce el riesgo de perder secuencias de palabras ocultas de alta probabilidad al mantener el num_beams
más probable en cada paso de tiempo y, finalmente, elegir la hipótesis que tenga la probabilidad más alta en general.
Para generar con beam search
es necesario añadir el parámetro num_beams
from transformers import AutoTokenizer, AutoModelForCausalLM
tokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish")
tokenizer.pad_token = tokenizer.eos_token
model = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish", device_map=0)
tokens_input = tokenizer(["Me encanta aprender de"], return_tensors="pt", padding=True).to("cuda")
tokens_output = model.generate(**tokens_input, max_new_tokens=500, num_beams=5)
sentence_output = tokenizer.decode(tokens_output[0], skip_special_tokens=True)
print(sentence_output)
Se repite bastante
Beam search multinomial sampling
Esta técnica junta el bean search
donde se busca por haz y el multinomial sampling
donde se selecciona aleatoriamente el siguiente token en función de la distribución de probabilidad de todo el vocabulario proporcionado por el modelo.
from transformers import AutoTokenizer, AutoModelForCausalLM
tokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish")
tokenizer.pad_token = tokenizer.eos_token
model = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish", device_map=0)
tokens_input = tokenizer(["Me encanta aprender de"], return_tensors="pt", padding=True).to("cuda")
tokens_output = model.generate(**tokens_input, max_new_tokens=500, num_beams=5, do_sample=True)
sentence_output = tokenizer.decode(tokens_output[0], skip_special_tokens=True)
print(sentence_output)
Se repite bastante
Beam search n-grams penalty
Para evitar la repetición podemos penalizar por la repetición de n-gramas. Para ello usamos el parámetro no_repeat_ngram_size
from transformers import AutoTokenizer, AutoModelForCausalLM
tokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish")
tokenizer.pad_token = tokenizer.eos_token
model = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish", device_map=0)
tokens_input = tokenizer(["Me encanta aprender de"], return_tensors="pt", padding=True).to("cuda")
tokens_output = model.generate(**tokens_input, max_new_tokens=500, num_beams=5, no_repeat_ngram_size=2)
sentence_output = tokenizer.decode(tokens_output[0], skip_special_tokens=True)
print(sentence_output)
Este texto ya no se repite y además tiene algo más de coherencia.
Sin embargo, las penalizaciones de n-gramas deben utilizarse con cuidado. Un artículo generado sobre la ciudad de Nueva York no debería usar una penalización de 2 gramos o de lo contrario, ¡el nombre de la ciudad solo aparecería una vez en todo el texto!
Beam search n-grams penalty return sequences
Podemos generar varias secuancias para compararlas y quedarnos con la mejor. Para ello usamos el parámetro num_return_sequences
con la condición de que num_return_sequences <= num_beams
from transformers import AutoTokenizer, AutoModelForCausalLM
tokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish")
tokenizer.pad_token = tokenizer.eos_token
model = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish", device_map=0)
tokens_input = tokenizer(["Me encanta aprender de"], return_tensors="pt", padding=True).to("cuda")
tokens_outputs = model.generate(**tokens_input, max_new_tokens=500, num_beams=5, no_repeat_ngram_size=2, num_return_sequences=3)
for i, tokens_output in enumerate(tokens_outputs):
if i != 0:
print("\n\n")
sentence_output = tokenizer.decode(tokens_output, skip_special_tokens=True)
print(f"{i}: {sentence_output}")
Ahora podemos quedarnos con la mejor secuencia
Diverse beam search decoding
La diverse beam search decoding es una extensión de la estrategia de búsqueda de haces que permite generar un conjunto más diverso de secuencias de haces para elegir.
Para poder generar texto de esta manera tenemos que usar los parámetros num_beams
, num_beam_groups
, y diversity_penalty
from transformers import AutoTokenizer, AutoModelForCausalLM
tokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish")
tokenizer.pad_token = tokenizer.eos_token
model = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish", device_map=0)
tokens_input = tokenizer(["Me encanta aprender de"], return_tensors="pt", padding=True).to("cuda")
tokens_output = model.generate(**tokens_input, max_new_tokens=500, num_beams=5, num_beam_groups=5, diversity_penalty=1.0)
sentence_output = tokenizer.decode(tokens_output[0], skip_special_tokens=True)
print(sentence_output)
Este método parece que se repite bastante
Speculative Decoding
La decodificación especulativa (también conocida como decodificación asistida) es una modificación de las estrategias de decodificación anteriores, que utiliza un modelo asistente (idealmente uno mucho más pequeño) con el mismo tokenizador, para generar algunos tokens candidatos. Luego, el modelo principal valida los tokens candidatos en un único paso hacia adelante, lo que acelera el proceso de decodificación
Para generar texto de esta manera es necesario usar el parámetro do_sample=True
Actualmente, la decodificación asistida solo admite greedy search, y la decodificación asistida no admite entradas por lotes
from transformers import AutoTokenizer, AutoModelForCausalLM
tokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish")
tokenizer.pad_token = tokenizer.eos_token
model = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish", device_map=0)
assistant_model = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish", device_map=0)
tokens_input = tokenizer(["Me encanta aprender de"], return_tensors="pt", padding=True).to("cuda")
tokens_output = model.generate(**tokens_input, max_new_tokens=500, assistant_model=assistant_model, do_sample=True)
sentence_output = tokenizer.decode(tokens_output[0], skip_special_tokens=True)
print(sentence_output)
Este método tiene muy buenos resultados
Speculative Decoding randomness control
Cuando se utiliza la decodificación asistida con métodos de muestreo, se puede utilizar el parámetro temperature
para controlar la aleatoriedad. Sin embargo, en la decodificación asistida, reducir la temperatura puede ayudar a mejorar la latencia.
from transformers import AutoTokenizer, AutoModelForCausalLM
tokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish")
tokenizer.pad_token = tokenizer.eos_token
model = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish", device_map=0)
assistant_model = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish", device_map=0)
tokens_input = tokenizer(["Me encanta aprender de"], return_tensors="pt", padding=True).to("cuda")
tokens_output = model.generate(**tokens_input, max_new_tokens=500, assistant_model=assistant_model, do_sample=True, temperature=0.5)
sentence_output = tokenizer.decode(tokens_output[0], skip_special_tokens=True)
print(sentence_output)
Aquí no lo ha generado tan bien
Sampling
Aquí empiezan las técnicas usadas por los LLMs actuales
El método de muestreo se basa en lugar de siempre seleccionar la palabra más probable (que podría llevar a textos predecibles o repetitivos), el muestreo introduce aleatoriedad en el proceso de selección, permitiendo que el modelo explore una variedad de palabras posibles basadas en sus probabilidades. Es como lanzar un dado ponderado para cada palabra. Así, mientras más alta sea la probabilidad de una palabra, más probabilidad tiene de ser seleccionada, pero aún hay una oportunidad para que palabras menos probables sean elegidas, enriqueciendo la diversidad y creatividad del texto generado. Este método ayuda a evitar respuestas monótonas y aumenta la variabilidad y naturalidad del texto producido.
Como se puede ver en la imagen, el primer token, que es el que tiene mayor probabilidad se ha repetido hasta 11 veces, el segundo hasta 8 veces, el tercero hasta 4 y el último solo se ha añadido en 1. De esta manera se elige aleatoriamente entre todos, pero lo más probable es que salga el primer token, ya que es el que aparece más veces
Para usar este método elegimos do_sample=True
y top_k=0
from transformers import AutoTokenizer, AutoModelForCausalLM
tokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish")
tokenizer.pad_token = tokenizer.eos_token
model = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish", device_map=0)
tokens_input = tokenizer(["Me encanta aprender de"], return_tensors="pt", padding=True).to("cuda")
tokens_output = model.generate(**tokens_input, max_new_tokens=500, do_sample=True, top_k=0)
sentence_output = tokenizer.decode(tokens_output[0], skip_special_tokens=True)
print(sentence_output)
No genera texto repetitivo, pero genera un texto que no parece muy coherente. Este es el problema de poder elegir cualquier palabra
Sampling temperature
Para solventar el problema del método de muestreo se añade un parámetro de temperatura
que ajusta el nivel de aleatoriedad en la selección de palabras.
La temperatura es un parámetro que modifica cómo se distribuyen las probabilidades de las posibles siguientes palabras.
Con una temperatura de 1, la distribución de probabilidad se mantiene según lo aprendido por el modelo, manteniendo un equilibrio entre previsibilidad y creatividad
Si se baja la temperatura (menos de 1), se aumenta el peso de las palabras más probables, haciendo que el texto generado sea más predecible y coherente, pero menos diverso y creativo.
Al aumentar la temperatura (más de 1), se reduce la diferencia de probabilidad entre las palabras, dando a las menos probables una mayor probabilidad de ser seleccionadas, lo que incrementa la diversidad y la creatividad del texto, pero puede comprometer su coherencia y relevancia.
La temperatura permite afinar el equilibrio entre la originalidad y la coherencia del texto generado, ajustándolo a las necesidades específicas de la tarea.
Para añadir este parámetro, usamos el parámetro temperature
de la librería
Primero probamos con un valor bajo
from transformers import AutoTokenizer, AutoModelForCausalLM
tokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish")
tokenizer.pad_token = tokenizer.eos_token
model = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish", device_map=0)
tokens_input = tokenizer(["Me encanta aprender de"], return_tensors="pt", padding=True).to("cuda")
tokens_output = model.generate(**tokens_input, max_new_tokens=500, do_sample=True, top_k=0, temperature=0.7)
sentence_output = tokenizer.decode(tokens_output[0], skip_special_tokens=True)
print(sentence_output)
Vemos que el texto generado tiene más coherencia, pero vuelve a ser repetitivo
Probamos ahora con un valor más alto
from transformers import AutoTokenizer, AutoModelForCausalLM
tokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish")
tokenizer.pad_token = tokenizer.eos_token
model = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish", device_map=0)
tokens_input = tokenizer(["Me encanta aprender de"], return_tensors="pt", padding=True).to("cuda")
tokens_output = model.generate(**tokens_input, max_new_tokens=500, do_sample=True, top_k=0, temperature=1.3)
sentence_output = tokenizer.decode(tokens_output[0], skip_special_tokens=True)
print(sentence_output)
Vemos que el texto generado ahora no se repite, pero no tiene ningún sentido
Sampling top-k
Otra forma de resolver los problemas del muestreo, es seleccionar las k
palabras más probables, de manera que ahora se genera texto que puede no ser repetitivo, pero que tendrá más coherencia. Esta es la solución que se optó en GPT-2
from transformers import AutoTokenizer, AutoModelForCausalLM
tokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish")
tokenizer.pad_token = tokenizer.eos_token
model = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish", device_map=0)
tokens_input = tokenizer(["Me encanta aprender de"], return_tensors="pt", padding=True).to("cuda")
tokens_output = model.generate(**tokens_input, max_new_tokens=500, do_sample=True, top_k=50)
sentence_output = tokenizer.decode(tokens_output[0], skip_special_tokens=True)
print(sentence_output)
Ahora el texto no es repetitivo y tiene coherencia
Sampling top-p (nucleus sampling)
Con top-p lo que se hace es seleccionar el conjunto de palabras que hace que la suma de sus probabilidades sea mayor que p (por ejemplo 0.9). De esta manera se evitan palabras que no tienen nada que ver con la frase, pero hace que haya mayor riqueza de palabras posibles
Como se puede ver en la imagen, si se suma la probabilidad de los primeros tokens se tiene una probabilidad mayor de 0.8, por lo que nos quedamos con esos para generar el siguiente token
from transformers import AutoTokenizer, AutoModelForCausalLM
tokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish")
tokenizer.pad_token = tokenizer.eos_token
model = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish", device_map=0)
tokens_input = tokenizer(["Me encanta aprender de"], return_tensors="pt", padding=True).to("cuda")
tokens_output = model.generate(**tokens_input, max_new_tokens=500, do_sample=True, top_k=0, top_p=0.92)
sentence_output = tokenizer.decode(tokens_output[0], skip_special_tokens=True)
print(sentence_output)
Se obtiene un texto muy bueno
Sampling top-k y top-p
Cuando se combinan top-k
y top-p
se obtienen muy buenos resultados
from transformers import AutoTokenizer, AutoModelForCausalLM
tokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish")
tokenizer.pad_token = tokenizer.eos_token
model = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish", device_map=0)
tokens_input = tokenizer(["Me encanta aprender de"], return_tensors="pt", padding=True).to("cuda")
tokens_output = model.generate(**tokens_input, max_new_tokens=500, do_sample=True, top_k=50, top_p=0.95)
sentence_output = tokenizer.decode(tokens_output[0], skip_special_tokens=True)
print(sentence_output)
Efecto de la temperatura, top-k
y top-p
En este space de de HuggingFace podemos ver el efecto de la temperatura, top-k
y top-p
en la generación de texto
Streaming
Podemos hacer que las palabras vayan saliendo una a una mediante la clase TextStreamer
from transformers import AutoModelForCausalLM, AutoTokenizer, TextStreamer
tokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish")
tokenizer.pad_token = tokenizer.eos_token
model = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish")
tokens_input = tokenizer(["Me encanta aprender de"], return_tensors="pt")
streamer = TextStreamer(tokenizer)
_ = model.generate(**tokens_input, streamer=streamer, max_new_tokens=500, do_sample=True, top_k=50, top_p=0.95)
De esta manera se ha generado la salida palabra a palabra
Plantillas de chat
Tokenización del contexto
Un uso muy importante de los LLMs son los chatbots. A la hora de usar un chatbot es importante darle un contexto. Sin embargo, la tokenización de este contexto es diferente para cada modelo. Así que una manera de tokenizar este contexto es usar el método apply_chat_template
de los tokenizadores
Por ejemplo, vemos cómo se tokeniza el contexto del modelo facebook/blenderbot-400M-distill
from transformers import AutoTokenizer, AutoModelForCausalLMtokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish")tokenizer.pad_token = tokenizer.eos_tokenmodel = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish", device_map=0)tokens_input = tokenizer(["Me encanta aprender de"], return_tensors="pt", padding=True).to("cuda")tokens_output = model.generate(**tokens_input, max_new_tokens=500)sentence_output = tokenizer.decode(tokens_output[0], skip_special_tokens=True)print(sentence_output)from transformers import AutoTokenizer, AutoModelForCausalLMtokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish")tokenizer.pad_token = tokenizer.eos_tokenmodel = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish", device_map=0)tokens_input = tokenizer(["Me encanta aprender de"], return_tensors="pt", padding=True).to("cuda")tokens_output = model.generate(**tokens_input, max_new_tokens=500, penalty_alpha=0.6, top_k=4)sentence_output = tokenizer.decode(tokens_output[0], skip_special_tokens=True)print(sentence_output)from transformers import AutoTokenizer, AutoModelForCausalLMtokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish")tokenizer.pad_token = tokenizer.eos_tokenmodel = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish", device_map=0)tokens_input = tokenizer(["Me encanta aprender de"], return_tensors="pt", padding=True).to("cuda")tokens_output = model.generate(**tokens_input, max_new_tokens=500, do_sample=True, num_beams=1)sentence_output = tokenizer.decode(tokens_output[0], skip_special_tokens=True)print(sentence_output)from transformers import AutoTokenizer, AutoModelForCausalLMtokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish")tokenizer.pad_token = tokenizer.eos_tokenmodel = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish", device_map=0)tokens_input = tokenizer(["Me encanta aprender de"], return_tensors="pt", padding=True).to("cuda")tokens_output = model.generate(**tokens_input, max_new_tokens=500, num_beams=5)sentence_output = tokenizer.decode(tokens_output[0], skip_special_tokens=True)print(sentence_output)from transformers import AutoTokenizer, AutoModelForCausalLMtokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish")tokenizer.pad_token = tokenizer.eos_tokenmodel = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish", device_map=0)tokens_input = tokenizer(["Me encanta aprender de"], return_tensors="pt", padding=True).to("cuda")tokens_output = model.generate(**tokens_input, max_new_tokens=500, num_beams=5, do_sample=True)sentence_output = tokenizer.decode(tokens_output[0], skip_special_tokens=True)print(sentence_output)from transformers import AutoTokenizer, AutoModelForCausalLMtokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish")tokenizer.pad_token = tokenizer.eos_tokenmodel = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish", device_map=0)tokens_input = tokenizer(["Me encanta aprender de"], return_tensors="pt", padding=True).to("cuda")tokens_output = model.generate(**tokens_input, max_new_tokens=500, num_beams=5, no_repeat_ngram_size=2)sentence_output = tokenizer.decode(tokens_output[0], skip_special_tokens=True)print(sentence_output)from transformers import AutoTokenizer, AutoModelForCausalLMtokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish")tokenizer.pad_token = tokenizer.eos_tokenmodel = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish", device_map=0)tokens_input = tokenizer(["Me encanta aprender de"], return_tensors="pt", padding=True).to("cuda")tokens_outputs = model.generate(**tokens_input, max_new_tokens=500, num_beams=5, no_repeat_ngram_size=2, num_return_sequences=3)for i, tokens_output in enumerate(tokens_outputs):if i != 0:print(" ")sentence_output = tokenizer.decode(tokens_output, skip_special_tokens=True)print(f"{i}: {sentence_output}")from transformers import AutoTokenizer, AutoModelForCausalLMtokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish")tokenizer.pad_token = tokenizer.eos_tokenmodel = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish", device_map=0)tokens_input = tokenizer(["Me encanta aprender de"], return_tensors="pt", padding=True).to("cuda")tokens_output = model.generate(**tokens_input, max_new_tokens=500, num_beams=5, num_beam_groups=5, diversity_penalty=1.0)sentence_output = tokenizer.decode(tokens_output[0], skip_special_tokens=True)print(sentence_output)from transformers import AutoTokenizer, AutoModelForCausalLMtokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish")tokenizer.pad_token = tokenizer.eos_tokenmodel = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish", device_map=0)assistant_model = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish", device_map=0)tokens_input = tokenizer(["Me encanta aprender de"], return_tensors="pt", padding=True).to("cuda")tokens_output = model.generate(**tokens_input, max_new_tokens=500, assistant_model=assistant_model, do_sample=True)sentence_output = tokenizer.decode(tokens_output[0], skip_special_tokens=True)print(sentence_output)from transformers import AutoTokenizer, AutoModelForCausalLMtokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish")tokenizer.pad_token = tokenizer.eos_tokenmodel = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish", device_map=0)assistant_model = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish", device_map=0)tokens_input = tokenizer(["Me encanta aprender de"], return_tensors="pt", padding=True).to("cuda")tokens_output = model.generate(**tokens_input, max_new_tokens=500, assistant_model=assistant_model, do_sample=True, temperature=0.5)sentence_output = tokenizer.decode(tokens_output[0], skip_special_tokens=True)print(sentence_output)from transformers import AutoTokenizer, AutoModelForCausalLMtokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish")tokenizer.pad_token = tokenizer.eos_tokenmodel = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish", device_map=0)tokens_input = tokenizer(["Me encanta aprender de"], return_tensors="pt", padding=True).to("cuda")tokens_output = model.generate(**tokens_input, max_new_tokens=500, do_sample=True, top_k=0)sentence_output = tokenizer.decode(tokens_output[0], skip_special_tokens=True)print(sentence_output)from transformers import AutoTokenizer, AutoModelForCausalLMtokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish")tokenizer.pad_token = tokenizer.eos_tokenmodel = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish", device_map=0)tokens_input = tokenizer(["Me encanta aprender de"], return_tensors="pt", padding=True).to("cuda")tokens_output = model.generate(**tokens_input, max_new_tokens=500, do_sample=True, top_k=0, temperature=0.7)sentence_output = tokenizer.decode(tokens_output[0], skip_special_tokens=True)print(sentence_output)from transformers import AutoTokenizer, AutoModelForCausalLMtokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish")tokenizer.pad_token = tokenizer.eos_tokenmodel = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish", device_map=0)tokens_input = tokenizer(["Me encanta aprender de"], return_tensors="pt", padding=True).to("cuda")tokens_output = model.generate(**tokens_input, max_new_tokens=500, do_sample=True, top_k=0, temperature=1.3)sentence_output = tokenizer.decode(tokens_output[0], skip_special_tokens=True)print(sentence_output)from transformers import AutoTokenizer, AutoModelForCausalLMtokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish")tokenizer.pad_token = tokenizer.eos_tokenmodel = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish", device_map=0)tokens_input = tokenizer(["Me encanta aprender de"], return_tensors="pt", padding=True).to("cuda")tokens_output = model.generate(**tokens_input, max_new_tokens=500, do_sample=True, top_k=50)sentence_output = tokenizer.decode(tokens_output[0], skip_special_tokens=True)print(sentence_output)from transformers import AutoTokenizer, AutoModelForCausalLMtokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish")tokenizer.pad_token = tokenizer.eos_tokenmodel = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish", device_map=0)tokens_input = tokenizer(["Me encanta aprender de"], return_tensors="pt", padding=True).to("cuda")tokens_output = model.generate(**tokens_input, max_new_tokens=500, do_sample=True, top_k=0, top_p=0.92)sentence_output = tokenizer.decode(tokens_output[0], skip_special_tokens=True)print(sentence_output)from transformers import AutoTokenizer, AutoModelForCausalLMtokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish")tokenizer.pad_token = tokenizer.eos_tokenmodel = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish", device_map=0)tokens_input = tokenizer(["Me encanta aprender de"], return_tensors="pt", padding=True).to("cuda")tokens_output = model.generate(**tokens_input, max_new_tokens=500, do_sample=True, top_k=50, top_p=0.95)sentence_output = tokenizer.decode(tokens_output[0], skip_special_tokens=True)print(sentence_output)from transformers import AutoModelForCausalLM, AutoTokenizer, TextStreamertokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish")tokenizer.pad_token = tokenizer.eos_tokenmodel = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish")tokens_input = tokenizer(["Me encanta aprender de"], return_tensors="pt")streamer = TextStreamer(tokenizer)_ = model.generate(**tokens_input, streamer=streamer, max_new_tokens=500, do_sample=True, top_k=50, top_p=0.95)from transformers import AutoTokenizercheckpoint = "facebook/blenderbot-400M-distill"tokenizer = AutoTokenizer.from_pretrained("facebook/blenderbot-400M-distill")chat = [{"role": "user", "content": "Hola, ¿Cómo estás?"},{"role": "assistant", "content": "Estoy bien. ¿Cómo te puedo ayudar?"},{"role": "user", "content": "Me gustaría saber cómo funcionan los chat templates"},]input_token_chat_template = tokenizer.apply_chat_template(chat, tokenize=True, return_tensors="pt")print(f"input tokens chat_template: {input_token_chat_template}")input_chat_template = tokenizer.apply_chat_template(chat, tokenize=False, return_tensors="pt")print(f"input chat_template: {input_chat_template}")
Setting `pad_token_id` to `eos_token_id`:50256 for open-end generation.Setting `pad_token_id` to `eos_token_id`:50256 for open-end generation.Setting `pad_token_id` to `eos_token_id`:50256 for open-end generation.Setting `pad_token_id` to `eos_token_id`:50256 for open-end generation.Setting `pad_token_id` to `eos_token_id`:50256 for open-end generation.Setting `pad_token_id` to `eos_token_id`:50256 for open-end generation.Setting `pad_token_id` to `eos_token_id`:50256 for open-end generation.Setting `pad_token_id` to `eos_token_id`:50256 for open-end generation.Setting `pad_token_id` to `eos_token_id`:50256 for open-end generation.Setting `pad_token_id` to `eos_token_id`:50256 for open-end generation.Setting `pad_token_id` to `eos_token_id`:50256 for open-end generation.Setting `pad_token_id` to `eos_token_id`:50256 for open-end generation.Setting `pad_token_id` to `eos_token_id`:50256 for open-end generation.Setting `pad_token_id` to `eos_token_id`:50256 for open-end generation.Setting `pad_token_id` to `eos_token_id`:50256 for open-end generation.Setting `pad_token_id` to `eos_token_id`:50256 for open-end generation.Setting `pad_token_id` to `eos_token_id`:50256 for open-end generation.input tokens chat_template: tensor([[ 391, 7521, 19, 5146, 131, 42, 135, 119, 773, 2736, 135, 102,90, 38, 228, 477, 300, 874, 275, 1838, 21, 5146, 131, 42,135, 119, 773, 574, 286, 3478, 86, 265, 96, 659, 305, 38,228, 228, 2365, 294, 367, 305, 135, 263, 72, 268, 439, 276,280, 135, 119, 773, 941, 74, 337, 295, 530, 90, 3879, 4122,1114, 1073, 2]])input chat_template: Hola, ¿Cómo estás? Estoy bien. ¿Cómo te puedo ayudar? Me gustaría saber cómo funcionan los chat templates</s>
Como se puede ver, el contexto se tokeniza simplemente dejando espacios en blanco entre las sentencias
Veamos ahora cómo se tokeniza para el modelo mistralai/Mistral-7B-Instruct-v0.1
from transformers import AutoTokenizercheckpoint = "mistralai/Mistral-7B-Instruct-v0.1"tokenizer = AutoTokenizer.from_pretrained("mistralai/Mistral-7B-Instruct-v0.1")chat = [{"role": "user", "content": "Hola, ¿Cómo estás?"},{"role": "assistant", "content": "Estoy bien. ¿Cómo te puedo ayudar?"},{"role": "user", "content": "Me gustaría saber cómo funcionan los chat templates"},]input_token_chat_template = tokenizer.apply_chat_template(chat, tokenize=True, return_tensors="pt")print(f"input tokens chat_template: {input_token_chat_template}")input_chat_template = tokenizer.apply_chat_template(chat, tokenize=False, return_tensors="pt")print(f"input chat_template: {input_chat_template}")
input tokens chat_template: tensor([[ 1, 733, 16289, 28793, 4170, 28708, 28725, 18297, 28743, 28825,5326, 934, 2507, 28804, 733, 28748, 16289, 28793, 14644, 904,9628, 28723, 18297, 28743, 28825, 5326, 711, 11127, 28709, 15250,554, 283, 28804, 2, 28705, 733, 16289, 28793, 2597, 319,469, 26174, 14691, 263, 21977, 5326, 2745, 296, 276, 1515,10706, 24906, 733, 28748, 16289, 28793]])input chat_template: <s>[INST] Hola, ¿Cómo estás? [/INST]Estoy bien. ¿Cómo te puedo ayudar?</s> [INST] Me gustaría saber cómo funcionan los chat templates [/INST]
Podemos ver que este modelo mete las etiquetas [INST]
y [/INST]
al principio y al final de cada sentencia
Añadir generación de prompts
Podemos decirle al tokenizador que tokenice el contexto añadiendo el turno del asistente añadiendo add_generation_prompt=True
. Vamos a verlo, primero tokenizamos con add_generation_prompt=False
from transformers import AutoTokenizercheckpoint = "HuggingFaceH4/zephyr-7b-beta"tokenizer = AutoTokenizer.from_pretrained(checkpoint)chat = [{"role": "user", "content": "Hola, ¿Cómo estás?"},{"role": "assistant", "content": "Estoy bien. ¿Cómo te puedo ayudar?"},{"role": "user", "content": "Me gustaría saber cómo funcionan los chat templates"},]input_chat_template = tokenizer.apply_chat_template(chat, tokenize=False, return_tensors="pt", add_generation_prompt=False)print(f"input chat_template: {input_chat_template}")
input chat_template: <|user|>Hola, ¿Cómo estás?</s><|assistant|>Estoy bien. ¿Cómo te puedo ayudar?</s><|user|>Me gustaría saber cómo funcionan los chat templates</s>
Ahora hacemos lo mismo pero con add_generation_prompt=True
from transformers import AutoTokenizercheckpoint = "HuggingFaceH4/zephyr-7b-beta"tokenizer = AutoTokenizer.from_pretrained(checkpoint)chat = [{"role": "user", "content": "Hola, ¿Cómo estás?"},{"role": "assistant", "content": "Estoy bien. ¿Cómo te puedo ayudar?"},{"role": "user", "content": "Me gustaría saber cómo funcionan los chat templates"},]input_chat_template = tokenizer.apply_chat_template(chat, tokenize=False, return_tensors="pt", add_generation_prompt=True)print(f"input chat_template: {input_chat_template}")
input chat_template: <|user|>Hola, ¿Cómo estás?</s><|assistant|>Estoy bien. ¿Cómo te puedo ayudar?</s><|user|>Me gustaría saber cómo funcionan los chat templates</s><|assistant|>
Como se puede ver añade al final <|assistant|>
para ayudar al LLM a saber que le toca responder. Esto garantiza que cuando el modelo genere texto, escribirá una respuesta de bot en lugar de hacer algo inesperado, como continuar con el mensaje del usuario
No todos los modelos requieren indicaciones de generación. Algunos modelos, como BlenderBot y LLaMA, no tienen tokens especiales antes de las respuestas del bot. En estos casos, add_generation_prompt
no tendrá efecto. El efecto exacto que tendrá add_generation_prompt
dependerá del modelo que se utilice.
Generación de texto
Como vemos es sencillo tokenizar el contexto sin necesitar saber cómo hacerlo para cada modelo. Así que ahora vamos a ver cómo generar texto es también muy sencillo
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
checkpoint = "flax-community/gpt-2-spanish"
tokenizer = AutoTokenizer.from_pretrained(checkpoint, torch_dtype=torch.float16)
model = AutoModelForCausalLM.from_pretrained(checkpoint, device_map=0, torch_dtype=torch.float16)
tokenizer.pad_token = tokenizer.eos_token
messages = [
{
"role": "system",
"content": "Eres un chatbot amigable que siempre de una forma graciosa",
},
{"role": "user", "content": "¿Cuántos helicópteros puede comer un ser humano de una sentada?"},
]
input_token_chat_template = tokenizer.apply_chat_template(messages, tokenize=True, add_generation_prompt=True, return_tensors="pt").to("cuda")
output = model.generate(input_token_chat_template, max_new_tokens=128, do_sample=True)
sentence_output = tokenizer.decode(output[0])
print("")
print(sentence_output)
Como se ha podido ver, se ha tokenizado el prompt con apply_chat_template
y esos tokens se han metido al modelo
Generación de texto con pipeline
La librería transformers
también permite usar pipeline
para generar texto con un chatbot, haciendo por debajo lo mismo que hemos hecho antes
from transformers import pipeline
import torch
checkpoint = "flax-community/gpt-2-spanish"
generator = pipeline("text-generation", checkpoint, device=0, torch_dtype=torch.float16)
messages = [
{
"role": "system",
"content": "Eres un chatbot amigable que siempre de una forma graciosa",
},
{"role": "user", "content": "¿Cuántos helicópteros puede comer un ser humano de una sentada?"},
]
print("")
print(generator(messages, max_new_tokens=128)[0]['generated_text'][-1])
Train
Hasta ahora hemos usado modelos preentrenados, pero en el caso que se quiera hacer fine tuning, la librería transformers
lo deja muy fácil de hacer
Como hoy en día los modelos de lenguaje son enormes, reentrenarlos es casi imposible en una GPU que cualquiera pueda tener en su casa, por lo que vamos reentrenar un modelo más pequeño. En este caso vamos a reentrenar bert-base-cased
que es un modelo de 109M parámetros.
Dataset
Tenemos que descargarnos un dataset, para ello usamos la librería datasets
de Hugging Face. Vamos a usar el conjunto de datos de reseñas de Yelp.
from transformers import AutoModelForCausalLM, AutoTokenizerimport torchcheckpoint = "flax-community/gpt-2-spanish"tokenizer = AutoTokenizer.from_pretrained(checkpoint, torch_dtype=torch.float16)model = AutoModelForCausalLM.from_pretrained(checkpoint, device_map=0, torch_dtype=torch.float16)tokenizer.pad_token = tokenizer.eos_tokenmessages = [{"role": "system","content": "Eres un chatbot amigable que siempre de una forma graciosa",},{"role": "user", "content": "¿Cuántos helicópteros puede comer un ser humano de una sentada?"},]input_token_chat_template = tokenizer.apply_chat_template(messages, tokenize=True, add_generation_prompt=True, return_tensors="pt").to("cuda")output = model.generate(input_token_chat_template, max_new_tokens=128, do_sample=True)sentence_output = tokenizer.decode(output[0])print("")print(sentence_output)from transformers import pipelineimport torchcheckpoint = "flax-community/gpt-2-spanish"generator = pipeline("text-generation", checkpoint, device=0, torch_dtype=torch.float16)messages = [{"role": "system","content": "Eres un chatbot amigable que siempre de una forma graciosa",},{"role": "user", "content": "¿Cuántos helicópteros puede comer un ser humano de una sentada?"},]print("")print(generator(messages, max_new_tokens=128)[0]['generated_text'][-1])from datasets import load_datasetdataset = load_dataset("yelp_review_full")
Vamos a ver que pinta tiene el dataset
from transformers import AutoModelForCausalLM, AutoTokenizerimport torchcheckpoint = "flax-community/gpt-2-spanish"tokenizer = AutoTokenizer.from_pretrained(checkpoint, torch_dtype=torch.float16)model = AutoModelForCausalLM.from_pretrained(checkpoint, device_map=0, torch_dtype=torch.float16)tokenizer.pad_token = tokenizer.eos_tokenmessages = [{"role": "system","content": "Eres un chatbot amigable que siempre de una forma graciosa",},{"role": "user", "content": "¿Cuántos helicópteros puede comer un ser humano de una sentada?"},]input_token_chat_template = tokenizer.apply_chat_template(messages, tokenize=True, add_generation_prompt=True, return_tensors="pt").to("cuda")output = model.generate(input_token_chat_template, max_new_tokens=128, do_sample=True)sentence_output = tokenizer.decode(output[0])print("")print(sentence_output)from transformers import pipelineimport torchcheckpoint = "flax-community/gpt-2-spanish"generator = pipeline("text-generation", checkpoint, device=0, torch_dtype=torch.float16)messages = [{"role": "system","content": "Eres un chatbot amigable que siempre de una forma graciosa",},{"role": "user", "content": "¿Cuántos helicópteros puede comer un ser humano de una sentada?"},]print("")print(generator(messages, max_new_tokens=128)[0]['generated_text'][-1])from datasets import load_datasetdataset = load_dataset("yelp_review_full")type(dataset)
The attention mask and the pad token id were not set. As a consequence, you may observe unexpected behavior. Please pass your input's `attention_mask` to obtain reliable results.Setting `pad_token_id` to `eos_token_id`:50256 for open-end generation.A decoder-only architecture is being used, but right-padding was detected! For correct generation results, please set `padding_side='left'` when initializing the tokenizer.Setting `pad_token_id` to `eos_token_id`:50256 for open-end generation.A decoder-only architecture is being used, but right-padding was detected! For correct generation results, please set `padding_side='left'` when initializing the tokenizer.datasets.dataset_dict.DatasetDict
Parece que es una especie de diccionario, vamos a ver qué claves tiene
dataset.keys()
dict_keys(['train', 'test'])
Vamos a ver cuantas reseñas tiene en cada subconjunto
len(dataset["train"]), len(dataset["test"])
(650000, 50000)
Vamos a ver una muestra
dataset["train"][100]
{'label': 0,'text': 'My expectations for McDonalds are t rarely high. But for one to still fail so spectacularly...that takes something special! The cashier took my friends 's order, then promptly ignored me. I had to force myself in front of a cashier who opened his register to wait on the person BEHIND me. I waited over five minutes for a gigantic order that included precisely one kid 's meal. After watching two people who ordered after me be handed their food, I asked where mine was. The manager started yelling at the cashiers for "serving off their orders " when they didn 't have their food. But neither cashier was anywhere near those controls, and the manager was the one serving food to customers and clearing the boards. The manager was rude when giving me my order. She didn 't make sure that I had everything ON MY RECEIPT, and never even had the decency to apologize that I felt I was getting poor service. I 've eaten at various McDonalds restaurants for over 30 years. I 've worked at more than one location. I expect bad days, bad moods, and the occasional mistake. But I have yet to have a decent experience at this store. It will remain a place I avoid unless someone in my party needs to avoid illness from low blood sugar. Perhaps I should go back to the racially biased service of Steak n Shake instead!'}
Como vemos cada muestra tiene el texto y la puntuación, vamos a ver cuantas clases hay
clases = dataset["train"].featuresclases
{'label': ClassLabel(names=['1 star', '2 star', '3 stars', '4 stars', '5 stars'], id=None),'text': Value(dtype='string', id=None)}
Vemos que tiene 5 clases distintas
num_classes = len(clases["label"].names)num_classes
5
Vamos a ver una muestra de test
dataset["test"][100]
{'label': 0,'text': 'This was just bad pizza. For the money I expect that the toppings will be cooked on the pizza. The cheese and pepparoni were added after the crust came out. Also the mushrooms were out of a can. Do not waste money here.'}
Como el objetivo de este post no es entrenar el mejor modelo, sino explicar la librería transformers
de Hugging Face, vamos a hacer un pequeño subset para poder entrenar más rápido
small_train_dataset = dataset["train"].shuffle(seed=42).select(range(1000))small_eval_dataset = dataset["test"].shuffle(seed=42).select(range(500))
Tokenización
Ya tenemos el dataset, como hemos visto en el pipeline, primero se realiza la tokenización y después se aplica el modelo. Por lo que tenemos que tokenizar el dataset
Definimos el tokenizador
small_train_dataset = dataset["train"].shuffle(seed=42).select(range(1000))small_eval_dataset = dataset["test"].shuffle(seed=42).select(range(500))from transformers import AutoTokenizertokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
La clase AutoTokenizer
tiene un método llamado map
que nos permite aplicar una función al dataset, por lo que vamos a crear una función que tokenize el texto
small_train_dataset = dataset["train"].shuffle(seed=42).select(range(1000))small_eval_dataset = dataset["test"].shuffle(seed=42).select(range(500))from transformers import AutoTokenizertokenizer = AutoTokenizer.from_pretrained("bert-base-cased")def tokenize_function(examples):return tokenizer(examples["text"], padding=True, truncation=True, max_length=3)
Como vemos de momento hemos tokenizado truncando a solo 3 tokens, esto es para poder ver mejor qué es lo que pasa por debajo
Usamos el método map
para usar la función que acabamos de definir sobre el dataset
small_train_dataset = dataset["train"].shuffle(seed=42).select(range(1000))small_eval_dataset = dataset["test"].shuffle(seed=42).select(range(500))from transformers import AutoTokenizertokenizer = AutoTokenizer.from_pretrained("bert-base-cased")def tokenize_function(examples):return tokenizer(examples["text"], padding=True, truncation=True, max_length=3)tokenized_small_train_dataset = small_train_dataset.map(tokenize_function, batched=True)tokenized_small_eval_dataset = small_eval_dataset.map(tokenize_function, batched=True)
Vemos ejemplos del dataset tokenizado
small_train_dataset = dataset["train"].shuffle(seed=42).select(range(1000))small_eval_dataset = dataset["test"].shuffle(seed=42).select(range(500))from transformers import AutoTokenizertokenizer = AutoTokenizer.from_pretrained("bert-base-cased")def tokenize_function(examples):return tokenizer(examples["text"], padding=True, truncation=True, max_length=3)tokenized_small_train_dataset = small_train_dataset.map(tokenize_function, batched=True)tokenized_small_eval_dataset = small_eval_dataset.map(tokenize_function, batched=True)tokenized_small_train_dataset[100]
{'label': 3,'text': "I recently brough my car up to Edinburgh from home, where it had sat on the drive pretty much since I had left home to go to university. As I'm sure you can imagine, it was pretty filthy, so I pulled up here expecting to shell out £5 or so for a crappy was that wouldnt really be that great. Needless to say, when I realised that the cheapest was was £2, i was suprised and I was even more suprised when the car came out looking like a million dollars. Very impressive for £2, but thier prices can go up to around £6 - which I'm sure must involve so many polishes and waxes and cleans that dirt must be simply repelled from the body of your car, never getting dirty again.",'input_ids': [101, 146, 102],'token_type_ids': [0, 0, 0],'attention_mask': [1, 1, 1]}
tokenized_small_eval_dataset[100]
{'label': 4,'text': 'Had a great dinner at Elephant Bar last night! Got a coupon in the mail for 2 meals and an appetizer for $20! While they did limit the selections you could get with the coupon, we were happy with the choices so it worked out fine. Food was delicious and the service was fantastic! Waitress was very attentive and polite. Location was a plus too! Had a lovely walk around The District shops afterward. All and all, a hands down 5 stars!','input_ids': [101, 6467, 102],'token_type_ids': [0, 0, 0],'attention_mask': [1, 1, 1]}
Como vemos se ha añadido una key con los input_ids
de los tokens, los token_type_ids
y otra con la atención mask
.
Tokenizamos ahora truncando a 20 tokens para poder usar una GPU pequeña
def tokenize_function(examples):return tokenizer(examples["text"], padding=True, truncation=True, max_length=20)tokenized_small_train_dataset = small_train_dataset.map(tokenize_function, batched=True)tokenized_small_eval_dataset = small_eval_dataset.map(tokenize_function, batched=True)
Modelo
Tenemos que crear el modelo que vamos a reentrenar. Como es un problema de clasificación vamos a usar AutoModelForSequenceClassification
def tokenize_function(examples):return tokenizer(examples["text"], padding=True, truncation=True, max_length=20)tokenized_small_train_dataset = small_train_dataset.map(tokenize_function, batched=True)tokenized_small_eval_dataset = small_eval_dataset.map(tokenize_function, batched=True)from transformers import AutoModelForSequenceClassificationmodel = AutoModelForSequenceClassification.from_pretrained("bert-base-cased", num_labels=5)
Some weights of BertForSequenceClassification were not initialized from the model checkpoint at bert-base-cased and are newly initialized: ['classifier.bias', 'classifier.weight']You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.
Como se puede ver se ha creado un modelo que clasifica entre 5 clases
Métrica de evaluación
Creamos una métrica de evaluación con la librería evaluate
de Hugging Face. Para instalarla usamos
pip install evaluate
import numpy as npimport evaluatemetric = evaluate.load("accuracy")def compute_metrics(eval_pred):logits, labels = eval_predpredictions = np.argmax(logits, axis=-1)return metric.compute(predictions=predictions, references=labels)
Trainer
Ahora para entrenar usamos el objeto Trainer
. Para poder usar Trainer
necesitamos accelerate>=0.21.0
pip install accelerate>=0.21.0
Antes de crear el trainer tenemos que crear un TrainingArguments
que es un objeto que contiene todos los argumentos que necesita Trainer
para entrenar, es decir, los hiperparámetros
Hay que pasarle un argumento obligatorio, output_dir
que es el directorio de salida donde se escribirán las predicciones del modelo y los checkpoints, que es como llama Hugging Face a los pesos del modelo
Además le pasamos varios argumentos más
per_device_train_batch_size
: tamaño del batch por dispositivo para el entrenamientoper_device_eval_batch_size
: tamaño del batch por dispositivo para la evaluaciónlearning_rate
: tasa de aprendizajenum_train_epochs
: número de épocas
import numpy as npimport evaluatemetric = evaluate.load("accuracy")def compute_metrics(eval_pred):logits, labels = eval_predpredictions = np.argmax(logits, axis=-1)return metric.compute(predictions=predictions, references=labels)from transformers import TrainingArgumentstraining_args = TrainingArguments(output_dir="test_trainer",per_device_train_batch_size=16,per_device_eval_batch_size=32,learning_rate=1e-4,num_train_epochs=5,)
Vamos a ver todos los hiperparámetros que configura
import numpy as npimport evaluatemetric = evaluate.load("accuracy")def compute_metrics(eval_pred):logits, labels = eval_predpredictions = np.argmax(logits, axis=-1)return metric.compute(predictions=predictions, references=labels)from transformers import TrainingArgumentstraining_args = TrainingArguments(output_dir="test_trainer",per_device_train_batch_size=16,per_device_eval_batch_size=32,learning_rate=1e-4,num_train_epochs=5,)training_args.__dict__
{'output_dir': 'test_trainer','overwrite_output_dir': False,'do_train': False,'do_eval': False,'do_predict': False,'evaluation_strategy': <IntervalStrategy.NO: 'no'>,'prediction_loss_only': False,'per_device_train_batch_size': 16,'per_device_eval_batch_size': 32,'per_gpu_train_batch_size': None,'per_gpu_eval_batch_size': None,'gradient_accumulation_steps': 1,'eval_accumulation_steps': None,'eval_delay': 0,'learning_rate': 0.0001,'weight_decay': 0.0,'adam_beta1': 0.9,'adam_beta2': 0.999,'adam_epsilon': 1e-08,'max_grad_norm': 1.0,'num_train_epochs': 5,'max_steps': -1,'lr_scheduler_type': <SchedulerType.LINEAR: 'linear'>,'lr_scheduler_kwargs': {},'warmup_ratio': 0.0,'warmup_steps': 0,'log_level': 'passive','log_level_replica': 'warning','log_on_each_node': True,'logging_dir': 'test_trainer/runs/Mar08_16-41-27_SAEL00531','logging_strategy': <IntervalStrategy.STEPS: 'steps'>,'logging_first_step': False,'logging_steps': 500,'logging_nan_inf_filter': True,'save_strategy': <IntervalStrategy.STEPS: 'steps'>,'save_steps': 500,'save_total_limit': None,'save_safetensors': True,'save_on_each_node': False,'save_only_model': False,'no_cuda': False,'use_cpu': False,'use_mps_device': False,'seed': 42,'data_seed': None,'jit_mode_eval': False,'use_ipex': False,'bf16': False,'fp16': False,'fp16_opt_level': 'O1','half_precision_backend': 'auto','bf16_full_eval': False,'fp16_full_eval': False,'tf32': None,'local_rank': 0,'ddp_backend': None,'tpu_num_cores': None,'tpu_metrics_debug': False,'debug': [],'dataloader_drop_last': False,'eval_steps': None,'dataloader_num_workers': 0,'dataloader_prefetch_factor': None,'past_index': -1,'run_name': 'test_trainer','disable_tqdm': False,'remove_unused_columns': True,'label_names': None,'load_best_model_at_end': False,'metric_for_best_model': None,'greater_is_better': None,'ignore_data_skip': False,'fsdp': [],'fsdp_min_num_params': 0,'fsdp_config': {'min_num_params': 0,'xla': False,'xla_fsdp_v2': False,'xla_fsdp_grad_ckpt': False},'fsdp_transformer_layer_cls_to_wrap': None,'accelerator_config': AcceleratorConfig(split_batches=False, dispatch_batches=None, even_batches=True, use_seedable_sampler=True),'deepspeed': None,'label_smoothing_factor': 0.0,'optim': <OptimizerNames.ADAMW_TORCH: 'adamw_torch'>,'optim_args': None,'adafactor': False,'group_by_length': False,'length_column_name': 'length','report_to': [],'ddp_find_unused_parameters': None,'ddp_bucket_cap_mb': None,'ddp_broadcast_buffers': None,'dataloader_pin_memory': True,'dataloader_persistent_workers': False,'skip_memory_metrics': True,'use_legacy_prediction_loop': False,'push_to_hub': False,'resume_from_checkpoint': None,'hub_model_id': None,'hub_strategy': <HubStrategy.EVERY_SAVE: 'every_save'>,'hub_token': None,'hub_private_repo': False,'hub_always_push': False,'gradient_checkpointing': False,'gradient_checkpointing_kwargs': None,'include_inputs_for_metrics': False,'fp16_backend': 'auto','push_to_hub_model_id': None,'push_to_hub_organization': None,'push_to_hub_token': None,'mp_parameters': '','auto_find_batch_size': False,'full_determinism': False,'torchdynamo': None,'ray_scope': 'last','ddp_timeout': 1800,'torch_compile': False,'torch_compile_backend': None,'torch_compile_mode': None,'dispatch_batches': None,'split_batches': None,'include_tokens_per_second': False,'include_num_input_tokens_seen': False,'neftune_noise_alpha': None,'distributed_state': Distributed environment: DistributedType.NONum processes: 1Process index: 0Local process index: 0Device: cuda,'_n_gpu': 1,'__cached__setup_devices': device(type='cuda', index=0),'deepspeed_plugin': None}
Ahora creamos un objeto Trainer
que es el que se encargará de entrenar el modelo
from transformers import Trainertrainer = Trainer(model=model,train_dataset=tokenized_small_train_dataset,eval_dataset=tokenized_small_eval_dataset,compute_metrics=compute_metrics,args=training_args,)
Una vez tenemos un Trainer
, en que hemos indicado el dataset de entrenamiento, el de test, el modelo, la métrica de evaluación y los argumentos con los hiperparámetroe, podemos entrenar el modelo con el método train
del Trainer
trainer.train()
Ya tenemos el modelo entrenado, como se puede ver con muy poco código podemos entrenar un modelo de manera muy rápida
Aconsejo mucho aprender Pytorch y entrenar muchos modelos antes de usar una librería de alto nivel como transformers
, ya que así aprendemos muchos fundamentos de deep learing y podemos entender mejor lo que pasa, sobre todo porque se va a aprender mucho de los errores. Pero una vez se ha pasado por ese periodo, usar librerías de alto nivel como transformers
acelera mucho el desarrollo.
Probando el modelo
Ahora que tenemos el modelo entrenado, vamos a probarlo con un texto. Como el dataset que nos hemos descargado es de reseñas en inglés, vamos a probarlo con una reseña en inglés
from transformers import Trainertrainer = Trainer(model=model,train_dataset=tokenized_small_train_dataset,eval_dataset=tokenized_small_eval_dataset,compute_metrics=compute_metrics,args=training_args,)trainer.train()from transformers import pipelineclasificator = pipeline("text-classification", model=model, tokenizer=tokenizer)
from transformers import Trainertrainer = Trainer(model=model,train_dataset=tokenized_small_train_dataset,eval_dataset=tokenized_small_eval_dataset,compute_metrics=compute_metrics,args=training_args,)trainer.train()from transformers import pipelineclasificator = pipeline("text-classification", model=model, tokenizer=tokenizer)clasification = clasificator("I'm liking this post a lot")clasification
0%| | 0/315 [00:00<?, ?it/s][{'label': 'LABEL_2', 'score': 0.5032550692558289}]
Vamos a ver a qué corresponde la clase que ha salido
clases
{'label': ClassLabel(names=['1 star', '2 star', '3 stars', '4 stars', '5 stars'], id=None),'text': Value(dtype='string', id=None)}
La relación sería
- LABEL_0: 1 estrella
- LABEL_1: 2 estrellas
- LABEL_2: 3 estrellas
- LABEL_3: 4 estrellas
- LABEL_4: 5 estrellas
Por lo que ha calificado el comentario con 3 estrellas. Recordemos que hemos está entrenado en un subconjunto de datos y con solo 5 épocas, por lo que no esperamos que sea muy bueno
Compartir el modelo en el Hub de Hugging Face
Una vez tenemos el modelo reentrenado podemos subirlo a nuestro espacio en el Hub de Hugging Face para que otros lo puedan usar. Para ello es necesario tener una cuenta en Hugging Face
Logging
Para poder subir el modelo primero nos tenemos que loguear.
Se puede hacer a través de la terminal con
huggingface-cli login
O a través del notebook habiendo instalado antes la librería huggingface_hub
con
pip install huggingface_hub
Ahora podemos loguearnos con la función notebook_login
, que creará una pequeña interfaz gráfica en la que tenemos que introducir un token de Hugging Face
Para crear un token hay que ir a la página de setings/tokens de nuestra cuenta, nos aparecerá algo así
Le damos a New token
y nos aparecerá una ventana para crear un nuevo token
Le damos un nombre al token y lo creamos con el rol write
.
Una vez creado lo copiamos
from huggingface_hub import notebook_loginnotebook_login()
VBox(children=(HTML(value='<center> <img src=https://huggingface.co/front/assets/huggingface_logo-noborder.sv…
Subida una vez entenado
Como hemos entrenado al modelo podemos subirlo al Hub mediante la función push_to_hub
. Esta función tiene un parámetro obligatorio que es el nombre del modelo, que tiene que ser único, si ya existe un modelo en tu Hub con ese nombre no se podrá subir. Es decir, el nombre completo del modelo será
Además tiene otros parámetros opcionales, pero que son interesantes:
use_temp_dir
(bool, optional): Si usar o no un directorio temporal para almacenar los ficheros guardados antes de ser enviados al Hub. Por defecto será True si no existe un directorio con el mismo nombre querepo_id
, False en caso contrario.commit_message
(str, optional): Mensaje de commit. Por defecto seráUpload {object}
.private
(bool, optional): Si el repositorio creado debe ser privado o no.token
(bool or str, optional): El token a usar como autorización HTTP para archivos remotos. Si es True, se usará el token generado al ejecutarhuggingface-cli
login (almacenado en ~/.huggingface). Por defecto será True si no se especificarepo_url
.max_shard_size
(int or str, optional, defaults to "5GB"): Sólo aplicable a modelos. El tamaño máximo de un punto de control antes de ser fragmentado. Los puntos de control fragmentados serán cada uno de un tamaño inferior a este tamaño. Si se expresa como una cadena, debe tener dígitos seguidos de una unidad (como "5MB"). Por defecto es "5GB" para que los usuarios puedan cargar fácilmente modelos en instancias de Google Colab de nivel libre sin problemas de OOM (out of memory) de CPU.create_pr
(bool, optional, defaults to False): Si crear o no un PR con los archivos subidos o confirmar directamente.safe_serialization
(bool, optional, defaults to True): Si convertir o no los pesos del modelo en formato safetensors para una serialización más segura.revision
(str, optional): Rama a la que enviar los archivos cargados.commit_description
(str, optional): Descripción del commit que se crearátags
(List[str], optional): Lista de tags para insertar en Hub.
model.push_to_hub(
"bert-base-cased_notebook_transformers_5-epochs_yelp_review_subset",
commit_message="bert base cased fine tune on yelp review subset",
commit_description="Fine-tuned on a subset of the yelp review dataset. Model retrained for post of transformers library. 5 epochs.",
)
Si ahora vamos a nuestro Hub podemos ver que se ha subido el modelo
Si ahora entramos a la model card a ver
Vemos que todo está sin rellenar, más adelante haremos esto
Subida mientras se entrena
Otra opción es subirlo mientras estamos entrenando el modelo. Esto es muy útil cuando entrenamos modelos durante muchas épocas y nos lleva mucho tiempo, ya que si se para el entrenamiento (porque se apaga el ordenador, se termina la sesión de colab, se acaban los créditos de la nube) no se pierde el trabajo. Para hacer esto hay que añadir push_to_hub=True
en el TrainingArguments
model.push_to_hub("bert-base-cased_notebook_transformers_5-epochs_yelp_review_subset",commit_message="bert base cased fine tune on yelp review subset",commit_description="Fine-tuned on a subset of the yelp review dataset. Model retrained for post of transformers library. 5 epochs.",)training_args = TrainingArguments(output_dir="bert-base-cased_notebook_transformers_30-epochs_yelp_review_subset",per_device_train_batch_size=16,per_device_eval_batch_size=32,learning_rate=1e-4,num_train_epochs=30,push_to_hub=True,)trainer = Trainer(model=model,train_dataset=tokenized_small_train_dataset,eval_dataset=tokenized_small_eval_dataset,compute_metrics=compute_metrics,args=training_args,)
Podemos ver que hemos cambiado las épocas a 30, por lo que el entrenamiento va a llevar más tiempo, así que al añadir push_to_hub=True
se subirá el modelo a nuestro Hub mientras se entrena.
Además hemos cambiado el output_dir
porque es el nombre que tendrá el modelo en el Hub
trainer.train()
Si volvemos a mirar nuestro hub, ahora aparece el nuevo modelo
Hub como repositorio git
En Hugging Face tanto los modelos, como los espacios, como los datasets son repositorios de git, por lo que se puede trabajar con ellos como eso. Es decir, puedes clonar, hacer forks, pull requests, etc.
Pero otra gran ventaja de esto es que puedes usar un modelo en una versión determinada
from transformers import AutoModelForSequenceClassification
model = AutoModelForSequenceClassification.from_pretrained("bert-base-cased", num_labels=5, revision="393e083")