A 04-04 vas aprendre a convertir seqüències en finestres lliscants per predir «el valor següent», i va quedar promès que aquest mateix truc serviria per generar text. Ha arribat l'hora. En aquest segon projecte del mòdul construiràs un generador d'esborranys de descripcions de producte per als venedors de TecnoMarket: un model que, caràcter a caràcter, aprèn l'estil de les fitxes de producte i proposa text nou. A més de les fases habituals del projecte, aquesta lliçó introdueix l'única peça tècnica nova que necessites: el mostreig amb temperatura, que controla l'equilibri entre text segur-però-repetitiu i text creatiu-però-caòtic.
Contingut
- Enunciat del projecte i enfocament caràcter a caràcter
- Fase 1: el corpus de descripcions
- Fase 2: vectorització a nivell de caràcter i finestres de seqüències
- Fase 3: model Embedding + LSTM
- Fase 4: entrenament
- Fase 5: mostreig amb temperatura
- Fase 6: generació iterativa i resultats típics
- Límits de l'enfocament i ús responsable
Enunciat del projecte i enfocament caràcter a caràcter
Context de negoci. Els venedors de TecnoMarket triguen a redactar les descripcions dels seus articles i moltes queden buides o telegràfiques. Volem un assistent que generi un esborrany amb l'estil de la casa, que el venedor edita i aprova. L'humà sempre signa el text final.
Per què caràcter a caràcter? És la formulació més simple i didàctica de la generació: el model veu una seqüència de caràcters i prediu quin ve després. És exactament el problema de 04-04 (predir el pas següent d'una sèrie) canviant números per caràcters: mateixes finestres lliscants, mateix cap de classificació que a 04-03. Amb vocabularis de ~50 símbols no cal gestionar paraules desconegudes i el model cap a qualsevol màquina. El preu — coherència limitada a frases curtes — l'analitzarem al final.
Fase 1: el corpus de descripcions
Per al prototip fem servir un corpus sintètic de descripcions fictícies de TecnoMarket. En un cas real faries servir l'històric de fitxes aprovades; aquí generem material d'estil homogeni (també pots practicar amb qualsevol text públic en català, però el corpus propi manté el fil del projecte):
import numpy as np
import tensorflow as tf
tf.random.set_seed(42) # 06-04: reproduïbilitat
productes = ["auriculars sense fils", "teclat mecanic", "monitor corbat",
"robot aspirador", "fregidora d'aire", "camera de seguretat",
"altaveu bluetooth", "cafetera espresso", "ratoli gaming",
"lampada intelligent", "bascula digital", "ventilador de torre"]
adjectius = ["compacte", "potent", "silencios", "elegant", "versatil",
"ergonomic", "resistent", "lleuger"]
avantatges = ["bateria de llarga durada", "connexio estable", "disseny modern",
"installacio facil", "baix consum", "gran capacitat",
"control des del mobil", "materials de primera qualitat"]
tancaments = ["ideal per a la llar.", "perfecte per al teu dia a dia.",
"la millor opcio qualitat preu.", "enviament en 24 hores."]
rng = np.random.default_rng(42)
frases = []
for _ in range(3000):
p, a = rng.choice(productes), rng.choice(adjectius)
v1, v2 = rng.choice(avantatges, size=2, replace=False)
c = rng.choice(tancaments)
frases.append(f"{p} {a} amb {v1} i {v2}. {c}\n")
corpus = "".join(frases)
print(len(corpus)) # ~300 000 caràcters
print(corpus[:200])Fixa't en dues decisions: el corpus està en minúscules i sense accents ni ela geminada (vocabulari petit i net) i cada descripció acaba en \n, que el model aprendrà com a senyal de «fi de descripció». Uns 300 000 caràcters són pocs per a generació de qualitat, però suficients perquè el model n'aprengui l'estil — calibra les expectatives en conseqüència.
Fase 2: vectorització a nivell de caràcter i finestres de seqüències
A 04-03 vam fer servir TextVectorization a nivell de paraula; aquí baixem al caràcter amb StringLookup, i apliquem les finestres lliscants de 04-04 amb tf.data (06-01):
vocab = sorted(set(corpus))
print(len(vocab)) # ~45 símbols: lletres, dígits, espai, puntuació, \n
char_a_id = tf.keras.layers.StringLookup(vocabulary=vocab, mask_token=None)
id_a_char = tf.keras.layers.StringLookup(vocabulary=vocab, invert=True,
mask_token=None)
ids = char_a_id(tf.strings.unicode_split(corpus, "UTF-8"))
LONG = 80 # longitud de finestra: ~una descripció completa
ds = tf.data.Dataset.from_tensor_slices(ids)
ds = ds.batch(LONG + 1, drop_remainder=True) # trossos de 81 caràcters
def entrada_objectiu(tros):
return tros[:-1], tros[1:] # entrada: 80 chars; objectiu: els mateixos desplaçats 1
BATCH = 64
train_ds = (ds.map(entrada_objectiu)
.shuffle(10_000)
.batch(BATCH, drop_remainder=True)
.prefetch(tf.data.AUTOTUNE))La clau és a entrada_objectiu: per a l'entrada "teclat mecani", l'objectiu és "eclat mecanic" — a cada posició temporal el model aprèn a predir el caràcter següent. És la mateixa idea de finestra→pas-següent de 04-04, però entrenant les 80 posicions de la finestra alhora, cosa que multiplica el senyal d'aprenentatge per exemple.
Fase 3: model Embedding + LSTM
L'arquitectura reutilitza les peces de 04-02 i 04-03: un Embedding (aquí de caràcters, no de paraules) i una LSTM amb return_sequences=True, perquè necessitem una predicció a cada pas temporal, no només al final:
from tensorflow.keras import layers, models
VOCAB = len(char_a_id.get_vocabulary()) # inclou el token [UNK]
model = models.Sequential([
layers.Embedding(VOCAB, 64), # cada char -> vector de 64
layers.LSTM(256, return_sequences=True), # estat per cada posició
layers.Dropout(0.2), # regularització (05-04)
layers.Dense(VOCAB), # logits: puntuació per char
])
model.compile(
optimizer="adam",
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
)
model.build(input_shape=(None, None))
model.summary() # ~0.4 M de paràmetresDos detalls importants:
- La capa final no porta softmax (
from_logits=Truea la pèrdua): a la fase de mostreig manipularem els logits directament per aplicar-hi la temperatura. - La pèrdua és la CCE de sempre (02-04) aplicada per pas temporal: Keras fa la mitjana de l'entropia creuada de les 80 prediccions de cada finestra.
Fase 4: entrenament
callbacks = [
tf.keras.callbacks.ModelCheckpoint("models/generador_millor.keras",
monitor="loss", save_best_only=True),
tf.keras.callbacks.EarlyStopping(monitor="loss", patience=3,
restore_best_weights=True),
]
historial = model.fit(train_ds, epochs=30, callbacks=callbacks)Guia de lectura de la pèrdua (amb un vocabulari de ~45 símbols, la pèrdua inicial ronda ln(45) ≈ 3.8 — predicció aleatòria):
| Pèrdua | Què genera el model |
|---|---|
| ~3.8 | Sopa de caràcters aleatoris |
| ~2.0 | Pseudo-paraules pronunciables, espais al seu lloc |
| ~1.2 | Paraules reals del corpus, sintaxi aproximada |
| < 0.8 | Frases amb l'estructura de les descripcions |
Amb aquest corpus sintètic (molt regular) la pèrdua baixa ràpid, cap a 0.5-0.7 en 15-25 èpoques i pocs minuts de CPU. Amb un corpus real, més variat, esperaries valors més alts i més temps. Aquí no monitorem validació: en generació de prototip ens interessa capturar l'estil del corpus, i l'avaluació decisiva serà qualitativa (fase 6).
Fase 5: mostreig amb temperatura
Entrenat el model, com triem el caràcter següent a partir dels seus logits? Aquí apareix el concepte central de la lliçó.
- Greedy (voraç): triar sempre el caràcter més probable. Determinista i «segur», però cau en bucles:
amb bateria de llarga durada i bateria de llarga durada i .... - Mostreig amb temperatura: dividir els logits per una temperatura
Tabans del softmax i mostrejar de la distribució resultant:
def seguent_char(logits, temperatura):
logits = logits / temperatura
id_mostra = tf.random.categorical(logits[tf.newaxis, :], num_samples=1)
return int(id_mostra[0, 0])Efecte de T sobre la distribució:
| Temperatura | Efecte | Sortida típica |
|---|---|---|
| T → 0 | Gairebé greedy: guanya sempre el més probable | Correcta però repetitiva, frases clonades del corpus |
| T = 0.5 | Conservadora: reparteix poc | Bon compromís per defecte |
| T = 1.0 | Distribució tal com la va aprendre el model | Variada, alguna ensopegada |
| T = 1.5 | Aplana la distribució: els improbables guanyen opcions | Inventa paraules: fregidora d'aire vertatil amb connexo estible |
La temperatura no canvia el model: canvia quant risc acceptes en mostrejar. Per a esborranys comercials, temperatures baixes-mitjanes (0.4-0.8) són el que és raonable.
Fase 6: generació iterativa i resultats típics
Generar és iterar: predir un caràcter, afegir-lo a la seqüència, tornar a predir. Versió didàctica (re-processa tota la seqüència a cada pas; suficient per a un prototip):
def generar(llavor, n_chars=150, temperatura=0.5):
ids_gen = char_a_id(tf.strings.unicode_split(llavor, "UTF-8"))
ids_gen = list(ids_gen.numpy())
for _ in range(n_chars):
entrada = tf.constant([ids_gen[-LONG:]]) # última finestra
logits = model(entrada)[0, -1, :] # logits de l'últim pas
ids_gen.append(seguent_char(logits, temperatura))
chars = id_a_char(tf.constant(ids_gen))
return tf.strings.reduce_join(chars).numpy().decode("utf-8")
print(generar("robot aspirador ", temperatura=0.5))Sortides típiques reals d'un model entrenat així (les teves variaran):
- T=0.2:
robot aspirador silencios amb bateria de llarga durada i installacio facil. ideal per a la llar.— impecable, però gairebé calcada del corpus. - T=0.7:
robot aspirador compacte amb control des del mobil i baix consum. la millor opcio qualitat preu.— combina peces d'una manera nova i correcta: el punt dolç. - T=1.4:
robot aspirador ergonamic amb gron copocitat i connexo estoble. enviament en 24 hares.— creativitat desbocada: errors ortogràfics i sintaxi trencada.
Lliurament. Igual que a 07-01, el generador s'empaqueta amb el seu preprocessament (l'StringLookup viatja amb el model si l'integres en un model d'inferència, 06-05) i se serviria darrere d'un endpoint FastAPI (POST /esborrany amb producte i temperatura) que retorna 3 esborranys perquè el venedor triï i editi.
Límits de l'enfocament i ús responsable
Sigues honest amb el que has construït:
- Coherència curta: una LSTM de caràcters manté el fil unes desenes de caràcters; en textos llargs es contradiu o divaga. El nostre corpus de frases curtes encaixa just dins d'aquest límit — per això funciona.
- No sap de fets: pot escriure «bateria de llarga durada» per a un producte sense bateria. Genera estil, no veritat.
- Els sistemes comercials de generació fan servir transformers/LLMs basats en l'atenció que vas veure a 05-05: context de milers de tokens i coneixement general. El mecanisme de mostreig amb temperatura que has après aquí és exactament el mateix que fan servir ells — aquest projecte t'ha ensenyat el motor en petit.
- Ús responsable a TecnoMarket: el model produeix esborranys; un humà revisa, corregeix la fitxa tècnica i aprova abans de publicar. És la mateixa filosofia de la cua de revisió de 03-04: automatitzar el que és mecànic, supervisar el que arriba al client.
Errors Comuns i Consells
- Oblidar
return_sequences=True: la LSTM retornaria només l'últim estat i les formes no quadrarien amb l'objectiu de 80 posicions. Repassa 04-02 si l'error de formes et desconcerta. - Posar softmax a l'última capa i a més
from_logits=True: doble softmax silenciós que degrada l'entrenament. Tria un dels dos; aquí, logits. - Avaluar la generació només per la pèrdua: una pèrdua baixa amb un corpus repetitiu pot significar pura memorització. Genera i llegeix: la inspecció qualitativa és part de l'avaluació.
- Fer servir temperatura alta «per a més creativitat» en producció: en textos comercials, les paraules inventades destrueixen la confiança del client. Comença a 0.5 i ajusta amb exemples al davant.
- Finestres més llargues que les descripcions sense motiu: allarguen l'entrenament sense millorar un corpus de frases curtes. Ajusta
LONGal text real.
Exercicis
- Entrena el mateix model amb
LSTM(128)i ambLSTM(512)i compara pèrdua final, temps per època i qualitat de tres mostres a T=0.7. On són els rendiments decreixents? - Afegeix al corpus un 10 % de descripcions amb un format nou (per exemple, començant per «oferta: »). Reentrena i comprova amb quina freqüència i fidelitat el model genera aquest format.
- Implementa
generar_lot(llavor, k, temperatura)que retornikesborranys diferents i descarti els que continguin paraules fora d'un diccionari del corpus (control de qualitat automàtic previ a la revisió humana).
Solucions
- Amb 128 unitats la pèrdua es queda típicament 0.1-0.2 per sobre i apareixen més ensopegades ortogràfiques; amb 512 la pèrdua millora poc (el corpus és simple) i el temps per època es multiplica per ~3. Amb aquest corpus, 256 és el punt dolç: més capacitat no aporta res perquè no hi ha més complexitat per aprendre.
- N'hi ha prou d'afegir
frases.append(f"oferta: {p} {a} amb {v1}. {c}\n")dins d'un bucle addicional (~300 frases). Després de reentrenar, llavors que comencin per"oferta: "completen el format de manera consistent; sense aquesta llavor, el patró apareix espontàniament al voltant del 10 % de les mostres — el model reprodueix les freqüències del corpus. - Construeix el diccionari amb
paraules_valides = set(corpus.split()); genera en bucle ambgenerar, divideix cada esborrany amb.split()i accepta'l només siall(p.strip('.') in paraules_valides for p in esborrany.split()). Retorna elskprimers acceptats (amb un límit d'intents). Aquest filtre barat elimina la majoria d'artefactes de temperatures altes abans que els vegi el venedor.
Conclusió
Segon projecte lliurat: un generador d'esborranys que aprèn l'estil de TecnoMarket caràcter a caràcter, amb les finestres lliscants de 04-04, el tàndem Embedding+LSTM de 04-03 i un mostreig amb temperatura que ara saps llegir i ajustar. Igual de valuós és el que saps que no fa: coherència llarga i veracitat queden per als transformers de 05-05, i tot text generat passa per revisió humana. A la propera lliçó tornem a canviar de problema i complim la promesa de 05-02: fer servir l'error de reconstrucció d'un autoencoder per detectar transaccions fraudulentes a TecnoMarket — el nostre primer projecte amb dades desbalancejades de veritat.
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
