Riassunti
Appunti universitari in formato Markdown da Obsidian.
Transformer per Traduzione
download Scarica MDCostruiremo un modello Transformer completo per tradurre dal Portoghese all'Inglese. Keras fornisce MultiHeadAttention, ma dobbiamo assemblare noi l'architettura Encoder-Decoder.
1. Positional Encoding
Poiché non usiamo RNN, dobbiamo iniettare l'informazione sulla posizione. Creiamo un layer che somma l'embedding delle parole a un vettore calcolato con seni e coseni.
import tensorflow as tf
import numpy as np
def get_positional_encoding(seq_len, d_model):
angles = np.arange(seq_len)[:, np.newaxis] / np.power(10000, (2 * (np.arange(d_model)[np.newaxis, :] // 2)) / np.float32(d_model))
angles[:, 0::2] = np.sin(angles[:, 0::2]) # Indici pari: Seno
angles[:, 1::2] = np.cos(angles[:, 1::2]) # Indici dispari: Coseno
return tf.cast(angles[np.newaxis, ...], dtype=tf.float32)
class PositionalEmbedding(tf.keras.layers.Layer):
def __init__(self, vocab_size, d_model):
super().__init__()
self.d_model = d_model
self.embedding = tf.keras.layers.Embedding(vocab_size, d_model, mask_zero=True)
self.pos_encoding = get_positional_encoding(2048, d_model)
def call(self, x):
length = tf.shape(x)[1]
x = self.embedding(x)
x *= tf.math.sqrt(tf.cast(self.d_model, tf.float32)) # Scaling
x = x + self.pos_encoding[:, :length, :]
return x
2. Blocchi Costitutivi (Encoder & Decoder Layers)
Ogni layer del Transformer segue uno schema standard:
Input -> Attention -> Add & Norm -> FeedForward -> Add & Norm
class EncoderLayer(tf.keras.layers.Layer):
def __init__(self, d_model, num_heads, dff, dropout_rate=0.1):
super().__init__()
self.mha = tf.keras.layers.MultiHeadAttention(num_heads=num_heads, key_dim=d_model)
self.ffn = tf.keras.Sequential([
tf.keras.layers.Dense(dff, activation='relu'),
tf.keras.layers.Dense(d_model)
])
self.layernorm1 = tf.keras.layers.LayerNormalization(epsilon=1e-6)
self.layernorm2 = tf.keras.layers.LayerNormalization(epsilon=1e-6)
self.dropout1 = tf.keras.layers.Dropout(dropout_rate)
self.dropout2 = tf.keras.layers.Dropout(dropout_rate)
def call(self, x, training):
# 1. Self Attention
attn_output = self.mha(x, x)
attn_output = self.dropout1(attn_output, training=training)
out1 = self.layernorm1(x + attn_output) # Residual Connection
# 2. Feed Forward
ffn_output = self.ffn(out1)
ffn_output = self.dropout2(ffn_output, training=training)
out2 = self.layernorm2(out1 + ffn_output) # Residual Connection
return out2
Il DecoderLayer è simile, ma ha due blocchi di attenzione: uno masked_mha (per l'input del decoder) e uno cross_mha (che prende Key/Value dall'encoder).
3. Assemblaggio del Modello
class Transformer(tf.keras.Model):
def __init__(self, num_layers, d_model, num_heads, dff, input_vocab_size, target_vocab_size, dropout_rate=0.1):
super().__init__()
self.encoder = Encoder(num_layers, d_model, num_heads, dff, input_vocab_size, dropout_rate)
self.decoder = Decoder(num_layers, d_model, num_heads, dff, target_vocab_size, dropout_rate)
self.final_layer = tf.keras.layers.Dense(target_vocab_size)
def call(self, inputs, training):
# inputs = [inp_sentence, tar_sentence]
inp, tar = inputs
enc_output = self.encoder(inp, training)
dec_output, attention_weights = self.decoder(tar, enc_output, training)
final_output = self.final_layer(dec_output)
return final_output
4. Training (Loss Masking)
Poiché usiamo il padding, la loss non deve considerare gli zeri alla fine delle frasi. Dobbiamo implementare una funzione di perdita personalizzata.
loss_object = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True, reduction='none')
def masked_loss(real, pred):
mask = tf.math.logical_not(tf.math.equal(real, 0)) # Maschera dove real != 0
loss_ = loss_object(real, pred)
mask = tf.cast(mask, dtype=loss_.dtype)
loss_ *= mask # Azzera la loss sul padding
return tf.reduce_sum(loss_) / tf.reduce_sum(mask) # Media solo sui token validi