En este artículo, aprenderá a comparar tres enfoques de clasificación de texto, desde una canalización TF-IDF clásica hasta un modelo de lenguaje grande de disparo cero, para comprender cuándo cada uno es más apropiado.
Los temas que cubriremos incluyen:
Cómo implementar y evaluar un canal de clasificación de textos de regresión logística y TF-IDF clásico. Cómo aplicar la clasificación de disparo cero utilizando un modelo basado en transformador (BART) y compararlo con la línea de base clásica. Cómo utilizar scikit-LLM con un modelo de lenguaje grande alojado en Groq para una clasificación inmediata lista para producción con cambios mínimos de código.
Scikit-LLM frente a clasificadores de texto tradicionales: ¿Cuándo debería utilizar un LLM?
Introducción
En los últimos años, los modelos de IA generativa como los LLM (modelos de lenguaje grande) han ido reemplazando gradualmente a los clásicos de aprendizaje automático para abordar determinadas tareas, por ejemplo, la clasificación de textos. Pero la verdad es que, en lugar de tener una solución única, hay compensaciones críticas que los desarrolladores deben enfrentar: ¿deberíamos seguir con modelos convencionales rápidos y probados en batalla, invertir en ajustar un LLM basado en transformadores o tal vez aprovechar el potencial de razonamiento de disparo cero de los LLM?
En este artículo, implementaremos una evaluación comparativa entre tres enfoques distintos para la clasificación de texto:
TF-IDF y regresión logística (línea de base clásica). Clasificación de disparo cero con BART: una arquitectura estándar de aprendizaje profundo basada en transformadores. Scikit-LLM con clasificación de disparo cero: el enfoque más moderno basado en indicaciones.
El tutorial a continuación se mantiene completamente gratuito para que todos lo prueben, sin costos ni límites de tasa de API. Para hacerlo, usaremos scikit-LLM junto con un modelo disponible en Groq. Deberá registrarse en Groq y obtener una clave API para evaluar la tercera solución a continuación.
Implementación de la evaluación comparativa
Primero, instalamos todas las bibliotecas principales que necesitaremos.
!pip install scikit-learn transformers scikit-llm scikit-ollama pandas torch
! pip install scikit – learn transformers scikit – llm scikit – ollama pandas torch
Para permitir la reproducibilidad, creamos un pequeño conjunto de datos sintéticos que contiene mensajes de atención al cliente. Los billetes se clasifican en cinco clases. Una vez creado, lo almacenamos en un objeto DataFrame y lo dividimos en conjuntos de entrenamiento y prueba.
import pandas as pd
from sklearn.model_selection import train_test_split
data = {
“text”: [
# Technical
“My screen is completely black and won’t turn on.”, “The app keeps crashing every time I click save.”,
“The Wi-Fi module is failing to connect to the router.”, “Data sync isn’t working across my devices.”,
“My bluetooth headphones won’t pair with the app.”, “I keep getting an Error 404 on the login screen.”,
“The database connection timed out during the export.”, “API rate limit exceeded even though I haven’t used it.”,
“Profile images won’t load on the dashboard.”, “The software installation failed at 99%.”,
# Billing
“I was charged twice this month, please fix this.”, “How do I update my credit card information?”,
“My invoice for last month is missing from the portal.”, “The VAT calculation on my receipt is wrong.”,
“My transaction was declined but I have funds.”, “Can I change my billing cycle from monthly to annual?”,
“Where can I find my official receipt?”, “My saved credit card expired and I need to swap it.”,
“I was overcharged on my last statement.”, “Please remove my saved payment method.”,
# Account
“My account is locked and I forgot my password.”, “How do I change the email address on my profile?”,
“Please delete my account and all associated data.”, “I want to update my profile picture.”,
“How do I enable two-factor authentication (2FA)?”, “I didn’t receive the email verification link.”,
“Can I merge two different accounts into one?”, “Is there a way to change my username?”,
“I need to transfer account ownership to my manager.”, “I am locked out because I lost my 2FA phone.”,
# Sales
“Do you offer enterprise discounts for large teams?”, “Do you have an annual plan with a discount?”,
“Can you compare the pro and basic tiers for me?”, “What is the pricing for a 50-user bulk license?”,
“Is there a student discount available?”, “Can I schedule a demo with your sales team?”,
“Do you sell and ship to customers in Europe?”, “How does your partner and reseller program work?”,
“What are the usage limits on the free tier?”, “I need a custom quote for a government contract.”,
# Refund
“Can I get a refund for my last purchase? It was a mistake.”, “I want my money back for the subscription.”,
“Accidental purchase, please reverse the charge.”, “I am not satisfied with the product, need a refund.”,
“Cancel my subscription immediately and refund me.”, “I was charged after my free trial ended.”,
“I need a prorated refund for the remaining months.”, “What is your official refund policy?”,
“I was promised a refund last week but haven’t received it.”, “The item arrived broken, I want a full refund.”
],
“label”: [
“Technical”] * 10 + [“Billing”] * 10 + [“Account”] * 10 + [“Sales”] * 10 + [“Refund”] * 10
}
df = pd.DataFrame(data)
# Stratified train-test splitting ensures all 5 categories are proportionally represented in both subsets when the dataset is small
X_train, X_test, y_train, y_test = train_test_split(
df[“text”], df[“label”], test_size=0.3, random_state=42, stratify=df[“label”]
)
print(f”Training rows: {len(X_train)} | Testing rows: {len(X_test)}”)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import pandas as pd
from sklearn . model_selection import train_test_split
data = {
“text” : [
# Technical
“My screen is completely black and won’t turn on.” , “The app keeps crashing every time I click save.” ,
“The Wi-Fi module is failing to connect to the router.” , “Data sync isn’t working across my devices.” ,
“My bluetooth headphones won’t pair with the app.” , “I keep getting an Error 404 on the login screen.” ,
“The database connection timed out during the export.” , “API rate limit exceeded even though I haven’t used it.” ,
“Profile images won’t load on the dashboard.” , “The software installation failed at 99%.” ,
# Facturación
"Me cobraron dos veces este mes, por favor solucionen esto". , "¿Cómo actualizo la información de mi tarjeta de crédito?" ,
"Mi factura del mes pasado no aparece en el portal". , "El cálculo del IVA en mi recibo es incorrecto." ,
"Mi transacción fue rechazada pero tengo fondos". , "¿Puedo cambiar mi ciclo de facturación de mensual a anual?" ,
"¿Dónde puedo encontrar mi recibo oficial?" , "Mi tarjeta de crédito guardada expiró y necesito cambiarla". ,
"Me cobraron de más en mi última declaración". , "Elimine mi método de pago guardado". ,
# Cuenta
"Mi cuenta está bloqueada y olvidé mi contraseña". , "¿Cómo cambio la dirección de correo electrónico en mi perfil?" ,
"Elimine mi cuenta y todos los datos asociados". , "Quiero actualizar mi foto de perfil". ,
"¿Cómo habilito la autenticación de dos factores (2FA)?" , "No recibí el enlace de verificación por correo electrónico". ,
"¿Puedo fusionar dos cuentas diferentes en una?" , "¿Hay alguna manera de cambiar mi nombre de usuario?" ,
"Necesito transferir la propiedad de la cuenta a mi administrador". , "Estoy bloqueado porque perdí mi teléfono 2FA". ,
# Ventas
"¿Ofrecen descuentos empresariales para equipos grandes?" , "¿Tienes un plan anual con descuento?" ,
"¿Puedes compararme los niveles profesional y básico?" , "¿Cuál es el precio de una licencia masiva para 50 usuarios?" ,
"¿Hay algún descuento para estudiantes disponible?" , "¿Puedo programar una demostración con su equipo de ventas?" ,
"¿Venden y realizan envíos a clientes en Europa?" , "¿Cómo funciona su programa de socios y revendedores?" ,
"¿Cuáles son los límites de uso en la capa gratuita?" , "Necesito una cotización personalizada para un contrato gubernamental". ,
# Reembolso
"¿Puedo obtener un reembolso por mi última compra? Fue un error". , "Quiero que me devuelvan el dinero de la suscripción". ,
"Compra accidental, revierta el cargo". , "No estoy satisfecho con el producto, necesito un reembolso." ,
"Cancelar mi suscripción inmediatamente y reembolsarme." , "Me cobraron después de que finalizó mi prueba gratuita". ,
"Necesito un reembolso prorrateado por los meses restantes". , "¿Cuál es su política oficial de reembolso?" ,
"Me prometieron un reembolso la semana pasada pero no lo he recibido". , "El artículo llegó roto, quiero un reembolso completo".
] ,
"etiqueta" : [
"Técnico" ] * 10 + [ "Facturación" ] * 10 + [ "Cuenta" ] * 10 + [ "Ventas" ] * 10 + [ "Reembolso" ] * 10
}
df = PD . Marco de datos ( datos )
# La división estratificada de pruebas de tren garantiza que las 5 categorías estén representadas proporcionalmente en ambos subconjuntos cuando el conjunto de datos es pequeño
X_tren , X_prueba , y_tren , y_prueba = tren_prueba_split (
df [ "texto" ] , df [ "etiqueta" ] , tamaño_prueba = 0,3 , estado_aleatorio = 42 , estratificar = df [ "etiqueta" ]
)
print ( f "Filas de entrenamiento: {len(X_train)} | Filas de prueba: {len(X_test)}" )
Primero implementamos y evaluamos el enfoque más clásico: TF-IDF combinado con un clasificador de regresión logística. El proceso se muestra a continuación:
import time
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import make_pipeline
from sklearn.metrics import classification_report
start_time = time.time()
# Creating and training the classical pipeline
logreg_clf = make_pipeline(TfidfVectorizer(), LogisticRegression())
logreg_clf.fit(X_train, y_train)
# Inference: predictions on the test examples
y_pred_logreg = logreg_clf.predict(X_test)
logreg_latency = time.time() – start_time
# Latency is also measured to assess the model’s efficiency
print(f”Logistic Regression Latency: {logreg_latency:.4f} seconds”)
print(classification_report(y_test, y_pred_logreg, zero_division=0))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import time
from sklearn . feature_extraction . text import TfidfVectorizer
from sklearn . linear_model import LogisticRegression
from sklearn . pipeline import make_pipeline
from sklearn . metrics import classification_report
start_time = time . time ( )
# Creating and training the classical pipeline
logreg_clf = make_pipeline ( TfidfVectorizer ( ) , LogisticRegression ( ) )
logreg_clf . fit ( X_train , y_train )
# Inference: predictions on the test examples
y_pred_logreg = logreg_clf . predict ( X_test )
logreg_latency = time . time ( ) – start _ time
# Latency is also measured to assess the model’s efficiency
print ( f “Logistic Regression Latency: {logreg_latency:.4f} seconds” )
print ( classification_report ( y_test , y_pred_logreg , zero_division = 0 ) )
Producción:
Logistic Regression Latency: 0.0615 seconds
precision recall f1-score support
Account 0.25 0.33 0.29 3
Billing 1.00 1.00 1.00 3
Refund 0.67 0.67 0.67 3
Sales 0.25 0.33 0.29 3
Technical 1.00 0.33 0.50 3
accuracy 0.53 15
macro avg 0.63 0.53 0.55 15
weighted avg 0.63 0.53 0.55 15
Logistic Regression Latency : 0.0615 seconds
precision recall f1 – score support
Account 0.25 0.33 0.29 3
Billing 1.00 1.00 1.00 3
Refund 0.67 0.67 0.67 3
Sales 0.25 0.33 0.29 3
Technical 1.00 0.33 0.50 3
accuracy 0.53 15
macro avg 0.63 0.53 0.55 15
weighted avg 0.63 0.53 0.55 15
El clasificador muestra un comportamiento mixto: funciona bien en categorías como Facturación y, hasta cierto punto, Reembolso, pero tiene problemas con el resto. Este es, con diferencia, el enfoque más rápido; sin embargo, su rendimiento de clasificación está limitado por su incapacidad para capturar los matices lingüísticos complejos que los modelos de lenguaje más modernos pueden manejar de manera efectiva. Si nos atenemos a los resultados agregados, obtenemos precisiones que oscilan entre 0,53 y 0,55 en general.
Veamos qué tiene para ofrecer nuestro segundo enfoque: clasificación de tiro cero con facebook/bart-large-mnli:
from transformers import pipeline
import time
# Using a HuggingFace zero-shot classification pipeline as our transformer representative
# We need to overload the default classifier to specify our own label set
classifier = pipeline(“zero-shot-classification”, model=”facebook/bart-large-mnli”)
candidate_labels = [“Technical”, “Billing”, “Account”, “Sales”, “Refund”]
start_time = time.time()
# Inference time!
bert_preds =[]para texto en X_test: resultado = clasificador(texto, etiquetas_candidatas) bert_preds.append(resultado['etiquetas'][0]) # Obtenga la etiqueta de puntuación más alta bert_latency = time.time() – start_time print(f"Latencia de inferencia del transformador: {bert_latency:.4f} segundos") print(classification_report(y_test, bert_preds, zero_division=0))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
de tubería de importación de transformadores
tiempo de importación
# Utilizando un canal de clasificación de disparo cero de HuggingFace como nuestro representante de transformadores
# Necesitamos sobrecargar el clasificador predeterminado para especificar nuestro propio conjunto de etiquetas
clasificador = tubería ( "clasificación de tiro cero" , modelo = "facebook/bart-large-mnli" )
etiquetas_candidatos = [ "Técnico" , "Facturación" , "Cuenta" , "Ventas" , "Reembolso" ]
hora_inicio = tiempo . tiempo ( )
# ¡Tiempo de inferencia!
bert_preds = [ ]
para texto en X_prueba :
resultado = clasificador ( texto , etiquetas_candidatas )
bert_preds . agregar ( resultado [ 'etiquetas' ] [ 0 ] ) # Obtenga la etiqueta de puntuación más alta
bert_latencia = tiempo . tiempo ( ) – hora_inicio
print ( f "Latencia de inferencia del transformador: {bert_latency:.4f} segundos" )
imprimir ( informe_clasificación ( prueba_y , bert_preds , división_cero = 0 ) )
Estos son los resultados:
Transformer Inference Latency: 32.2503 seconds
precision recall f1-score support
Account 0.40 0.67 0.50 3
Billing 1.00 0.33 0.50 3
Refund 0.75 1.00 0.86 3
Sales 1.00 0.33 0.50 3
Technical 0.75 1.00 0.86 3
accuracy 0.67 15
macro avg 0.78 0.67 0.64 15
weighted avg 0.78 0.67 0.64 15
Transformer Inference Latency : 32.2503 seconds
precision recall f1 – score support
Account 0.40 0.67 0.50 3
Billing 1.00 0.33 0.50 3
Refund 0.75 1.00 0.86 3
Sales 1.00 0.33 0.50 3
Technical 0.75 1.00 0.86 3
accuracy 0.67 15
promedio macro 0,78 0,67 0,64 15
promedio ponderado 0,78 0,67 0,64 15
Latencia mucho mayor y sólo una modesta mejora en la precisión: 0,64–0,67 en términos generales.
Finalmente, el clasificador LLM de tiro cero con un canal scikit-LLM y un modelo Groq:
from skllm.config import SKLLMConfig
from skllm.models.gpt.classification.zero_shot import ZeroShotGPTClassifier
import getpass
import time
from sklearn.metrics import classification_report
# 1. Securely asking for the key in a private input box:
# GET YOURS AT https://console.groq.com/keys
print(“Get your free Groq API key here: https://console.groq.com/keys”)
api_key = getpass.getpass(“Paste your API Key here: “)
# 2. Configuring Scikit-LLM
SKLLMConfig.set_openai_key(api_key)
SKLLMConfig.set_gpt_url(“https://api.groq.com/openai/v1/”)
# 3. Initializing with the latest active model for zero-shot classification
# ‘llama-3.3-70b-versatile’ is supported by Groq at the time of writing
llm_clf = ZeroShotGPTClassifier(model=”custom_url::llama-3.3-70b-versatile”)
start_time = time.time()
# 4. Running the classification task
llm_clf.fit(X_train, y_train)
y_pred_llm = llm_clf.predict(X_test)
llm_latency = time.time() – start_time
print(f”nScikit-LLM Latency: {llm_latency:.4f} seconds”)
print(classification_report(y_test, y_pred_llm, zero_division=0))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from skllm . config import SKLLMConfig
from skllm . models . gpt . classification . zero_shot import ZeroShotGPTClassifier
import getpass
import time
from sklearn . metrics import classification _ report
# 1. Securely asking for the key in a private input box:
# GET YOURS AT https://console.groq.com/keys
print ( “Get your free Groq API key here: https://console.groq.com/keys” )
api_key = getpass . getpass ( “Paste your API Key here: “ )
# 2. Configuring Scikit-LLM
SKLLMConfig . set_openai_key ( api_key )
SKLLMConfig . set_gpt_url ( “https://api.groq.com/openai/v1/” )
# 3. Initializing with the latest active model for zero-shot classification
# ‘llama-3.3-70b-versatile’ is supported by Groq at the time of writing
llm_clf = ZeroShotGPTClassifier ( model = “custom_url::llama-3.3-70b-versatile” )
start_time = time . time ( )
# 4. Running the classification task
llm_clf . fit ( X_train , y_train )
y_pred_llm = llm_clf . predict ( X_test )
llm_latency = time . time ( ) – start_time
print ( f “nScikit-LLM Latency: {llm_latency:.4f} seconds” )
print ( classification_report ( y_test , y_pred_llm , zero_division = 0 ) )
Resultados finales:
Scikit-LLM Latency: 2.5905 seconds
precision recall f1-score support
Account 0.67 0.67 0.67 3
Billing 1.00 0.67 0.80 3
Refund 1.00 1.00 1.00 3
Sales 1.00 1.00 1.00 3
Technical 0.75 1.00 0.86 3
accuracy 0.87 15
macro avg 0.88 0.87 0.86 15
weighted avg 0.88 0.87 0.86 15
Scikit – LLM Latency : 2.5905 seconds
precision recall f1 – score support
Account 0.67 0.67 0.67 3
Facturación 1.00 0,67 0,80 3
Reembolso 1.00 1.00 1.00 3
Ventas 1.00 1.00 1.00 3
Técnico 0,75 1.00 0,86 3
exactitud 0,87 15
promedio macro 0,88 0,87 0,86 15
promedio ponderado 0,88 0,87 0,86 15
Este es, con diferencia, el mejor resultado en términos de precisión de clasificación (0,86–0,87). Y, sorprendentemente, también es considerablemente más rápido que el modelo de disparo cero basado en BART. Esto no es tan sorprendente: el modelo alojado en Groq se entrenó en un conjunto de datos amplio y masivo. No necesita aprender lo que significa un determinado tipo de ticket de atención al cliente; ya lo sabe, a diferencia del modelo BART de disparo cero utilizado anteriormente.
Entonces, ¡tenemos un claro ganador!
Una nota final: aquí es donde reside el valor de scikit-LLM. Cierra la brecha entre la IA clásica y la moderna a través de una interfaz estandarizada y lista para producción, utilizando una sintaxis similar a la de scikit-learn en todo momento. Con esto en la mano, puede cambiar entre un regresor logístico clásico y un Groq LLM moderno con un mínimo esfuerzo.
Concluyendo
Este artículo comparó, en un conjunto de datos de juguetes, la clasificación de disparo cero de scikit-LLM con enfoques más clásicos: regresión logística con TF-IDF y un modelo de transformador de disparo cero (BART) que se encuentra en algún punto intermedio. En cuanto a la pregunta planteada en el título, ¿cuándo debería utilizar un LLM para la clasificación de textos? La elección de un pequeño conjunto de datos de juguete aquí fue deliberada. Cuando la cantidad de datos disponibles es limitada y la tarea requiere un razonamiento lingüístico profundo y una comprensión contextual, scikit-LLM es un activo convincente: hace posible implementar instantáneamente el conocimiento mundial previamente entrenado de un modelo en un proceso como el nuestro, eliminando tanto el tiempo como los costos de infraestructura de entrenar un modelo de esta magnitud desde cero.