Capítulo 7 de 37 · básico
Splits de entrenamiento, validación y prueba
Qué cubre este capítulo
Hay un error que aparece en más proyectos rotos de machine learning que cualquier otro, y no tiene nada que ver con el modelo. Es evaluar el modelo con los mismos datos de los que aprendió, y luego creerse el número. Ese número siempre sale bien. Sale bien igual que le va bien a un estudiante que vio el examen de antemano: la calificación mide memoria, no aprendizaje. Este capítulo entero trata sobre la solución, que es aburrida, mecánica e innegociable: aparta una parte de los datos, entrena sin ella y evalúa con ella una sola vez.
Construimos los splits a mano en NumPy — un split aleatorio train/test, un split de tres vías train/validation/test y un split estratificado que mantiene intactas las proporciones de clase — y luego le pasamos el mismo trabajo a scikit-learn para confirmar que los tamaños y las proporciones coinciden. La pieza central es una animación que no te deberías saltar: un árbol de decisión que crece cada vez más profundo, con su accuracy de entrenamiento y su accuracy sobre el conjunto de prueba apartado graficadas lado a lado, cuadro por cuadro. Ves la línea de entrenamiento marchar hasta un 1.0 perfecto mientras la línea de prueba alcanza su pico temprano y luego da la vuelta y cae. La brecha entre esas dos líneas es el modelo memorizando, dibujada con datos reales. Esa brecha es el argumento completo a favor de apartar datos, y una vez que la hayas visto abrirse no se te volverá a olvidar hacer el split.
Allá en la semana 1 el clasificador de umbral ya lo susurraba — su accuracy sobre los datos de entrenamiento salía optimista, y el enfrentamiento honesto usó un split apartado. Este capítulo es esa nota al pie ascendida a texto principal.
Un poco de historia
La idea de que debes probar un modelo con datos que no vio es más vieja que el machine learning, y empezó como una advertencia sobre una mentira específica. En 1931 Selig Larson, trabajando en psicología educativa, notó que los pesos de regresión ajustados sobre una muestra y luego aplicados a una muestra fresca predecían notablemente peor de lo que lo hacían sobre la muestra con la que fueron ajustados. La correlación "se encogía". Había encontrado overfitting en regresión lineal treinta años antes de que alguien tuviera una computadora con qué sobreajustar, y la solución que señaló fue ajustar con un lote de datos y verificar con otro.
El reconocimiento de patrones lo convirtió en un protocolo experimental formal. En 1962 Wallace Highleyman, en Bell Labs, dejó por escrito cómo diseñar correctamente un experimento de reconocimiento, dividiendo los ejemplos en un conjunto de diseño y un conjunto de prueba — el "split de Highleyman" — para que la tasa de error reportada significara algo. Y en 1974 Mervyn Stone le dio a toda la práctica su columna vertebral estadística en un artículo titulado "Cross-validatory choice and assessment of statistical predictions", formalizando qué significa apartar datos, rotar qué datos apartas y usar el resultado tanto para elegir un modelo como para evaluarlo. El artículo de Stone es la razón de que siquiera tengamos un vocabulario limpio para esto; leave-one-out, el caso extremo, ya había aparecido en el trabajo de Lachenbruch y Mickey en 1968. La validación cruzada es el tema del siguiente capítulo — este capítulo es la versión de split único que aquella generaliza, y vale la pena dejarla exactamente bien primero.
La intuición
Tienes una pila fija de datos etiquetados y quieres sacarle dos cosas que pelean entre sí. Quieres usar la mayor cantidad posible para entrenar, porque más datos hacen un mejor modelo. Y quieres una estimación honesta de cómo le irá al modelo con datos que nunca ha visto, que solo puedes obtener manteniendo algunos datos completamente fuera del entrenamiento. Cada fila que apartas es una fila con la que no entrenaste. Esa es la tensión, y el split es cómo la resuelves.
La versión más simple son dos pilas. Barajas las filas, cortas un pedazo — digamos el 30% — y lo llamas conjunto de prueba. Entrenas con el otro 70%. Evalúas con el 30%. Esa calificación es tu estimación del desempeño en el mundo real, y es honesta precisamente porque el modelo nunca tocó esas filas mientras aprendía.
Pero en cuanto empiezas a hacer tuning, dos pilas no alcanzan, y esta es la parte que la gente hace mal. En el momento en que miras la calificación de prueba, intentas un árbol más profundo, vuelves a mirar y te quedas con la versión que salió mejor, empezaste a entrenar sobre el conjunto de prueba — lentamente, a través de tus propias decisiones. Así que cortas una tercera pila. El conjunto de validación es el que tienes permitido espiar tantas veces como quieras mientras eliges modelos y configuraciones. El conjunto de prueba lo tocas una vez, al final de todo, para reportar un único número honesto. Aquí está el split de tres vías sobre nuestros datos, dibujado a escala:
Un corte 60/20/20 de 768 pacientes: 460 para entrenar, 154 para hacer tuning y
154 apartados para el veredicto final. Los tamaños exactos vienen de
results.json. Nada en esas fracciones es sagrado — 80/10/10 es común cuando
los datos escasean, 60/20/20 cuando abundan — pero la forma siempre es la
misma: una pila grande de la cual aprender, una pila intermedia con la cual
elegir, una última pila que no gastas hasta el final.
Las matemáticas
Escribe el modelo como una función que mapea las mediciones de un paciente a una etiqueta predicha. Lo que realmente te importa es el error de generalización: qué tan seguido se equivoca con un paciente completamente nuevo extraído de la misma población de la que salieron los datos.
Esa esperanza nunca la puedes calcular — es sobre cada paciente que pudiera existir, y tú tienes 768. Así que la estimas. La estimación equivocada es el error sobre las mismas filas con las que entrenaste:
Esta está sesgada hacia abajo, y no por poquito. El modelo eligió sus parámetros para minimizar exactamente esta cantidad, así que claro que se ve bien aquí — optimizó para ella. La estimación correcta usa filas que el modelo nunca vio durante el entrenamiento:
Como esas filas estaban apartadas, este promedio es una estimación insesgada del error de generalización — el número que verías de verdad en producción. El split en sí es solo una partición de los índices de fila en piezas disjuntas que lo cubren todo:
con el tamaño de prueba fijado por la fracción que elegiste, redondeado hacia arriba para que el conjunto apartado nunca quede vacío:
La estratificación agrega una restricción encima de la partición. Si la clase es una fracción de los datos completos, un split estratificado hace que cada pieza cargue esa misma fracción:
Un split aleatorio a ciegas acierta esto en promedio, pero cualquier extracción individual oscila — y entre más chico el conjunto de prueba o más rara la clase, más oscila. Fijarlo con exactitud es lo que la estratificación te compra.
Qué cuesta, qué compra
Apartar datos no es gratis, y vale la pena ser honestos sobre el precio. Cada fila del conjunto de prueba es una fila de la que tu modelo no pudo aprender, así que un split de hold-out hace tu modelo ligeramente peor que uno entrenado con todos los datos — estás gastando accuracy para comprar una medición honesta. Y la medición en sí es ruidosa: un conjunto de prueba de 154 pacientes da una estimación del error con varianza real, así que una diferencia de uno o dos puntos entre dos modelos sobre un mismo split de prueba podría no ser nada. La validación cruzada, el siguiente capítulo, existe precisamente para exprimirle una estimación de menor varianza a los mismos datos escasos.
Lo que compras es el único número que importa: una estimación de cómo se desempeña el modelo con datos que nunca ha visto, que es la única condición que enfrentará jamás en producción. Sin un hold-out no tienes una mala estimación de ese número — no tienes estimación alguna, solo una medición de qué tan bien memorizó el modelo. Ese intercambio no está ni cerca de estar parejo. Gasta los datos.
Los datos
El mismo conjunto de la semana 1, para que los números sean comparables entre
capítulos: los datos de diabetes de los indios Pima, 768 pacientes, ocho
mediciones cada uno y una etiqueta binaria de si fueron diagnosticados con
diabetes. De los 768, 268 son positivos y 500 negativos — una tasa positiva de
0.349, según results.json. Ese desbalance importa aquí: es exactamente el
tipo de sesgo que un split aleatorio descuidado puede distorsionar, y la razón
por la que la estratificación se gana su lugar más adelante en el capítulo.
El punto de este capítulo no es el modelo, así que el modelo se queda en el fondo: un árbol de decisión, el mismo que la semana 3 construye desde cero, usado aquí solo como algo con una perilla de flexibilidad que puedo girar. Sube la perilla y el árbol queda más libre para memorizar. Eso es todo lo que necesito para argumentar a favor del split.
Constrúyelo, una función a la vez
Todo el asunto son cuatro funciones, y ninguna es más larga que un párrafo. Un split no es una pieza difícil de código — la disciplina está en usarlo, no en escribirlo. Todo empieza con un barajado, porque las filas de un dataset real casi nunca están en orden aleatorio. Están ordenadas por fecha, agrupadas por hospital, agrupadas por lo que sea que impuso el proceso de recolección, y si rebanas el último 30% de un archivo ordenado obtienes un conjunto de prueba sistemáticamente distinto de tu conjunto de entrenamiento. Así que: baraja primero, con semilla, siempre.
def shuffle_indices(n, seed=42):
"""A reproducible permutation of the row positions 0..n-1.
Every split starts here. Seed it and the split is identical forever; that
is the whole difference between a result you can rerun and a number someone
just has to trust.
"""
rng = np.random.default_rng(seed)
return rng.permutation(n)
La semilla no es un detalle. Una permutación con semilla es la diferencia entre un experimento que puedes volver a correr y obtener la misma respuesta, y un número que cambia cada vez que lo miras. Ponle semilla a todo lo que toque aleatoriedad; es la reproducibilidad más barata que jamás comprarás.
Con los índices barajados, el split simple de dos vías es un solo corte:
def train_test_split(n, test_frac=0.3, seed=42):
"""Shuffle the row positions, then slice the last chunk off as the test set.
n_test = ceil(n * test_frac), carved from the end so training keeps the
rest — the same sizes scikit-learn produces for the same fraction. Returns
(train_idx, test_idx) as index arrays, so the caller can split any column.
"""
idx = shuffle_indices(n, seed)
n_test = int(np.ceil(n * test_frac))
split = n - n_test
return idx[:split], idx[split:]
Devuelvo índices en lugar de los datos ya divididos, para que el mismo split
aplique a tus features, tus etiquetas y cualquier otra cosa indexada por fila.
El tamaño de prueba es ceil(n * test_frac) contado desde el final — que es
exactamente cómo lo dimensiona scikit-learn, así que nuestro split y el suyo
caen en los mismos conteos.
El split de tres vías es la misma idea con dos cortes en lugar de uno. La rebanada de prueba sale del extremo intacto y se aparta; la validación viene después; el resto entrena:
def train_val_test_split(n, val_frac=0.2, test_frac=0.2, seed=42):
"""One shuffle, two cuts. The test slice is carved off first and set aside;
the validation slice comes next; everything left over trains.
Test comes off the untouched end on purpose — it is the slice you promise
not to look at until the very end. Returns (train_idx, val_idx, test_idx).
"""
idx = shuffle_indices(n, seed)
n_test = int(np.ceil(n * test_frac))
n_val = int(np.ceil(n * val_frac))
test_idx = idx[n - n_test:]
val_idx = idx[n - n_test - n_val:n - n_test]
train_idx = idx[:n - n_test - n_val]
return train_idx, val_idx, test_idx
El orden es deliberado. Corta el conjunto de prueba primero y déjalo en paz. Todo lo que hagas después — cada modelo que compares en el conjunto de validación — sucede sobre las otras dos pilas, y los índices de prueba se quedan ahí intactos hasta la última línea del proyecto.
Ahora el que tiene colmillos. Un split aleatorio mantiene el balance de clases bien en promedio, pero "en promedio" es un consuelo frío cuando tienes un dataset y un split. Un split estratificado lo garantiza dividiendo cada clase por separado y tomando la misma fracción de cada una:
def stratified_split(y, test_frac=0.3, seed=42):
"""A random split done one class at a time, so each class keeps its share.
For every distinct label, shuffle just that class's rows and send a
test_frac slice to the test set. The test set then carries the same class
proportions as the full data — which a blind random split only matches on
average, not on any single draw. Returns (train_idx, test_idx).
"""
rng = np.random.default_rng(seed)
y = np.asarray(y)
train_idx, test_idx = [], []
for c in np.unique(y):
members = np.where(y == c)[0]
members = members[rng.permutation(len(members))]
n_test = int(round(len(members) * test_frac))
test_idx.append(members[:n_test])
train_idx.append(members[n_test:])
return np.concatenate(train_idx), np.concatenate(test_idx)
Itera sobre las clases, baraja las filas de cada clase por su cuenta, desprende
una rebanada de test_frac de cada una y vuelve a pegar las piezas. El
conjunto de prueba termina con la misma proporción de diabéticos que los datos
completos, exactamente, no aproximadamente. El helper que mide lo que estamos
preservando es trivial, y es de lo que está construida la gráfica de
comparación de más adelante:
def class_proportions(y):
"""Fraction of rows in each class — the thing a stratified split preserves."""
y = np.asarray(y)
classes, counts = np.unique(y, return_counts=True)
return {int(c): float(n) / len(y) for c, n in zip(classes, counts)}
Míralo trabajar
Este es el argumento. Todo lo de arriba es preparación para esta única animación, así que ve despacio aquí.
Tomamos un split estratificado train/test de los datos Pima y ajustamos un árbol de decisión, luego lo ajustamos otra vez con un tope de profundidad mayor, y otra vez, más profundo cada vez — desde un tocón de profundidad 1 hasta profundidad 16. Un árbol más profundo es un modelo más flexible: puede partir los datos de entrenamiento en cajas cada vez más finas hasta que cada caja contiene un puñado de puntos. Cada cuadro es un árbol real a una profundidad real. La línea cian es su accuracy sobre los datos de entrenamiento; la línea morada es su accuracy sobre el conjunto de prueba apartado que nunca vio. Dale play y mira las dos líneas separarse.
Esto es lo que estás viendo, y es el capítulo completo en una sola imagen. A
profundidad 1 el árbol es un tocón — accuracy de entrenamiento 0.740, accuracy
de prueba 0.678, las dos líneas casi tocándose. Conforme el árbol se
profundiza, la línea cian de entrenamiento sube sin pausa y alcanza un
impecable 1.000 a profundidad 16: con suficiente profundidad, el árbol memoriza
perfectamente a cada paciente de entrenamiento. La línea morada de prueba hace
algo completamente distinto. Sube hasta 0.735 a profundidad 4 y luego da la
vuelta y cae, todo el camino de regreso hasta 0.674 a profundidad 16. La brecha
entre las dos líneas se abre de 0.062 a 0.326 — los números están en el pie de
figura y en results.json.
Esa brecha que se ensancha es overfitting, y no es nada sutil una vez que puedes verla. Cada nivel de profundidad después del cuatro hace que el árbol se vea mejor con datos que ya vio y peor con datos que no. Si solo miraras la línea de entrenamiento — la que obtienes gratis, sin apartar nada — concluirías que más profundo siempre es mejor y mandarías a producción el árbol de profundidad 16 que saca un 1.000 perfecto. Es el peor modelo de la gráfica. La única forma de saberlo es la línea morada, y la única forma de dibujar la línea morada es haber apartado datos.
Ahora la misma lección como una comparación única y contundente. Toma un árbol sobreajustado — uno sin restricciones, sin tope de profundidad, libre para memorizar — y evalúalo de dos maneras: con los datos con los que entrenó, y con el conjunto de prueba apartado.
Con sus propios datos de entrenamiento el árbol saca un 1.000 perfecto. Con el conjunto de prueba apartado saca 0.674. El mismo modelo, el mismo día, el mismo código — lo único que cambió es si los datos fueron vistos durante el entrenamiento. Si reportaras el 1.000, estarías equivocado por una tercera parte y con toda confianza. Esto no es un caso raro ni un modelo patológico; es el resultado por defecto de evaluar con datos de entrenamiento, y es por lo que "¿cuál es tu accuracy?" es una pregunta incompleta hasta que digas accuracy sobre qué.
La implementación completa
Cuatro funciones y un loader, sin librería, de arriba a abajo. Este es el archivo que la animación realmente corrió:
"""Honest data splits, built from scratch.
Holding data out is the one habit that separates measuring learning from
measuring memorization. This file builds the splits by hand in NumPy: a random
train/test split, a three-way train/validation/test split, and a stratified
split that preserves class proportions. Pure NumPy — the only model in the whole
chapter lives in library.py, and it's just a prop to make the overfitting gap
visible.
Every function below appears in the chapter one step at a time (the
`# region:` markers are what the book's include directives pull in).
"""
import numpy as np
import pandas as pd
# region: shuffle_indices
def shuffle_indices(n, seed=42):
"""A reproducible permutation of the row positions 0..n-1.
Every split starts here. Seed it and the split is identical forever; that
is the whole difference between a result you can rerun and a number someone
just has to trust.
"""
rng = np.random.default_rng(seed)
return rng.permutation(n)
# endregion
# region: train_test_split
def train_test_split(n, test_frac=0.3, seed=42):
"""Shuffle the row positions, then slice the last chunk off as the test set.
n_test = ceil(n * test_frac), carved from the end so training keeps the
rest — the same sizes scikit-learn produces for the same fraction. Returns
(train_idx, test_idx) as index arrays, so the caller can split any column.
"""
idx = shuffle_indices(n, seed)
n_test = int(np.ceil(n * test_frac))
split = n - n_test
return idx[:split], idx[split:]
# endregion
# region: train_val_test_split
def train_val_test_split(n, val_frac=0.2, test_frac=0.2, seed=42):
"""One shuffle, two cuts. The test slice is carved off first and set aside;
the validation slice comes next; everything left over trains.
Test comes off the untouched end on purpose — it is the slice you promise
not to look at until the very end. Returns (train_idx, val_idx, test_idx).
"""
idx = shuffle_indices(n, seed)
n_test = int(np.ceil(n * test_frac))
n_val = int(np.ceil(n * val_frac))
test_idx = idx[n - n_test:]
val_idx = idx[n - n_test - n_val:n - n_test]
train_idx = idx[:n - n_test - n_val]
return train_idx, val_idx, test_idx
# endregion
# region: stratified_split
def stratified_split(y, test_frac=0.3, seed=42):
"""A random split done one class at a time, so each class keeps its share.
For every distinct label, shuffle just that class's rows and send a
test_frac slice to the test set. The test set then carries the same class
proportions as the full data — which a blind random split only matches on
average, not on any single draw. Returns (train_idx, test_idx).
"""
rng = np.random.default_rng(seed)
y = np.asarray(y)
train_idx, test_idx = [], []
for c in np.unique(y):
members = np.where(y == c)[0]
members = members[rng.permutation(len(members))]
n_test = int(round(len(members) * test_frac))
test_idx.append(members[:n_test])
train_idx.append(members[n_test:])
return np.concatenate(train_idx), np.concatenate(test_idx)
# endregion
# region: class_proportions
def class_proportions(y):
"""Fraction of rows in each class — the thing a stratified split preserves."""
y = np.asarray(y)
classes, counts = np.unique(y, return_counts=True)
return {int(c): float(n) / len(y) for c, n in zip(classes, counts)}
# endregion
def load_data(path="../data/diabetes.csv"):
"""Pima Indians Diabetes dataset: 768 patients, 8 features, Outcome."""
return pd.read_csv(path)
La versión de librería
En la práctica no vas a armar splits a mano, y después de construirlos una vez
no deberías. sklearn.model_selection.train_test_split es la función que vas a
llamar, y hace exactamente lo que hace la nuestra — barajar y luego rebanar:
def sk_split(X, y, test_size=0.3, seed=42):
"""A plain random split. Returns X_train, X_test, y_train, y_test. The
sizes match our from-scratch train_test_split for the same fraction and
seed — both carve ceil(n * test_size) rows into the test set."""
return sk_split_fn(X, y, test_size=test_size, random_state=seed, shuffle=True)
El mismo barajar-y-cortar, el mismo tamaño de prueba para una fracción dada,
con semilla vía random_state igual que la nuestra usa seed. La única
adición que vale la pena conocer es stratify. Pásale las etiquetas y obtienes
nuestro split estratificado gratis:
def sk_stratified_split(X, y, test_size=0.3, seed=42):
"""The same call with stratify=y. Now the test set gets each class in the
same proportion it holds in the full data — the library twin of our
stratified_split."""
return sk_split_fn(X, y, test_size=test_size, random_state=seed,
shuffle=True, stratify=y)
Ese único keyword es el gemelo de librería de todo el loop por clase que
escribimos. En el trabajo real stratify=y es el argumento que uso por defecto
en cualquier problema de clasificación — no cuesta nada y elimina una forma de
estar silenciosamente equivocado. El modelo de la animación también es el árbol
de sklearn, girado una profundidad a la vez; es la utilería, no el punto:
def tree_at_depth(X_train, y_train, X_test, y_test, depth, seed=42):
"""Fit one decision tree capped at max_depth=depth; return (train_acc,
test_acc). Deepen the cap and you widen the model's freedom to memorize the
training rows — that is the flexibility dial the overfitting animation
turns."""
clf = DecisionTreeClassifier(max_depth=depth, random_state=seed)
clf.fit(X_train, y_train)
return float(clf.score(X_train, y_train)), float(clf.score(X_test, y_test))
Desde cero contra librería
La comparación honesta aquí no es accuracy de desde-cero contra librería — ambos dividen las mismas filas en los mismos tamaños, así que no hay nada que competir. La comparación que importa es aleatorio contra estratificado, y se trata del balance de clases, no de accuracy. Aquí está la proporción de clase positiva en el conjunto de prueba de tres maneras: el dataset completo, un split aleatorio simple y un split estratificado.
Los datos completos son 0.349 positivos. El conjunto de prueba estratificado es
0.348 — la frontera entre los colores queda justo donde queda la de los datos
completos, que es la garantía que hace la estratificación. El conjunto de
prueba aleatorio se desvía a 0.338: cerca, pero desviado, y se desvió puramente
por suerte del barajado. Cada número aquí viene de results.json.
En estos datos la desviación es pequeña, y quiero ser directo al respecto en lugar de sobrevenderla — Pima está apenas medianamente desbalanceado y el conjunto de prueba tiene unas saludables 231 filas. Pero la desviación crece rápido exactamente en las situaciones que más duelen: una clase positiva más rara, un conjunto de prueba más chico, un split en muchos folds. Cuando tu clase positiva es el 3% de los datos, un split de prueba aleatorio puede caer fácilmente en 1% o 5%, y ahora tu métrica principal está medida sobre una población que no coincide con producción. La estratificación elimina ese modo de falla por un solo keyword. No hay razón para no usarla, así que yo siempre la uso.
Conclusiones
Nunca evalúes con los datos de entrenamiento y te creas el número. Ese es el capítulo completo, y suena demasiado obvio para decirlo hasta que has visto a un colega — o a ti mismo — publicar una accuracy sin validación que se evaporó en producción. La calificación de entrenamiento mide memorización; la calificación sobre datos apartados mide aprendizaje; son números distintos y la animación los mostró divergiendo por una tercera parte de la escala. Aparta datos, cada vez, sin excepciones.
Mantén claros los dos conjuntos apartados, porque hacen trabajos distintos. El conjunto de validación es donde tomas decisiones — qué modelo, qué profundidad, qué features — y puedes mirarlo tantas veces como quieras. El conjunto de prueba responde una pregunta, una vez: ¿qué tan bueno es el modelo final, honestamente? En el instante en que usas el conjunto de prueba para elegir algo, deja de ser un conjunto de prueba y se convierte en otro conjunto de validación, y ya no tienes un número insesgado. Córtalo primero, no lo toques hasta el final, repórtalo una vez.
Dos hábitos que salen casi gratis. Ponle semilla a cada barajado, para que tu split sea el mismo mañana que hoy y tus resultados sean reproducibles. Y estratifica tus splits sobre la etiqueta siempre que estés clasificando, para que una clase rara no quede chueca por suerte — cuesta un keyword y cierra toda una categoría de bugs silenciosos. Y ya que estás, cuídate del leakage: escalar o imputar antes de hacer el split, con lo que las estadísticas del conjunto de prueba se filtran al entrenamiento; o repartir filas que pertenecen al mismo paciente entre train y test. Ambos inflan tu calificación con el mismo optimismo que evaluar sobre entrenamiento, y ambos son más difíciles de detectar porque el split técnicamente está ahí.
Un split único es el mínimo honesto. Su debilidad es la varianza — un conjunto de prueba de 154 filas da una estimación tambaleante, y dos modelos separados por un punto podrían estar empatados. La solución es rotar qué datos apartas y promediar, que es la validación cruzada, el siguiente capítulo. Pero la validación cruzada es esta idea repetida, no un reemplazo de ella. Deja el split único bien hecho, mantén sagrado el conjunto de prueba, y todo lo que viene después tendrá un cimiento sobre el cual pararse.