Aprenentatge Supervisat
Introducció
L'aprenentatge supervisat és el paradigma de Machine Learning més estès en aplicacions reals. La idea fonamental és senzilla però poderosa: proporcionem al model un conjunt d'exemples on coneixem la resposta correcta (les "etiquetes" o "labels"), i el model aprèn a fer prediccions correctes sobre dades noves que no ha vist mai. És equivalent al funcionament d'un alumne que aprèn resolent problemes amb solucions, en lloc d'haver de descobrir les regles per si sol.
El 2025, l'aprenentatge supervisat és la base de centenars d'aplicacions professionals crítiques: detecció de frau bancari, diagnòstic mèdic assistit per IA, reconeixement de veu, filtratge de spam, predicció de la demanda en logística, scoring de crèdit i molt més. Dominar el pipeline complet d'un projecte supervisat és una competència fonamental per a qualsevol professional de les dades.
El Pipeline Complet d'Aprenentatge Supervisat
Un projecte d'ML supervisat mai comença directament amb el model. Existeix una seqüència d'etapes que determinen en gran mesura el seu èxit o fracàs:
flowchart LR
A["Definicio\ndel problema"] --> B["Recopilacio\nde dades"]
B --> C["Exploracio\nEDA"]
C --> D["Preprocés\ni FE"]
D --> E["Seleccio\nde model"]
E --> F["Entrenament"]
F --> G["Avaluacio\ni diagnostics"]
G -->|Millores| D
G -->|Acceptat| H["Optimitzacio\nhiperparametres"]
H --> I["Avaluacio\nfinal en test"]
I --> J["Desplegament\ni monitoritzacio"]
Etapa 1: Definició del problema
Abans de tocar dades, cal respondre:
- Quin és l'objectiu de negoci? (reduir frau, augmentar conversions, millorar diagnòstic)
- Quin és el problema ML? (classificació binària, multiclasse, regressió)
- Quina mètrica importa? (accuracy per a problemes balancejats, recall per a detecció de malalties, precisió per a spam)
- Quines restriccions existeixen? (latència de predicció, explicabilitat, privacitat de dades)
Definir malament el problema és la causa principal de fracàs dels projectes ML. Optimitzar la mètrica equivocada —per exemple, accuracy en datasets molt desequilibrats— produeix models inútils malgrat tenir "bons números".
Etapa 2: Exploració de Dades (EDA)
L'Exploratory Data Analysis (EDA) és l'etapa on s'entenen les dades abans de modelitzar:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
# Càrrega i inspecció inicial
df = pd.read_csv("credit_card_fraud.csv")
print("Forma del dataset:", df.shape)
print("\nTipus de dades:")
print(df.dtypes)
print("\nValors nuls per columna:")
print(df.isnull().sum())
print("\nEstadística descriptiva:")
print(df.describe())
# Distribució de la variable objectiu (desequilibri de classes)
print("\nDistribució de la classe:")
print(df["is_fraud"].value_counts(normalize=True) * 100)
# Visualització del desequilibri
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
df["is_fraud"].value_counts().plot(kind="bar", ax=axes[0], color=["#2196F3", "#F44336"])
axes[0].set_title("Distribució de classes (absolut)")
axes[0].set_xlabel("Classe (0=legítim, 1=frau)")
df["is_fraud"].value_counts(normalize=True).plot(kind="bar", ax=axes[1], color=["#2196F3", "#F44336"])
axes[1].set_title("Distribució de classes (%)")
plt.tight_layout()
plt.savefig("eda_classes.png", dpi=150, bbox_inches="tight")
Preprocés i Feature Engineering
El preprocés és l'etapa que més impacte té en el rendiment final del model. "Garbage in, garbage out" —dades de mala qualitat produiran models de mala qualitat independentment de l'algoritme usat.
Imputació de valors nuls
from sklearn.impute import SimpleImputer, KNNImputer
# Imputació simple: mediana per a numèrics, moda per a categòrics
imputer_num = SimpleImputer(strategy="median")
imputer_cat = SimpleImputer(strategy="most_frequent")
# Imputació KNN: usa els K veïns més similars per omplir valors nuls
# Millor per a relacions complexes entre columnes
imputer_knn = KNNImputer(n_neighbors=5)
# Per a valors nuls que no existeixen aleatòriament (Missing Not At Random),
# és útil crear una columna indicadora
df["edat_nul"] = df["edat"].isnull().astype(int)
Escalat de features numèriques
from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler
# StandardScaler: z-score (mitjana 0, desviació estàndard 1)
# Recomanat per a SVM, regressió logística, xarxes neuronals
scaler_std = StandardScaler()
# MinMaxScaler: escala a [0,1]
# Útil quan la distribució no és gaussiana
scaler_minmax = MinMaxScaler()
# RobustScaler: usa quartils en lloc de mitjana/std
# Robust davant outliers
scaler_robust = RobustScaler()
# Exemple: StandardScaler
X_train_scaled = scaler_std.fit_transform(X_train)
X_test_scaled = scaler_std.transform(X_test) # IMPORTANT: transform, NO fit_transform!
Data leakage — L'error més comú
Mai facis fit_transform sobre el conjunt de test. El scaler s'ha d'ajustar únicament sobre les dades d'entrenament i després aplicar-se (transform) sobre test. Si escales primer tot el dataset i després divideixes, estàs filtrant informació del test cap a l'entrenament, cosa que dona mètriques d'avaluació falsament optimistes.
Encoding de variables categòriques
from sklearn.preprocessing import OrdinalEncoder, OneHotEncoder
from sklearn.preprocessing import LabelEncoder
# OneHotEncoder: crea una columna binària per cada categoria
# Recomanat per a variables nominals (no hi ha ordre intrínsec)
ohe = OneHotEncoder(handle_unknown="ignore", sparse_output=False)
X_encoded = ohe.fit_transform(df[["pais", "tipus_compte"]])
# OrdinalEncoder: assigna un número enter a cada categoria
# Recomanat per a variables ordinals (hi ha un ordre: baix < mig < alt)
oe = OrdinalEncoder(categories=[["baix", "mig", "alt"]])
X_ord = oe.fit_transform(df[["risc"]])
# Target Encoding (útil per a categories amb molts nivells)
# Reemplaça cada categoria per la mitjana de la variable objectiu
from category_encoders import TargetEncoder
te = TargetEncoder()
X_te = te.fit_transform(df["pais"], y_train)
Pipeline de scikit-learn
La classe Pipeline de scikit-learn permet encadenar transformacions i estimadors de manera ordenada, evitant data leakage i simplificant el codi:
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.ensemble import RandomForestClassifier
# Definim les columnes numèriques i categòriques
cols_num = ["edat", "salari", "historial_credit", "import_transaccio"]
cols_cat = ["pais", "tipus_compte", "canal"]
# Preprocessadors per a cada tipus
preprocessor = ColumnTransformer(transformers=[
("num", Pipeline([
("imputer", SimpleImputer(strategy="median")),
("scaler", RobustScaler())
]), cols_num),
("cat", Pipeline([
("imputer", SimpleImputer(strategy="most_frequent")),
("encoder", OneHotEncoder(handle_unknown="ignore", sparse_output=False))
]), cols_cat)
])
# Pipeline complet: preprocessador + model
pipeline = Pipeline([
("preprocessador", preprocessor),
("model", RandomForestClassifier(n_estimators=200, random_state=42))
])
# Entrenar i avaluar — el pipeline gestiona automàticament tot el preprocés
pipeline.fit(X_train, y_train)
y_pred = pipeline.predict(X_test)
Divisió de Dades
Train / Validation / Test
La divisió correcta del dataset és fonamental per avaluar honestament el rendiment del model:
- Conjunt d'entrenament (train, 60-70%): el model aprèn ajustant els seus paràmetres sobre aquest conjunt.
- Conjunt de validació (validation, 10-20%): serveix per prendre decisions de disseny del model (hiperparàmetres, arquitectura) sense contaminar el test.
- Conjunt de test (test, 20-30%): s'usa una sola vegada per reportar el rendiment final. No s'ha de consultar durant el desenvolupament.
from sklearn.model_selection import train_test_split
# Primera divisió: separem test (20%)
X_temp, X_test, y_temp, y_test = train_test_split(
X, y,
test_size=0.20,
random_state=42,
stratify=y # Stratify manté la proporció de classes
)
# Segona divisió: separem validation del train restant (20% del 80% = 16% total)
X_train, X_val, y_train, y_val = train_test_split(
X_temp, y_temp,
test_size=0.20,
random_state=42,
stratify=y_temp
)
print(f"Mida train: {X_train.shape[0]} ({X_train.shape[0]/len(X)*100:.1f}%)")
print(f"Mida validation: {X_val.shape[0]} ({X_val.shape[0]/len(X)*100:.1f}%)")
print(f"Mida test: {X_test.shape[0]} ({X_test.shape[0]/len(X)*100:.1f}%)")
Cross-Validation
Quan el dataset és petit, la divisió train/val pot donar estimacions de rendiment poc estables (molt dependent de quins exemples cauen en el validation set). La validació creuada (k-fold cross-validation) resol aquest problema:
from sklearn.model_selection import cross_val_score, StratifiedKFold
from sklearn.metrics import f1_score, make_scorer
# k-Fold estratificat: 5 plecs, mantenint la proporció de classes
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
# Avaluació creuada amb F1-score (macro per a multiclasse)
f1_scorer = make_scorer(f1_score, average="binary")
scores = cross_val_score(
pipeline,
X_train,
y_train,
cv=cv,
scoring=f1_scorer,
n_jobs=-1 # Paral·lelitzar en tots els cores
)
print(f"F1-score per plec: {scores}")
print(f"Mitjana: {scores.mean():.4f} ± {scores.std():.4f}")
Algoritmes Principals d'Aprenentatge Supervisat
Regressió Lineal
La regressió lineal modela la relació entre la variable objectiu $y$ i les features $\mathbf{x}$ com una funció lineal:
$$\hat{y} = w_0 + w_1 x_1 + w_2 x_2 + \dots + w_n x_n = \mathbf{w}^T \mathbf{x} + b$$
L'entrenament minimitza l'error quadràtic mitjà (MSE) trobant els pesos $\mathbf{w}$ òptims. Amb regularització Ridge (L2), s'afegeix una penalització $\lambda |\mathbf{w}|^2$ que limita la magnitud dels pesos i prevé l'overfitting.
from sklearn.linear_model import Ridge, Lasso, ElasticNet
# Ridge: L2 regularisation (shrinks coefficients, keeps all features)
ridge = Ridge(alpha=1.0) # alpha controla la força de la regularització
# Lasso: L1 regularisation (can set some coefficients to exactly zero = feature selection)
lasso = Lasso(alpha=0.1)
# ElasticNet: combinació de L1 i L2
elastic = ElasticNet(alpha=0.1, l1_ratio=0.5)
Regressió Logística (Classificació)
Malgrat el nom, la regressió logística és un algoritme de classificació. Modela la probabilitat de pertinença a una classe aplicant la funció sigmoide sobre una combinació lineal de les features:
$$P(y=1 | \mathbf{x}) = \sigma(\mathbf{w}^T \mathbf{x} + b) = \frac{1}{1 + e^{-(\mathbf{w}^T \mathbf{x} + b)}}$$
És excel·lent com a baseline per a classificació binària, molt interpretable (els pesos indiquen la importància relativa de cada feature) i computacionalment eficient.
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression(
C=1.0, # C = 1/lambda, controla la regularització (C gran = menys regularització)
max_iter=1000, # Per convergència en datasets grans
class_weight="balanced", # Per a classes desequilibrades
random_state=42
)
lr.fit(X_train, y_train)
# Probabilitats de predicció
probs = lr.predict_proba(X_test)[:, 1] # Probabilitat de la classe positiva
Arbres de Decisió
Un arbre de decisió divideix recursivament l'espai de features en regions, on cada divisió maximitza la puresa (Gini o Entropia). Molt interpretables però propensos a overfitting si no es limita la profunditat.
from sklearn.tree import DecisionTreeClassifier, export_text
arbre = DecisionTreeClassifier(
max_depth=5, # Limitar la profunditat és la principal tècnica de regularització
min_samples_leaf=10, # Cada fulla ha de tenir almenys 10 mostres
class_weight="balanced",
random_state=42
)
arbre.fit(X_train, y_train)
# Visualitzar les regles de decisió
print(export_text(arbre, feature_names=list(X_train.columns), max_depth=3))
Random Forest
El Random Forest entrena molts arbres de decisió sobre subconjunts aleatoris de les dades i les features (bagging + feature randomness), i agrega les prediccions per majoria de vots. Molt robust, gestiona bé l'overfitting i dona estimacions de la importància de les features.
from sklearn.ensemble import RandomForestClassifier
rf = RandomForestClassifier(
n_estimators=300, # Nombre d'arbres: més arbres = menys variança, però més lent
max_features="sqrt", # Cada arbre veu sqrt(n_features) features aleatòries
max_depth=None, # Arbres profunds; el bagging controla l'overfitting
min_samples_leaf=5,
class_weight="balanced_subsample",
n_jobs=-1, # Paral·lelitzar en tots els cores disponibles
random_state=42
)
rf.fit(X_train, y_train)
# Importància de les features
importances = pd.Series(rf.feature_importances_, index=X_train.columns)
importances.sort_values(ascending=False).head(10).plot(kind="bar")
plt.title("Importància de features - Random Forest")
plt.tight_layout()
Gradient Boosting: XGBoost, LightGBM, CatBoost
El Gradient Boosting construeix l'ensemble de manera seqüencial: cada arbre nou intenta corregir els errors del conjunt anterior, ajustant-se al gradient de la funció de pèrdua. XGBoost, LightGBM i CatBoost són implementacions altament optimitzades que dominen les competicions de dades tabulars.
import xgboost as xgb
import lightgbm as lgb
# XGBoost
xgb_model = xgb.XGBClassifier(
n_estimators=500,
max_depth=6,
learning_rate=0.05, # Taxa d'aprenentatge: menor = menys overfitting, cal més arbres
subsample=0.8, # Cada arbre usa el 80% de les dades
colsample_bytree=0.8, # Cada arbre usa el 80% de les features
scale_pos_weight=neg/pos, # Per a classes desequilibrades: pes de la classe positiva
use_label_encoder=False,
eval_metric="auc",
random_state=42
)
# LightGBM: molt més ràpid que XGBoost per a datasets grans
lgb_model = lgb.LGBMClassifier(
n_estimators=500,
num_leaves=31, # Controla la complexitat de l'arbre (en lloc de max_depth)
learning_rate=0.05,
feature_fraction=0.8,
bagging_fraction=0.8,
bagging_freq=5,
class_weight="balanced",
random_state=42,
verbose=-1
)
# Entrenament amb early stopping (atura automàticament si no millora en N rounds)
lgb_model.fit(
X_train, y_train,
eval_set=[(X_val, y_val)],
callbacks=[lgb.early_stopping(50), lgb.log_evaluation(100)]
)
Màquines de Vectors de Suport (SVM)
L'SVM troba l'hiperplà que maximitza el marge entre les classes. Amb el truc del kernel (RBF, polinòmic), pot aprendre fronteres de decisió no lineals en espais d'alta dimensió. Efectiu per a datasets petits i mitjans, però lent per a datasets grans.
from sklearn.svm import SVC
svm = SVC(
kernel="rbf", # Radial Basis Function: el més comú
C=1.0, # Regularització: C gran = marge menor, menys errors d'entrenament
gamma="scale", # Gamma controla l'abast de cada punt de suport
probability=True, # Necessari per predict_proba (atenció: més lent)
class_weight="balanced",
random_state=42
)
# IMPORTANT: SVM és molt sensible a l'escala de les features
# Sempre escalar primer!
K-Nearest Neighbors (KNN)
KNN és un algoritme peresós ("lazy"): no entrena cap model, sinó que classifica cada punt nou basant-se en les K mostres d'entrenament més properes (per distància euclidiana o altra mètrica).
from sklearn.neighbors import KNeighborsClassifier
knn = KNeighborsClassifier(
n_neighbors=5, # El K a triar per cross-validation
metric="euclidean",
weights="distance", # Veïns més propers pesen més
n_jobs=-1
)
# IMPORTANT: KNN és molt sensible a l'escala → sempre escalar!
Miniactivitat — Comparativa d'algoritmes
Carrega el dataset make_classification de scikit-learn amb n_features=10, n_informative=5, n_redundant=3. Entrena i compara el rendiment (accuracy i F1) de: Regressió Logística, Random Forest, XGBoost i SVM. Utilitza cross-validation 5-fold. Presenta els resultats en un DataFrame ordenat per F1-score. Quins factors expliquen les diferències de rendiment?
Overfitting i Underfitting
El Biaix-Variança (Bias-Variance Tradeoff)
Tot model de ML enfronta la tensió entre dos tipus d'error:
- Biaix (Bias): error provinent de suposicions simplificadores del model. Un model amb molt biaix no capta la complexitat de les dades (underfitting).
- Variança (Variance): error provinent de la sensibilitat excessiva a les fluctuacions del conjunt d'entrenament. Un model amb molta variança memoritza les dades d'entrenament però no generalitza (overfitting).
graph LR
subgraph Underfitting
A["Model massa simple\nError alt en TRAIN\nError alt en VAL"]
end
subgraph Optim ["Punt optim"]
B["Equilibri biaix-varianca\nError baix en TRAIN\nError baix en VAL"]
end
subgraph Overfitting
C["Model massa complex\nError molt baix en TRAIN\nError alt en VAL"]
end
Underfitting --> Optim --> Overfitting
Diagnòstic amb Corbes d'Aprenentatge
from sklearn.model_selection import learning_curve
train_sizes, train_scores, val_scores = learning_curve(
estimator=pipeline,
X=X_train,
y=y_train,
train_sizes=np.linspace(0.1, 1.0, 10),
cv=5,
scoring="f1",
n_jobs=-1
)
# Diagrama de corbes d'aprenentatge
fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(train_sizes, train_scores.mean(axis=1), "o-", color="blue", label="Train F1")
ax.plot(train_sizes, val_scores.mean(axis=1), "o-", color="red", label="Validation F1")
ax.fill_between(train_sizes,
train_scores.mean(axis=1) - train_scores.std(axis=1),
train_scores.mean(axis=1) + train_scores.std(axis=1),
alpha=0.1, color="blue")
ax.fill_between(train_sizes,
val_scores.mean(axis=1) - val_scores.std(axis=1),
val_scores.mean(axis=1) + val_scores.std(axis=1),
alpha=0.1, color="red")
ax.set_xlabel("Mida del conjunt d'entrenament")
ax.set_ylabel("F1-Score")
ax.set_title("Corbes d'aprenentatge")
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
Interpretació: Si la corba de train és molt superior a la de validation → overfitting. Si ambdues corbes convergen en un valor alt → bon ajust. Si ambdues corbes convergex en un valor baix → underfitting (cal model més complex o més dades).
Solucions a l'Overfitting
| Situació | Solucions |
|---|---|
| Overfitting en arbre de decisió | Limitar max_depth, augmentar min_samples_leaf |
| Overfitting en Random Forest | Reduir n_estimators (no és la causa), reduir max_features |
| Overfitting en XGBoost | Reduir learning_rate, subsample, colsample_bytree; augmentar min_child_weight |
| Overfitting en xarxa neuronal | Dropout, weight decay, early stopping, data augmentation |
| Overfitting general | Més dades d'entrenament, regularització L1/L2, cross-validation |
Optimització d'Hiperparàmetres
GridSearchCV
Cerca exhaustiva sobre una graella de combinacions d'hiperparàmetres:
from sklearn.model_selection import GridSearchCV
param_grid = {
"model__n_estimators": [100, 200, 300],
"model__max_depth": [3, 5, 7, None],
"model__min_samples_leaf": [5, 10, 20],
"model__class_weight": ["balanced", None]
}
grid_search = GridSearchCV(
estimator=pipeline,
param_grid=param_grid,
cv=StratifiedKFold(n_splits=5),
scoring="f1",
n_jobs=-1,
verbose=2,
refit=True # Reentrena el millor model sobre tot el train
)
grid_search.fit(X_train, y_train)
print("Millors paràmetres:", grid_search.best_params_)
print("Millor F1 (CV):", grid_search.best_score_:.4f)
# El millor model ja entrenat sobre tot X_train
best_model = grid_search.best_estimator_
Optuna (Bayesian Optimization)
Optuna és molt més eficient que GridSearch perquè utilitza optimització bayesiana per explorar l'espai d'hiperparàmetres intel·ligentment, concentrant-se en les regions prometedores:
import optuna
def objective(trial):
"""Funció objectiu: retorna el F1-score del model per als paràmetres del trial."""
params = {
"n_estimators": trial.suggest_int("n_estimators", 100, 500),
"max_depth": trial.suggest_int("max_depth", 3, 10),
"learning_rate": trial.suggest_float("learning_rate", 0.01, 0.3, log=True),
"subsample": trial.suggest_float("subsample", 0.6, 1.0),
"colsample_bytree": trial.suggest_float("colsample_bytree", 0.6, 1.0),
}
model = xgb.XGBClassifier(**params, random_state=42, eval_metric="logloss")
scores = cross_val_score(model, X_train, y_train, cv=5, scoring="f1", n_jobs=-1)
return scores.mean()
# Crear i optimitzar l'estudi
study = optuna.create_study(direction="maximize", study_name="xgb_fraude")
study.optimize(objective, n_trials=100, timeout=3600)
print("Millors hiperparàmetres:", study.best_params)
print("Millor F1:", study.best_value:.4f)
# Visualitzar la importància dels hiperparàmetres
optuna.visualization.plot_param_importances(study)
Exemple Complet: Classificació de Frau Bancari
"""
Pipeline complet de detecció de frau bancari
Dataset: IEEE-CIS Fraud Detection (Kaggle)
"""
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split, StratifiedKFold, cross_val_score
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OrdinalEncoder
from sklearn.impute import SimpleImputer
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import (classification_report, roc_auc_score,
confusion_matrix, ConfusionMatrixDisplay)
import xgboost as xgb
import matplotlib.pyplot as plt
import joblib
# ============================================================
# 1. CÀRREGA I EXPLORACIÓ
# ============================================================
df = pd.read_csv("fraude_bancari.csv")
# Variable objectiu i features
TARGET = "is_fraud"
FEATURES_NUM = ["import", "edat", "dies_compte", "n_transaccions_7d",
"import_mig_7d", "distancia_comerciant"]
FEATURES_CAT = ["pais", "tipus_targeta", "canal", "dispositiu"]
X = df[FEATURES_NUM + FEATURES_CAT]
y = df[TARGET]
print(f"Dataset: {len(df)} transaccions")
print(f"Taxa de frau: {y.mean():.2%}") # Típicament <1%
# ============================================================
# 2. DIVISIÓ DE DADES
# ============================================================
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# ============================================================
# 3. PIPELINE DE PREPROCESSAMENT
# ============================================================
preprocessor = ColumnTransformer(transformers=[
("num", Pipeline([
("imputer", SimpleImputer(strategy="median")),
("scaler", StandardScaler())
]), FEATURES_NUM),
("cat", Pipeline([
("imputer", SimpleImputer(strategy="most_frequent")),
("encoder", OrdinalEncoder(handle_unknown="use_encoded_value", unknown_value=-1))
]), FEATURES_CAT)
], remainder="drop")
# ============================================================
# 4. MODEL XGBOOST (EXCEL·LENT PER A FRAU DETECCIÓ)
# ============================================================
# Calculem el pes per a les classes desequilibrades
neg, pos = np.bincount(y_train)
scale_pos = neg / pos
model_pipeline = Pipeline([
("preprocessador", preprocessor),
("model", xgb.XGBClassifier(
n_estimators=300,
max_depth=6,
learning_rate=0.05,
subsample=0.8,
colsample_bytree=0.7,
scale_pos_weight=scale_pos,
eval_metric="auc",
random_state=42,
n_jobs=-1
))
])
# ============================================================
# 5. ENTRENAMENT I AVALUACIÓ
# ============================================================
model_pipeline.fit(X_train, y_train)
# Prediccions
y_pred = model_pipeline.predict(X_test)
y_proba = model_pipeline.predict_proba(X_test)[:, 1]
# Mètriques
print("\n" + "="*60)
print("RESULTATS EN EL CONJUNT DE TEST")
print("="*60)
print(classification_report(y_test, y_pred, target_names=["Legítim", "Frau"]))
print(f"AUC-ROC: {roc_auc_score(y_test, y_proba):.4f}")
# Matriu de confusió
cm = confusion_matrix(y_test, y_pred)
disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=["Legítim", "Frau"])
disp.plot(cmap="Blues")
plt.title("Matriu de Confusió — Model Detecció Frau")
plt.tight_layout()
plt.savefig("matriu_confusio.png", dpi=150)
# ============================================================
# 6. EXPORTAR EL MODEL
# ============================================================
joblib.dump(model_pipeline, "model_fraude.pkl")
print("\nModel desat com 'model_fraude.pkl'")
# Carregar i usar
model_carregat = joblib.load("model_fraude.pkl")
nova_transaccio = pd.DataFrame([{
"import": 2500.0, "edat": 35, "dies_compte": 730,
"n_transaccions_7d": 3, "import_mig_7d": 120.0,
"distancia_comerciant": 850.0, "pais": "ES",
"tipus_targeta": "credit", "canal": "online", "dispositiu": "mobile"
}])
prob_frau = model_carregat.predict_proba(nova_transaccio)[0, 1]
print(f"\nProbabilitat de frau per a la nova transacció: {prob_frau:.2%}")
Miniactivitat — Detecció de frau
Adapta el codi anterior per a un dataset de la teva elecció (Credit Card Fraud Detection de Kaggle és una excel·lent opció, accessible gratuïtament). Experimenta canviant el llindar de classificació de 0.5 a 0.3 i observa com canvia el trade-off entre precision i recall. Per a detecció de frau, quin llindar és més adequat i per quin motiu?
Exercici Pràctic — AC5072/02
Objectiu: Construir un pipeline complet de classificació per a predicció de malalties cardíaques.
Dataset: Heart Disease UCI (disponible a Kaggle i UCI ML Repository). Conté dades mèdiques de 303 pacients amb 14 features (edat, sexe, tipus de dolor al pit, colesterol, etc.) i la variable objectiu binària (0=sa, 1=malaltia cardíaca).
Passos:
- Carrega el dataset i fes un EDA complert (distribucions, correlacions, valors nuls).
- Divideix en train/test estratificat (80%/20%).
- Construeix un
Pipelineamb preprocés (imputer + scaler per a numèrics, encoder per a categòrics). - Entrena i compara 4 models: Logistic Regression, Decision Tree, Random Forest, XGBoost.
- Avalua amb cross-validation 5-fold i reporta accuracy, precision, recall, F1 i AUC-ROC.
- Identifica el millor model i optimitza els seus hiperparàmetres amb Optuna (50 trials).
- Avalua el model final en el test set i interpreta la matriu de confusió.
- Reporta les 5 features més importants segons el model final.
Entrega: Notebook Jupyter amb totes les cel·les executades i un informe breu de conclusions (300 paraules).
Codi de l'activitat: AC5072/02