Entrenamiento de Modelos de Machine Learning¶
Estudio de Caso — Semana 6¶
| Titulo | Entrenamiento de Modelos de Machine Learning |
| Estudiante | Alexander Oviedo Fadul |
| Curso | Machine Learning (NRC-200) |
| Semana | 6 |
| Docente | Rodolfo Muriel |
| Fecha de entrega | 23 de febrero de 2026 |
| Institucion | UNIMINUTO |
| URL Google Colab | https://colab.research.google.com/drive/1F-YkLVr1fnf9ZZuiJPGAgeZzdOg3syiJ |
Parte I — Script de Entrenamiento de Modelos¶
1. Introduccion¶
En la sesion de clase del 21 de febrero de 2026, el profesor Rodolfo Muriel demostro como entrenar un modelo de regresion logistica sobre el dataset Iris usando scikit-learn, enfatizando la importancia de stratify para mantener la proporcion de clases en la division de datos. La idea clave que me quedo de la sesion es que un modelo entrenado sin distribucion proporcional de clases puede aprender muy bien unas categorias y fallar con otras.
Para esta actividad trabajo con dos datasets:
- Parte I: Dataset Iris (clasificacion multiclase de 3 especies de flores)
- Parte II: Breast Cancer Wisconsin (clasificacion binaria de tumores)
Ambos casos aplican aprendizaje supervisado de clasificacion, como describe Bonaccorso (2018, pp. 104-106): el objetivo es aprender una funcion que mapee variables de entrada a una etiqueta de clase conocida.
2. Importacion de Librerias¶
# ============================================================
# IMPORTACION DE LIBRERIAS
#
# numpy y pandas: manejo de datos tabulares
# matplotlib y seaborn: visualizacion de resultados
# sklearn: modelos de ML, metricas y utilidades
#
# Campesato (2020, pp. 46-52) describe el flujo tipico:
# cargar datos -> dividir -> entrenar -> predecir -> evaluar
# ============================================================
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.datasets import load_iris, load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier, plot_tree
from sklearn.ensemble import RandomForestClassifier
from sklearn.svm import SVC
from sklearn.metrics import (
accuracy_score, precision_score, recall_score, f1_score,
classification_report, confusion_matrix, ConfusionMatrixDisplay
)
import warnings
warnings.filterwarnings('ignore')
plt.style.use('seaborn-v0_8-whitegrid')
sns.set_palette('muted')
print('Librerias importadas correctamente.')
Librerias importadas correctamente.
3. Carga del Dataset Iris y Analisis Exploratorio¶
# ============================================================
# DATASET IRIS - CLASIFICACION MULTICLASE
#
# 150 muestras de 3 especies de flores: setosa, versicolor, virginica.
# 4 caracteristicas: longitud/ancho del sepalo, longitud/ancho del petalo.
# Clase objetivo: especie (0, 1, 2).
#
# Es el dataset clasico de ML para clasificacion, usado por el
# profesor Muriel en la sesion de clase como ejemplo de referencia.
# ============================================================
iris = load_iris()
df_iris = pd.DataFrame(data=iris.data, columns=iris.feature_names)
df_iris['target'] = iris.target
df_iris['especie'] = df_iris['target'].map({0: 'setosa', 1: 'versicolor', 2: 'virginica'})
print('Primeras filas del dataset Iris:')
print(df_iris.head(10))
print(f'\nDimensiones: {df_iris.shape[0]} filas x {df_iris.shape[1]} columnas')
print(f'\nDistribucion de clases (debe ser balanceada):')
print(df_iris['especie'].value_counts())
print(f'\nEstadisticas descriptivas:')
print(df_iris.describe().round(3))
Primeras filas del dataset Iris:
sepal length (cm) sepal width (cm) petal length (cm) petal width (cm) \
0 5.1 3.5 1.4 0.2
1 4.9 3.0 1.4 0.2
2 4.7 3.2 1.3 0.2
3 4.6 3.1 1.5 0.2
4 5.0 3.6 1.4 0.2
5 5.4 3.9 1.7 0.4
6 4.6 3.4 1.4 0.3
7 5.0 3.4 1.5 0.2
8 4.4 2.9 1.4 0.2
9 4.9 3.1 1.5 0.1
target especie
0 0 setosa
1 0 setosa
2 0 setosa
3 0 setosa
4 0 setosa
5 0 setosa
6 0 setosa
7 0 setosa
8 0 setosa
9 0 setosa
Dimensiones: 150 filas x 6 columnas
Distribucion de clases (debe ser balanceada):
especie
setosa 50
versicolor 50
virginica 50
Name: count, dtype: int64
Estadisticas descriptivas:
sepal length (cm) sepal width (cm) petal length (cm) \
count 150.000 150.000 150.000
mean 5.843 3.057 3.758
std 0.828 0.436 1.765
min 4.300 2.000 1.000
25% 5.100 2.800 1.600
50% 5.800 3.000 4.350
75% 6.400 3.300 5.100
max 7.900 4.400 6.900
petal width (cm) target
count 150.000 150.000
mean 1.199 1.000
std 0.762 0.819
min 0.100 0.000
25% 0.300 0.000
50% 1.300 1.000
75% 1.800 2.000
max 2.500 2.000
# ============================================================
# VISUALIZACION EXPLORATORIA DEL DATASET IRIS
# ============================================================
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# Scatter: petalo longitud vs petalo ancho (las 2 mas discriminantes)
for sp, color in zip([0,1,2], ['steelblue','darkorange','seagreen']):
mask = df_iris['target'] == sp
axes[0].scatter(df_iris.loc[mask, 'petal length (cm)'],
df_iris.loc[mask, 'petal width (cm)'],
c=color, label=iris.target_names[sp], alpha=0.7, s=40)
axes[0].set_xlabel('Longitud del petalo (cm)')
axes[0].set_ylabel('Ancho del petalo (cm)')
axes[0].set_title('Grafico 1. Distribucion de Especies (Petalo)')
axes[0].legend()
# Distribucion de clases
df_iris['especie'].value_counts().plot.bar(ax=axes[1], color='steelblue', alpha=0.7)
axes[1].set_title('Grafico 2. Frecuencia por Especie')
axes[1].set_ylabel('Cantidad')
axes[1].set_xticklabels(axes[1].get_xticklabels(), rotation=0)
plt.tight_layout()
plt.show()
print('Las 3 clases estan perfectamente balanceadas (50 muestras cada una).')
print('Setosa es linealmente separable; versicolor y virginica se solapan parcialmente.')
Las 3 clases estan perfectamente balanceadas (50 muestras cada una). Setosa es linealmente separable; versicolor y virginica se solapan parcialmente.
4. Division de Datos (Train/Test Split con Stratify)¶
# ============================================================
# DIVISION DE DATOS CON STRATIFY
#
# Como explico el profesor Muriel en clase (21/02/2026):
# "stratify=y es para mantener proporcion en las clases"
#
# Sin stratify, podria ocurrir que el 80% de entrenamiento
# tenga solo clases 0 y 1, y el 20% de prueba tenga mayoria
# de clase 2 - lo que sesga el rendimiento del modelo.
#
# Campesato (2020, pp. 46-52) describe que la division
# tipica es 80/20 o 70/30 para entrenamiento/prueba.
# ============================================================
X = df_iris[iris.feature_names] # las 4 caracteristicas como predictoras
y = df_iris['target'] # la especie como variable objetivo
X_train, X_test, y_train, y_test = train_test_split(
X, y,
test_size=0.20, # 80% entrenamiento, 20% prueba
random_state=42, # semilla para reproducibilidad
stratify=y # mantener la proporcion de clases
)
print('Division de datos exitosa:')
print(f' Entrenamiento: {X_train.shape[0]} muestras')
print(f' Prueba : {X_test.shape[0]} muestras')
print(f'\nVerificacion de stratify en entrenamiento:')
print(y_train.value_counts().sort_index())
print(f'\nVerificacion de stratify en prueba:')
print(y_test.value_counts().sort_index())
print('\nLas clases mantienen proporciones iguales en ambos conjuntos.')
Division de datos exitosa: Entrenamiento: 120 muestras Prueba : 30 muestras Verificacion de stratify en entrenamiento: target 0 40 1 40 2 40 Name: count, dtype: int64 Verificacion de stratify en prueba: target 0 10 1 10 2 10 Name: count, dtype: int64 Las clases mantienen proporciones iguales en ambos conjuntos.
5. Modelo 1 — Regresion Logistica¶
Bonaccorso (2018, pp. 107-112) explica que la regresion logistica modela la probabilidad de pertenencia a cada clase mediante la funcion sigmoide. Aunque se llama "regresion", en realidad es un algoritmo de clasificacion.
# ============================================================
# MODELO 1: REGRESION LOGISTICA
#
# max_iter=5000: numero maximo de iteraciones de optimizacion.
# El profesor Muriel lo uso con este valor en clase.
# Si no converge con el valor por defecto (100), el modelo
# puede dar resultados suboptimos o un warning.
#
# fit(): entrena el modelo aprendiendo los coeficientes optimos
# predict(): genera predicciones sobre datos no vistos (test)
#
# Bonaccorso (2018, pp. 107-112)
# ============================================================
modelo_lr = LogisticRegression(max_iter=5000, random_state=42)
modelo_lr.fit(X_train, y_train)
print('Modelo de Regresion Logistica entrenado exitosamente.')
# Generar predicciones
y_pred_lr = modelo_lr.predict(X_test)
# Comparacion de predicciones vs valores reales
comparacion_lr = pd.DataFrame({
'Real': y_test.values,
'Predicho': y_pred_lr,
'Correcto': y_test.values == y_pred_lr
})
print('\nPrimeras 20 comparaciones (Real vs Predicho):')
print(comparacion_lr.head(20).to_string(index=False))
aciertos = comparacion_lr['Correcto'].sum()
total = len(comparacion_lr)
print(f'\nPredicciones correctas: {aciertos}/{total} ({aciertos/total*100:.1f}%)')
Modelo de Regresion Logistica entrenado exitosamente.
Primeras 20 comparaciones (Real vs Predicho):
Real Predicho Correcto
0 0 True
2 2 True
1 1 True
1 1 True
0 0 True
1 1 True
0 0 True
0 0 True
2 2 True
1 1 True
2 2 True
2 2 True
2 2 True
1 1 True
0 0 True
0 0 True
0 0 True
1 1 True
1 1 True
2 2 True
Predicciones correctas: 29/30 (96.7%)
# ============================================================
# METRICAS DE EVALUACION — REGRESION LOGISTICA
#
# Accuracy: proporcion total de predicciones correctas.
# Precision: de los que predijo como clase X, cuantos son realmente X.
# Recall: de los que realmente son clase X, cuantos detecto.
# F1-score: media armonica de precision y recall.
#
# Bonaccorso (2018, pp. 138-140) describe estas metricas y
# su importancia para evaluar modelos de clasificacion.
# ============================================================
print('REPORTE DE CLASIFICACION — Regresion Logistica')
print('=' * 55)
print(classification_report(y_test, y_pred_lr, target_names=iris.target_names))
acc_lr = accuracy_score(y_test, y_pred_lr)
print(f'Accuracy global: {acc_lr:.4f}')
# Matriz de confusion
fig, ax = plt.subplots(figsize=(6, 5))
ConfusionMatrixDisplay.from_predictions(y_test, y_pred_lr,
display_labels=iris.target_names,
cmap='Blues', ax=ax)
ax.set_title('Grafico 3. Matriz de Confusion — Regresion Logistica')
plt.tight_layout()
plt.show()
print('La regresion logistica clasifica correctamente la gran mayoria de las muestras.')
print('Los errores, si los hay, tienden a ocurrir entre versicolor y virginica.')
REPORTE DE CLASIFICACION — Regresion Logistica
=======================================================
precision recall f1-score support
setosa 1.00 1.00 1.00 10
versicolor 1.00 0.90 0.95 10
virginica 0.91 1.00 0.95 10
accuracy 0.97 30
macro avg 0.97 0.97 0.97 30
weighted avg 0.97 0.97 0.97 30
Accuracy global: 0.9667
La regresion logistica clasifica correctamente la gran mayoria de las muestras. Los errores, si los hay, tienden a ocurrir entre versicolor y virginica.
6. Modelo 2 — Arbol de Decision¶
Los arboles de decision dividen el espacio de caracteristicas en regiones rectangulares mediante preguntas binarias. El criterio de impureza de Gini mide la probabilidad de clasificar incorrectamente un elemento elegido al azar si se le asigna la clase segun la distribucion del nodo (Bonaccorso, 2018, pp. 113-118).
# ============================================================
# MODELO 2: ARBOL DE DECISION
#
# criterion='gini': criterio de impureza de Gini (por defecto).
# max_depth=4: limita la profundidad para evitar sobreajuste.
# Un arbol demasiado profundo memoriza los datos de entrenamiento
# (overfitting) y generaliza mal con datos nuevos.
# Campesato (2020, pp. 90-100) describe el overfitting.
#
# Bonaccorso (2018, pp. 113-118)
# ============================================================
modelo_dt = DecisionTreeClassifier(criterion='gini', max_depth=4, random_state=42)
modelo_dt.fit(X_train, y_train)
print('Modelo de Arbol de Decision entrenado exitosamente.')
# Predicciones
y_pred_dt = modelo_dt.predict(X_test)
comparacion_dt = pd.DataFrame({
'Real': y_test.values,
'Predicho': y_pred_dt,
'Correcto': y_test.values == y_pred_dt
})
print('\nPrimeras 20 comparaciones (Real vs Predicho):')
print(comparacion_dt.head(20).to_string(index=False))
aciertos_dt = comparacion_dt['Correcto'].sum()
print(f'\nPredicciones correctas: {aciertos_dt}/{total} ({aciertos_dt/total*100:.1f}%)')
Modelo de Arbol de Decision entrenado exitosamente.
Primeras 20 comparaciones (Real vs Predicho):
Real Predicho Correcto
0 0 True
2 2 True
1 1 True
1 1 True
0 0 True
1 1 True
0 0 True
0 0 True
2 2 True
1 1 True
2 2 True
2 2 True
2 2 True
1 1 True
0 0 True
0 0 True
0 0 True
1 1 True
1 1 True
2 2 True
Predicciones correctas: 28/30 (93.3%)
# ============================================================
# METRICAS DE EVALUACION — ARBOL DE DECISION
# ============================================================
print('REPORTE DE CLASIFICACION — Arbol de Decision')
print('=' * 55)
print(classification_report(y_test, y_pred_dt, target_names=iris.target_names))
acc_dt = accuracy_score(y_test, y_pred_dt)
print(f'Accuracy global: {acc_dt:.4f}')
# Matriz de confusion
fig, ax = plt.subplots(figsize=(6, 5))
ConfusionMatrixDisplay.from_predictions(y_test, y_pred_dt,
display_labels=iris.target_names,
cmap='Greens', ax=ax)
ax.set_title('Grafico 4. Matriz de Confusion — Arbol de Decision')
plt.tight_layout()
plt.show()
REPORTE DE CLASIFICACION — Arbol de Decision
=======================================================
precision recall f1-score support
setosa 1.00 1.00 1.00 10
versicolor 0.90 0.90 0.90 10
virginica 0.90 0.90 0.90 10
accuracy 0.93 30
macro avg 0.93 0.93 0.93 30
weighted avg 0.93 0.93 0.93 30
Accuracy global: 0.9333
# ============================================================
# VISUALIZACION DEL ARBOL DE DECISION
#
# Uno de los beneficios fundamentales de los arboles es su
# interpretabilidad: se puede ver exactamente que preguntas
# hace el modelo para clasificar cada muestra.
# (Bonaccorso, 2018, pp. 113-118)
# ============================================================
fig, ax = plt.subplots(figsize=(16, 8))
plot_tree(modelo_dt, filled=True, rounded=True,
feature_names=iris.feature_names,
class_names=iris.target_names,
fontsize=9, ax=ax)
ax.set_title('Grafico 5. Arbol de Decision — Dataset Iris', fontsize=14)
plt.tight_layout()
plt.show()
print('La primera decision del arbol se basa en la longitud del petalo.')
print('Si petalo <= 2.45 cm, la flor es setosa con 100% de certeza.')
La primera decision del arbol se basa en la longitud del petalo. Si petalo <= 2.45 cm, la flor es setosa con 100% de certeza.
7. Comparacion de Modelos (Parte I)¶
# ============================================================
# COMPARACION DE MODELOS
#
# Ozdemir y Turanli (2024, pp. 11-12) comparan modelos de
# clasificacion usando accuracy, precision, recall y F1
# como criterios para decidir cual modelo es mejor.
# ============================================================
comparacion = pd.DataFrame({
'Modelo': ['Regresion Logistica', 'Arbol de Decision'],
'Accuracy': [accuracy_score(y_test, y_pred_lr), accuracy_score(y_test, y_pred_dt)],
'Precision (macro)': [
precision_score(y_test, y_pred_lr, average='macro'),
precision_score(y_test, y_pred_dt, average='macro')
],
'Recall (macro)': [
recall_score(y_test, y_pred_lr, average='macro'),
recall_score(y_test, y_pred_dt, average='macro')
],
'F1-Score (macro)': [
f1_score(y_test, y_pred_lr, average='macro'),
f1_score(y_test, y_pred_dt, average='macro')
]
})
print('COMPARACION DE MODELOS — Dataset Iris')
print('=' * 65)
print(comparacion.to_string(index=False))
print('=' * 65)
# Grafico de barras comparativo
fig, ax = plt.subplots(figsize=(10, 5))
metrics = ['Accuracy', 'Precision (macro)', 'Recall (macro)', 'F1-Score (macro)']
x = np.arange(len(metrics))
w = 0.35
bars1 = ax.bar(x - w/2, comparacion[metrics].iloc[0], w, label='Reg. Logistica', color='steelblue', alpha=0.8)
bars2 = ax.bar(x + w/2, comparacion[metrics].iloc[1], w, label='Arbol Decision', color='seagreen', alpha=0.8)
ax.set_ylabel('Puntuacion')
ax.set_title('Grafico 6. Comparacion de Metricas entre Modelos — Iris')
ax.set_xticks(x)
ax.set_xticklabels(metrics, rotation=15, ha='right')
ax.set_ylim(0, 1.15)
ax.legend()
ax.bar_label(bars1, fmt='%.3f', padding=2, fontsize=8)
ax.bar_label(bars2, fmt='%.3f', padding=2, fontsize=8)
plt.tight_layout()
plt.show()
COMPARACION DE MODELOS — Dataset Iris
=================================================================
Modelo Accuracy Precision (macro) Recall (macro) F1-Score (macro)
Regresion Logistica 0.966667 0.969697 0.966667 0.966583
Arbol de Decision 0.933333 0.933333 0.933333 0.933333
=================================================================
Parte II — Estudio de Caso: ML en el Sector Medico¶
8. Objeto de Estudio¶
El cancer de mama es una de las principales causas de mortalidad en mujeres a nivel mundial. La deteccion temprana mediante biopsia permite determinar si un tumor es benigno o maligno, pero el diagnostico depende de la interpretacion manual de imagenes celulares. Los modelos de machine learning pueden asistir a los patologos automatizando la clasificacion de tumores a partir de mediciones numericas de las celulas, reduciendo el error humano y acelerando el proceso diagnostico.
El dataset Breast Cancer Wisconsin contiene 569 muestras con 30 caracteristicas de nucleos celulares derivadas de biopsias por aspiracion con aguja fina (FNA). El objetivo es clasificar los tumores como benignos (357 muestras) o malignos (212 muestras) sin usar el ID del paciente ni la columna de diagnostico como predictor.
9. Actores y Contextos¶
- Pacientes: mujeres que se someten a biopsias por sospecha de cancer de mama.
- Medicos/Patologos: profesionales que interpretan las biopsias y toman decisiones clinicas.
- Investigadores: Wolberg, Street y Mangasarian (UCI), quienes recolectaron y publicaron el dataset.
- Contexto clinico: la clasificacion erronea de un tumor maligno como benigno (falso negativo) tiene consecuencias potencialmente fatales, por lo que el recall para la clase maligna es la metrica mas critica.
10. Objetivos del Estudio de Caso¶
- Aplicar dos modelos de clasificacion supervisada para predecir si un tumor es benigno o maligno.
- Evaluar los modelos con metricas de clasificacion, priorizando el recall para minimizar falsos negativos.
- Comparar el desempeno de los modelos y seleccionar el mas adecuado para el contexto medico.
11. Carga y Analisis Descriptivo del Dataset¶
# ============================================================
# DATASET BREAST CANCER WISCONSIN
#
# 569 muestras, 30 caracteristicas numericas derivadas de
# imagenes digitalizadas de biopsias FNA de mama.
# Clases: 0 = maligno (212), 1 = benigno (357)
#
# Como indica la actividad, se excluyen 'id' y 'diagnosis'
# del conjunto de predictores.
# ============================================================
cancer = load_breast_cancer()
df_cancer = pd.DataFrame(data=cancer.data, columns=cancer.feature_names)
df_cancer['diagnosis'] = cancer.target
df_cancer['tipo'] = df_cancer['diagnosis'].map({0: 'Maligno', 1: 'Benigno'})
print('Dataset Breast Cancer Wisconsin')
print(f'Dimensiones: {df_cancer.shape[0]} muestras x {df_cancer.shape[1]} columnas')
print(f'\nDistribucion de clases:')
print(df_cancer['tipo'].value_counts())
print(f'\nPorcentaje benigno: {(cancer.target.sum()/len(cancer.target)*100):.1f}%')
print(f'Porcentaje maligno: {(1 - cancer.target.sum()/len(cancer.target))*100:.1f}%')
print(f'\nVerificacion de valores faltantes:')
print(f'Total nulos: {df_cancer.isnull().sum().sum()}')
print(f'\nEstadisticas descriptivas (primeras 10 variables):')
print(df_cancer.iloc[:, :10].describe().round(3))
Dataset Breast Cancer Wisconsin
Dimensiones: 569 muestras x 32 columnas
Distribucion de clases:
tipo
Benigno 357
Maligno 212
Name: count, dtype: int64
Porcentaje benigno: 62.7%
Porcentaje maligno: 37.3%
Verificacion de valores faltantes:
Total nulos: 0
Estadisticas descriptivas (primeras 10 variables):
mean radius mean texture mean perimeter mean area mean smoothness \
count 569.000 569.000 569.000 569.000 569.000
mean 14.127 19.290 91.969 654.889 0.096
std 3.524 4.301 24.299 351.914 0.014
min 6.981 9.710 43.790 143.500 0.053
25% 11.700 16.170 75.170 420.300 0.086
50% 13.370 18.840 86.240 551.100 0.096
75% 15.780 21.800 104.100 782.700 0.105
max 28.110 39.280 188.500 2501.000 0.163
mean compactness mean concavity mean concave points mean symmetry \
count 569.000 569.000 569.000 569.000
mean 0.104 0.089 0.049 0.181
std 0.053 0.080 0.039 0.027
min 0.019 0.000 0.000 0.106
25% 0.065 0.030 0.020 0.162
50% 0.093 0.062 0.034 0.179
75% 0.130 0.131 0.074 0.196
max 0.345 0.427 0.201 0.304
mean fractal dimension
count 569.000
mean 0.063
std 0.007
min 0.050
25% 0.058
50% 0.062
75% 0.066
max 0.097
# ============================================================
# VISUALIZACION EXPLORATORIA — BREAST CANCER
# ============================================================
fig, axes = plt.subplots(1, 3, figsize=(16, 5))
# Distribucion de clases
df_cancer['tipo'].value_counts().plot.bar(ax=axes[0], color=['crimson','steelblue'], alpha=0.8)
axes[0].set_title('Grafico 7. Distribucion de Diagnosticos')
axes[0].set_ylabel('Cantidad')
axes[0].set_xticklabels(['Benigno','Maligno'], rotation=0)
# Boxplot de las 2 variables mas discriminantes
for i, var in enumerate(['mean radius', 'mean concavity']):
sns.boxplot(x='tipo', y=var, data=df_cancer, ax=axes[i+1],
palette=['crimson','steelblue'])
axes[i+1].set_title(f'Grafico {8+i}. {var.title()} por Diagnostico')
plt.tight_layout()
plt.show()
print('Los tumores malignos tienden a tener mayor radio medio y mayor concavidad.')
print('Estas diferencias visibles sugieren que un clasificador puede distinguir ambas clases.')
Los tumores malignos tienden a tener mayor radio medio y mayor concavidad. Estas diferencias visibles sugieren que un clasificador puede distinguir ambas clases.
12. Procesamiento de Datos¶
# ============================================================
# PREPROCESAMIENTO
#
# 1. Separar variables predictoras (X) de la variable objetivo (y)
# 2. Dividir en entrenamiento (80%) y prueba (20%) con stratify
# 3. Escalar las caracteristicas con StandardScaler
#
# La estandarizacion (media=0, std=1) es critica para algoritmos
# sensibles a la escala como SVM y regresion logistica.
# Lokanan et al. (2019, pp. 7-9) describen la normalizacion.
# ============================================================
X_cancer = df_cancer[cancer.feature_names]
y_cancer = df_cancer['diagnosis']
X_train_c, X_test_c, y_train_c, y_test_c = train_test_split(
X_cancer, y_cancer,
test_size=0.20,
random_state=42,
stratify=y_cancer
)
# Estandarizacion
scaler = StandardScaler()
X_train_cs = scaler.fit_transform(X_train_c)
X_test_cs = scaler.transform(X_test_c)
print('Procesamiento completado:')
print(f' Entrenamiento: {X_train_c.shape[0]} muestras')
print(f' Prueba : {X_test_c.shape[0]} muestras')
print(f'\nVerificacion de stratify:')
print(f' Entrenamiento - Benigno: {(y_train_c==1).sum()}, Maligno: {(y_train_c==0).sum()}')
print(f' Prueba - Benigno: {(y_test_c==1).sum()}, Maligno: {(y_test_c==0).sum()}')
Procesamiento completado: Entrenamiento: 455 muestras Prueba : 114 muestras Verificacion de stratify: Entrenamiento - Benigno: 285, Maligno: 170 Prueba - Benigno: 72, Maligno: 42
13. Modelo A — Regresion Logistica (Tumores)¶
# ============================================================
# MODELO A: REGRESION LOGISTICA PARA TUMORES
#
# En el contexto medico, un falso negativo (clasificar maligno
# como benigno) es mas peligroso que un falso positivo.
# Por eso el recall de la clase maligna (0) es la metrica
# mas importante para seleccionar el modelo.
# ============================================================
modelo_lr_c = LogisticRegression(max_iter=5000, random_state=42)
modelo_lr_c.fit(X_train_cs, y_train_c)
y_pred_lr_c = modelo_lr_c.predict(X_test_cs)
print('REPORTE DE CLASIFICACION — Regresion Logistica (Tumores)')
print('=' * 55)
print(classification_report(y_test_c, y_pred_lr_c, target_names=['Maligno','Benigno']))
acc_lr_c = accuracy_score(y_test_c, y_pred_lr_c)
print(f'Accuracy global: {acc_lr_c:.4f}')
fig, ax = plt.subplots(figsize=(6, 5))
ConfusionMatrixDisplay.from_predictions(y_test_c, y_pred_lr_c,
display_labels=['Maligno','Benigno'],
cmap='Reds', ax=ax)
ax.set_title('Grafico 10. Matriz de Confusion — Reg. Logistica (Tumores)')
plt.tight_layout()
plt.show()
REPORTE DE CLASIFICACION — Regresion Logistica (Tumores)
=======================================================
precision recall f1-score support
Maligno 0.98 0.98 0.98 42
Benigno 0.99 0.99 0.99 72
accuracy 0.98 114
macro avg 0.98 0.98 0.98 114
weighted avg 0.98 0.98 0.98 114
Accuracy global: 0.9825
14. Modelo B — Random Forest (Tumores)¶
Random Forest construye multiples arboles de decision y promedia sus predicciones mediante votacion por mayoria, lo que reduce el overfitting inherente a un arbol individual (Bonaccorso, 2018, pp. 119-123).
# ============================================================
# MODELO B: RANDOM FOREST PARA TUMORES
#
# n_estimators=100: numero de arboles en el ensamble.
# Mas arboles generalmente dan mejores resultados, pero
# aumentan el costo computacional.
#
# Bonaccorso (2018, pp. 119-123)
# ============================================================
modelo_rf_c = RandomForestClassifier(n_estimators=100, random_state=42)
modelo_rf_c.fit(X_train_cs, y_train_c)
y_pred_rf_c = modelo_rf_c.predict(X_test_cs)
print('REPORTE DE CLASIFICACION — Random Forest (Tumores)')
print('=' * 55)
print(classification_report(y_test_c, y_pred_rf_c, target_names=['Maligno','Benigno']))
acc_rf_c = accuracy_score(y_test_c, y_pred_rf_c)
print(f'Accuracy global: {acc_rf_c:.4f}')
fig, ax = plt.subplots(figsize=(6, 5))
ConfusionMatrixDisplay.from_predictions(y_test_c, y_pred_rf_c,
display_labels=['Maligno','Benigno'],
cmap='Purples', ax=ax)
ax.set_title('Grafico 11. Matriz de Confusion — Random Forest (Tumores)')
plt.tight_layout()
plt.show()
REPORTE DE CLASIFICACION — Random Forest (Tumores)
=======================================================
precision recall f1-score support
Maligno 0.95 0.93 0.94 42
Benigno 0.96 0.97 0.97 72
accuracy 0.96 114
macro avg 0.96 0.95 0.95 114
weighted avg 0.96 0.96 0.96 114
Accuracy global: 0.9561
15. Evaluacion Comparativa y Seleccion del Modelo¶
# ============================================================
# COMPARACION FINAL DE MODELOS — TUMORES
# ============================================================
comp_cancer = pd.DataFrame({
'Modelo': ['Regresion Logistica', 'Random Forest'],
'Accuracy': [accuracy_score(y_test_c, y_pred_lr_c),
accuracy_score(y_test_c, y_pred_rf_c)],
'Precision (macro)': [
precision_score(y_test_c, y_pred_lr_c, average='macro'),
precision_score(y_test_c, y_pred_rf_c, average='macro')
],
'Recall (macro)': [
recall_score(y_test_c, y_pred_lr_c, average='macro'),
recall_score(y_test_c, y_pred_rf_c, average='macro')
],
'F1-Score (macro)': [
f1_score(y_test_c, y_pred_lr_c, average='macro'),
f1_score(y_test_c, y_pred_rf_c, average='macro')
],
'Recall Maligno': [
recall_score(y_test_c, y_pred_lr_c, pos_label=0),
recall_score(y_test_c, y_pred_rf_c, pos_label=0)
]
})
print('COMPARACION DE MODELOS — Clasificacion de Tumores')
print('=' * 75)
print(comp_cancer.to_string(index=False))
print('=' * 75)
# Grafico comparativo
fig, ax = plt.subplots(figsize=(12, 5))
metrics_c = ['Accuracy', 'Precision (macro)', 'Recall (macro)', 'F1-Score (macro)', 'Recall Maligno']
x = np.arange(len(metrics_c))
w = 0.35
bars1 = ax.bar(x - w/2, comp_cancer[metrics_c].iloc[0], w, label='Reg. Logistica', color='steelblue', alpha=0.8)
bars2 = ax.bar(x + w/2, comp_cancer[metrics_c].iloc[1], w, label='Random Forest', color='purple', alpha=0.6)
ax.set_ylabel('Puntuacion')
ax.set_title('Grafico 12. Comparacion de Modelos — Clasificacion de Tumores')
ax.set_xticks(x)
ax.set_xticklabels(metrics_c, rotation=15, ha='right')
ax.set_ylim(0, 1.15)
ax.legend()
ax.bar_label(bars1, fmt='%.3f', padding=2, fontsize=8)
ax.bar_label(bars2, fmt='%.3f', padding=2, fontsize=8)
plt.tight_layout()
plt.show()
print('\nEn el contexto medico, el Recall Maligno es la metrica decisiva.')
print('Un falso negativo (tumor maligno clasificado como benigno) puede costar una vida.')
COMPARACION DE MODELOS — Clasificacion de Tumores
===========================================================================
Modelo Accuracy Precision (macro) Recall (macro) F1-Score (macro) Recall Maligno
Regresion Logistica 0.982456 0.981151 0.981151 0.981151 0.976190
Random Forest 0.956140 0.955062 0.950397 0.952638 0.928571
===========================================================================
En el contexto medico, el Recall Maligno es la metrica decisiva. Un falso negativo (tumor maligno clasificado como benigno) puede costar una vida.
# ============================================================
# IMPORTANCIA DE CARACTERISTICAS — RANDOM FOREST
#
# Random Forest permite calcular la importancia relativa
# de cada variable para la prediccion. Esto tiene valor
# clinico: indica que mediciones de la biopsia son mas
# informativas para distinguir tumores benignos de malignos.
# (Bonaccorso, 2018, pp. 119-123)
# ============================================================
importancias = pd.DataFrame({
'Variable': cancer.feature_names,
'Importancia': modelo_rf_c.feature_importances_
}).sort_values('Importancia', ascending=False).head(10)
fig, ax = plt.subplots(figsize=(10, 5))
ax.barh(importancias['Variable'], importancias['Importancia'], color='purple', alpha=0.7)
ax.set_xlabel('Importancia relativa')
ax.set_title('Grafico 13. Top 10 Variables mas Importantes (Random Forest)')
ax.invert_yaxis()
plt.tight_layout()
plt.show()
print(f'Variable mas importante: {importancias.iloc[0]["Variable"]} ({importancias.iloc[0]["Importancia"]:.4f})')
print('Esta informacion puede guiar protocolos clinicos de priorizacion de mediciones.')
Variable mas importante: worst area (0.1400) Esta informacion puede guiar protocolos clinicos de priorizacion de mediciones.
16. Conclusiones¶
Este trabajo me permitio experimentar en primera persona la diferencia entre entrenar un modelo de clasificacion multiclase (Iris) y un modelo de clasificacion binaria con consecuencias reales (tumores). La leccion mas importante no fue tecnica sino etica: en el ambito medico, una metrica global como el accuracy puede ser enganosa. Un modelo con 95 por ciento de accuracy puede estar fallando precisamente en los casos que mas importan, los tumores malignos no detectados.
En el sistema judicial donde trabajo, ocurre algo parecido. Un modelo que prediga correctamente el 90 por ciento de los casos puede estar fallando sistematicamente en las tutelas urgentes que involucran derechos fundamentales. Por eso Bonaccorso (2018, pp. 138-140) insiste en no limitarse al accuracy y evaluar precision, recall y F1-score por separado para cada clase.
La comparacion entre regresion logistica y Random Forest mostro que ambos modelos logran excelentes resultados en estos datasets bien preparados. Sin embargo, en datos reales con ruido, valores faltantes y desbalance de clases, la seleccion del modelo y el preprocesamiento se vuelven criticos. El profesor Muriel lo resumio bien en clase: el modelo es solo tan bueno como los datos con los que se entrena.
El uso de stratify en la division de datos fue un detalle que parece menor pero marca la diferencia. Sin el, la distribucion desigual de clases en entrenamiento y prueba puede inflar o deflactar artificialmente las metricas, como lo discutio el profesor en la sesion del 21 de febrero.
Finalmente, la importancia de caracteristicas del Random Forest tiene valor clinico directo: saber que el peor radio y la peor concavidad son las variables mas discriminantes puede orientar futuras investigaciones sobre biomarcadores para la deteccion temprana del cancer de mama.
17. Referencias (Normas APA, 7.a edicion)¶
Bonaccorso, G. (2018). Machine learning algorithms: Popular algorithms for data science and machine learning (pp. 104-140). Packt Publishing.
Cady, F. (2017). The data science handbook (pp. 165-170). John Wiley & Sons.
Campesato, O. (2020). Artificial intelligence, machine learning, and deep learning (pp. 23-57). Mercury Learning.
Lokanan, M., Tran, V., y Vuong, N. H. (2019). Detecting anomalies in financial statements using machine learning algorithm: The case of Vietnamese listed firms. Asian Journal of Accounting Research, 4(2), 181-201. https://doi.org/10.1108/AJAR-09-2018-0032
Ozdemir, R. y Turanli, M. (2024). Comparison and evaluation of classification algorithms for customer purchase prediction. Journal of Business and Management Studies, 6, 59-73.
Pedregosa, F., Varoquaux, G., Gramfort, A., et al. (2011). Scikit-learn: Machine learning in Python. Journal of Machine Learning Research, 12, 2825-2830.
Wolberg, W. H., Street, W. N., y Mangasarian, O. L. (1995). Breast Cancer Wisconsin (Diagnostic) Dataset. UCI Machine Learning Repository. https://archive.ics.uci.edu/ml/datasets/breast+cancer+wisconsin+(diagnostic)