Zusammenfassungen
Universitätsnotizen im Markdown-Format aus Obsidian.
Transformer per Traduzione
download MD herunterladenCostruiremo 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