En este artículo, aprenderá a utilizar un modelo de lenguaje grande previamente entrenado para extraer características estructuradas del texto y combinarlas con columnas numéricas para entrenar un clasificador supervisado.
Los temas que cubriremos incluyen:
Crear un conjunto de datos de juguete con texto mixto y campos numéricos para clasificación Usar un modelo LLaMA alojado en Groq para extraer características JSON del texto del ticket con un esquema Pydantic Entrenar y evaluar un clasificador scikit-learn en el conjunto de datos tabulares diseñado
No perdamos más tiempo.
Del texto a las tablas: ingeniería de funciones con LLM para datos tabulares
Imagen del editor
Introducción
Si bien los modelos de lenguaje grande (LLM) generalmente se usan con fines conversacionales en casos de uso que giran en torno a interacciones de lenguaje natural, también pueden ayudar con tareas como la ingeniería de funciones en conjuntos de datos complejos. Específicamente, puede aprovechar los LLM previamente capacitados de proveedores como Groq (por ejemplo, modelos de la familia Llama) para llevar a cabo tareas de preprocesamiento y transformación de datos, incluida la conversión de datos no estructurados, como texto, en datos tabulares completamente estructurados que pueden usarse para impulsar modelos predictivos de aprendizaje automático.
En este artículo, lo guiaré a través del proceso completo de aplicación de ingeniería de características al texto estructurado, convirtiéndolo en datos tabulares adecuados para un modelo de aprendizaje automático, es decir, un clasificador entrenado en características creadas a partir de texto mediante un LLM.
Configuración e importaciones
Primero, realizaremos todas las importaciones necesarias para este ejemplo práctico:
import pandas as pd
import json
from pydantic import BaseModel, Field
from openai import OpenAI
from google.colab import userdata
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
from sklearn.preprocessing import StandardScaler
import pandas as pd
import json
from pydantic import BaseModel , Field
from openai import OpenAI
from google . colab import userdata
from sklearn . ensemble import RandomForestClassifier
from sklearn . model_selection import train_test_split
from sklearn . metrics import classification_report
from sklearn . preprocessing import StandardScaler
Tenga en cuenta que además de las bibliotecas comunes para el aprendizaje automático y el preprocesamiento de datos como scikit-learn, importamos la clase OpenAI, no porque usaremos directamente un modelo OpenAI, sino porque muchas API de LLM (incluidas las de Groq) han adoptado el mismo estilo de interfaz y especificaciones que OpenAI. Por lo tanto, esta clase lo ayuda a interactuar con una variedad de proveedores y acceder a una amplia gama de LLM a través de un solo cliente, incluidos los modelos Llama a través de Groq, como veremos en breve.
A continuación, configuramos un cliente Groq para permitir el acceso a un LLM previamente entrenado al que podemos llamar a través de API para realizar inferencias durante la ejecución:
groq_api_key = userdata.get(‘GROQ_API_KEY’)
client = OpenAI(
base_url=”https://api.groq.com/openai/v1″,
api_key=groq_api_key
)
groq_api_key = userdata . get ( ‘GROQ_API_KEY’ )
client = OpenAI (
base_url = “https://api.groq.com/openai/v1” ,
api_key = groq_api _ key
)
Nota importante: para que el código anterior funcione, debe definir una clave secreta API para Groq. En Google Colab, puedes hacer esto a través del ícono "Secretos" en la barra lateral izquierda (este ícono parece una llave). Aquí, asigne a su clave el nombre 'GROQ_API_KEY', luego regístrese en el sitio web de Groq para obtener una clave real y péguela en el campo de valor.
Crear un conjunto de datos de boletos de juguete
El siguiente paso genera un conjunto de datos de juguetes sintéticos, parcialmente aleatorios, con fines ilustrativos. Si tiene su propio conjunto de datos de texto, no dude en adaptar el código en consecuencia y utilizar el suyo propio.
import random
import time
random.seed(42)
categories = [“access”, “inquiry”, “software”, “billing”, “hardware”]
templates = {
“access”: [
“I’ve been locked out of my account for {days} days and need urgent help!”,
“I can’t log in, it keeps saying bad password.”,
“Reset my access credentials immediately.”,
“My 2FA isn’t working, please help me get into my account.”
],
“inquiry”: [
“When will my new credit card arrive in the mail?”,
“Just checking on the status of my recent order.”,
“What are your business hours on weekends?”,
“Can I upgrade my current plan to the premium tier?”
],
“software”: [
“The app keeps crashing every time I try to view my transaction history.”,
“Software bug: the submit button is greyed out.”,
“Pages are loading incredibly slowly since the last update.”,
“I’m getting a 500 Internal Server Error on the dashboard.”
],
“billing”: [
“I need a refund for the extra charges on my bill.”,
“Why was I billed twice this month?”,
“Please update my payment method, the old card expired.”,
“I didn’t authorize this $49.99 transaction.”
],
“hardware”: [
“My hardware token is broken, I can’t log in.”,
“The screen on my physical device is cracked.”,
“The card reader isn’t scanning properly anymore.”,
“Battery drains in 10 minutes, I need a replacement unit.”
]
}
data =[]for _ in range(100): cat = random.choice(categories) # Inyectar un número aleatorio de días en plantillas específicas para fomentar la variedad text = random.choice(templates[cat]).format(days=random.randint(1, 14)) data.append({ "text": text, "account_age_days": random.randint(1, 2000), "prior_tickets": elecciones.aleatorias([0, 1, 2, 3, 4, 5], pesos=[40, 30, 15, 10, 3, 2])[0], "etiqueta": gato }) df = pd.DataFrame(datos)
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
48
49
50
51
52
53
importar aleatoriamente
tiempo de importación
aleatorio . semilla ( 42 )
categorias = [ "acceso" , "consulta" , "software" , "facturación" , "hardware" ]
plantillas = {
"acceso" : [
"¡He estado bloqueado en mi cuenta durante {days} días y necesito ayuda urgente!" ,
"No puedo iniciar sesión, sigue diciendo una contraseña incorrecta". ,
"Restablecer mis credenciales de acceso inmediatamente." ,
"Mi 2FA no funciona, ayúdenme a ingresar a mi cuenta".
] ,
"consulta" : [
"¿Cuándo llegará mi nueva tarjeta de crédito por correo?" ,
"Solo estoy comprobando el estado de mi pedido reciente". ,
"¿Cuál es su horario comercial los fines de semana?" ,
"¿Puedo actualizar mi plan actual al nivel premium?"
] ,
"software" : [
"La aplicación sigue fallando cada vez que intento ver mi historial de transacciones". ,
"Error de software: el botón de enviar está atenuado". ,
"Las páginas se cargan increíblemente lentamente desde la última actualización". ,
"Recibo un error interno del servidor 500 en el panel".
] ,
"facturación" : [
"Necesito un reembolso por los cargos adicionales en mi factura". ,
"¿Por qué me facturaron dos veces este mes?" ,
"Actualice mi método de pago, la tarjeta anterior expiró". ,
"No autoricé esta transacción de $49,99".
] ,
"hardware" : [
"Mi token de hardware está roto, no puedo iniciar sesión". ,
"La pantalla de mi dispositivo físico está rota". ,
"El lector de tarjetas ya no escanea correctamente". ,
"La batería se agota en 10 minutos, necesito una unidad de reemplazo".
]
}
datos = [ ]
para _ en rango ( 100 ) :
gato = aleatorio . elección ( categorías )
# Inyectar un número aleatorio de días en plantillas específicas para fomentar la variedad
texto = aleatorio . elección ( plantillas [ cat ] ) . formato ( días = aleatorio . randint ( 1 , 14 ) )
datos . agregar ( {
"texto" : texto ,
"días_edad_cuenta" : aleatorio . randint ( 1 , 2000 ) ,
"entradas_anteriores" : aleatorio . opciones ( [ 0 , 1 , 2 , 3 , 4 , 5 ] , pesos = [ 40 , 30 , 15 , 10 , 3 , 2 ] ) [ 0 ] ,
"etiqueta" : gato
} )
df = PD . Marco de datos ( datos )
El conjunto de datos generado contiene tickets de atención al cliente, combinando descripciones de texto con características numéricas estructuradas como la antigüedad de la cuenta y la cantidad de tickets anteriores, así como una etiqueta de clase que abarca varias categorías de tickets. Estas etiquetas se utilizarán posteriormente para entrenar y evaluar un modelo de clasificación al final del proceso.
Extracción de características de LLM
A continuación, definimos las características tabulares deseadas que queremos extraer del texto. La elección de funciones depende del dominio y es totalmente personalizable, pero utilizará el LLM más adelante para extraer estos campos en un formato estructurado y coherente:
class TicketFeatures(BaseModel):
urgency_score: int = Field(description=”Urgency of the ticket on a scale of 1 to 5″)
is_frustrated: int = Field(description=”1 if the user expresses frustration, 0 otherwise”)
class TicketFeatures ( BaseModel ) :
urgency_score : int = Field ( description = “Urgency of the ticket on a scale of 1 to 5” )
is_frustrated : int = Field ( description = “1 if the user expresses frustration, 0 otherwise” )
Por ejemplo, la urgencia y la frustración a menudo se correlacionan con tipos de tickets específicos (por ejemplo, los bloqueos e interrupciones del acceso tienden a ser más urgentes y cargados emocionalmente que las consultas generales), por lo que estas señales pueden ayudar a un clasificador posterior a separar categorías de manera más efectiva que el texto sin formato por sí solo.
La siguiente función es un elemento clave del proceso, ya que encapsula la integración LLM necesaria para transformar el texto de un ticket en un objeto JSON que coincida con nuestro esquema.
def extract_features(text: str) -> dict:
# Sleep for 2.5 seconds for safer use under the constraints of the 30 RPM free-tier limit
time.sleep(2.5)
schema_instructions = json.dumps(TicketFeatures.model_json_schema())
response = client.chat.completions.create(
model=”llama-3.3-70b-versatile”,
messages=[
{
“role”: “system”,
“content”: f”You are an extraction assistant. Output ONLY valid JSON matching this schema: {schema_instructions}”
},
{“role”: “user”, “content”: text}
],
response_format={“type”: “json_object”},
temperature=0.0
)
return json.loads(response.choices[0].mensaje.contenido)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def extraer_características ( texto : cadena ) -> dictar :
# Duerma durante 2,5 segundos para un uso más seguro bajo las limitaciones del límite de nivel gratuito de 30 RPM
tiempo . dormir ( 2.5 )
instrucciones_esquema = json . volcados ( TicketFeatures . model_json_schema ( ) )
respuesta = cliente . charlar . terminaciones . crear (
modelo = "llama-3.3-70b-versátil" ,
mensajes = [
{
"role" : "sistema" ,
"contenido" : f "Usted es un asistente de extracción. Genere SÓLO JSON válido que coincida con este esquema: {schema_instructions}"
} ,
{ "role" : "usuario" , "contenido" : texto }
] ,
formato_respuesta = { "tipo" : "json_object" } ,
temperatura = 0.0
)
devolver json . cargas ( respuesta . opciones [ 0 ] . mensaje . contenido )
¿Por qué la función devuelve objetos JSON? Primero, JSON es una forma confiable de pedirle a un LLM que produzca resultados estructurados. En segundo lugar, los objetos JSON se pueden convertir fácilmente en objetos de la serie Pandas, que luego se pueden fusionar sin problemas con otras columnas de un DataFrame existente para convertirse en columnas nuevas. Las siguientes instrucciones funcionan y agregan las nuevas funciones, almacenadas en Engineered_features, al resto del conjunto de datos original:
print(“1. Extracting structured features from text using LLM…”)
engineered_features = df[“text”].apply(extract_features)
features_df = pd.DataFrame(engineered_features.tolist())
X_raw = pd.concat([df.drop(columns=[“text”, “label”]), features_df], axis=1)
y = df[“label”]
print(“n2. Final Engineered Tabular Dataset:”)
print(X_raw)
print ( “1. Extracting structured features from text using LLM…” )
engineered_features = df [ “text” ] . apply ( extract_features )
features_df = pd . DataFrame ( engineered_features . tolist ( ) )
X_raw = pd . concat ( [ df . drop ( columns = [ “text” , “label” ] ) , features_df ] , axis = 1 )
y = df [ “label” ]
print ( “n2. Final Engineered Tabular Dataset:” )
print ( X_raw )
Así es como se ven los datos tabulares resultantes:
account_age_days prior_tickets urgency_score is_frustrated
0 564 0 5 1
1 1517 3 4 0
2 62 0 5 1
3 408 2 4 0
4 920 1 5 1
.. … … … …
95 91 2 4 1
96 884 0 4 1
97 1737 0 5 1
98 837 0 5 1
99 862 1 4 1
[100 rows x 4 columns]
account_age_days prior_tickets urgency_score is _ frustrated
0 564 0 5 1
1 1517 3 4 0
2 62 0 5 1
3 408 2 4 0
4 920 1 5 1
. . . . . . . . . . . . . .
95 91 2 4 1
96 884 0 4 1
97 1737 0 5 1
98 837 0 5 1
99 862 1 4 1
[ 100 rows x 4 columns ]
Nota práctica sobre costo y latencia: llamar a un LLM una vez por fila puede resultar lento y costoso en conjuntos de datos más grandes. En producción, normalmente querrás (1) solicitudes por lotes (procesar muchos tickets por llamada, si tu proveedor y el diseño del mensaje lo permiten), (2) almacenar en caché los resultados codificados por un identificador estable (o un hash del texto del ticket) para que las repeticiones no vuelvan a facturar los mismos ejemplos, y (3) implementar reintentos con retroceso para manejar límites de tarifas transitorios y errores de red. Estas tres prácticas suelen hacer que el proceso sea más rápido, más económico y mucho más confiable.
Entrenamiento y evaluación del modelo
Finalmente, aquí viene el proceso de aprendizaje automático, donde el conjunto de datos actualizado y completamente tabular se escala, se divide en subconjuntos de entrenamiento y prueba, y se utiliza para entrenar y evaluar un clasificador de bosque aleatorio.
print(“n3. Scaling and Training Random Forest…”)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X_raw)
# Split the data into training and test
X_train, X_test, y_train, y_test = train_test_split(X_scaled, y, test_size=0.4, random_state=42)
# Train a random forest classification model
clf = RandomForestClassifier(random_state=42)
clf.fit(X_train, y_train)
# Predict and Evaluate
y_pred = clf.predict(X_test)
print(“n4. Classification Report:”)
print(classification_report(y_test, y_pred, zero_division=0))
print ( “n3. Scaling and Training Random Forest…” )
scaler = StandardScaler ( )
X_scaled = scaler . fit_transform ( X_raw )
# Split the data into training and test
X_train , X_test , y_train , y_test = train_test_split ( X_scaled , y , test_size = 0.4 , random_state = 42 )
# Train a random forest classification model
clf = RandomForestClassifier ( random_state = 42 )
clf . fit ( X_train , y_train )
# Predict and Evaluate
y_pred = clf . predict ( X_test )
print ( “n4. Classification Report:” )
print ( classification_report ( y_test , y_pred , zero_division = 0 ) )
Aquí están los resultados del clasificador:
Classification Report:
precision recall f1-score support
access 0.22 0.18 0.20 11
billing 0.29 0.33 0.31 6
hardware 0.29 0.25 0.27 8
inquiry 1.00 1.00 1.00 8
software 0.44 0.57 0.50 7
accuracy 0.45 40
macro avg 0.45 0.47 0.45 40
weighted avg 0.44 0.45 0.44 40
Classification Report :
precision recall f1 – score support
access 0.22 0.18 0.20 11
billing 0.29 0.33 0.31 6
hardware 0.29 0.25 0.27 8
inquiry 1.00 1.00 1.00 8
software 0.44 0.57 0.50 7
accuracy 0.45 40
macro avg 0.45 0.47 0.45 40
weighted avg 0.44 0.45 0.44 40
Si utilizó el código para generar un conjunto de datos de juguetes sintéticos, es posible que obtenga un resultado del clasificador bastante decepcionante en términos de exactitud, precisión, recuperación, etc. Esto es normal: en aras de la eficiencia y la simplicidad, utilizamos un conjunto pequeño, parcialmente aleatorio, de 100 instancias, que normalmente es demasiado pequeño (y posiblemente demasiado aleatorio) para funcionar bien. La clave aquí es el proceso de convertir texto sin formato en características significativas mediante el uso de un LLM previamente capacitado a través de API, que debería funcionar de manera confiable.
Resumen
Este artículo realiza un breve recorrido por el proceso de convertir texto sin formato en funciones totalmente tabulares para el modelado de aprendizaje automático posterior. El truco clave que se muestra a lo largo del camino es utilizar un LLM previamente capacitado para realizar inferencias y devolver resultados estructurados mediante indicaciones efectivas.