Antes de empezar
Varios ejercicios usan las variables de la teoría: X_ent, X_prueba, y_ent, y_prueba (los tumores), proba (los puntajes de la regresión logística en la prueba), y3_prueba y p3 (el problema de tres clases), cm10, y10_prueba y pred10 (los dígitos), knn_c, Xc_aj, Xc_cal, Xc_prueba y modelos_c (la calibración), además de las funciones de sklearn.metrics que importamos allí. Abre el cuaderno del tema en Colab (botón «Abrir en Colab», en la ficha Código), ejecuta las celdas de la teoría y trabaja en los ejercicios, que vienen debajo. Intenta cada ejercicio antes de abrir la respuesta.
Ejercicios
1. Métricas a mano
Un filtro de correo basura se probó con 1000 correos (cifras inventadas para el ejercicio). La clase positiva es «basura». Esta es su matriz de confusión:
| Dice «normal» | Dice «basura» | |
|---|---|---|
| Real: normal | 850 | 10 |
| Real: basura | 40 | 100 |
a) ¿Cuántos son VP, FP, VN y FN?
b) Calcula sin computadora la exactitud, la precisión, la exhaustividad y F1.
c) Explica con una frase, sin usar los nombres de las métricas, qué quiere decir la precisión en este problema, y otra para la exhaustividad.
Respuesta
a) VN = 850 (correos normales que llegan a la bandeja), FP = 10 (correos normales mandados a la basura), FN = 40 (basura que se coló) y VP = 100 (basura atrapada).
b)
- Exactitud = (100 + 850) / 1000 = 0,95
- Precisión = 100 / (100 + 10) = 100 / 110 ≈ 0,909
- Exhaustividad = 100 / (100 + 40) = 100 / 140 ≈ 0,714
- F1 = 2 · 0,909 · 0,714 / (0,909 + 0,714) ≈ 0,8
c) Precisión: de cada 11 correos que el filtro manda a la basura, 10 eran basura de verdad. Exhaustividad: de cada 7 correos basura que llegan, el filtro atrapa 5 y deja pasar 2.
Para comprobarlo con código, armamos dos arreglos que tengan esa matriz:
# 860 correos normales y 140 de basura
y_r = np.array([0] * 860 + [1] * 140)
y_p = np.array([0] * 850 + [1] * 10 + [0] * 40 + [1] * 100)
print(confusion_matrix(y_r, y_p))
print(f"exactitud {accuracy_score(y_r, y_p):.3f}"
f" precisión {precision_score(y_r, y_p):.3f}"
f" exhaustividad {recall_score(y_r, y_p):.3f}"
f" F1 {f1_score(y_r, y_p):.3f}")
[[850 10]
[ 40 100]]
exactitud 0.950 precisión 0.909 exhaustividad 0.714 F1 0.800
2. ¿Qué métrica importa?
Para cada caso, di qué error es más grave (falso positivo o falso negativo) y qué métrica mirarías primero. Todos los casos son imaginarios.
- Un análisis de sangre para detectar temprano una enfermedad grave. Si sale positivo, se repite con una prueba más cara que no se equivoca.
- Un filtro de correo basura.
- Un programa que, entre 5000 casas, elige 20 para que un inspector las revise por posibles fallas eléctricas. El inspector solo tiene tiempo para esas 20.
- Un clasificador de fotos de gatos y perros, con la mitad de cada uno, donde los dos errores dan igual.
- Comparar dos modelos de detección de fraude con tarjetas antes de decidir con qué umbral se va a usar el elegido.
Respuesta
- Es peor un falso negativo: un enfermo que no se detecta pierde el tratamiento temprano, mientras que un falso positivo solo cuesta repetir el análisis. Mira la exhaustividad y baja el umbral.
- Es peor un falso positivo: un correo importante que termina en la basura y nadie ve. Un correo basura que se cuela solo molesta. Mira la precisión.
- Lo que importa es que, de las 20 casas elegidas, la mayor cantidad tenga fallas de verdad: es la precisión entre los 20 primeros. Que se escapen otras casas es inevitable con un solo inspector.
- Clases equilibradas y errores del mismo costo: la exactitud sirve bien.
- Todavía no hay umbral: el AUC compara qué tan bien ordena cada modelo. Como el fraude es muy raro, conviene mirar también la curva precisión–exhaustividad.
3. La matriz de confusión para varias clases
Escribe una función matriz_confusion(y_real, y_pred, k) que devuelva la matriz de confusión de un problema con k clases (numeradas de 0 a k − 1) usando solo NumPy. Pruébala con y3_prueba y p3 del problema de tres clases de la teoría y compárala con la de scikit-learn.
Pista: empieza con una matriz de ceros de k × k y suma 1 en la fila de la clase real y la columna de la clase predicha de cada caso.
Respuesta
def matriz_confusion(y_real, y_pred, k):
m = np.zeros((k, k), dtype=int)
for r, p in zip(y_real, y_pred):
m[r, p] += 1 # fila = real, columna = predicho
return m
mia = matriz_confusion(y3_prueba, p3, 3)
print(mia)
print("Igual a scikit-learn:",
np.array_equal(mia, confusion_matrix(y3_prueba, p3)))
# La exhaustividad de cada clase: la diagonal entre el total de su fila
print("Exhaustividad por clase:",
np.round(np.diag(mia) / mia.sum(axis=1), 2))
[[338 18 2]
[ 34 30 5]
[ 10 5 8]]
Igual a scikit-learn: True
Exhaustividad por clase: [0.94 0.43 0.35]
La diagonal son los aciertos. La exhaustividad de cada clase se lee por filas (de los casos reales de esa clase, cuántos acertó) y la precisión, por columnas. Sin el bucle, np.add.at(m, (y_real, y_pred), 1) hace lo mismo de una vez.
4. ¿Le gana al tonto?
Un modelo para detectar una falla rara en piezas de una fábrica (2 de cada 100 piezas la tienen; datos inventados) se probó con 1000 piezas y dio esta matriz: VN = 955, FP = 25, FN = 5, VP = 15.
a) ¿Qué exactitud tiene este modelo? ¿Y un modelo tonto que siempre dice «sin falla»?
b) ¿Cuál de los dos es mejor? Justifica con otra métrica.
Respuesta
vn, fp, fn, vp = 955, 25, 5, 15
total = vn + fp + fn + vp
print(f"Exactitud del modelo: {(vp + vn) / total:.3f}")
print(f"Exactitud del tonto: {(vn + fp) / total:.3f}")
print(f"Exhaustividad del modelo: {vp / (vp + fn):.2f}"
f" precisión: {vp / (vp + fp):.3f}")
Exactitud del modelo: 0.970
Exactitud del tonto: 0.980
Exhaustividad del modelo: 0.75 precisión: 0.375
a) El modelo tiene una exactitud de 0,97 y el tonto, de 0,98: el tonto «gana» en exactitud.
b) El tonto no encuentra ninguna pieza fallada (exhaustividad 0). El modelo encuentra 15 de las 20 (exhaustividad 0,75), a cambio de 25 falsas alarmas: de cada 8 piezas que marca, 3 tienen la falla. Si una pieza fallada que sale de la fábrica es cara, el modelo es mucho mejor, aunque su exactitud sea más baja. La exactitud sola habría elegido al modelo inútil.
5. Lo que el AUC no ve
Predice primero, sin correr nada, qué AUC dará cada línea, y después compruébalo:
print(f"Original: {roc_auc_score(y_prueba, proba):.4f}")
print(f"Puntajes / 2: {roc_auc_score(y_prueba, proba / 2):.4f}")
print(f"Puntajes al cubo: {roc_auc_score(y_prueba, proba ** 3):.4f}")
print(f"1 − puntajes: {roc_auc_score(y_prueba, 1 - proba):.4f}")
Respuesta
Original: 0.9917
Puntajes / 2: 0.9917
Puntajes al cubo: 0.9917
1 − puntajes: 0.0083
Dividir entre 2 o elevar al cubo cambia los números, pero no cambia el orden de los casos: el que tenía más puntaje sigue teniendo más. Como el AUC solo mira el orden (la probabilidad de que un positivo tenga más puntaje que un negativo), queda igual. En cambio, la exactitud con umbral 0,5 sí cambiaría, porque con los puntajes divididos entre 2 casi ninguno pasaría de 0,5.
Con 1 − puntajes el orden se invierte y el AUC pasa a 1 − 0,9917 = 0,0083: un modelo que se equivoca casi siempre en el orden. Un AUC muy por debajo de 0,5 no es un modelo inútil, es uno al revés: invirtiendo su respuesta sería excelente. Lo verdaderamente inútil es un AUC cerca de 0,5.
6. El Brier de dos pronosticadoras
Dos personas pronostican la lluvia durante los mismos 10 días (datos inventados para el ejercicio). Ana dice todos los días «80 % de lluvia»; Beto dice todos los días «100 %». Llovió en 8 de los 10 días.
a) Calcula sin computadora el puntaje de Brier de cada uno. ¿Quién está mejor calibrado?
b) Si alguien dijera siempre el mismo número, ¿qué número le daría el Brier más bajo con estos 10 días? Pruébalo con código.
Respuesta
a) Ana: en los 8 días de lluvia se aleja 1 − 0,8 = 0,2, que al cuadrado da 0,04; en los 2 días secos se aleja 0,8, que al cuadrado da 0,64. Brier = (8 · 0,04 + 2 · 0,64) / 10 = (0,32 + 1,28) / 10 = 0,16. Beto: en los días de lluvia acierta de lleno (0) y en los 2 secos se aleja 1, que al cuadrado da 1. Brier = 2 / 10 = 0,2. Ana está mejor calibrada: cuando dice 80 %, llueve el 80 % de las veces; Beto dice 100 % y se equivoca 2 de cada 10 veces.
b)
lluvia = np.array([1] * 8 + [0] * 2)
for dice in [0.5, 0.7, 0.8, 0.9, 1.0]:
p = np.full(10, dice)
print(f"Siempre {dice}: Brier {brier_score_loss(lluvia, p):.3f}")
Siempre 0.5: Brier 0.250
Siempre 0.7: Brier 0.170
Siempre 0.8: Brier 0.160
Siempre 0.9: Brier 0.170
Siempre 1.0: Brier 0.200
El mejor número fijo es 0,8, justo la fracción de días con lluvia. El Brier premia decir la probabilidad verdadera: exagerar hacia 1 o achicar hacia 0,5 lo empeora.
7. Calibrar con los datos equivocados
Vuelve al k-NN de la sección de calibración (knn_c).
a) Calcula su puntaje de Brier con sus propios datos de ajuste (Xc_aj) y con los de calibración (Xc_cal). ¿Con cuáles parece mejor? ¿Por qué?
b) Calibra el k-NN con el método sigmoide usando Xc_aj y yc_aj (lo que no se debe hacer) y compáralo en la prueba con el que calibramos bien, mirando los casos de prueba en los que el k-NN dice 100 %.
Respuesta
for nombre, Xs, ys in [("ajuste", Xc_aj, yc_aj),
("calibración", Xc_cal, yc_cal)]:
p = knn_c.predict_proba(Xs)[:, 1]
print(f"Brier con los datos de {nombre}: "
f"{brier_score_loss(ys, p):.3f}")
mal = CalibratedClassifierCV(FrozenEstimator(knn_c), method="sigmoid")
mal.fit(Xc_aj, yc_aj) # ¡con los datos de entrenamiento!
bien = modelos_c["k-NN sigmoid"]
seguro = p_knn_prueba == 1 # donde el k-NN dice 100 %
print(f"El k-NN dice 100 % en {seguro.sum()} casos de prueba; "
f"eran positivos el {yc_prueba[seguro].mean():.1%}")
for nombre, mod in [("bien", bien), ("mal", mal)]:
p = mod.predict_proba(Xc_prueba)[:, 1]
print(f"Calibrado {nombre}: ahí dice {p[seguro].mean():.1%}"
f" log-loss {log_loss(yc_prueba, p):.3f}")
Brier con los datos de ajuste: 0.080
Brier con los datos de calibración: 0.119
El k-NN dice 100 % en 346 casos de prueba; eran positivos el 94.8%
Calibrado bien: ahí dice 95.7% log-loss 0.383
Calibrado mal: ahí dice 98.6% log-loss 0.413
a) Con sus datos de ajuste el k-NN parece mucho mejor que con datos nuevos: Brier 0,080 contra 0,119. Cada caso de ajuste es uno de sus propios 5 vecinos (está a distancia 0 de sí mismo), así que siempre «vota» por su clase verdadera.
b) El calibrador que aprendió con los datos de ajuste vio al k-NN en su mejor día y le cree de más: donde el k-NN dice 100 %, él dice 98,6 %, cuando en la prueba fueron positivos el 94,8 %. El que calibramos con datos separados dice 95,7 %, mucho más cerca, y su log-loss es más baja (0,383 contra 0,413). La corrección se aprende siempre con datos que el modelo no vio al entrenar.
8. Leer la matriz de los dígitos
Vuelve a la matriz de 10 × 10 de los dígitos (cm10).
a) Sin usar classification_report, calcula con la matriz los falsos positivos y los falsos negativos de cada dígito. Pista: los aciertos están en la diagonal (np.diag), cada fila suma los casos reales de un dígito y cada columna, las veces que el modelo dijo ese dígito.
b) ¿Qué dígito «dice» el modelo de más, es decir, cuál tiene más falsos positivos? Calcula su precisión con la matriz y compárala con la de precision_score.
c) Mira la fila y la columna del 5. ¿Con qué dígitos se confunde, en cada dirección?
Respuesta
vp10 = np.diag(cm10) # los aciertos de cada dígito
fp10 = cm10.sum(axis=0) - vp10 # por columnas: lo que dijo de más
fn10 = cm10.sum(axis=1) - vp10 # por filas: lo que se le escapó
print("FP por dígito:", fp10)
print("FN por dígito:", fn10)
d = fp10.argmax()
print(f"Más falsos positivos: el {d}, con precisión "
f"{vp10[d]}/{vp10[d] + fp10[d]} = "
f"{vp10[d] / (vp10[d] + fp10[d]):.3f}")
prec10 = precision_score(y10_prueba, pred10, average=None)
print(f"Según scikit-learn: {prec10[d]:.3f}")
print("Fila del 5: ", cm10[5])
print("Columna del 5:", cm10[:, 5])
FP por dígito: [0 4 0 0 0 2 1 1 2 1]
FN por dígito: [0 2 1 1 2 2 0 0 2 1]
Más falsos positivos: el 1, con precisión 35/39 = 0.897
Según scikit-learn: 0.897
Fila del 5: [ 0 0 0 0 0 35 1 1 0 0]
Columna del 5: [ 0 0 0 1 0 35 0 0 0 1]
a) Los falsos positivos de un dígito son su columna menos el acierto, y los falsos negativos, su fila menos el acierto. En total hay 11 de cada uno: cada error es a la vez un falso negativo del dígito real y un falso positivo del dígito que dijo el modelo.
b) El 1: el modelo dijo «1» 39 veces y 4 no lo eran (un 2, un 4 y dos 8). Su precisión es 35 / 39 = 0,897, igual a la de scikit-learn. Es la precisión más baja de los diez dígitos, aunque su exhaustividad (0,946) no sea la peor.
c) En su fila, un 5 se leyó como 6 y otro como 7: esos son sus falsos negativos. En su columna, el modelo dijo «5» ante un 3 y ante un 9: esos son sus falsos positivos. Las confusiones no son simétricas: que un 5 se lea como 6 no quiere decir que un 6 se lea como 5 (aquí, ningún 6 se leyó como 5).
9. Elegir la pérdida y la métrica
Para cada caso (todos imaginarios), di con qué pérdida entrenarías y qué métrica informarías. Usa las dos tablas de la sección «Pérdidas y métricas: el mapa».
- Predecir el precio del alquiler de departamentos en bolivianos. Entre los anuncios hay unos pocos con precios absurdos por errores de tipeo, y quieres decirle a la gente «en promedio, me equivoco por tantos bolivianos».
- Avisar de pagos con tarjeta que podrían ser fraude, para que una persona los revise. Uno de cada 500 pagos es fraude, y dejar pasar un fraude cuesta mucho más que revisar un pago normal.
- Una aplicación que reconoce cinco especies de plantas en una foto y le muestra al usuario «estoy 90 % seguro». Una de las especies es rara, pero es tóxica y es la que más importa reconocer.
Respuesta
- Es regresión. Una regresión lineal se entrena con el MSE; como unos pocos atípicos lo dominan, el MAE es una alternativa como pérdida (o revisar y limpiar esos anuncios antes de entrenar). Como métrica, el MAE, que se lee en bolivianos y no se deja llevar por los atípicos; el R² sirve además para compararse con predecir siempre el precio promedio.
- Son dos clases: una regresión logística con entropía cruzada binaria. La exactitud no sirve: el modelo que dice siempre «no es fraude» acierta el 99,8 %. Como el falso negativo es el error caro, mira la exhaustividad de la clase fraude y, para no ahogar a quien revisa, la precisión (o F1); el umbral se elige con validación, como en el mini-proyecto. Para comparar modelos antes de elegir el umbral, el AUC, junto con la curva precisión–exhaustividad, porque la clase es muy rara.
- Son varias clases: entropía cruzada categórica con softmax (una logística o una red). Como la especie rara es la que importa, informa la exhaustividad de esa clase y el F1 macro, no la exactitud ni el micro, que la esconden. Y como la aplicación muestra probabilidades, revisa también la log-loss o el Brier y la calibración: ese «90 %» tiene que ser creíble.
Mini-proyecto: un detector que no deja pasar casi nada
Un hospital quiere usar el modelo de los tumores como primer filtro: los casos que el modelo marque como malignos irán a una revisión más detallada. La condición es que no se escape más del 5 % de los tumores malignos, es decir, una exhaustividad de al menos 0,95. Entre los umbrales que la cumplen, se quiere el que dé menos falsas alarmas.
La trampa es elegir el umbral mirando los datos de prueba: entonces la prueba ya no medirá lo que pasa con casos nuevos (tema 7). Hazlo así:
- Usa solo
X_entyy_ent. Concross_val_predict(..., method="predict_proba")y 5 pliegues, consigue para cada tumor de entrenamiento el puntaje de un modelo que no lo vio. Usamake_pipeline(StandardScaler(), LogisticRegression())para que en cada pliegue el escalado se ajuste solo con su parte de entrenamiento (los pipelines los verás a fondo en el tema 10; aquí guardamos el nuestro en la variabletuberia). - Con
precision_recall_curvesobre esos puntajes, busca el umbral más alto cuya exhaustividad sea de al menos 0,95. ¿Por qué el más alto? Busca también un segundo umbral con un margen de seguridad: el más alto con exhaustividad de al menos 0,98. - Entrena la pipeline con todo el entrenamiento y aplica a
X_prueba, una sola vez, los dos umbrales y el 0,5 de siempre. Para cada uno, muestra los cuatro conteos de la matriz de confusión, la exhaustividad y la precisión. - ¿Con cuál de los tres umbrales se cumple la condición en la prueba? ¿Cuántas falsas alarmas más cuesta?
- ¿Por qué el umbral elegido justo en 0,95 puede fallar en la prueba?
Resultados de referencia, para comprobar los tuyos
from sklearn.model_selection import cross_val_predict
from sklearn.pipeline import make_pipeline
tuberia = make_pipeline(StandardScaler(), LogisticRegression())
# Paso 1: puntajes «fuera de pliegue» del entrenamiento
proba_cv = cross_val_predict(tuberia, X_ent, y_ent, cv=5,
method="predict_proba")[:, 1]
# Paso 2: el umbral más alto que cumple cada objetivo
prec_cv, exh_cv, umb_cv = precision_recall_curve(y_ent, proba_cv)
elegidos = []
for objetivo in [0.95, 0.98]:
cumple = exh_cv[:-1] >= objetivo
u = umb_cv[cumple].max()
i = list(umb_cv).index(u)
elegidos.append(u)
# Métricas medidas en la validación cruzada
print(f"Objetivo {objetivo}: umbral {u:.3f} exhaustividad"
f" {exh_cv[i]:.3f} precisión {prec_cv[i]:.3f}")
# Pasos 3 y 4: una sola vez sobre la prueba
tuberia.fit(X_ent, y_ent)
proba_t = tuberia.predict_proba(X_prueba)[:, 1]
print("En la prueba:")
for u in elegidos + [0.5]:
p = (proba_t >= u).astype(int)
vn, fp, fn, vp = confusion_matrix(y_prueba, p).ravel()
print(f"umbral {u:.3f}: VP={vp} FN={fn} FP={fp} VN={vn}"
f" exhaustividad={recall_score(y_prueba, p):.3f}"
f" precisión={precision_score(y_prueba, p):.3f}")
Objetivo 0.95: umbral 0.565 exhaustividad 0.953 precisión 1.000
Objetivo 0.98: umbral 0.179 exhaustividad 0.986 precisión 0.912
En la prueba:
umbral 0.565: VP=59 FN=5 FP=2 VN=105 exhaustividad=0.922 precisión=0.967
umbral 0.179: VP=61 FN=3 FP=13 VN=94 exhaustividad=0.953 precisión=0.824
umbral 0.500: VP=60 FN=4 FP=4 VN=103 exhaustividad=0.938 precisión=0.938
- Por qué el umbral más alto: todos los umbrales más bajos también cumplen la condición (bajar el umbral nunca baja la exhaustividad), pero dan más falsas alarmas. El más alto que todavía la cumple es el de mayor precisión.
- El umbral justo en 0,95 no alcanza en la prueba: en la validación cruzada llegaba a 0,953, pero en la prueba se escapan 5 de 64 malignos (exhaustividad 0,922, un 7,8 % de malignos perdidos). Ni siquiera el 0,5 cumple: pierde 4.
- Con el margen sí se cumple: el umbral de 0,179 deja escapar 3 de 64 (exhaustividad 0,953, un 4,7 % perdido). El precio son 13 falsas alarmas en lugar de 4: de cada 74 tumores que el modelo manda a revisión, 61 son malignos. Para un primer filtro médico, es un buen negocio.
- Por qué hace falta el margen: las métricas se miden con pocos casos. Con 64 malignos en la prueba, cada tumor que se escapa mueve la exhaustividad en más de 0,015, así que un umbral elegido justo en el límite puede fallar fácilmente con casos nuevos. El margen se decide antes de mirar la prueba; lo que no se debe hacer es volver a elegir el umbral después de ver cómo le fue en ella.
Errores comunes
- Juzgar un modelo solo por su exactitud. Con clases desbalanceadas, un modelo tonto saca una exactitud altísima. Compárate siempre con él y mira la precisión y la exhaustividad de la clase que importa.
- Confundir precisión con exactitud. La exactitud mira todas las predicciones; la precisión, solo las que dijeron «positivo».
- Leer la matriz al revés. En scikit-learn las filas son lo real y las columnas lo predicho, con la clase 0 primero. Otros libros y programas usan otro orden: revisa los ejes.
- No fijarse cuál es la clase positiva. En
load_breast_cancerla clase 1 es «benigno». Si no la inviertes, la «exhaustividad» que calcules será la de los benignos. - Creer que 0,5 es el umbral correcto. Es solo el que usa
predict. El umbral se elige según el costo de cada error. - Elegir el umbral (o el modelo) mirando la prueba. Se elige con validación cruzada o con un conjunto de validación, y la prueba se usa una sola vez al final.
- Calcular el AUC con las predicciones 0 y 1.
roc_auc_scorenecesita los puntajes depredict_proba; con las predicciones finales la curva tiene un solo punto y el AUC sale peor de lo que es. - Dividir sin estratificar con una clase rara. La prueba puede quedar con muy pocos positivos y las métricas cambian mucho de una semilla a otra. Usa
stratify=y. - Fiarse de una curva ROC bonita con clases muy desbalanceadas. La tasa de falsos positivos se divide entre muchos negativos y parece pequeña aunque la precisión sea baja; mira también la curva precisión–exhaustividad.
- Calibrar con los datos de entrenamiento. El calibrador aprende a confiar de más. La corrección se aprende con datos que el modelo no vio, y la prueba sigue apartada para la medición final.
- Leer cualquier puntaje como una probabilidad. Que un modelo diga 0,9 no quiere decir que acierte 9 de cada 10 veces: revísalo con un diagrama de fiabilidad.
¿Lo lograste?
Al terminar este tema deberías poder hacer lo siguiente. Al lado, dónde lo practicaste.
- Construir e interpretar una matriz de confusión (ejercicios 1 y 3).
- Calcular la exactitud, la precisión, la exhaustividad y F1, y elegir la métrica según el costo de cada error (ejercicios 1 y 2).
- Trazar una curva ROC, calcular el AUC y elegir un umbral (ejercicio 5 y el mini-proyecto).
- Detectar el problema de la exactitud con clases desbalanceadas (ejercicio 4).
- Revisar si las probabilidades de un modelo son creíbles con un diagrama de fiabilidad y el puntaje de Brier, y calibrarlas con datos separados (ejercicios 6 y 7).
- Leer una matriz de confusión de diez clases: aciertos, falsos positivos y negativos de cada clase, y qué clases se confunden (ejercicio 8).
- Distinguir la función de pérdida de la métrica y elegir las dos según el problema (ejercicio 9).