Un model amb un 97 % de precisió que només existeix a la teva sessió de Python val exactament zero per al negoci. L'equip de TecnoMarket ho té clar: el classificador de ressenyes de 04-03 només aporta valor quan la web el pot consultar en viu, i el classificador de fotos de 05-03 només estalvia feina quan el sistema del magatzem l'invoca amb cada producte nou. Aquesta lliçó tanca el cicle que va començar amb aquell tímid model.save() de 02-05: aprendràs els formats de desat de Keras i PyTorch i quan fer servir cadascun, per què el preprocessament s'ha de desar al costat del model, les opcions per servir prediccions (d'un script batch a una API REST amb FastAPI, amb exemple complet), ONNX com a pont entre frameworks, i com validar i vigilar un model abans i després de desplegar-lo. És la darrera milla: on el deep learning es converteix en producte.
Contingut
- Desar i carregar en Keras: .keras, SavedModel i només pesos
- Desar i carregar en PyTorch: state_dict i checkpoints
- El model no viatja sol: desar el preprocessament
- Servir un model: opcions en escala
- Una API REST amb FastAPI per a les ressenyes de TecnoMarket
- ONNX: el format pont
- Validar abans de desplegar i vigilar després
- Checklist de desplegament de TecnoMarket
Desar i carregar en Keras: .keras, SavedModel i només pesos
A 02-05 vas fer model.save("tecnomarket-dl/models/mnist_dens_v1.keras") i vam seguir endavant. Professionalitzem-ho: Keras té tres maneres de desar, cadascuna amb el seu cas d'ús.
from tensorflow import keras
# 1. Format .keras (el recomanat): TOT en un sol fitxer
model.save("models/ressenyes_v3.keras")
model_carregat = keras.models.load_model("models/ressenyes_v3.keras")
# Desa: arquitectura + pesos + estat de l'optimitzador + compile()
# -> pots continuar entrenant exactament on ho vas deixar
# 2. SavedModel (format de TensorFlow, un directori complet)
model.export("models/ressenyes_v3_savedmodel")
# Desa el graf de computació autocontingut, SENSE necessitar el teu codi Python
# -> és el que consumeixen TF Serving i els conversors (TFLite)
# 3. Només pesos
model.save_weights("models/ressenyes_v3.weights.h5")
model_nou = crear_model() # necessites RECONSTRUIR l'arquitectura
model_nou.load_weights("models/ressenyes_v3.weights.h5")| Format | Què desa | Quan fer-lo servir |
|---|---|---|
.keras |
Arquitectura + pesos + optimitzador | Ús general: pausar/reprendre, arxivar, compartir amb qui tingui Keras |
SavedModel (export) |
Graf autocontingut per a inferència | Lliurar a sistemes de producció TF (Serving, TFLite) |
| Només pesos | Únicament els números apresos | El codi defineix l'arquitectura (transfer learning com a 05-03, checkpoints lleugers) |
Nota pràctica: el ModelCheckpoint de 06-01 escriu en format .keras; ja portaves mitja lliçó apresa.
Desar i carregar en PyTorch: state_dict i checkpoints
PyTorch gira al voltant del state_dict: un diccionari {nom_de_parametre: tensor} amb tots els pesos del model. La manera canònica de desar és aquest diccionari, no l'objecte model:
import torch
# DESAR: la manera recomanada (només el state_dict)
torch.save(model.state_dict(), "models/ressenyes_v3.pt")
# CARREGAR: reconstruir l'arquitectura i abocar-hi els pesos
model = ClassificadorRessenyes() # la classe de 06-02
model.load_state_dict(torch.load("models/ressenyes_v3.pt"))
model.eval() # a mode inferència! (vegeu 06-02)Per què no desar el model sencer amb torch.save(model, ...)? Es pot fer, però serialitza referències al teu codi (classe, mòdul, rutes): en carregar-lo mesos després amb el projecte reorganitzat, es trenca. El state_dict són només tensors amb nom: robust i portable. És l'anàleg del "només pesos" de Keras — a PyTorch, l'opció per defecte.
Checkpoint complet: pausar i reprendre entrenaments
Per reprendre un entrenament no n'hi ha prou amb els pesos: Adam (02-04) manté estadístiques internes per paràmetre que també cal restaurar. El patró professional:
# Desar un checkpoint complet al final de cada època
checkpoint = {
"epoch": epoch,
"model_state": model.state_dict(),
"optimizer_state": optimizer.state_dict(), # l'estat d'Adam també!
"val_loss": val_loss,
}
torch.save(checkpoint, "models/checkpoint_darrer.pt")
# Reprendre dies després, exactament on s'havia quedat
ckpt = torch.load("models/checkpoint_darrer.pt")
model.load_state_dict(ckpt["model_state"])
optimizer.load_state_dict(ckpt["optimizer_state"])
epoca_inicial = ckpt["epoch"] + 1| Necessitat | Keras | PyTorch |
|---|---|---|
| Arxivar/compartir un model a punt | .keras |
state_dict + codi de la classe |
| Reprendre un entrenament | .keras (porta l'optimitzador) |
checkpoint dict (model + optimitzador + època) |
| Lliurar a producció | SavedModel | state_dict (o exportar a ONNX/TorchScript) |
El model no viatja sol: desar el preprocessament
Aquí hi ha l'error que més desplegaments trenca, i és conceptual, no tècnic. El teu model va aprendre sobre dades transformades: si en producció rep dades brutes o transformades d'una altra manera, les seves prediccions seran escombraries sense donar cap error. Recorda dos casos del curs:
- A 04-04, el predictor de demanda va entrenar amb vendes escalades per un
scalerajustat sobre el train (amb cura de no fugar informació). Si en producció li passes euros crus on esperava valors en [0, 1], predirà disbarats amb total seguretat en si mateix. - A 04-03, el classificador de ressenyes fa servir una capa
TextVectorizationel vocabulari de la qual es va aprendre del corpus d'entrenament. Un altre vocabulari = altres índexs = una altra "llengua": la xarxa rebria un galimaties.
La regla d'or: el parell (preprocessament, model) és LA unitat desplegable. Mai no viatgen per separat.
# Opció A (Keras): posar el preprocessament DINS del model abans de desar
# (TextVectorization és una capa: pot formar part del graf)
model_complet = keras.Sequential([
keras.Input(shape=(1,), dtype="string"), # hi entra TEXT cru!
vectoritzador, # la TextVectorization ja adaptada (04-03)
model_entrenat, # la xarxa que espera índexs
])
model_complet.save("models/ressenyes_complet.keras")
# En producció: model.predict(["El carregador va arribar trencat"]) -> directe del text
# Opció B (general, vàlida també per a PyTorch): desar el transformador a part
import joblib
joblib.dump(scaler, "models/demanda_scaler.joblib") # el scaler de 04-04
# ...i en producció, SEMPRE en parella:
scaler = joblib.load("models/demanda_scaler.joblib")
x = scaler.transform(dades_brutes) # mateix scaler, mateixos paràmetres
prediccio = model.predict(x)L'opció A és la més segura (impossible desincronitzar); la B és la més flexible. Triïs la que triïs: mateixos fitxers versionats junts, amb el mateix nom de versió.
Servir un model: opcions en escala
"Desplegar" no vol dir sempre "muntar un servidor". Hi ha una escala d'opcions, de menys a més complexitat, i el que és professional és triar la mínima que cobreixi la necessitat:
| Opció | Com funciona | Cas de TecnoMarket |
|---|---|---|
| Script batch | Un script s'executa periòdicament, prediu sobre el que s'ha acumulat i desa els resultats | Predicció de demanda setmanal (04-04): no cal temps real |
| API REST | Un servidor exposa el model per HTTP; altres aplicacions demanen prediccions al moment | Classificar cada ressenya en publicar-se (04-03) |
| Servidor de models (TF Serving / TorchServe) | Programari especialitzat: versions convivint, lots automàtics, alt rendiment | Quan les peticions/segon desbordin l'API artesanal |
| Edge (TFLite) | El model, comprimit, corre AL dispositiu, sense xarxa | El classificador de fotos a l'app del magatzem (pla de 06-03) |
Un script batch mínim (de vegades la millor arquitectura és la més avorrida):
# predir_demanda_setmanal.py -- es programa perquè s'executi cada dilluns
import joblib
from tensorflow import keras
model = keras.models.load_model("models/demanda_v2.keras")
scaler = joblib.load("models/demanda_scaler_v2.joblib") # SEMPRE en parella
vendes = carregar_vendes_darreres_setmanes() # de la base de dades
pred = model.predict(scaler.transform(vendes))
desar_prediccions(scaler_invers(pred)) # a la taula que llegeix compresTF Serving i TorchServe queden anotats al teu radar: són l'evolució natural quan l'escala apreti, i consumeixen precisament els formats "de producció" de la primera part (SavedModel i state_dict empaquetat).
Una API REST amb FastAPI per a les ressenyes de TecnoMarket
Servirem el classificador de ressenyes en viu. FastAPI és un framework web de Python modern i minimalista, ideal per embolcallar un model. Instal·lació: pip install fastapi uvicorn.
# api_ressenyes.py
from fastapi import FastAPI
from pydantic import BaseModel
from tensorflow import keras
app = FastAPI(title="TecnoMarket - Analisi de ressenyes")
# 1. Carregar el model UN cop, en arrencar el servidor (no a cada peticio)
# Es el model "complet" amb TextVectorization a dins (opcio A d'abans)
model = keras.models.load_model("models/ressenyes_complet.keras")
LLINDAR_REVISIO = 0.65 # el llindar de confianca del pipeline de 03-04
class Ressenya(BaseModel): # 2. contracte d'entrada: JSON amb un camp "text"
text: str
@app.post("/classificar") # 3. endpoint: POST /classificar
def classificar(ressenya: Ressenya):
prob = float(model.predict([ressenya.text], verbose=0)[0][0]) # 4. inferencia
sentiment = "positiva" if prob >= 0.5 else "negativa"
confianca = max(prob, 1 - prob)
return { # 5. resposta JSON per a la web/CRM
"sentiment": sentiment,
"confianca": round(confianca, 3),
"revisio_humana": confianca < LLINDAR_REVISIO, # cua de revisio (03-04)
"versio_model": "ressenyes_v3",
}Explicació de les decisions:
- Carregar en arrencar: carregar el model triga segons; una predicció, mil·lisegons. Carregar-lo a cada petició mataria l'API. La càrrega va a nivell de mòdul.
- Pydantic (
BaseModel) defineix i valida l'entrada: si arriba un JSON sensetext, FastAPI retorna un error clar automàticament, sense que el model vegi res. - L'endpoint és
POSTperquè envia dades al cos de la petició. - Gràcies a l'opció A (vectoritzador a dins), l'API rep text cru: zero risc de desincronitzar preprocessaments.
- La resposta inclou la versió del model (traçabilitat) i el camp
revisio_humana: el mateix patró de llindar de confiança + cua de revisió que vam dissenyar a 03-04, ara en producció.
Arrencar i provar:
uvicorn api_ressenyes:app --port 8000
# Documentacio interactiva automatica: http://localhost:8000/docs
curl -X POST http://localhost:8000/classificar \
-H "Content-Type: application/json" \
-d '{"text": "El carregador va arribar trencat i ningu respon"}'
# {"sentiment":"negativa","confianca":0.94,"revisio_humana":false,"versio_model":"ressenyes_v3"}Amb això, qualsevol sistema de TecnoMarket (la web, el CRM d'atenció al client) consumeix el model amb una simple petició HTTP, sense saber res de deep learning.
ONNX: el format pont
A 06-03 vam deixar anotat ONNX (Open Neural Network Exchange): un format estàndard d'intercanvi de models. La idea: entrenes on vulguis, exportes a .onnx, i executes amb ONNX Runtime gairebé a qualsevol lloc (Python, C#, Java, mòbil...), sense instal·lar el framework d'origen.
# Exportar el model PyTorch de ressenyes (06-02) a ONNX
import torch
model.eval()
exemple = torch.randn(1, 200) # entrada d'exemple (defineix shapes)
torch.onnx.export(model, exemple, "models/ressenyes.onnx",
input_names=["ressenya"], output_names=["logit"])
# Executar-lo SENSE PyTorch, nomes amb onnxruntime (pip install onnxruntime)
import onnxruntime as ort
import numpy as np
sessio = ort.InferenceSession("models/ressenyes.onnx")
sortida = sessio.run(None, {"ressenya": np.random.rand(1, 200).astype("float32")})
print(sortida[0]) # el logit, identic al que donaria el model originalQuan interessa: lliurar un model a un equip que treballa en un altre llenguatge o framework, unificar la inferència de models d'orígens diferents (just el cas de TecnoMarket després de la decisió de 06-03: Keras en producció, PyTorch en exploració), o aprofitar les optimitzacions d'inferència d'ONNX Runtime. Bones pràctiques mínimes: després d'exportar, comparar les sortides del model original i de l'ONNX sobre un lot de prova (han de coincidir llevat de decimals) — i recorda que el preprocessament extern continua viatjant a part, també aquí.
Validar abans de desplegar i vigilar després
Desplegar no és un acte de fe; és un procés amb dos guardes:
Abans: validació sobre un conjunt congelat. L'equip manté un conjunt de test fix i versionat (ressenyes reals etiquetades a mà, mai no usades per entrenar — la disciplina anti-fuga de 04-04 elevada a norma d'equip). Tot candidat a producció, sigui Keras o PyTorch (la regla acordada a 06-03), ha de:
- Igualar o superar en aquest conjunt les mètriques del model actual en producció.
- Comprovar-se també en els segments crítics (ressenyes curtes, productes nous): un model millor "de mitjana" pot ser pitjor just on més fa mal.
- Passar una prova de fum de la unitat desplegable completa: aixecar l'API amb l'artefacte final i verificar prediccions conegudes d'extrem a extrem.
Després: monitorar la deriva (drift). El món canvia i les dades de producció s'allunyen a poc a poc de les d'entrenament: TecnoMarket llança categories noves, el vocabulari de les ressenyes evoluciona ("no em funciona l'aparellament BLE" no existia al corpus original). El rendiment decau en silenci: no hi ha excepció ni log d'error, només prediccions cada cop pitjors. Vigilància conceptual mínima:
- Registrar cada predicció amb la seva confiança i la versió del model (per això l'API la retorna).
- Vigilar senyals indirectes: % de casos enviats a revisió humana pujant, distribució de confiances desplaçant-se, i les correccions dels revisors humans com a mostreig continu de la veritat.
- Definir el llindar d'acció per endavant: "si X empitjora més de Y durant Z setmanes, es reentrena amb dades recents" — i el reentrenament torna a passar pel conjunt congelat. El cicle es tanca.
Checklist de desplegament de TecnoMarket
La llista que l'equip repassa abans de cada posada en producció:
- Model desat en el format correcte (
.keras/ state_dict) amb nom de versió (ressenyes_v3), nomodel_final_BO. - Preprocessament desat i versionat al costat del model (dins del graf o com a artefacte parella).
- Mètriques sobre el conjunt congelat ≥ model actual, inclosos els segments crítics.
- Llavors, config i versions de llibreries registrades (la reproduïbilitat de 06-04): aquest model es pot reconstruir des de zero.
- Prova de fum de la unitat desplegable: API aixecada en local, prediccions conegudes verificades d'extrem a extrem.
- La resposta de l'API inclou versió del model i senyal de confiança; els casos dubtosos van a la cua de revisió humana (03-04).
- Monitoratge actiu: logging de prediccions i alertes de deriva definides abans del llançament, no després del primer ensurt.
- Pla de tornada enrere: la versió anterior (
ressenyes_v2) queda arxivada i a punt per restaurar en minuts.
Errors Comuns i Consells
- Desplegar el model sense el seu preprocessament (o amb una altra versió d'aquest): la fallada silenciosa número u. No hi ha error, només prediccions absurdes amb confiança alta. La unitat desplegable és el parell, sempre.
torch.save(model, ...)de l'objecte sencer: funciona avui, es trenca quan refactoritzes el projecte. State_dict + codi de la classe, com a norma.- Oblidar
model.eval()en carregar en PyTorch per a inferència: amb el dropout actiu, cada petició a la teva API donaria una predicció diferent per a la mateixa ressenya (ho vas veure a 06-02; en producció és un bug desconcertant). - Carregar el model dins de l'endpoint: cada petició trigaria segons i l'API s'arrossegaria. Carregar un cop en arrencar; predir moltes vegades.
- Validar sobre un test que canvia: si cada candidat es mesura contra dades diferents, les comparacions no signifiquen res. Conjunt congelat i versionat.
- Confiar que "si no hi ha errors, funciona": la degradació per deriva no llança excepcions. Sense monitoratge, el teu model pot portar mesos equivocant-se quan algú se n'adoni.
- Consell: després de cada
save, fes el cicle complet en un intèrpret net: carregar, predir sobre 3 exemples coneguts, comparar amb els valors esperats. Trenta segons que detecten el 90 % dels problemes de serialització.
Exercicis
Exercici 1: triar format
Per a cada situació, indica el mecanisme de desat adequat i per què: (a) l'entrenament PyTorch del mòdul 7 durarà tres nits a Colab i s'ha de poder reprendre cada matí; (b) cal lliurar el classificador Keras de fotos a l'equip de la plataforma per servir-lo amb TF Serving; (c) vols arxivar el classificador de ressenyes Keras per reprendre'l (fins i tot reentrenar-lo) d'aquí a sis mesos; (d) l'equip backend, que programa en C# i no vol instal·lar PyTorch, necessita executar el model de frau.
Exercici 2: el desplegament que prediu disbarats
El predictor de demanda de 04-04 es desplega com a script batch. En proves donava un error mitjà del 8 %; en producció prediu quantitats absurdes (milers d'unitats d'un producte que en ven desenes), sense cap error als logs. L'script carrega demanda_v2.keras correctament i la base de dades lliura bé les vendes. Quina és la causa més probable, per què no apareix cap error, i quins dos canvis faries per arreglar-ho i perquè no torni a passar?
Exercici 3: ampliar l'API
Amplia api_ressenyes.py amb un endpoint POST /classificar_lot que rebi una llista de ressenyes (fins a 100) i retorni una llista de resultats, aprofitant que el model prediu més eficientment per lots que d'una en una. Afegeix també un endpoint GET /salut que retorni {"estat": "ok", "versio_model": "ressenyes_v3"} i explica per a què serveix en producció.
Solucions
Solució 1:
- (a) Checkpoint complet PyTorch (dict amb
model_state,optimizer_statei època) desat a Drive cada època: reprendre exigeix l'estat d'Adam, no només pesos. - (b) SavedModel (
model.export(...)): és el format autocontingut que TF Serving consumeix directament. - (c)
.keras: un sol fitxer amb arquitectura, pesos i optimitzador; d'aquí a sis mesos,load_modeli continuar (reentrenament inclòs). - (d) ONNX: exportar amb
torch.onnx.exporti lliurar el.onnx; el backend l'executa amb ONNX Runtime per a C#, sense PyTorch. (Verificant abans que les sortides coincideixen amb l'original.)
Solució 2: Causa més probable: l'script aplica malament (o no aplica) el scaler — en fa servir un de reajustat sobre altres dades, una versió desincronitzada, o passa vendes crues al model que esperava valors escalats; també hi encaixa oblidar la transformació inversa de la sortida. No hi ha error perquè les shapes i els dtypes són vàlids: el model opera amb números "legals" però a l'escala equivocada — la fallada silenciosa clàssica. Arranjaments: (1) desplegar el parell com a unitat — carregar demanda_scaler_v2.joblib versionat al costat del model, mateix sufix de versió, i aplicar transform a l'entrada i la inversa a la sortida; (2) afegir una prova de fum al checklist: després de cada desplegament, predir sobre 3 casos històrics coneguts i comparar amb els valors esperats abans d'escriure res a la taula de compres (més una validació de rangs: si la predicció surt del rang històric raonable, alerta i no publicar).
Solució 3:
from typing import List
from pydantic import BaseModel, Field
class LotRessenyes(BaseModel):
textos: List[str] = Field(..., max_length=100) # valida el limit de 100
@app.post("/classificar_lot")
def classificar_lot(lot: LotRessenyes):
probs = model.predict(lot.textos, verbose=0)[:, 0] # UNA passada per lots
resultats = []
for text, prob in zip(lot.textos, probs):
prob = float(prob)
confianca = max(prob, 1 - prob)
resultats.append({
"sentiment": "positiva" if prob >= 0.5 else "negativa",
"confianca": round(confianca, 3),
"revisio_humana": confianca < LLINDAR_REVISIO,
})
return {"versio_model": "ressenyes_v3", "resultats": resultats}
@app.get("/salut")
def salut():
return {"estat": "ok", "versio_model": "ressenyes_v3"}La clau d'eficiència: una sola crida a model.predict amb les 100 ressenyes (el paral·lelisme per lots de tot el curs), no 100 crides. L'endpoint /salut (health check) permet a la infraestructura comprovar cada pocs segons que l'API és viva i quina versió serveix: si deixa de respondre, es reinicia o s'avisa automàticament — i durant un desplegament confirma que la versió nova és realment en marxa.
Conclusió
Es tanca el cercle que va obrir el model.save() de 02-05. Ara saps desar amb criteri (.keras/SavedModel/pesos en Keras; state_dict i checkpoints en PyTorch), saps que la unitat desplegable és el parell preprocessament + model (el scaler de 04-04 i el vocabulari de 04-03 viatgen sempre amb la seva xarxa), saps triar la manera de servir segons la necessitat (batch → FastAPI → servidors de models → edge), tens ONNX com a pont entre mons, i entens que desplegar exigeix validar contra un conjunt congelat abans i vigilar la deriva després — tot condensat al checklist de TecnoMarket.
I amb això, el mòdul 6 queda complet: coneixes TensorFlow per dins, parles PyTorch, saps triar entre ells, treballes amb entorn i disciplina professionals, i portes models fins a producció. Tècniques (mòduls 2-5) i eines (mòdul 6): la caixa és plena. Al mòdul 7 la buidarem sencera: construirem de principi a fi els projectes complets de TecnoMarket — classificació d'imatges, generació de text, detecció d'anomalies, una GAN i el fine-tuning d'un model preentrenat. Deixa de ser un curs; comença a ser un portafolis.
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
