Agregando Proyecto Final

This commit is contained in:
2026-06-24 07:43:49 +00:00
parent 1f4893730a
commit 5ba20605c3
9 changed files with 1507 additions and 0 deletions

87
proyecto-final/README.md Normal file
View File

@@ -0,0 +1,87 @@
# Clasificador Multiclase — Dry Bean Dataset
**Curso:** Fundamentos de Machine Learning
**Nombre:** Alejandro Lembke Barrientos | Carné: 12002840
**Dataset:** Dry Bean Dataset (UCI ID 602) — 7 clases, 16 features morfológicas, 13,611 instancias
**Repo:** https://gitea.p-lao.com/aleleba/Machine_Learning_Aplicado
## Objetivo
Clasificar automáticamente variedades de frijol seco (BARBUNYA, BOMBAY, CALI, DERMASON, HOROZ, SEKER, SIRA) a partir de 16 medidas morfológicas extraídas de imágenes. El problema replica un sistema de control de calidad agrícola automatizado.
Se comparan tres familias de modelos (Random Forest, XGBoost, Red Neuronal) con ≥5 experimentos cada una, y se construye un ensamble por majority voting con bootstrap.
## Setup
```bash
pip install -r requirements.txt
```
El dataset se descarga automáticamente desde UCI vía `ucimlrepo` — no requiere ningún archivo local.
## Estructura
```
proyecto-final/
├── dry_bean_classifier.ipynb # único notebook entregable
├── outputs/
│ ├── bitacora_experimentos.csv # log append de todos los experimentos
│ └── figures/ # gráficas de feature importance
├── src/
│ └── utils.py # metricas(), registrar(), plot_importancia()
└── requirements.txt
```
## Secciones del Notebook
| # | Sección | Descripción |
|---|---------|-------------|
| 1 | Setup & Imports | Librerías, `RANDOM_STATE = 42`, `fetch_ucirepo` |
| 2 | Carga y Exploración | `fetch_ucirepo(id=602)`, distribución de clases, estadísticas |
| 3 | Preparación de Datos | Split 80/20 estratificado + `StandardScaler` para la NN |
| 4 | Bitácora — Funciones | `metricas()` y `registrar()` (helpers en `src/utils.py`) |
| 5 | Experimentos — Random Forest | ≥5 experimentos variando `n_estimators`, `max_depth`, `min_samples_leaf`, `max_features`, `criterion` |
| 6 | Experimentos — XGBoost | ≥5 experimentos variando `n_estimators`, `max_depth`, `learning_rate`, `subsample`, `colsample_bytree`, `reg_lambda` |
| 7 | Experimentos — Red Neuronal (Keras) | ≥5 experimentos variando capas, neuronas, `dropout`, `lr`, `batch_size`, `epochs` |
| 8 | Feature Importance | `mejor_rf` vs `mejor_xgb` — barplots + comparación de rankings |
| 9 | Ensamble | Majority voting + bootstrap; NN usa features escaladas |
| 10 | Tabla Comparativa | F1 train/test de los 4 modelos + Δ + diagnóstico |
| 11 | Conclusiones | Resumen automático basado en resultados reales |
## Archivos del Proyecto
| Archivo | Descripción |
|---------|-------------|
| [dry_bean_classifier.ipynb](dry_bean_classifier.ipynb) | Notebook principal con todo el código |
| [outputs/bitacora_experimentos.csv](outputs/bitacora_experimentos.csv) | Bitácora de todos los experimentos (append) |
| [outputs/figures/feature_importance__random_forest.png](outputs/figures/feature_importance__random_forest.png) | Feature importance — Random Forest |
| [outputs/figures/feature_importance__xgboost.png](outputs/figures/feature_importance__xgboost.png) | Feature importance — XGBoost |
| [src/utils.py](src/utils.py) | Helpers: `metricas()`, `registrar()`, `plot_importancia()` |
| [requirements.txt](requirements.txt) | Dependencias del proyecto |
---
## Tabla Comparativa Final
| Modelo | F1 train | F1 test | Δ | Diagnóstico |
|--------|----------|---------|---|-------------|
| Random Forest | 0.9739 | 0.9280 | 0.0459 | Bien ajustado |
| XGBoost | 0.9779 | 0.9327 | 0.0452 | Bien ajustado |
| **Red Neuronal (Keras)** | **0.9565** | **0.9404** | **0.0161** | **Bien ajustado** |
| Ensamble (majority voting) | 0.9754 | 0.9361 | 0.0393 | Bien ajustado |
**Mejor modelo individual:** Red Neuronal (Keras) con F1 test = **0.9404**
---
## Conclusiones
1. **Mejor modelo:** La Red Neuronal (Keras + StandardScaler) obtuvo el mayor F1 en test (0.9404), superando a XGBoost (0.9327) y Random Forest (0.9280).
2. **Ensamble:** El majority voting con bootstrap obtuvo F1 test = 0.9361, sin superar al mejor modelo individual (Δ = 0.0043). Los tres modelos votaron de forma coherente en la mayoría de casos.
3. **Diagnóstico:** Los cuatro modelos quedaron bien ajustados. La NN tuvo el menor Δ (0.0161), lo que indica muy buena generalización sin overfitting.
4. **Feature Importance:** ShapeFactor3 fue la feature más importante para RF y XGBoost. ShapeFactor4, Solidity y Extent coincidieron como las menos relevantes. Mayor discrepancia en Perimeter (RF rank 2 vs XGB rank 9).
5. **Aprendizaje clave:** La Red Neuronal (Keras) con `StandardScaler` superó a los modelos basados en árboles cuando las features están correctamente escaladas. El escalado es crítico para el desempeño de redes neuronales con datos de magnitudes muy distintas.

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,16 @@
experimento_id,modelo,hiperparametros,arquitectura,precision_train,recall_train,f1_train,accuracy_train,precision_test,recall_test,f1_test,accuracy_test
RF_01,random_forest,"{'n_estimators': 100, 'max_depth': None, 'min_samples_leaf': 1, 'max_features': 'sqrt', 'criterion': 'gini'}",,1.0,1.0,1.0,1.0,0.9349176812697626,0.9308725039707921,0.9327724377144396,0.9203084832904884
RF_02,random_forest,"{'n_estimators': 200, 'max_depth': 10, 'min_samples_leaf': 1, 'max_features': 'sqrt', 'criterion': 'gini'}",,0.9731775711190289,0.9686270125964206,0.9707547781581585,0.965191036002939,0.9347906630645412,0.9294541680440085,0.9319017166110111,0.9199412412780023
RF_03,random_forest,"{'n_estimators': 200, 'max_depth': 20, 'min_samples_leaf': 2, 'max_features': 'sqrt', 'criterion': 'gini'}",,0.9903948234299282,0.9886133978226317,0.9894877918994095,0.9880602498163116,0.9334076449913921,0.9290154634490754,0.9310578989186767,0.9188395152405435
RF_04,random_forest,"{'n_estimators': 500, 'max_depth': None, 'min_samples_leaf': 5, 'max_features': 'log2', 'criterion': 'gini'}",,0.9698886043179353,0.9669393350582488,0.9683599857843987,0.9636296840558413,0.9337158223094045,0.9298809359586047,0.931652433121954,0.9192067572530297
RF_05,random_forest,"{'n_estimators': 300, 'max_depth': None, 'min_samples_leaf': 1, 'max_features': 'sqrt', 'criterion': 'entropy'}",,1.0,1.0,1.0,1.0,0.9334557081244913,0.9299161263046146,0.9315722997775326,0.9199412412780023
XGB_01,xgboost,"{'n_estimators': 100, 'max_depth': 6, 'learning_rate': 0.3, 'subsample': 1.0, 'colsample_bytree': 1.0, 'reg_lambda': 1}",,0.9999496627403605,0.9999322630901578,0.9999409504488485,0.9999081557678178,0.9381833017622513,0.9345106923717922,0.9362721257358918,0.9232464193903782
XGB_02,xgboost,"{'n_estimators': 200, 'max_depth': 6, 'learning_rate': 0.1, 'subsample': 0.8, 'colsample_bytree': 0.8, 'reg_lambda': 1}",,0.9995124631182911,0.9993828425668196,0.9994472778499259,0.9991734019103601,0.9400145643891352,0.9355998108933601,0.9377337700729521,0.9258171134777818
XGB_03,xgboost,"{'n_estimators': 300, 'max_depth': 4, 'learning_rate': 0.05, 'subsample': 0.8, 'colsample_bytree': 0.8, 'reg_lambda': 2}",,0.9738431057592221,0.9714270969690408,0.972586594741007,0.964731814842028,0.9390495539072736,0.935051375447481,0.9369779245757606,0.9239809034153507
XGB_04,xgboost,"{'n_estimators': 200, 'max_depth': 8, 'learning_rate': 0.1, 'subsample': 0.6, 'colsample_bytree': 0.6, 'reg_lambda': 5}",,0.9979483687785702,0.9974251802181292,0.9976852599170984,0.9971528288023512,0.9360823265498792,0.9320238330654388,0.9339747695658638,0.9206757253029747
XGB_05,xgboost,"{'n_estimators': 500, 'max_depth': 6, 'learning_rate': 0.01, 'subsample': 0.8, 'colsample_bytree': 0.8, 'reg_lambda': 1}",,0.9711283975656463,0.968488525587758,0.9697589584183979,0.960966201322557,0.9388577890936005,0.9344234986407992,0.9365260895250824,0.9243481454278369
NN_01,neural_network,"{'capas': (64,), 'activacion': 'relu', 'dropout': 0.0, 'lr': 0.001, 'batch_size': 32, 'epochs': 50}",[64]→softmax,0.9514075483337615,0.9497502280290414,0.9505399462571212,0.9393828067597355,0.9382200228163496,0.9367635261121906,0.9373738091629885,0.9265515975027543
NN_02,neural_network,"{'capas': (128, 64), 'activacion': 'relu', 'dropout': 0.3, 'lr': 0.001, 'batch_size': 64, 'epochs': 100}","[128,64]+Dropout(0.3)→softmax",0.9544504646790793,0.951693687099693,0.95297564727531,0.942505510653931,0.942351208286615,0.9395367498811359,0.9408371424891058,0.9302240176276166
NN_03,neural_network,"{'capas': (256, 128, 64), 'activacion': 'relu', 'dropout': 0.3, 'lr': 0.001, 'batch_size': 64, 'epochs': 100}","[256,128,64]+Dropout(0.3)→softmax",0.9596197331839835,0.9560282590407893,0.9576542667718927,0.9473732549595886,0.9403300598776779,0.9375579241769321,0.9387617954675829,0.9283878075651855
NN_04,neural_network,"{'capas': (128, 64), 'activacion': 'relu', 'dropout': 0.2, 'lr': 0.0001, 'batch_size': 32, 'epochs': 150}","[128,64]+Dropout(0.2)→softmax(lr=0.0001)",0.9515492560718786,0.9492242953999462,0.950294386638384,0.9401175606171932,0.9396175891521139,0.9377347900370906,0.9386047132899191,0.9280205655526992
NN_05,neural_network,"{'capas': (256, 128), 'activacion': 'relu', 'dropout': 0.4, 'lr': 0.001, 'batch_size': 128, 'epochs': 100}","[256,128]+Dropout(0.4)→softmax",0.9554800613552519,0.9537600530164673,0.9545517843251645,0.9441587068332109,0.9395108985072946,0.9372280998211393,0.9382606172667207,0.9272860815277267
1 experimento_id modelo hiperparametros arquitectura precision_train recall_train f1_train accuracy_train precision_test recall_test f1_test accuracy_test
2 RF_01 random_forest {'n_estimators': 100, 'max_depth': None, 'min_samples_leaf': 1, 'max_features': 'sqrt', 'criterion': 'gini'} 1.0 1.0 1.0 1.0 0.9349176812697626 0.9308725039707921 0.9327724377144396 0.9203084832904884
3 RF_02 random_forest {'n_estimators': 200, 'max_depth': 10, 'min_samples_leaf': 1, 'max_features': 'sqrt', 'criterion': 'gini'} 0.9731775711190289 0.9686270125964206 0.9707547781581585 0.965191036002939 0.9347906630645412 0.9294541680440085 0.9319017166110111 0.9199412412780023
4 RF_03 random_forest {'n_estimators': 200, 'max_depth': 20, 'min_samples_leaf': 2, 'max_features': 'sqrt', 'criterion': 'gini'} 0.9903948234299282 0.9886133978226317 0.9894877918994095 0.9880602498163116 0.9334076449913921 0.9290154634490754 0.9310578989186767 0.9188395152405435
5 RF_04 random_forest {'n_estimators': 500, 'max_depth': None, 'min_samples_leaf': 5, 'max_features': 'log2', 'criterion': 'gini'} 0.9698886043179353 0.9669393350582488 0.9683599857843987 0.9636296840558413 0.9337158223094045 0.9298809359586047 0.931652433121954 0.9192067572530297
6 RF_05 random_forest {'n_estimators': 300, 'max_depth': None, 'min_samples_leaf': 1, 'max_features': 'sqrt', 'criterion': 'entropy'} 1.0 1.0 1.0 1.0 0.9334557081244913 0.9299161263046146 0.9315722997775326 0.9199412412780023
7 XGB_01 xgboost {'n_estimators': 100, 'max_depth': 6, 'learning_rate': 0.3, 'subsample': 1.0, 'colsample_bytree': 1.0, 'reg_lambda': 1} 0.9999496627403605 0.9999322630901578 0.9999409504488485 0.9999081557678178 0.9381833017622513 0.9345106923717922 0.9362721257358918 0.9232464193903782
8 XGB_02 xgboost {'n_estimators': 200, 'max_depth': 6, 'learning_rate': 0.1, 'subsample': 0.8, 'colsample_bytree': 0.8, 'reg_lambda': 1} 0.9995124631182911 0.9993828425668196 0.9994472778499259 0.9991734019103601 0.9400145643891352 0.9355998108933601 0.9377337700729521 0.9258171134777818
9 XGB_03 xgboost {'n_estimators': 300, 'max_depth': 4, 'learning_rate': 0.05, 'subsample': 0.8, 'colsample_bytree': 0.8, 'reg_lambda': 2} 0.9738431057592221 0.9714270969690408 0.972586594741007 0.964731814842028 0.9390495539072736 0.935051375447481 0.9369779245757606 0.9239809034153507
10 XGB_04 xgboost {'n_estimators': 200, 'max_depth': 8, 'learning_rate': 0.1, 'subsample': 0.6, 'colsample_bytree': 0.6, 'reg_lambda': 5} 0.9979483687785702 0.9974251802181292 0.9976852599170984 0.9971528288023512 0.9360823265498792 0.9320238330654388 0.9339747695658638 0.9206757253029747
11 XGB_05 xgboost {'n_estimators': 500, 'max_depth': 6, 'learning_rate': 0.01, 'subsample': 0.8, 'colsample_bytree': 0.8, 'reg_lambda': 1} 0.9711283975656463 0.968488525587758 0.9697589584183979 0.960966201322557 0.9388577890936005 0.9344234986407992 0.9365260895250824 0.9243481454278369
12 NN_01 neural_network {'capas': (64,), 'activacion': 'relu', 'dropout': 0.0, 'lr': 0.001, 'batch_size': 32, 'epochs': 50} [64]→softmax 0.9514075483337615 0.9497502280290414 0.9505399462571212 0.9393828067597355 0.9382200228163496 0.9367635261121906 0.9373738091629885 0.9265515975027543
13 NN_02 neural_network {'capas': (128, 64), 'activacion': 'relu', 'dropout': 0.3, 'lr': 0.001, 'batch_size': 64, 'epochs': 100} [128,64]+Dropout(0.3)→softmax 0.9544504646790793 0.951693687099693 0.95297564727531 0.942505510653931 0.942351208286615 0.9395367498811359 0.9408371424891058 0.9302240176276166
14 NN_03 neural_network {'capas': (256, 128, 64), 'activacion': 'relu', 'dropout': 0.3, 'lr': 0.001, 'batch_size': 64, 'epochs': 100} [256,128,64]+Dropout(0.3)→softmax 0.9596197331839835 0.9560282590407893 0.9576542667718927 0.9473732549595886 0.9403300598776779 0.9375579241769321 0.9387617954675829 0.9283878075651855
15 NN_04 neural_network {'capas': (128, 64), 'activacion': 'relu', 'dropout': 0.2, 'lr': 0.0001, 'batch_size': 32, 'epochs': 150} [128,64]+Dropout(0.2)→softmax(lr=0.0001) 0.9515492560718786 0.9492242953999462 0.950294386638384 0.9401175606171932 0.9396175891521139 0.9377347900370906 0.9386047132899191 0.9280205655526992
16 NN_05 neural_network {'capas': (256, 128), 'activacion': 'relu', 'dropout': 0.4, 'lr': 0.001, 'batch_size': 128, 'epochs': 100} [256,128]+Dropout(0.4)→softmax 0.9554800613552519 0.9537600530164673 0.9545517843251645 0.9441587068332109 0.9395108985072946 0.9372280998211393 0.9382606172667207 0.9272860815277267

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

View File

@@ -0,0 +1,9 @@
pandas
numpy
scikit-learn
xgboost
matplotlib
seaborn
ucimlrepo
tensorflow
jupyter

View File

@@ -0,0 +1,57 @@
import os
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.metrics import precision_score, recall_score, f1_score, accuracy_score
BITACORA = os.path.join(os.path.dirname(__file__), '..', 'outputs', 'bitacora_experimentos.csv')
FIGURES_PATH = os.path.join(os.path.dirname(__file__), '..', 'outputs', 'figures')
def metricas(y_true, y_pred, sufijo):
"""Calcula precision, recall, f1 y accuracy (macro) para un split."""
return {
f'precision_{sufijo}': precision_score(y_true, y_pred, average='macro', zero_division=0),
f'recall_{sufijo}': recall_score(y_true, y_pred, average='macro', zero_division=0),
f'f1_{sufijo}': f1_score(y_true, y_pred, average='macro', zero_division=0),
f'accuracy_{sufijo}': accuracy_score(y_true, y_pred),
}
def registrar(exp_id, modelo, hiperparams, arquitectura, model, X_train, y_train, X_test, y_test):
"""Append de una fila de experimento a la bitácora CSV."""
fila = {
'experimento_id': exp_id,
'modelo': modelo,
'hiperparametros': str(hiperparams),
'arquitectura': arquitectura,
}
fila.update(metricas(y_train, model.predict(X_train), 'train'))
fila.update(metricas(y_test, model.predict(X_test), 'test'))
path = os.path.abspath(BITACORA)
os.makedirs(os.path.dirname(path), exist_ok=True)
write_header = not os.path.exists(path)
pd.DataFrame([fila]).to_csv(path, mode='a', header=write_header, index=False)
print(f"[bitacora] {modelo} | {exp_id} | F1 test={fila['f1_test']:.4f}")
def plot_importancia(model, feature_names, titulo, top_n=16, save=True):
"""Barplot horizontal de feature importances ordenado descendentemente."""
importancias = pd.Series(model.feature_importances_, index=feature_names)
importancias = importancias.nlargest(top_n).sort_values()
fig, ax = plt.subplots(figsize=(8, max(4, top_n * 0.4)))
importancias.plot(kind='barh', ax=ax)
ax.set_title(titulo)
ax.set_xlabel('Importancia')
plt.tight_layout()
if save:
os.makedirs(os.path.abspath(FIGURES_PATH), exist_ok=True)
filename = titulo.lower().replace(' ', '_').replace('', '').replace('/', '_').strip('_') + '.png'
path = os.path.join(os.path.abspath(FIGURES_PATH), filename)
fig.savefig(path, dpi=150)
print(f"[figura] guardada en {path}")
plt.show()
return importancias