CoolFace
Datasetpublic

guerrerotook/CIMA-4.8-ADR-NER

CIMA Sección 4.8 NER Dataset de reconocimiento de entidades nombradas (NER) en español sobre la sección 4.8 ("Reacciones adversas") de las fichas técnicas publicadas por la Agencia Española de Medicamentos y Productos Sanitarios (AEMPS) en el Centro de Información Online de Medicamentos (CIMA). Cada documento es la sección 4.8 completa de un medicamento, tokenizada con spaCy (es_core_news_sm) y etiquetada en formato CoNLL BIO (IOB2) con hasta cuatro tipos de entidad: Tag… See the full description on the dataset page: https://huggingface.co/datasets/guerrerotook/CIMA-4.8-ADR-NER.

sourceHugging Facecc-by-4.0updated 5d agoView on Hugging Face
0likes113downloads
Dataset Card

CIMA Sección 4.8 NER

Dataset de reconocimiento de entidades nombradas (NER) en español sobre la sección 4.8 ("Reacciones adversas") de las fichas técnicas publicadas por la Agencia Española de Medicamentos y Productos Sanitarios (AEMPS) en el Centro de Información Online de Medicamentos (CIMA).

Cada documento es la sección 4.8 completa de un medicamento, tokenizada con spaCy (es_core_news_sm) y etiquetada en formato CoNLL BIO (IOB2) con hasta cuatro tipos de entidad:

TagSignificadoOrigen (columna CSV)
REACTReacción adversa (p. ej. cefalea, náuseas, trombocitopenia)REACADV
ACTIVEPrincipio activo (p. ej. SOTALOL HIDROCLORURO)PACTIVO
FREQFrecuencia de la reacción (p. ej. MUY FRECUENTE, POCO FRECUENTE)FRECUENCIA
SYSSistema u órgano afectado (p. ej. TRASTORNOS DEL SISTEMA NERVIOSO)SISTEMA

El dataset se publica como parte del Proyecto Fin de Grado (PFG) de la UNED sobre extracción automática de información de pacto de seguridad de medicamentos.

Autoría

Dataset elaborado en el marco del Trabajo Fin de Grado (PFG) del Grado en Ingeniería Informática de la Universidad Nacional de Educación a Distancia (UNED), curso 2025/2026.

  • —Autor: Luis Miguel Guerrero Guirado (@guerrerotook).
  • —Director del PFG: Salvador Ros Muñoz, UNED.

Configuraciones

Se publican cinco configuraciones (config_name), una por cada combinación de tags entrenada en el trabajo original. Todas comparten exactamente los mismos documentos, sentencias y tokens; sólo cambia el vocabulario de etiquetas y, en consecuencia, qué spans están marcados como B-/I- y cuáles caen a O.

`config_name`Tags incluidosLabels (orden de IDs)
reactREACTO, B-REACT, I-REACT
activeACTIVEO, B-ACTIVE, I-ACTIVE
freqFREQO, B-FREQ, I-FREQ
sysSYSO, B-SYS, I-SYS
react-active-freq-sysREACT + ACTIVE + FREQ + SYS (default)O, B-REACT, I-REACT, B-ACTIVE, I-ACTIVE, B-FREQ, I-FREQ, B-SYS, I-SYS

La configuración react-active-freq-sys es la multi-tag y la marcada como default — es la más útil porque cualquier consumidor puede derivar las versiones single-tag por filtrado local sin perder información.

Estadísticas

Mismos documentos y tokens para todas las configuraciones; sólo cambia la proporción de etiquetas no-O:

SplitFármacosSentenciasTokens
train352 18034 886
test1594117 316

Proporción de tokens etiquetados (no-O) por configuración:

Confignon-O trainnon-O test
react10,00 %12,76 %
active0,53 %0,32 %
freq0,28 %0,25 %
sys0,87 %0,79 %
react-active-freq-sys11,64 %14,12 %

Distribución detallada por etiqueta para react-active-freq-sys:

LabelTrainTest
O30 82614 871
B-REACT1 8561 120
I-REACT1 6321 090
B-ACTIVE16350
I-ACTIVE215
B-FREQ6332
I-FREQ3511
B-SYS8840
I-SYS20297

Cada stats/{config}.json del repositorio contiene el detalle completo.

Esquema del dataset

Cada ejemplo es una sentencia (no un documento completo) con cuatro campos:

CampoTipoDescripción
codigostringCódigo nacional CIMA del medicamento al que pertenece la frase
sent_idxint32Índice de la sentencia dentro del documento (0-indexado)
tokensSequence(string)Tokens spaCy de la sentencia
ner_tagsSequence(ClassLabel(names=[...]))IDs de etiqueta BIO alineados 1:1 con tokens

Para recuperar las etiquetas en texto:

python
from datasets import load_dataset
ds = load_dataset("guerrerotook/cima-section48-ner", "react-active-freq-sys")
label_names = ds["train"].features["ner_tags"].feature.names
print(label_names)
# ['O', 'B-REACT', 'I-REACT', 'B-ACTIVE', 'I-ACTIVE', 'B-FREQ', 'I-FREQ', 'B-SYS', 'I-SYS']

example = ds["train"][5]
for tok, tag_id in zip(example["tokens"], example["ner_tags"]):
    print(f"{tok}\t{label_names[tag_id]}")

Cómo se construyó

Pipeline determinista en cuatro pasos:

  1. 1.Lectura del CSV de menciones: train.csv / test.csv con columnas CODIGO, MEDICAMENTO, PACTIVO, REACADV, FRECUENCIA, SISTEMA (una fila por reacción adversa de cada fármaco).
  2. 2.Tokenización word-level con spaCy (es_core_news_sm) del texto bruto de la sección 4.8 de cada fármaco, con segmentación por sentencizer + corte adicional por saltos de línea.
  3. 3.Etiquetado BIO mediante matching word-boundary insensible a acentos/case sobre el texto normalizado (unicodedata + lower). Si dos menciones solapan, gana la más larga (longest-first ordering). Se aplica promoción IOB2 (I-X huérfano → B-X).
  4. 4.Serialización a Parquet (Sequence(ClassLabel)) y, en paralelo, a token<TAB>label BIO plano (formato CoNLL clásico, blank line entre sentencias y # {codigo} como cabecera de documento).

Todo el código del builder está en `src/conll_ner_builder.py` del repositorio original. El export se reproduce con python tools/export_hf_dataset.py.

Formatos disponibles

El mismo dataset se distribuye dos veces para máxima compatibilidad:

data/<config>/train.parquet   ← canónico, recomendado para HF Datasets
data/<config>/test.parquet
conll/<config>/train.conll    ← BIO plano, recomendado para seqeval/flair/spaCy
conll/<config>/test.conll
stats/<config>.json           ← estadísticas por split (label counts, etc.)

Los .conll siguen exactamente el contrato token<TAB>label con líneas en blanco entre sentencias y # {codigo} como cabecera de documento. Son compatibles directamente con el parse_conll de la mayoría de baselines NER en español.

Uso

Opción 1 — Fine-tuning con transformers (recomendado)

python
import numpy as np
from datasets import load_dataset
from transformers import (
    AutoTokenizer,
    AutoModelForTokenClassification,
    DataCollatorForTokenClassification,
    Trainer,
    TrainingArguments,
)
from seqeval.metrics import classification_report, f1_score

ds = load_dataset("guerrerotook/cima-section48-ner", "react-active-freq-sys")
label_names = ds["train"].features["ner_tags"].feature.names
num_labels = len(label_names)

model_name = "PlanTL-GOB-ES/roberta-base-biomedical-es"
tokenizer = AutoTokenizer.from_pretrained(model_name, add_prefix_space=True)

def tokenize_and_align(batch):
    enc = tokenizer(
        batch["tokens"],
        is_split_into_words=True,
        truncation=True,
        max_length=256,
    )
    labels = []
    for i, tag_seq in enumerate(batch["ner_tags"]):
        word_ids = enc.word_ids(batch_index=i)
        prev = None
        seq = []
        for w in word_ids:
            if w is None:
                seq.append(-100)
            elif w != prev:
                seq.append(tag_seq[w])
            else:
                seq.append(-100)
            prev = w
        labels.append(seq)
    enc["labels"] = labels
    return enc

tok_ds = ds.map(tokenize_and_align, batched=True, remove_columns=ds["train"].column_names)

model = AutoModelForTokenClassification.from_pretrained(
    model_name,
    num_labels=num_labels,
    id2label={i: n for i, n in enumerate(label_names)},
    label2id={n: i for i, n in enumerate(label_names)},
)

def compute_metrics(p):
    preds = np.argmax(p.predictions, axis=2)
    true_labels = [
        [label_names[l] for l in lab if l != -100]
        for lab in p.label_ids
    ]
    pred_labels = [
        [label_names[pr] for pr, l in zip(pre, lab) if l != -100]
        for pre, lab in zip(preds, p.label_ids)
    ]
    return {"f1": f1_score(true_labels, pred_labels)}

args = TrainingArguments(
    output_dir="./cima-ner-ft",
    eval_strategy="epoch",
    per_device_train_batch_size=16,
    num_train_epochs=10,
    learning_rate=2e-5,
)
trainer = Trainer(
    model=model,
    args=args,
    train_dataset=tok_ds["train"],
    eval_dataset=tok_ds["test"],
    tokenizer=tokenizer,
    data_collator=DataCollatorForTokenClassification(tokenizer),
    compute_metrics=compute_metrics,
)
trainer.train()

Opción 2 — Descarga directa del CoNLL plano

Útil si trabajas con seqeval, flair, spaCy o un pipeline propio que ya sabe parsear el formato token<TAB>label:

python
from huggingface_hub import hf_hub_download

path = hf_hub_download(
    repo_id="guerrerotook/cima-section48-ner",
    filename="conll/react-active-freq-sys/train.conll",
    repo_type="dataset",
)

def parse_conll(path):
    sentences, current_tokens, current_tags = [], [], []
    with open(path, encoding="utf-8") as f:
        for line in f:
            line = line.rstrip("\n")
            if line.startswith("#") or not line:
                if current_tokens:
                    sentences.append((current_tokens, current_tags))
                    current_tokens, current_tags = [], []
                continue
            tok, tag = line.split("\t")
            current_tokens.append(tok)
            current_tags.append(tag)
    if current_tokens:
        sentences.append((current_tokens, current_tags))
    return sentences

sentences = parse_conll(path)
print(f"{len(sentences):,} sentencias")

Opción 3 — Inferencia con un modelo ya entrenado

Si publicas tu modelo NER en HF (por ejemplo guerrerotook/cima-roberta-ner-react-active-freq-sys):

python
from transformers import pipeline

ner = pipeline(
    "token-classification",
    model="guerrerotook/cima-roberta-ner-react-active-freq-sys",
    aggregation_strategy="simple",
)
text = (
    "El medicamento puede producir cefalea, mareo y, con menos frecuencia, "
    "trombocitopenia. La hipertensión es POCO FRECUENTE."
)
for ent in ner(text):
    print(f"{ent['entity_group']:<8s} {ent['score']:.3f}  {ent['word']}")

Filtrar a una entidad concreta sobre la config multi-tag

python
from datasets import load_dataset, ClassLabel, Sequence

ds = load_dataset("guerrerotook/cima-section48-ner", "react-active-freq-sys")
src = ds["train"].features["ner_tags"].feature.names  # ['O', 'B-REACT', ...]

KEEP = {"O", "B-REACT", "I-REACT"}
remap = {i: (src.index(t) if t in KEEP else 0) for i, t in enumerate(src)}

def to_react_only(example):
    example["ner_tags"] = [remap[t] if src[t] in KEEP else 0 for t in example["ner_tags"]]
    return example

ds_react = ds.map(to_react_only)

Limitaciones y sesgos

  • —Tamaño reducido: 50 fármacos en total (35 train / 15 test). Apto para fine-tuning de modelos pre-entrenados, no para entrenar desde cero.
  • —Anotación silver-standard: las menciones provienen de las tablas estructuradas de la sección 4.8 (no de una anotación span-level manual); el etiquetado BIO se obtiene por matching exacto insensible a acentos/case con word boundaries.
  • —Dominio cerrado: vocabulario biomédico español, registro de ficha técnica. El rendimiento fuera de dominio (literatura clínica, foros de pacientes, etc.) no está caracterizado.
  • —Multi-tag desbalanceado: REACT representa >80 % de las menciones positivas. Las clases ACTIVE/FREQ/SYS son minoritarias y conviene evaluarlas con métricas por entidad (no sólo micro-F1).

Citación

bibtex
@thesis{guerreroros2026cima,
  author  = {Guerrero Guirado, Luis Miguel y Ros Munoz, Salvador},
  title   = {Detección de reacciones adversas de medicamentos en textos clínicos: Un estudio comparativo de los modelos basados en BERT y modelos LLMs},
  school  = {Universidad Nacional de Educación a Distancia (UNED)},
  year    = {2026},
  type    = {Trabajo Fin de Grado},
}

Licencia y atribución

Dataset publicado bajo [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/deed.es).

El texto bruto de la sección 4.8 procede de las fichas técnicas oficiales publicadas por la Agencia Española de Medicamentos y Productos Sanitarios (AEMPS) en su Centro de Información Online de Medicamentos (CIMA). Al reutilizar este dataset cita tanto el dataset original como la fuente AEMPS-CIMA.