Clasificador de Conflictos Sociales (BETO cased)
Clasificador binario de notas periodísticas en español: CONFLICTO vs
NO_CONFLICTO para conflicto civil/social — una confrontación o disputa
colectiva real con al menos un actor social/civil (trabajadores, estudiantes,
vecinos, sindicatos, asociaciones, población civil organizada), o una reacción
colectiva concreta frente a una necesidad, agravio o condición social explícita.
Full fine-tuning de dccuchile/bert-base-spanish-wwm-cased sobre 4.034 notas
periodísticas anotadas manualmente de noticias locales argentinas.
Ventaja sobre LLM: documentos largos
Este modelo usa ventanas deslizantes + max pooling para manejar notas que
exceden los 512 tokens de BETO: cada documento se trocea en ventanas con
solapamiento, se clasifica cada fragmento, y el score del documento es el
máximo. Esto reduce los falsos negativos a la mitad comparado con truncar
al inicio (head-512).
Demo
Familia de clasificadores
Mismo gold (4.034 notas) y la misma definición operacional v5.
| BETO | Qwen 0.5B | Qwen 1.5B LoRA |
|---|
| Accuracy OOF | 0.924 | 0.959 | 0.959 |
| Recall CONFLICTO | 0.963 | 0.895 | 0.906 |
| Precisión CONFLICTO | 0.765 | 0.917 | 0.910 |
| FN / FP | 33 / 273 | 94 / 72 | 84 / 80 |
Dataset
Uso
1import torch
2from transformers import AutoModelForSequenceClassification, AutoTokenizer
3
4model_dir = "agusnieto77/beto-conflict-classifier"
5tok = AutoTokenizer.from_pretrained(model_dir)
6model = AutoModelForSequenceClassification.from_pretrained(model_dir)
7model.eval()
8
9STRIDE = 256
10MAX_LENGTH = 512
11THRESHOLD = 0.52 # ajustable según prioridad recall/precision
12
13def clasificar(texto: str) -> tuple[str, float]:
14 """Trocea en ventanas deslizantes, clasifica cada chunk, agrega con max."""
15 enc = tok(
16 texto, truncation=True, max_length=MAX_LENGTH, stride=STRIDE,
17 return_overflowing_tokens=True, padding=True, return_tensors="pt",
18 )
19 ov_map = enc.pop("overflow_to_sample_mapping", None)
20 enc = {k: v for k, v in enc.items() if k != "overflow_to_sample_mapping"}
21 with torch.no_grad():
22 logits = model(**enc).logits
23 probs = torch.softmax(logits.float(), dim=-1)[:, 1]
24 score = float(probs.max()) # max pooling
25 etiqueta = "CONFLICTO" if score >= THRESHOLD else "NO_CONFLICTO"
26 return etiqueta, score
27
28texto = (
29 "Los trabajadores del hospital municipal decidieron continuar con el paro "
30 "de actividades en reclamo de mejoras salariales. El gremio anunció una "
31 "movilización hacia la Municipalidad y rechazó la propuesta de aumento "
32 "del Ejecutivo."
33)
34
35etiqueta, score = clasificar(texto)
36print(f"{etiqueta} (score = {score:.3f})")
37# → CONFLICTO (score ≈ 0.95)
Evaluación
Validación cruzada de 5 folds sobre las 4.034 notas de entrenamiento
(predicciones out-of-fold, inferencia por ventanas deslizantes stride=256
- max pooling, umbral 0.5).
| Métrica | Media | Desvío |
|---|
| Recall CONFLICTO | 0.9631 | 0.0318 |
| Precision CONFLICTO | 0.7648 | 0.0621 |
| F1 CONFLICTO | 0.8508 | 0.0351 |
| F2 CONFLICTO | 0.9141 | 0.0211 |
| PR-AUC | 0.9601 | 0.0028 |
| ROC-AUC | 0.9821 | — |
| Accuracy | 0.9241 | — |
| Macro F1 | 0.8993 | — |
| FN (total OOF) | 33 | — |
| FP (total OOF) | 273 | — |
Matriz de confusión OOF: TP=862 · FN=33 · FP=273 · TN=2866
Comparación con head-512 (truncado)
| Estrategia | FN | FP | Recall | F2 |
|---|
| head-512 (truncado) | 66 | 189 | 0.926 | 0.901 |
| ventanas + max | 33 | 273 | 0.963 | 0.914 |
La mitad de los falsos negativos se recuperan al procesar el texto completo.
Entrenamiento
- Full fine-tuning de
dccuchile/bert-base-spanish-wwm-cased
- 4.034 notas: 895 CONFLICTO (22,2%) / 3.139 NO_CONFLICTO (77,8%)
- Learning rate 3e-5, batch efectivo 32, bf16, 4 epochs (early stopping)
- Selección de checkpoint por F2 CONFLICTO en validation
- Threshold 0.5193 optimizado en OOF CV con inferencia chunked
Definición operacional (v5)
Una nota es CONFLICTO si contiene un acto colectivo civil/social ligado a
una disputa real entre partes identificables, o a una necesidad, agravio o
condición social explícita frente a la cual un colectivo protesta, se rebela
o ejerce presión.
Actos que hacen CONFLICTO: medida de fuerza laboral; petición o reclamo
formal de un actor colectivo con objeto concreto o contraparte identificada;
manifestación popular con reclamo; violencia o acción colectiva en una disputa
social; reacción colectiva frente a un delito o abuso; motín carcelario;
conflicto resuelto cuando la nota establece el reclamo subyacente.
Casos NO_CONFLICTO: asambleas sin disputa; gestión habitual de entidades
civiles; opinión individual; delito común sin reacción social; violencia
interpersonal; conflicto puramente militar; cobertura de crisis sin reclamo;
asistencia solidaria sin disputa; campaña y retórica electoral; contienda
político-institucional ordinaria entre poderes del Estado; marchas
conmemorativas, educativas o de concientización sin reclamo.
A diferencia del clasificador
Qwen, BETO no requiere la definición en el
prompt: la definición está representada por el gold standard con el que fue
entrenado. El archivo completo está en
conflict_definition.md para referencia.
Limitaciones
- Dominio: noticias locales argentinas. El desempeño en otras regiones o
estilos periodísticos no está validado.
- Las métricas provienen de validación cruzada sobre el conjunto de
entrenamiento; el modelo final fue reentrenado sobre la totalidad de los
datos.
- El 52% de las notas excede 512 tokens; la estrategia de ventanas deslizantes
mitiga el truncamiento pero puede generar más falsos positivos que un
modelo que solo procesa el inicio.
Créditos
Guillermina Laitano, Luciana Nogueira, Nicolás Rabino, Rodrigo Fernández,
Ivana Teijón, Agustín Nieto