Abrir en Google Colab
|
Descargar notebook
|
Zero-shot and few-shot learning en un problema de clasificación con instruction-tuned LLM
Los grandes modelos de lenguaje exhiben grandes habilidades en zero-shot learning. Sin embargo, los resultados dependen mucho de la capacidad del modelo, y de la ténica que utilicemos para resolver el problema.
En este ejemplo, utizaremos un modelo de lenguaje para resolver el problema de clasificación de tweets sin entrenar ningún modelo (zero-shot).
Introdución
Los grandes modelos de lenguaje son capaces de resolver problemas de clasificación al utilizar determinadas estructuras del idioma. Algunos modelos de lenguaje están especificamente entrenados para seguir instrucciones, los cuales los hace muy útiles a la hora de implementar zero-shot or few-shot learning.
En este ejemplo, veremos como utilizar un modelo de aprendizaje automático entrenado de esta forma para resolver problemas de clasificación.
Para ejecutar este notebook
Para ejecutar este notebook, instale las siguientes librerias:
[55]:
!wget https://raw.githubusercontent.com/santiagxf/M72109/master/NLP/Datasets/mascorpus/tweets_marketing.csv \
--quiet --no-clobber --directory-prefix ./Datasets/mascorpus/
!pip -q install transformers[torch] accelerate datasets evaluate
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 84.1/84.1 kB 6.0 MB/s eta 0:00:00
Verificando el hardware disponible
[ ]:
import torch
device = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')
print("Este notebook se está ejecutando en", device)
Este notebook se está ejecutando en cuda
Iniciamos session en HF:
[2]:
from huggingface_hub import notebook_login
notebook_login()
Trabajando con LLMs entrenados para instrucciones
En este ejemplo, utilizaremos el modelo Gemma 2B (instruction-tuned). Para poder utilizar este modelo en Google Colab, necesitamos realizar algunas optimizaciones, entre las cuales, bajar la precisión numérica a 16 bits.
[5]:
from transformers import pipeline
import torch
gemma = pipeline(
model="google/gemma-2b-it",
dtype=torch.bfloat16, # Use bfloat16 for efficiency on supported hardware
device_map="auto",
max_length=1000
)
`torch_dtype` is deprecated! Use `dtype` instead!
Podemos verificar como funciona este modelo:
[13]:
print(gemma("Cual es la diferencia entre el volleyball y el volleyball de playa?")[0]["generated_text"])
Both `max_new_tokens` (=256) and `max_length`(=20) seem to have been set. `max_new_tokens` will take precedence. Please refer to the documentation for more information. (https://huggingface.co/docs/transformers/main/en/main_classes/text_generation)
Cual es la diferencia entre el volleyball y el volleyball de playa?
* El volleyball es un juego de campo, mientras que el volleyball de playa es un juego de playa.
* El volleyball tiene una superficie más grande, mientras que el volleyball de playa tiene una superficie más pequeña.
* El volleyball tiene una velocidad más alta, mientras que el volleyball de playa tiene una velocidad más baja.
* El volleyball tiene una regla más compleja, mientras que el volleyball de playa tiene una regla más simple.
En resumen, el volleyball es un juego de campo y el volleyball de playa es un juego de playa.
¿Que diferencia hay con modelos de lenguaje tradicionales?
Los modelos que están entrenados para seguir instrucciones, en general, fueron sometidos a un proceso de fine-tuning con conjunto de datos que disponen de instrucciones en determinado formato.
En el caso de modelos como Gemma (instruction-tuned), cada vez que lo ejecutamos, en realidad enviamos un texto distinto al modelo. El formato exacto del prompt puede variar entre modelos. Las siguientes celdas exploraban el formato específico de Dolly, pero para Gemma, se suele usar un formato de chat específico que el tokenizer puede aplicar automáticamente, o se sigue la documentación del modelo.
[15]:
gemma.tokenizer.get_chat_template()
[15]:
"{{ bos_token }}{% if messages[0]['role'] == 'system' %}{{ raise_exception('System role not supported') }}{% endif %}{% for message in messages %}{% if (message['role'] == 'user') != (loop.index0 % 2 == 0) %}{{ raise_exception('Conversation roles must alternate user/assistant/user/assistant/...') }}{% endif %}{% if (message['role'] == 'assistant') %}{% set role = 'model' %}{% else %}{% set role = message['role'] %}{% endif %}{{ '<start_of_turn>' + role + '\n' + message['content'] | trim + '<end_of_turn>\n' }}{% endfor %}{% if add_generation_prompt %}{{'<start_of_turn>model\n'}}{% endif %}"
[17]:
messages = [
{"role": "user", "content": "¿Quién eres?"},
]
prompt = gemma.tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
print("\n--- Prompt generado ---\n")
print(prompt)
print("\n--- Respuesta del modelo ---\n")
print(gemma(prompt)[0]["generated_text"])
Both `max_new_tokens` (=256) and `max_length`(=20) seem to have been set. `max_new_tokens` will take precedence. Please refer to the documentation for more information. (https://huggingface.co/docs/transformers/main/en/main_classes/text_generation)
--- Prompt generado ---
<bos><start_of_turn>user
¿Quién eres?<end_of_turn>
<start_of_turn>model
--- Respuesta del modelo ---
<bos><start_of_turn>user
¿Quién eres?<end_of_turn>
<start_of_turn>model
Soy un modelo lingüístico avanzado. Soy un modelo lingüístico que fue diseñada para ayudar a las personas a comprender el lenguaje y a generar lenguaje.
¿Qué puedo hacer para ayudarte hoy?
Zero-shot learning
En general, para resolver cualquier tarea necesitaremos generar un texto o prompt que haga que el modelo genere texto que resuelve el problema de negocio:
[18]:
prompt = "Decide whether the following product review's sentiment is positive, neutral, or negative.\n\nProduct review:\n{}\nSentiment:"
print(prompt)
Decide whether the following product review's sentiment is positive, neutral, or negative.
Product review:
{}
Sentiment:
Ahora, supongamos que nuestro texto es como sigue:
[19]:
sample = "The reactivity of your team has been good...ish. However, the overal experience is questionable."
Podemos generar la respuesta del modelo como sigue:
[20]:
gemma(prompt.format(sample))
Both `max_new_tokens` (=256) and `max_length`(=20) seem to have been set. `max_new_tokens` will take precedence. Please refer to the documentation for more information. (https://huggingface.co/docs/transformers/main/en/main_classes/text_generation)
[20]:
[{'generated_text': "Decide whether the following product review's sentiment is positive, neutral, or negative.\n\nProduct review:\nThe reactivity of your team has been good...ish. However, the overal experience is questionable.\nSentiment: Neutral"}]
Few-shot learning
En few-shot learning, intentaremos generar un prompt en el cual multiples ejemplos se mencionan antes de solicitarle al modelo que complete el prompt:
[21]:
base_prompt = "Decide whether the following product reviews' sentiment is positive, neutral, or negative."
examples = [
(
"I love my new chess board!",
"positive"
),
(
"Not what I expected but I guess it'll do",
"neutral"
),
(
"I'm so disappointed. The product seemed much better on the website",
"negative"
)
]
Podemos generar nuestro prompt entonces como sigue:
[22]:
def generate_prompt(base_prompt, examples, sample_name, prediction_name, sample=None):
prompt = base_prompt
for example in examples:
prompt += f"\n\n{sample_name}:\n'{example[0]}'\n{prediction_name}:\n{example[1]}"
prompt += f"\n\n{sample_name}:\n'{{}}'\n{prediction_name}:\n"
if sample:
return prompt.format(sample)
return prompt
[23]:
prompt = generate_prompt(base_prompt, examples, "Product review", "Sentiment", sample)
print(prompt)
Decide whether the following product reviews' sentiment is positive, neutral, or negative.
Product review:
'I love my new chess board!'
Sentiment:
positive
Product review:
'Not what I expected but I guess it'll do'
Sentiment:
neutral
Product review:
'I'm so disappointed. The product seemed much better on the website'
Sentiment:
negative
Product review:
'The reactivity of your team has been good...ish. However, the overal experience is questionable.'
Sentiment:
Podemos ver como funciona el modelo:
[25]:
gemma(prompt, return_full_text=False)
You seem to be using the pipelines sequentially on GPU. In order to maximize efficiency please use a dataset
Both `max_new_tokens` (=256) and `max_length`(=20) seem to have been set. `max_new_tokens` will take precedence. Please refer to the documentation for more information. (https://huggingface.co/docs/transformers/main/en/main_classes/text_generation)
[25]:
[{'generated_text': 'neutral'}]
Podemos ver que en este caso el modelo genero un texto que es bastante mas largo del que esperabamos. Nos interesaria que solo mencione el sentimiento. Podemos mejorar nuestras instrucciones para solicitar al modelo que realize esto, o simplemente buscar especificamente por tokens en particular:
Utilizando etiquetas
Primero, utilizaremos el tokenizer para generar los IDs correspondiente a los tokens de las etiquetas que disponemos:
[26]:
target_labels = ['positive', 'neutral', 'negative']
target_token_ids = [gemma.tokenizer.encode(k, add_special_tokens=False)[0] for k in target_labels]
Generaremos una función que solo obtiene la probabilidad más alta para los tokens que están representados por las etiquetas que tenemos. Esta técnica supone que, a pesar de que el modelo genera un texto diferente, la probabilidad de la clase correcta siempre es más alta a lo largo de todo el texto generado.
[50]:
def generate_for_classification(pipeline, prompt, target_labels, target_token_ids):
input_ids = pipeline.tokenizer(prompt, return_tensors="pt", padding=True).input_ids
with torch.no_grad():
outputs = pipeline.model(input_ids.to(pipeline.model.device))
result = torch.nn.Softmax(dim=-1)(outputs.logits[:, -1, target_token_ids])
predicted_token_ids = torch.argmax(result, axis=1)
return [target_labels[i] for i in predicted_token_ids], torch.max(result, axis=1)
Ejecutamos la función:
[51]:
predictions, outputs = generate_for_classification(gemma, prompt, target_labels, target_token_ids)
[39]:
predictions
[39]:
['neutral']
En la función, estamos devolviendo también la variable result que contiene la probabilidad de ese token en particular:
[53]:
outputs[0]
[53]:
tensor([0.9570], device='cuda:0', dtype=torch.bfloat16)
Resolviendo un problema de clasificación con few-shot learning
Cargamos el conjunto de datos:
[56]:
import pandas as pd
tweets = pd.read_csv('Datasets/mascorpus/tweets_marketing.csv')
Trataremos de resolver entonces el mismo problema de clasificación con el que veniamos trabajando: clasificar los tweets dependiendo del sector al que pertenecen. Recordemos que tenemos 7 categorias distintas.
Mejoraremos un poco los nombres de las categorías:
[57]:
tweets['SECTOR'] = tweets['SECTOR'].map({
"ALIMENTACION": "alimentos",
"AUTOMOCION": "automobiles",
"BANCA": "bancos",
"BEBIDAS": "bebidas",
"DEPORTES": "deportes",
"RETAIL": "supermercados",
"TELCO": "telefonía"
})
[58]:
labels = tweets['SECTOR'].unique().tolist()
labels
[58]:
['supermercados',
'telefonía',
'alimentos',
'automobiles',
'bancos',
'bebidas',
'deportes']
[59]:
from sklearn.model_selection import train_test_split
train, test = train_test_split(tweets, test_size=0.33, stratify=tweets['SECTOR'])
[60]:
base_prompt = f"Clasificar los siguientes tweets según el producto o categoría a la que hacen referencia. Las posibles categorías son: {', '.join(labels)}"
[61]:
examples_df = train.groupby('SECTOR').apply(lambda x: x.sample(1)).reset_index(drop=True)
examples = [(row[1]['TEXTO'], row[1]['SECTOR']) for row in examples_df.iterrows()]
/tmp/ipykernel_647/4046959856.py:1: DeprecationWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
examples_df = train.groupby('SECTOR').apply(lambda x: x.sample(1)).reset_index(drop=True)
[62]:
prompt = generate_prompt(base_prompt, examples, "Tweet", "Categoría", sample=None)
print(prompt)
Clasificar los siguientes tweets según el producto o categoría a la que hacen referencia. Las posibles categorías son: supermercados, telefonía, alimentos, automobiles, bancos, bebidas, deportes
Tweet:
'Me compré 2 alfajores Milka y uno Pepito😻😏'
Categoría:
alimentos
Tweet:
'#LigaEndesa #ACB Claves de la Jornada 20 del SuperManager KIA https://t.co/uFmGklRkk1 https://t.co/z5oNtmJhLT'
Categoría:
automobiles
Tweet:
'700.000 firmas exigen a BBVA que deje de financiar el calentamiento global y la vulneración de derechos humanos https://t.co/dEn6TERs7Y'
Categoría:
bancos
Tweet:
'me gusta la cruzcampo #Arruinaunacitacon4palabras'
Categoría:
bebidas
Tweet:
'@MiguuelHerrera @adidas holaaaaa @PUMAmexico'
Categoría:
deportes
Tweet:
'"Alcampo, el supermercado online más barato"
#politicadeprecios
https://t.co/a3wenDb2g8'
Categoría:
supermercados
Tweet:
'@AfectadosBBS Gracias por vuestro apoyo.
@Movistar @Telefónica me han proporcionado un Samsung Galaxy S5, que ya tengo en mi poder.'
Categoría:
telefonía
Tweet:
'{}'
Categoría:
Veamos como funciona con un ejemplo:
[63]:
sample = test.sample(1).iloc[0]
[64]:
sample['TEXTO']
[64]:
'Santander, BBVA y Barclays, líderes en bonos en 2016 https://t.co/LVaV4ic7Fu https://t.co/HZIBdsFSyt'
Utilizando el modelo generativo
[66]:
gemma(prompt.format(sample['TEXTO']), return_full_text=False)
Both `max_new_tokens` (=256) and `max_length`(=20) seem to have been set. `max_new_tokens` will take precedence. Please refer to the documentation for more information. (https://huggingface.co/docs/transformers/main/en/main_classes/text_generation)
[66]:
[{'generated_text': 'bancos'}]
Abrir en Google Colab
Descargar notebook