Traduttore NMT italiano-russo: perché cancella i cognomi
Negli ultimi anni la traduzione automatica è passata quasi ovunque dai vecchi sistemi basati su regole e statistiche a modelli neurali, i cosiddetti NMT — Neural Machine Translation. L'idea di fondo è semplice da enunciare e complessa da realizzare: una rete neurale legge una frase in una lingua sorgente e genera, parola dopo parola, la sua traduzione nella lingua di destinazione, senza dizionari o regole grammaticali scritte a mano. Tutto quello che il modello "sa" — grammatica, lessico, persino le convenzioni di trascrizione dei nomi propri — lo ha imparato osservando milioni di frasi tradotte durante il training.
L'architettura che ha reso possibile questo salto è il Transformer, introdotto nel 2017 (il paper "Attention Is All You Need"), che ha sostituito le precedenti reti ricorrenti (RNN, LSTM) con un meccanismo di attenzione capace di pesare le relazioni tra tutte le parole di una frase contemporaneamente, invece di leggerle una alla volta in sequenza. È l'architettura dietro sistemi come Google Translate, DeepL, e — su scala più ridotta — quella che sto usando io per costruire un traduttore italiano→russo da zero, in PyTorch.
Un modello NMT è composto in genere da due parti: un encoder, che legge la frase sorgente e la trasforma in una rappresentazione numerica densa (gli "embedding contestuali"), e un decoder, che genera la frase di destinazione un token alla volta, basandosi su quella rappresentazione e su quello che ha già generato. È proprio nel decoder — e nel modo in cui gestisce le parole che non ha mai visto in training — che nasce il problema che voglio raccontare in questo articolo.
Perché per quanto un NMT sia bravo a tradurre il significato di una frase, c'è una categoria di parole che sfugge sistematicamente alla sua logica di apprendimento: i nomi propri. E quando il modello se li trova davanti senza sapere cosa farne, non è che li traduce male — a volte li cancella e basta, come mi è successo testando il mio traduttore.
Durante i test mi sono imbattuto in un bug che all'inizio sembrava assurdo: il modello non traduceva male i cognomi sconosciuti, li cancellava. Non una trascrizione sbagliata, non un errore plausibile — il token spariva e basta, come se non fosse mai stato nella frase.
Loading...I nomi di battesimo comuni — Luca, Marco, Kristina — uscivano quasi sempre corretti, perché il modello li ha visti migliaia di volte durante il training e ne ha memorizzato la trascrizione. I cognomi rari o mai incontrati no: il decoder, non trovando nulla nel suo vocabolario che somigliasse a un'associazione plausibile, sceglieva di non generare nulla per quel token, invece di tentare qualcosa.
La prima reazione, ovvia, è stata: mi serve più corpus con più cognomi. Si è rivelata la diagnosi sbagliata, ed è utile spiegare perché prima di arrivare a cosa ho fatto davvero.
Perché non basta aggiungere dati di training
Il mio modello impara a trascrivere i nomi propri esattamente come impara qualsiasi altra parola: per memorizzazione statistica. Vede la coppia ("Marco", "Марко") migliaia di volte durante il training e ne impara l'associazione.
Il problema è strutturale: i cognomi sono un insieme aperto, potenzialmente infinito. A differenza dei pattern grammaticali — un numero finito di costruzioni sintattiche, che si può imparare con dati sufficienti — non esiste una quantità di dati che copra "tutti i cognomi del mondo". Ogni batch di frasi aggiunte copre qualche decina di cognomi in più, ma ci sarà sempre un cognome nuovo fuori dal training set. È un problema di rendimenti decrescenti, non di quantità: più dati aiutano sui cognomi comuni, ma non chiudono il problema di fondo.
Il vero nodo: due alfabeti diversi
Tradurre un nome proprio da italiano a russo non è "tradurre" nel senso classico — è trascrivere foneticamente da un alfabeto latino a uno cirillico ("Smorti" → "Сморти"). È un task diverso dalla traduzione semantica di una frase, tanto che nella letteratura NMT viene trattato come problema a sé: esiste una tradizione di ricerca dedicata, i workshop NEWS sulla trascrizione automatica dei nomi propri.
Perché non un copy mechanism
La prima idea che viene in mente è un meccanismo di copia — Pointer-Generator Networks, CopyNet — lo strumento standard in NMT per gestire parole rare o OOV (out-of-vocabulary). Funziona bene quando il segmento da preservare resta nello stesso alfabeto: un numero, un codice, un nome di marchio in una lingua che condivide la scrittura latina con la sorgente. Il decoder impara ad "attingere" direttamente dal testo sorgente invece di generarlo dal proprio vocabolario.
Nel mio caso non si applica: copiare i caratteri latini "Smorti" dentro una frase russa non produce una trascrizione corretta, produce testo latino incollato in mezzo al cirillico. Il copy mechanism risolve "preservare esattamente questo token", non "convertire questo token in un altro alfabeto secondo regole fonetiche". Sono due problemi diversi, e il secondo era il mio.
La soluzione: NER + regole fonetiche attorno al modello
Invece di modificare o riaddestrare il Transformer, ho costruito una pipeline di pre/post-processing che lavora attorno ad esso:
1. NER reale sul testo sorgente. Uso spaCy (modello it_core_news_sm) per individuare i nomi di persona nel testo italiano di partenza. Il vantaggio rispetto a una lista fissa: il NER generalizza a nomi e cognomi mai visti, perché riconosce il pattern dal contesto sintattico. Verificato che rileva correttamente anche cognomi completamente inventati.
2. Trascrizione a due livelli. Un dizionario di ~95 cognomi verificati manualmente copre i casi noti con la massima accuratezza possibile. Per tutto il resto, un motore di regole fonetiche italiano→cirillico (c/g davanti a e/i, sch, gli, gn, zz, qu, raddoppiamenti consonantici...) generalizza a qualsiasi cognome nuovo. Validato contro tutte le coppie verificate in sessione: 95% di accuratezza.
3. Riparazione post-hoc. Si confronta il nome individuato nella frase sorgente con l'output del modello. Se il cognome è sparito — come nell'esempio iniziale — viene reinserito accanto al nome di battesimo, che il modello quasi sempre mantiene. Le frasi già tradotte correttamente non vengono toccate.
Risultato, verificato end-to-end col modello reale:
Loading...I Limiti di questa soluzione
Non è una soluzione perfetta, e vale la pena dirlo chiaramente:
- Declinazione grammaticale russa. Non risolve il caso in cui un nome nuovo richiederebbe una declinazione specifica nel contesto (es. dativo per un nome femminile). Ho esplorato uno schema di tag di caso/genere insegnato al modello in training, ma resta un problema distinto e più complesso: capire quale caso serve in quel punto della frase è qualcosa che solo il modello NMT può dedurre dal contesto bilingue completo.
- Soggetti composti con due cognomi sconosciuti. Quando due persone con cognome ignoto compaiono nello stesso soggetto composto, il modello collassa comunque. È un gap di dati/training, non qualcosa che una riparazione post-hoc può correggere — la pipeline inserisce nomi mancanti che riesce a individuare, non inventa verbi o sostantivi.
- Regole fonetiche non perfette al 100%. Restano irregolarità reali dell'ortografia italiana e ambiguità (es. "gue"/"gui"). Ma il 95% di accuratezza su un cognome mai visto è incomparabilmente meglio dello 0% — sparizione totale — che avevo prima.
Conclusioni
Il punto interessante di questo caso, al di là del problema specifico italiano→russo, è che non tutti i problemi "il modello sbaglia su X" si risolvono nello stesso modo. A volte serve più training, a volte serve un'architettura diversa (copy mechanism), a volte — come qui — la soluzione più efficace è riconoscere che si tratta di un task diverso mascherato da caso particolare, e trattarlo con lo strumento giusto (NER + regole linguistiche) invece di forzarlo dentro il modello che già hai.