Al projecte 1 vas treballar amb dades tabulars, el terreny natural de scikit-learn. Ara canviem de món: imatges. L'objectiu d'aquest projecte és doble. Primer, comprovar fins on arriben els mètodes clàssics del mòdul 4 amb imatges petites (els dígits manuscrits de load_digits); després, fer el salt a Fashion-MNIST —70.000 fotos de peces de roba en 10 categories— i resoldre'l amb Keras, comparant una xarxa densa (MLP) amb una xarxa convolucional (CNN), reprenent a la pràctica tot el que vas veure a 07-04 "Xarxes neuronals profundes (Deep Learning)". Al final tindràs criteri propi per respondre la pregunta important: quan n'hi ha prou amb el clàssic i quan cal deep learning?
Contingut
- Definició del problema i mètrica objectiu
- Etapa A: dígits amb mètodes clàssics
- Els límits de l'enfocament clàssic
- Etapa B: Fashion-MNIST amb Keras
- MLP densa: el baseline neuronal
- CNN petita: convolucions en acció
- Overfitting a la pràctica: dropout i early stopping
- Avaluació final: matriu de confusió de les 10 classes
- Conclusions: clàssic o deep?
Definició del problema i mètrica objectiu
- Problema: classificació multiclasse d'imatges — 10 dígits primer, 10 peces de roba després.
- Mètrica principal: accuracy. A diferència del projecte de frau que t'espera a 09-04, aquí les classes estan perfectament equilibrades (un 10 % cadascuna), així que l'accuracy no enganya — és el cas benigne que vam descriure a 06-02 "Mètriques d'avaluació". L'acompanyarem amb la matriu de confusió per veure què es confon amb què.
- Criteri d'èxit: en dígits, superar el 95 % (és un dataset fàcil); en Fashion-MNIST, superar el 90 %, cosa que exigeix una CNN.
Una imatge en escala de grisos és una matriu de píxels (0 = negre, 255 = blanc). Per a un model clàssic, cada píxel és simplement una columna més: una imatge de 8×8 són 64 features. Aquesta "traducció" — aplanar la imatge a un vector — és la clau de l'etapa A i també el seu taló d'Aquil·les.
Etapa A: dígits amb mètodes clàssics
load_digits porta 1.797 dígits manuscrits de 8×8 píxels, en el mateix format que qualsevol dataset tabular de sklearn:
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split, cross_val_score
digits = load_digits()
X, y = digits.data, digits.target # X: (1797, 64), valors 0-16
print(X.shape, np.unique(y))
# Vegem alguns exemples
fig, eixos = plt.subplots(2, 8, figsize=(10, 3))
for eix, img, etiqueta in zip(eixos.ravel(), digits.images, y):
eix.imshow(img, cmap="gray_r")
eix.set_title(etiqueta); eix.axis("off")
plt.tight_layout(); plt.show()Dividim (estratificant, com sempre des de 06-01 "Divisió de dades: entrenament, validació i prova") i provem dos clàssics amb escalat en Pipeline — la logística de 04-02 "Regressió logística" i la SVM de 04-04 "Màquines de suport vectorial (SVM)":
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, stratify=y, random_state=42
)
classics = {
"Logistica": Pipeline([("esc", StandardScaler()),
("m", LogisticRegression(max_iter=2000))]),
"SVM (RBF)": Pipeline([("esc", StandardScaler()),
("m", SVC(kernel="rbf", gamma="scale"))]),
}
for nom, model in classics.items():
puntuacions = cross_val_score(model, X_train, y_train, cv=5)
print(f"{nom}: {puntuacions.mean():.3f} +- {puntuacions.std():.3f}")Resultats típics: logística ≈ 0,96 i SVM ≈ 0,98 d'accuracy en validació creuada. Amb imatges diminutes i ben centrades, el clàssic funciona d'allò més bé.
Per entendre per què, visualitza el dataset amb el PCA de 05-03 "Anàlisi de components principals (PCA)":
from sklearn.decomposition import PCA
pca = PCA(n_components=2)
X_2d = pca.fit_transform(StandardScaler().fit_transform(X_train))
plt.figure(figsize=(8, 6))
sc = plt.scatter(X_2d[:, 0], X_2d[:, 1], c=y_train, cmap="tab10", s=10, alpha=0.6)
plt.colorbar(sc, label="Digit")
plt.title("Digits projectats a 2D amb PCA")
plt.show()
print("Variancia explicada:", pca.explained_variance_ratio_.sum().round(3))Fins i tot amb només 2 components (≈ 22 % de la variància), s'hi intueixen núvols per dígit: les classes són força separables fins i tot després d'aplanar la imatge. Aquest és el secret de l'èxit clàssic aquí.
Els límits de l'enfocament clàssic
Per què no continuar així amb qualsevol imatge? Perquè aplanar destrueix l'estructura espacial:
- Un píxel deixa de "saber" qui eren els seus veïns: la columna 13 i la 14 són adjacents a la imatge, però per a la logística són dues features tan independents com
MedInciLatitudeal projecte 1. - El model no és invariant a translacions: el mateix dígit desplaçat dos píxels activa columnes completament diferents i sembla "un altre" patró.
- La dimensionalitat esclata: 8×8 són 64 features; una foto modesta de 224×224 en color són 150.528. Amb features tan crues, els mètodes del mòdul 4 s'ofeguen.
Amb dígits de 8×8, centrats i nets, aquests problemes gairebé no es noten. Amb fotos de roba de 28×28 començaran a pesar; amb fotos reals, serien fatals. Aquesta és exactament la motivació de les convolucions que vas veure a 07-04: processar la imatge respectant-ne la geometria.
Etapa B: Fashion-MNIST amb Keras
Fashion-MNIST és el substitut modern del MNIST original: 60.000 imatges d'entrenament i 10.000 de test, de 28×28 en escala de grisos, amb 10 tipus de peça de roba. Ve inclòs a Keras:
import tensorflow as tf
from tensorflow import keras
(X_train, y_train), (X_test, y_test) = keras.datasets.fashion_mnist.load_data()
classes = ["Samarreta", "Pantalo", "Jersei", "Vestit", "Abric",
"Sandalia", "Camisa", "Sabatilla", "Bossa", "Boti"]
print(X_train.shape, X_test.shape) # (60000, 28, 28) (10000, 28, 28)Normalització de píxels: els valors van de 0 a 255; les xarxes entrenen molt millor amb entrades petites i homogènies (és el mateix principi de 03-05 "Normalització i estandardització", aplicat a imatges). Dividim entre 255 per portar-los a [0, 1], i apartem un conjunt de validació per vigilar l'entrenament:
X_train = X_train / 255.0
X_test = X_test / 255.0
# Validacio: ultimes 10.000 imatges del train
X_val, y_val = X_train[50000:], y_train[50000:]
X_tr, y_tr = X_train[:50000], y_train[:50000]MLP densa: el baseline neuronal
Comencem per l'arquitectura que ja coneixes de 04-07 "Xarxes neuronals" i 07-04: capes denses sobre la imatge aplanada. És el nostre baseline "sense estructura espacial":
mlp = keras.Sequential([
keras.layers.Flatten(input_shape=(28, 28)), # 784 features
keras.layers.Dense(256, activation="relu"),
keras.layers.Dense(128, activation="relu"),
keras.layers.Dense(10, activation="softmax"), # 10 probabilitats
])
mlp.compile(optimizer="adam",
loss="sparse_categorical_crossentropy",
metrics=["accuracy"])
hist_mlp = mlp.fit(X_tr, y_tr, epochs=20, batch_size=128,
validation_data=(X_val, y_val), verbose=2)Recordatori de 07-04: softmax converteix les 10 sortides en probabilitats que sumen 1, i sparse_categorical_crossentropy és la pèrdua adequada quan les etiquetes són enters (0–9) en lloc de one-hot. Resultat típic: ≈ 0,89 d'accuracy en validació, amb la corba d'entrenament desenganxant-se de la de validació en les últimes èpoques — l'overfitting traient el cap, com a 06-05 "Overfitting i underfitting".
CNN petita: convolucions en acció
Ara l'alternativa que respecta la geometria. Reprenent 07-04: una capa Conv2D fa lliscar filtres petits (3×3) per tota la imatge, aprenent detectors locals (vores, textures) que s'apliquen igual a qualsevol posició — això dona invariància a translacions i moltíssims menys paràmetres. MaxPooling2D redueix la resolució a la meitat quedant-se amb l'activació més forta de cada finestra 2×2, guanyant robustesa davant petits desplaçaments:
cnn = keras.Sequential([
keras.layers.Reshape((28, 28, 1), input_shape=(28, 28)),
keras.layers.Conv2D(32, (3, 3), activation="relu"), # 32 detectors 3x3
keras.layers.MaxPooling2D((2, 2)),
keras.layers.Conv2D(64, (3, 3), activation="relu"), # combina patrons
keras.layers.MaxPooling2D((2, 2)),
keras.layers.Flatten(),
keras.layers.Dense(128, activation="relu"),
keras.layers.Dropout(0.3),
keras.layers.Dense(10, activation="softmax"),
])
cnn.summary()Llegeix el summary(): les capes convolucionals tenen pocs milers de paràmetres (els filtres es comparteixen per tota la imatge), mentre que a la MLP la primera capa densa tota sola en tenia 784 × 256 ≈ 200.000. Menys paràmetres fent més feina útil: aquesta és la idea convolucional.
Overfitting a la pràctica: dropout i early stopping
Dues defenses de 07-04 aplicades de debò:
- Dropout(0.3): a cada pas d'entrenament apaga a l'atzar el 30 % de les neurones d'aquella capa, impedint que la xarxa memoritzi coadaptacions fràgils.
- Early stopping: vigila la pèrdua de validació i atura l'entrenament quan deixa de millorar, restaurant els millors pesos — l'equivalent pràctic de triar el punt òptim de la corba de 06-05.
cnn.compile(optimizer="adam",
loss="sparse_categorical_crossentropy",
metrics=["accuracy"])
aturada = keras.callbacks.EarlyStopping(
monitor="val_loss", patience=3, restore_best_weights=True
)
hist_cnn = cnn.fit(X_tr, y_tr, epochs=30, batch_size=128,
validation_data=(X_val, y_val),
callbacks=[aturada], verbose=2)
# Corbes d'aprenentatge
plt.plot(hist_cnn.history["accuracy"], label="train")
plt.plot(hist_cnn.history["val_accuracy"], label="validacio")
plt.xlabel("Epoca"); plt.ylabel("Accuracy"); plt.legend()
plt.title("CNN: corbes d'aprenentatge"); plt.show()Resultat típic: ≈ 0,91–0,92 en validació, amb les corbes molt més enganxades que a la MLP. L'early stopping sol aturar-se entre les èpoques 10 i 15.
Avaluació final: matriu de confusió de les 10 classes
Com sempre, el test s'obre una sola vegada, amb el model ja decidit:
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay, accuracy_score
y_pred = cnn.predict(X_test).argmax(axis=1)
print("Accuracy test CNN:", round(accuracy_score(y_test, y_pred), 4))
cm = confusion_matrix(y_test, y_pred)
disp = ConfusionMatrixDisplay(cm, display_labels=classes)
fig, eix = plt.subplots(figsize=(9, 9))
disp.plot(ax=eix, cmap="Blues", xticks_rotation=45, colorbar=False)
plt.title("Fashion-MNIST: matriu de confusio (test)")
plt.tight_layout(); plt.show()| Model | Accuracy (validació) | Accuracy (test) | Paràmetres aprox. |
|---|---|---|---|
| MLP densa | ≈ 0,89 | ≈ 0,88 | ≈ 235.000 |
| CNN petita | ≈ 0,92 | ≈ 0,91 | ≈ 225.000 |
La matriu de confusió explica la història interessant: els errors no es reparteixen a l'atzar. Les confusions dominants són Camisa ↔ Samarreta ↔ Jersei ↔ Abric (totes són peces de tors amb una silueta semblant a 28×28 píxels) i, en menor mesura, Sabatilla ↔ Botí ↔ Sandàlia. En canvi, Pantaló i Bossa gairebé no es confonen amb res: les seves siluetes són inconfusibles. La xarxa s'equivoca allà on un humà, mirant una miniatura borrosa de 28×28, també dubtaria — un error raonable és senyal d'un model que ha après patrons reals i no soroll.
Errors Comuns i Consells
- Oblidar normalitzar els píxels. Amb valors 0–255 la xarxa entrena malament o molt a poc a poc (gradients desproporcionats). Dividir entre 255 és una línia que ho canvia tot.
- Normalitzar train i test amb criteris diferents. Aquí és una constant (255), però si fessis servir mitjana i desviació, es calcularien només amb el train — la mateixa disciplina de 03-05 i 06-01.
- Avaluar en test cada època. El test no és la validació: si el fas servir per decidir quan aturar-te o quina arquitectura triar, deixa de mesurar generalització. Per a això hi ha
validation_data. - Comparar xarxes amb una sola execució. La inicialització aleatòria fa variar el resultat unes dècimes; per afirmar que la CNN guanya la MLP convé repetir amb diverses llavors (amb dades tabulars faries servir la CV de 06-03; amb xarxes grans es repeteixen entrenaments, perquè la CV completa és cara).
- Xarxes enormes per a problemes petits. La temptació d'apilar capes arriba abans que la necessitat. Comença petit, mesura, i creix només si la validació ho demana.
Reptes per ampliar
- Data augmentation. Genera variants de les imatges d'entrenament (desplaçaments, girs lleus, mirall horitzontal) per ensenyar a la xarxa invariàncies que les dades no mostren. Pista: investiga les capes
keras.layers.RandomTranslationiRandomFlipcol·locades a l'inici del model; amb roba, compte amb el mirall vertical (un botí cap per avall continua sent un botí?). - Transfer learning. En problemes reals gairebé mai no s'entrena una CNN des de zero: es parteix d'una xarxa preentrenada amb milions de fotos (ResNet, MobileNet, disponibles a
keras.applications) i es reentrena només l'última capa. Pista: requereix redimensionar les imatges a la mida d'entrada de la xarxa triada i convertir-les a 3 canals; amb Fashion-MNIST el guany és modest, però el patró és el que faràs servir a la feina. - Les teves pròpies fotos. Fotografia una sabatilla o una samarreta sobre un fons clar, converteix-la a escala de grisos de 28×28 amb
PIL, inverteix el contrast si cal (Fashion-MNIST té el fons negre) i passa-la a la CNN. Pista: probablement fallarà més del que esperes — reflexiona sobre el drift entre les teves fotos i les d'entrenament, el mateix fenomen de 08-03 "Manteniment i monitoratge de models".
Conclusió
Aquest projecte t'ha donat la resposta pràctica a "clàssic o deep?": amb imatges diminutes i centrades com els dígits, una SVM amb escalat frega el 98 % i no necessites res més; amb imatges tot just una mica més riques com Fashion-MNIST, aplanar píxels comença a costar punts i les convolucions — filtres locals compartits, pooling, invariància a translacions — els recuperen amb menys paràmetres. També has practicat l'ofici d'entrenar xarxes: normalitzar píxels, vigilar les corbes train/validació, frenar l'overfitting amb dropout i early stopping, i llegir la matriu de confusió com un mapa d'errors raonables. La regla general que t'endus: comença pel mètode més simple que pugui funcionar, i que siguin les dades les que justifiquin cada capa addicional. En el pròxim projecte aplicaràs aquesta mateixa regla a un altre tipus de dada no tabular — el text — on els mètodes clàssics, amb la representació adequada, encara planten cara.
Curs de Machine Learning
Mòdul 1: Introducció al Machine Learning
- Què és el Machine Learning?
- Història i evolució del Machine Learning
- Tipus de Machine Learning
- Aplicacions del Machine Learning
- El flux de treball d'un projecte de Machine Learning
Mòdul 2: Fonaments d'Estadística i Probabilitat
- Conceptes bàsics d'estadística
- Distribucions de probabilitat
- Correlació i covariància
- Inferència estadística
- Teorema de Bayes
Mòdul 3: Preprocessament de Dades
- Neteja de dades
- Gestió de dades mancants
- Transformació de dades
- Codificació de variables categòriques
- Normalització i estandardització
- Enginyeria de característiques
Mòdul 4: Algorismes de Machine Learning Supervisat
- Regressió lineal
- Regressió logística
- Arbres de decisió
- Màquines de suport vectorial (SVM)
- K veïns més propers (K-NN)
- Naive Bayes
- Xarxes neuronals
Mòdul 5: Algorismes de Machine Learning No Supervisat
- Clustering: K-means
- Clustering jeràrquic
- Anàlisi de components principals (PCA)
- Anàlisi d'agrupament DBSCAN
- Visualització de dades amb t-SNE i UMAP
Mòdul 6: Avaluació i Validació de Models
- Divisió de dades: entrenament, validació i prova
- Mètriques d'avaluació
- Validació creuada
- Corba ROC i AUC
- Overfitting i underfitting
Mòdul 7: Tècniques Avançades i Optimització
- Regularització: Ridge, Lasso i Elastic Net
- Ensemble Learning
- Gradient Boosting
- Xarxes neuronals profundes (Deep Learning)
- Optimització d'hiperparàmetres
Mòdul 8: Implementació i Desplegament de Models
- Frameworks i biblioteques populars
- Implementació de models en producció
- Manteniment i monitoratge de models
- Consideracions ètiques i de privadesa
Mòdul 9: Projectes Pràctics
- Projecte 1: Predicció de preus d'habitatges
- Projecte 2: Classificació d'imatges
- Projecte 3: Anàlisi de sentiments a les xarxes socials
- Projecte 4: Detecció de fraus
- Projecte 5: Segmentació de clients
