Un centro de atención sanitaria recibe mensajes escritos por pacientes a través de WhatsApp. Tu objetivo es desarrollar un sistema automático capaz de clasificar estos mensajes según su naturaleza.
- Síntomas
- Administrativo (citas, bajas, recetas…)
- Ruido / irrelevante
- Urgencia potencial
- "Tengo 39 de fiebre y me cuesta respirar" → Urgencia
- "Necesito renovar la receta" → Administrativo
- "A qué hora cerráis hoy?" → Administrativo
- "Me duele la cabeza desde ayer" → Síntomas
- "Bombardiro crocodilo" → Ruido
Implementa una función que:
- pase el texto a minúsculas,
- elimine stopwords,
- elimine signos de puntuación,
- lematice,
- devuelva un texto limpio.
Puedes usar spaCy o NLTK.
import spacy
nlp = spacy.load("es_core_news_sm")
def preprocess(text):
[...]
return " ".join(tokens)
print(preprocess("Tengo 38,5 de fiebre y me duele la cabeza desde ayer"))- Crea al menos 40 mensajes etiquetados.
- Deben aparecer todas las categorías.
- Guarda el dataset en CSV.
data = [
("Tengo 38,5 de fiebre y me cuesta respirar", "urgencia"),
("Necesito renovar la receta", "administrativo"),
...
]
import pandas as pd
df = pd.DataFrame(data, columns=["message", "label"])
df.to_csv("dataset.csv", index=False)Elige uno:
- Bag-of-Words con
CountVectorizer - TF-IDF
- Embeddings de spaCy
from sklearn.feature_extraction.text import TfidfVectorizer
vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform([preprocess(msg) for msg, label in data])
y = [label for msg, label in data]- Usa un clasificador simple:
- Regresión logística
- Naïve Bayes
- SVM lineal
- Genera métricas de calidad
- accuracy
- precision, recall, F1
- matriz de confusión
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report
# Dividir datos
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# regresión logística example
clf = LogisticRegression()
# Entrenamiento
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
# Métricas
print(classification_report(y_test, y_pred))from sklearn.model_selection import train_test_split
from sklearn.naive_bayes import MultinomialNB
from sklearn.metrics import classification_report
# Dividir datos
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# naive bayes example
clf = MultinomialNB()
# Entrenamiento
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
# Métricas
print(classification_report(y_test, y_pred))from sklearn.model_selection import train_test_split
from sklearn.svm import LinearSVC
from sklearn.metrics import classification_report
# Dividir datos
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# SVM lineal example
clf = LinearSVC()
# Entrenamiento
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
# Métricas
print(classification_report(y_test, y_pred))
⚠️ Nota sobre el desbalanceo de clasesEs probable que el dataset tenga más ejemplos de algunas categorías que de otras (por ejemplo, mensajes administrativos). Esto puede provocar que el modelo se sesgue hacia la clase mayoritaria.
Para mitigarlo, puedes:
- Opción 1: habilitar el modo balanceado del clasificador:
clf = LogisticRegression(class_weight='balanced') clf = LinearSVC(class_weight='balanced')
- Opción 2: revisar la distribución de clases antes de entrenar:
import collections print(collections.Counter(y))No es obligatorio usarlo, pero sí recomendable evaluarlo.
from sklearn.metrics import confusion_matrix
import seaborn as sns
import matplotlib.pyplot as plt
sns.heatmap(confusion_matrix(y_test, y_pred), annot=True)
plt.show()- Guarda
sallexa_model.pkl.
import joblib
joblib.dump(clf, 'sallexa_model.pkl')Crea otro proyecto/script donde cargues el modelo y hagas predicciones sobre mensajes nuevos.
import joblib
clf = joblib.load('sallexa_model.pkl')
def classify_message(message):
clean = preprocess(message)
tokens = preprocess([clean])
X_new = vectorizer.transform([' '.join(tokens)])
prediction = clf.predict(X_new)
return prediction[0]
print(classify_message("Tengo dolor fuerte en el pecho"))- Prueba con al menos 5 mensajes nuevos.
test_messages = [
"Me siento mareado y con náuseas",
"Quiero pedir una cita con el médico",
"¿Cuál es el horario de atención?",
"Tengo un dolor intenso en el abdomen",
"asdfghjkl qwertyuiop"
]
for msg in test_messages:
print(f"Mensaje: {msg} → Clasificación: {classify_message(msg)}")Puedes usar Gradio o FastAPI + HTML para crear una interfaz sencilla donde un usuario pueda escribir un mensaje y ver la clasificación.
import gradio as gr
def bot(msg):
return classify_message(msg)
gr.ChatInterface(bot).launch()from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
app = FastAPI()
templates = Jinja2Templates(directory="templates")
@app.get("/", response_class=HTMLResponse)
async def read_form(request: Request):
return templates.TemplateResponse("index.html", {"request": request})
@app.post("/classify", response_class=HTMLResponse)
async def classify(request: Request):
form = await request.form()
message = form.get("message")
classification = classify_message(message)
return templates.TemplateResponse("index.html", {"request": request, "classification": classification})Crear un endpoint de predicción con FastAPI:
GET /predict?text=...
→ {"label": "urgencia"}Incluye:
- ¿Es seguro usar este modelo en un entorno sanitario real?
- ¿Qué riesgos tiene?
- ¿Qué mejoras serían necesarias?
notebook.ipynbosrc/*.pycon todo el desarrollo.dataset.csvsallexa_model.pklREADME.mdexplicando tu solución.