Agregando Proyecto Final
This commit is contained in:
@@ -15,3 +15,9 @@ Este repositorio contiene las tareas y laboratorios del curso.
|
|||||||
| 2 | Práctica Feature Engineering, Scaling y Vectorización | [Practica_02_feature_engineering.ipynb](Practica_02_feature_engineering.ipynb) |
|
| 2 | Práctica Feature Engineering, Scaling y Vectorización | [Practica_02_feature_engineering.ipynb](Practica_02_feature_engineering.ipynb) |
|
||||||
| 3 | Clasificador K-Nearest Neighbors | [KNN/K-nearest neighbor.ipynb](KNN/K-nearest%20neighbor.ipynb) |
|
| 3 | Clasificador K-Nearest Neighbors | [KNN/K-nearest neighbor.ipynb](KNN/K-nearest%20neighbor.ipynb) |
|
||||||
| 4 | Inferencia (Forward Propagation) en Redes Neuronales | [Tarea_4_Forward_Propagation.ipynb](Tarea_4_Forward_Propagation.ipynb) |
|
| 4 | Inferencia (Forward Propagation) en Redes Neuronales | [Tarea_4_Forward_Propagation.ipynb](Tarea_4_Forward_Propagation.ipynb) |
|
||||||
|
|
||||||
|
## Proyecto Final
|
||||||
|
|
||||||
|
| Nombre | Enlace |
|
||||||
|
|--------|--------|
|
||||||
|
| Clasificador Multiclase — Dry Bean Dataset | [proyecto-final/README.md](proyecto-final/README.md) |
|
||||||
|
|||||||
87
proyecto-final/README.md
Normal file
87
proyecto-final/README.md
Normal 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.
|
||||||
1332
proyecto-final/dry_bean_classifier.ipynb
Normal file
1332
proyecto-final/dry_bean_classifier.ipynb
Normal file
File diff suppressed because one or more lines are too long
16
proyecto-final/outputs/bitacora_experimentos.csv
Normal file
16
proyecto-final/outputs/bitacora_experimentos.csv
Normal 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
|
||||||
|
0
proyecto-final/outputs/figures/.gitkeep
Normal file
0
proyecto-final/outputs/figures/.gitkeep
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 62 KiB |
BIN
proyecto-final/outputs/figures/feature_importance__xgboost.png
Normal file
BIN
proyecto-final/outputs/figures/feature_importance__xgboost.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 63 KiB |
9
proyecto-final/requirements.txt
Normal file
9
proyecto-final/requirements.txt
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
pandas
|
||||||
|
numpy
|
||||||
|
scikit-learn
|
||||||
|
xgboost
|
||||||
|
matplotlib
|
||||||
|
seaborn
|
||||||
|
ucimlrepo
|
||||||
|
tensorflow
|
||||||
|
jupyter
|
||||||
57
proyecto-final/src/utils.py
Normal file
57
proyecto-final/src/utils.py
Normal 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
|
||||||
Reference in New Issue
Block a user