En tancar el mòdul 6 vam deixar la caixa d'eines completa: sabem entrenar amb Keras, controlar l'entrenament amb callbacks, muntar pipelines amb tf.data i empaquetar models per a producció. Toca complir la primera promesa pendent des del mòdul 3: construir, de cap a cap, el classificador d'imatges de producte de TecnoMarket. En aquesta lliçó desenvoluparàs un projecte complet — exploració de dades, pipeline, model, entrenament, avaluació, anàlisi d'errors, millora iterativa i lliurament — seguint la metodologia del curs: prototip amb un dataset públic (CIFAR-10) i trasllat al cas fictici de TecnoMarket.
Contingut
- Enunciat del projecte i pla de treball
- Fase 1: exploració del dataset
- Fase 2: pipeline de dades amb
tf.datai augment de dades - Fase 3: el model — una mini-VGG amb BN i dropout
- Fase 4: entrenament amb callbacks i TensorBoard
- Fase 5: avaluació — matriu de confusió per classes
- Fase 6: anàlisi visual d'errors i millora iterativa
- Fase 7: lliurament — desat i connexió amb la cua de revisió
Enunciat del projecte i pla de treball
Context de negoci. Els venedors de TecnoMarket pugen fotos en donar d'alta un article i avui trien la categoria a mà; el 12 % de les altes acaba mal classificada i perjudica el cercador. Volem un model que proposi la categoria automàticament a partir de la foto, amb la salvaguarda que vam dissenyar a 03-04: si la confiança no supera un llindar, l'alta va a la cua de revisió humana.
Substitut. Com que (encara) no tenim el dataset real de TecnoMarket, prototipem amb CIFAR-10: 60 000 imatges 32×32 en 10 classes (avió, cotxe, ocell, gat, cérvol, gos, granota, cavall, vaixell, camió). Deu classes fan de substitut de deu categories de producte; tot el pipeline serà transplantable quan arribin les fotos reals.
Pla per fases (el mateix esquelet que farem servir en tots els projectes del mòdul):
| Fase | Lliurable |
|---|---|
| Exploració | Conèixer mides, classes, balanç |
| Pipeline | tf.data amb augment de dades integrat |
| Model | CNN pròpia (mini-VGG de 03-03 + BN/dropout de 05-04) |
| Entrenament | Callbacks (06-01), corbes, TensorBoard |
| Avaluació | Exactitud en test + matriu de confusió interpretada |
| Millora | Iteracions guiades pel diagnòstic |
| Lliurament | Model+preprocessament desats (06-05) + llindar de confiança (03-04) |
Fase 1: exploració del dataset
No entrenis mai sobre dades que no has mirat. Treballem a tecnomarket-dl/notebooks/07_01_classificador.ipynb (estructura de projecte de 06-04):
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
tf.random.set_seed(42) # reproduïbilitat, com vam fixar a 06-04
np.random.seed(42)
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.cifar10.load_data()
CLASSES = ["avio", "cotxe", "ocell", "gat", "cervol",
"gos", "granota", "cavall", "vaixell", "camio"]
print(x_train.shape, x_test.shape) # (50000, 32, 32, 3) (10000, 32, 32, 3)
print(np.bincount(y_train.flatten())) # 5000 per classe: dataset balancejatPunts que verifiquem i per què importen:
- Forma: 32×32×3 — imatges en color, molt petites. La nostra CNN ha d'estar dimensionada per a això (la fórmula de mides de sortida de 03-02 ens dirà quants blocs hi caben abans de quedar-nos sense píxels).
- Balanç: 5 000 imatges per classe. Això legitima fer servir l'exactitud com a mètrica principal; a 07-03 veurem què passa quan no hi ha balanç.
- Inspecció visual: pinta una graella de 25 imatges amb la seva etiqueta. Veuràs que a 32×32 fins i tot a un humà li costa distingir un gat d'un gos — calibra expectatives.
plt.figure(figsize=(8, 8))
for i in range(25):
plt.subplot(5, 5, i + 1)
plt.imshow(x_train[i])
plt.title(CLASSES[int(y_train[i])], fontsize=8)
plt.axis("off")
plt.tight_layout()Fase 2: pipeline de dades amb tf.data i augment de dades
Apliquem el patró map → shuffle → batch → prefetch de 06-01, i hi afegim l'augment de dades amb capes de Keras que vam veure a 05-04. Reservem a més un conjunt de validació separat del test:
# Separar validació (últimes 5000 del train) — el test queda congelat (06-05)
x_val, y_val = x_train[45000:], y_train[45000:]
x_train, y_train = x_train[:45000], y_train[:45000]
BATCH = 64
AUTOTUNE = tf.data.AUTOTUNE
# Augment de dades com a capes (05-04): només actua en entrenament
augment = tf.keras.Sequential([
tf.keras.layers.RandomFlip("horizontal"),
tf.keras.layers.RandomTranslation(0.1, 0.1),
tf.keras.layers.RandomZoom(0.1),
], name="augment")
def preparar(x, y):
return tf.cast(x, tf.float32) / 255.0, y # normalització a [0, 1]
train_ds = (tf.data.Dataset.from_tensor_slices((x_train, y_train))
.map(preparar, num_parallel_calls=AUTOTUNE)
.shuffle(10_000)
.batch(BATCH)
.map(lambda x, y: (augment(x, training=True), y),
num_parallel_calls=AUTOTUNE)
.prefetch(AUTOTUNE))
val_ds = (tf.data.Dataset.from_tensor_slices((x_val, y_val))
.map(preparar).batch(BATCH).prefetch(AUTOTUNE))
test_ds = (tf.data.Dataset.from_tensor_slices((x_test, y_test))
.map(preparar).batch(BATCH).prefetch(AUTOTUNE))Detalls que marquen la diferència:
- L'augment va després del
batchi ambtraining=Trueexplícit: s'aplica a lots complets i només al pipeline d'entrenament. Validació i test veuen les imatges originals, com exigeix la validació honesta de 06-05. - Capgirament horitzontal sí, vertical no: un cotxe cap per avall no és una foto de catàleg realista. L'augment ha de generar variacions plausibles (criteri de 05-04).
- La normalització (
/255) viu apreparar; a la fase de lliurament la posarem dins del model perquè hi viatgi (06-05).
Fase 3: el model — una mini-VGG amb BN i dropout
Reprenem la mini-VGG que vam construir a 03-03 (blocs Conv-Conv-Pool amb filtres que es dupliquen) i hi incorporem les millores de 05-04: BatchNormalization després de cada convolució i Dropout creixent:
from tensorflow.keras import layers, models
def bloc_conv(x, filtres, dropout):
for _ in range(2):
x = layers.Conv2D(filtres, 3, padding="same", use_bias=False)(x)
x = layers.BatchNormalization()(x)
x = layers.Activation("relu")(x)
x = layers.MaxPooling2D()(x)
return layers.Dropout(dropout)(x)
entrades = layers.Input(shape=(32, 32, 3))
x = bloc_conv(entrades, 32, 0.2) # 32x32 -> 16x16
x = bloc_conv(x, 64, 0.3) # 16x16 -> 8x8
x = bloc_conv(x, 128, 0.4) # 8x8 -> 4x4
x = layers.Flatten()(x)
x = layers.Dense(128, activation="relu")(x)
x = layers.Dropout(0.5)(x)
sortides = layers.Dense(10, activation="softmax")(x)
model = models.Model(entrades, sortides, name="tecnomarket_cnn_v1")
model.summary() # ~1.2 M de paràmetresDecisions explicades:
- API funcional (introduïda a 03-03): ens permet factoritzar el bloc en una funció i facilita futures cirurgies (extreure embeddings com a 03-04, per exemple).
use_bias=Falseabans de la BN: la BatchNormalization ja aporta el seu propi desplaçament; el biaix de la convolució seria redundant.- Dropout creixent (0.2 → 0.5): les capes profundes, amb més paràmetres, necessiten més regularització (05-04).
- Tres blocs de pooling porten de 32×32 a 4×4 — amb la fórmula de 03-02 es comprova que un quart bloc deixaria mapes de 2×2, massa pobres.
Fase 4: entrenament amb callbacks i TensorBoard
Compilem amb Adam i entropia creuada (02-04) i entrenem amb els callbacks de 06-01:
model.compile(optimizer=tf.keras.optimizers.Adam(1e-3),
loss="sparse_categorical_crossentropy",
metrics=["accuracy"])
callbacks = [
tf.keras.callbacks.EarlyStopping(monitor="val_accuracy", patience=8,
restore_best_weights=True),
tf.keras.callbacks.ModelCheckpoint("models/cnn_v1_millor.keras",
monitor="val_accuracy",
save_best_only=True),
tf.keras.callbacks.ReduceLROnPlateau(monitor="val_loss", factor=0.5,
patience=3, min_lr=1e-5),
tf.keras.callbacks.TensorBoard(log_dir="logs/cnn_v1"),
]
historial = model.fit(train_ds, validation_data=val_ds,
epochs=60, callbacks=callbacks)Mentre entrena (en una GPU modesta, ~15-25 min; en CPU, compta amb més d'una hora), obre TensorBoard com a 06-04 (tensorboard --logdir logs) i vigila:
- Corbes train/val juntes i pujant: gràcies a l'augment de dades i al dropout, la bretxa entre entrenament i validació es manté petita (sense ells, aquesta mateixa xarxa es dispara al 99 % en train i s'estanca en ~72 % en validació — sobreajust de llibre, 05-04).
- Esglaons en baixar el learning rate:
ReduceLROnPlateausol provocar una millora visible d'1-2 punts quan actua.
Resultat típic honest: la validació s'estanca entre el 82 % i el 85 % cap a l'època 40-50. No esperis més d'una CNN pròpia entrenada des de zero sobre 32×32; ja està bé.
Fase 5: avaluació — matriu de confusió per classes
L'exactitud global no és suficient per a una decisió de negoci: hem de saber què confon el model.
from sklearn.metrics import confusion_matrix, classification_report
test_loss, test_acc = model.evaluate(test_ds)
print(f"Exactitud en test: {test_acc:.3f}") # típic: 0.82 - 0.85
y_prob = model.predict(test_ds)
y_pred = np.argmax(y_prob, axis=1)
print(classification_report(y_test.flatten(), y_pred, target_names=CLASSES))
cm = confusion_matrix(y_test.flatten(), y_pred)Interpretació típica de la matriu (els números exactes variaran):
| Confusió freqüent | Lectura | Anàleg a TecnoMarket |
|---|---|---|
| gat ↔ gos | Animals petits, textures semblants a 32×32 | «auriculars» ↔ «cascs gaming» |
| avió ↔ vaixell | Fons blaus dominen la imatge | productes fotografiats sobre el mateix fons |
| cotxe ↔ camió | Mateixa família semàntica | «mòbil» ↔ «tauleta» |
| granota, vaixell | Classes amb >90 % d'encert | categories visualment distintives |
La lliçó de negoci: els errors no es reparteixen uniformement. Les parelles confusibles són exactament on el llindar de confiança de 03-04 haurà de treballar més.
Fase 6: anàlisi visual d'errors i millora iterativa
Abans de tocar res, mira les errades amb els teus ulls:
errors = np.where(y_pred != y_test.flatten())[0]
plt.figure(figsize=(10, 6))
for i, idx in enumerate(errors[:15]):
plt.subplot(3, 5, i + 1)
plt.imshow(x_test[idx])
plt.title(f"real: {CLASSES[int(y_test[idx])]}\npred: {CLASSES[y_pred[idx]]}"
f" ({y_prob[idx].max():.2f})", fontsize=7)
plt.axis("off")
plt.tight_layout()Hi veuràs tres famílies d'errors: imatges genuïnament ambigües (ni tu l'encertes), errors amb confiança baixa (el llindar els atraparà) i errors amb confiança alta (els perillosos). Amb aquest diagnòstic, itera en aquest ordre — del més barat al més car:
- Més èpoques / paciència: si les corbes encara pujaven en aturar-se, és millora gratis.
- Ajustar l'augment de dades: si hi ha bretxa train-val, intensifica'l; si el train no arriba a bon nivell, suavitza'l (l'augment excessiu també fa mal, 05-04).
- Ajustar la regularització: moure el dropout ±0.1 segons la bretxa.
- Capacitat: un quart bloc convolucional o més filtres — només si el train s'ha quedat curt.
- Canviar d'arquitectura: la porta que obrirem a 07-05.
Regla d'or del mòdul: un canvi per iteració, amb llavor fixa (06-04), o no sabràs què va causar què.
Fase 7: lliurament — desat i connexió amb la cua de revisió
Seguint 06-05, empaquetem model + preprocessament com una unitat, perquè l'API no es pugui desincronitzar de l'entrenament:
# Model d'inferència: normalització dins + probabilitats fora
entrada = layers.Input(shape=(32, 32, 3), dtype=tf.uint8)
x = layers.Rescaling(1.0 / 255)(tf.cast(entrada, tf.float32))
prob = model(x, training=False)
model_servei = models.Model(entrada, prob)
model_servei.save("models/classificador_categories_v1.keras")I apliquem la política de 03-04 amb el llindar de confiança:
LLINDAR = 0.80 # calibrat sobre validació, mai sobre test
def classificar_alta(imatge_uint8):
p = model_servei.predict(imatge_uint8[np.newaxis, ...], verbose=0)[0]
if p.max() >= LLINDAR:
return {"categoria": CLASSES[int(p.argmax())], "auto": True}
return {"categoria": None, "auto": False, "desti": "cua_revisio",
"suggeriments": [CLASSES[i] for i in p.argsort()[-3:][::-1]]}Amb el llindar a 0.80, un resultat típic és que el model resol automàticament al voltant del 70 % de les altes amb una exactitud sobre aquest subconjunt propera al 92-94 %, i deriva la resta a revisió humana amb les tres categories més probables com a suggeriment — humans i model col·laborant, com vam dissenyar a 03-04. El servei s'exposaria amb la mateixa plantilla FastAPI de 06-05.
Errors Comuns i Consells
- Aplicar l'augment de dades també a validació/test: infla artificialment la dificultat i et fa prendre males decisions. L'augment només viu a
train_ds. - Triar el llindar de confiança mirant el test: el test és el conjunt congelat (06-05); si el fas servir per calibrar, ja no mesura res. Llindar i ajustos, sempre amb validació.
- Canviar tres coses alhora entre iteracions: no sabràs què va funcionar. Un canvi, una llavor fixa, una comparació.
- Frustrar-se per no passar del 85 %: és el sostre raonable d'aquest enfocament amb aquest dataset. La millora gran no vindrà d'insistir, sinó de canviar d'estratègia (07-05).
- Oblidar
restore_best_weights=True: sense això, l'EarlyStopping et deixa els pesos de l'última època, no els millors.
Exercicis
- Afegeix un quart bloc convolucional de 256 filtres (ajustant el dropout) i compara exactitud i temps d'entrenament amb la versió de tres blocs. Surt a compte?
- Genera la llista de les 20 prediccions errònies amb més confiança. Quines parelles de classes hi apareixen? Proposa un canvi d'augment de dades dirigit a elles.
- Calcula, per a llindars 0.6, 0.7, 0.8 i 0.9 sobre el conjunt de validació, quin percentatge d'imatges s'automatitza i quina exactitud té aquest subconjunt. Presenta la taula i tria llindar justificant el compromís.
Solucions
- Substitueix l'última crida per
x = bloc_conv(x, 256, 0.4)després del bloc de 128 (els mapes passen de 4×4 a 2×2, just al límit). Resultat típic: +0.5-1 punt d'exactitud a canvi de ~1.5× temps per època. Surt a compte només si aquest punt importa; documenta totes dues xifres. idx = errors[np.argsort(-y_prob[errors].max(axis=1))[:20]]i pinta com a la fase 6. Solen dominar gat↔gos i cotxe↔camió. Canvi dirigit:RandomContrast(0.2)o retalls aleatoris (RandomCropamb padding) que forcin la xarxa a fixar-se en la forma i no en el fons.- Per a cada llindar
u:mask = p_val.max(axis=1) >= u; cobertura =mask.mean(); exactitud automatitzada =(pred_val[mask] == y_val_flat[mask]).mean(). Taula típica: 0.6 → 85 %/89 %; 0.7 → 78 %/91 %; 0.8 → 70 %/93 %; 0.9 → 55 %/96 %. La tria depèn del cost d'un error davant del cost de revisar: per a altes de catàleg, 0.8 sol ser un bon compromís.
Conclusió
Has lliurat el primer projecte complet de TecnoMarket: un classificador de categories amb pipeline tf.data, augment de dades, una mini-VGG regularitzada, entrenament controlat per callbacks, avaluació amb matriu de confusió, anàlisi d'errors i un empaquetament a punt per servir darrere d'un llindar de confiança amb cua de revisió. El resultat — 82-85 % en test — és honest per a una CNN entrenada des de zero, i a 07-05 el batrem amb transfer learning gairebé sense escriure més codi. Abans, canviem de modalitat: a la propera lliçó complim la promesa de 04-04 i construïm un generador de text caràcter a caràcter per als esborranys de descripcions de producte de TecnoMarket.
Curs de Deep Learning
Mòdul 1: Introducció al Deep Learning
- Què és el Deep Learning?
- Història i evolució del Deep Learning
- Aplicacions del Deep Learning
- Conceptes bàsics de xarxes neuronals
- Preparació de l'entorn de treball
Mòdul 2: Fonaments de Xarxes Neuronals
- Perceptró i Perceptró Multicapa
- Funció d'activació
- Propagació cap endavant i cap enrere
- Optimització i funció de pèrdua
- La teva primera xarxa neuronal completa
Mòdul 3: Xarxes Neuronals Convolucionals (CNN)
- Introducció a les CNN
- Capes convolucionals i de pooling
- Arquitectures populars de CNN
- Aplicacions de CNN en reconeixement d'imatges
Mòdul 4: Xarxes Neuronals Recurrents (RNN)
- Introducció a les RNN
- LSTM i GRU
- Aplicacions de RNN en processament del llenguatge natural
- Seqüències i sèries temporals
Mòdul 5: Tècniques Avançades en Deep Learning
- Xarxes Generatives Adversàries (GAN)
- Autoencoders
- Transfer Learning
- Regularització i tècniques de millora
- Mecanismes d'atenció i Transformers
Mòdul 6: Eines i Frameworks
- Introducció a TensorFlow
- Introducció a PyTorch
- Comparació de frameworks
- Entorns de desenvolupament i recursos addicionals
- Desar, carregar i desplegar models
Mòdul 7: Projectes Pràctics
- Classificació d'imatges amb CNN
- Generació de text amb RNN
- Detecció d'anomalies amb Autoencoders
- Creació d'una GAN per a generació d'imatges
- Fine-tuning d'un model preentrenat
