Dalle parole
ai numeri
Introduzione al Natural Language Processing: come un computer trasforma una frase in qualcosa che può contare, confrontare e imparare.
A cura di Nicola Palmeri · Ingegnere dell'informazione, sviluppatore XR/VR, docente di informatica
Perché uno che costruisce mondi virtuali vi parla di parole
Di mestiere costruisco esperienze in realtà virtuale, film immersivi e software. In classe insegno informatica. In entrambi i lavori il linguaggio è diventato la porta d'ingresso alla tecnologia.
In laboratorio
Un visore che obbedisce a un comando vocale, un personaggio virtuale che capisce una domanda, una guida museale che risponde al visitatore: sotto c'è sempre l'elaborazione del linguaggio.
In classe
I nostri studenti usano ogni giorno assistenti AI, traduttori, correttori. Sapere come trasformano le parole in numeri è il primo passo per usarli con spirito critico.
Oggi
Non serve saper programmare. Costruiremo insieme, a mano e con le animazioni, lo strumento più semplice e più istruttivo di tutti: il bag-of-words.
Il percorso di oggi
La parte evidenziata è il cuore del corso. Cliccate su un blocco per saltare a quella sezione.
Un computer non legge. Calcola.
Noi capiamo una frase al volo. Un computer vede solo una sequenza di caratteri: non sa che «visore» e «caschetto VR» indicano la stessa cosa, né che «non» capovolge il senso.
Il Natural Language Processing (NLP) è l'insieme dei metodi che permettono a una macchina di trattare il linguaggio umano, scritto o parlato. Il primo passo è sempre lo stesso: trasformare il testo in numeri.
Tutta la lezione risponde a una domanda: come si passa dalla colonna di sinistra a quella di destra?
Ottant'anni in sei tappe
Cliccate su una tappa.
L'NLP lo usiamo già, ogni giorno
Otto esempi che incontriamo a scuola e nella tecnologia di tutti i giorni. Cliccate per leggere come funzionano.
Il primo filtro antispam: una lista di parole
I primi filtri cercavano parole sospette. Più parole sospette trovano, più il messaggio è probabilmente spam. Scrivete un messaggio e guardate cosa succede.
Il filtro è fragile: «gr4tis» passa, «la ricreazione è gratis» viene bloccato. Nella parte 3 rifaremo questo filtro con il bag-of-words e un po' di probabilità: imparerà da solo quali parole contano.
Testo predittivo: contare chi viene dopo chi
La tastiera del telefono suggerisce la parola successiva. La versione più semplice conta, in un insieme di testi, quante volte una parola segue un'altra.
Questo è un modello a bigrammi. I grandi modelli linguistici di oggi fanno la stessa cosa, prevedere il prossimo pezzo di testo, ma guardando contesti molto più lunghi.
Prima di contare, si pulisce
I testi reali sono pieni di rumore. Prima di darli a un modello passano in una catena di trasformazioni, scelte in base al compito. Premete «Avanti» per far attraversare la catena a una frase.
Il Prof. ROSSI ha spiegato 3 lezioni!!! <br>Il Prof ROSSI ha spiegato lezioniil prof rossi ha spiegato lezioniRimozione del rumore con le espressioni regolari
Il rumore sono i caratteri che non servono al nostro scopo: numeri, punteggiatura, link, tag HTML. In Python si usa la libreria re, che sostituisce tutto ciò che corrisponde a uno schema.
import re
Cosa è «rumore» dipende dal compito. Se analizziamo prezzi o date, i numeri sono proprio l'informazione da tenere.
Minuscolo: «Il» e «il» sono la stessa parola?
Per un computer "Il" e "il" sono stringhe diverse. Portando tutto in minuscolo con testo.lower() il modello non deve imparare due volte la stessa parola.
Ma a volte la maiuscola porta significato. Rosa è una persona, la rosa è un fiore: dopo il minuscolo diventano la stessa colonna.
Dove finisce una frase?
Tokenizzare significa spezzare il testo nelle unità che analizzeremo. Si parte di solito dalle frasi. Sembra facile: basta tagliare a ogni punto. Proviamo.
La libreria nltk offre sent_tokenize, basata su un modello addestrato a riconoscere abbreviazioni come «Prof.» o «n.».
Tokenizzazione in parole
Dividere in parole è obbligatorio per quasi ogni metodo NLP. Non si ottengono solo parole: anche numeri, punti, sigle diventano token. In nltk si usa word_tokenize.
Molti tokenizzatori pensati per l'inglese lasciano «l'esperienza» come un unico token. Per l'italiano conviene separare l'articolo: altrimenti «l'esperienza» ed «esperienza» diventano due parole diverse.
Stopword: le parole che non portano contenuto
Articoli, preposizioni, congiunzioni e ausiliari compaiono ovunque e dicono poco sull'argomento del testo. nltk ne fornisce una lista per l'italiano con stopwords.words("italian").
Perché «Il» è sopravvissuto? La lista contiene solo forme minuscole: «Il» con la maiuscola non c'è. L'ordine dei passaggi conta. Attenzione anche a «non»: è nella lista di nltk, quindi rimuovendo le stopword si perde la negazione.
Stemming: tagliare fino alla radice
Lo stemming toglie le desinenze con regole fisse. Non serve che la radice sia una parola vera: basta che parole della stessa famiglia finiscano sulla stessa radice. Così un motore di ricerca tratta «programmare» e «programmazione» come equivalenti.
In Python: SnowballStemmer("italian") di nltk. Applica molte più regole di questa versione semplificata.
Lemmatizzazione: tornare alla voce del dizionario
Il lemma è la forma che cerchereste nel vocabolario. Per trovarlo servono le regole e il lessico della lingua, non basta tagliare: «andò» e «vado» hanno forme diverse ma lo stesso lemma «andare».
Premete «Avanti» per vedere i lemmi accanto agli stem.
In nltk: WordNetLemmatizer con il dizionario wordnet, pensato per l'inglese (built → build). Per l'italiano si usano strumenti come spaCy o Stanza.
Componete la vostra catena
Provate a scrivere un testo della vostra materia.
Al rientro: il bag-of-words, il cuore del corso.
Da parole a numeri
Dopo la pulizia abbiamo ancora stringhe, cioè variabili categoriali. Un algoritmo di machine learning lavora con numeri: dobbiamo dare a ogni testo delle coordinate.
Vedremo due metodi:
Bag-of-words (BoW)
Conta quali parole ci sono. Semplice, trasparente, si fa a mano. Ci dedicheremo un'ora.
Word embedding (WE)
Impara vettori che rappresentano il significato. Più potente, meno trasparente.
Un mattone: il one-hot encoding
Fissiamo un elenco di parole, il vocabolario. Ogni parola diventa un vettore lungo quanto il vocabolario, con tutti 0 e un solo 1 nella sua posizione.
È come una fila di interruttori: per ogni parola se ne accende uno solo.
Il bag-of-words è la somma (o l'unione) dei vettori one-hot di tutte le parole di una frase.
Perché si chiama «sacchetto di parole»
Immaginate di ritagliare le parole di una frase e metterle in un sacchetto. Scuotete. Quello che resta è solo quali parole ci sono e quante volte. L'ordine è perso.
Avanti: 1) nel sacchetto · 2) conteggio
Il modello si chiede soltanto se una parola nota è presente, non dove. Il risultato è l'istogramma delle parole del testo: ogni parola diventa una caratteristica.
Passo 1 · Costruire il vocabolario
Leggiamo tutte le frasi parola per parola. Se una parola non è ancora nel vocabolario la aggiungiamo in fondo; se c'è già, andiamo avanti.
Passo 2 · Ogni frase diventa una riga di 0 e 1
Scorriamo il vocabolario una parola alla volta e per ognuna ci chiediamo: è nella frase? Sì → 1, no → 0.
La tabella completa: righe = frasi, colonne = parole
Ogni cella risponde alla domanda «questa parola è in questa frase?».
Le colonne il, visore, rende, la, lezione valgono 1 ovunque: non aiutano a distinguere le frasi. Le differenze stanno tutte in coinvolgente, e, divertente, non, immersiva.
Le colonne seguono l'ordine in cui abbiamo incontrato le parole. Va bene qualsiasi ordine, purché sia lo stesso per tutte le frasi.
Presenza (0/1) oppure conteggio?
Finora abbiamo segnato solo la presenza. Possiamo anche contare quante volte compare ogni parola. Come segnare una parola è una scelta di progetto, proprio come la scelta del vocabolario.
Con i conteggi un testo che parla molto di «drone» pesa di più su quella colonna. Per testi lunghi è quasi sempre la scelta migliore.
Laboratorio: il vostro bag-of-words
Accendete «Togli stopword»: le colonne inutili spariscono, ma con loro anche «non».
Ora che sono vettori, possiamo confrontarli
Due frasi simili hanno molte colonne in comune. La similarità del coseno misura l'angolo tra i due vettori: 1 se puntano nella stessa direzione, 0 se non condividono nulla.
Il grande limite: l'ordine non esiste
Il BoW non dà informazioni sul significato delle parole né sulla loro posizione. Chi corregge chi? Per il modello è la stessa frase.
Succede anche con la negazione
Stesse parole, giudizio opposto. Una recensione positiva e una negativa della stessa app diventano lo stesso vettore.
Un mare di zeri
Con 3 frasi avevamo 9 colonne. Con migliaia di documenti il vocabolario arriva a decine di migliaia di parole, ma ogni testo ne usa solo poche centinaia. Quasi tutte le celle valgono 0: la matrice si dice sparsa.
Ogni quadratino rappresenta un gruppo di parole del vocabolario. In blu, i gruppi che contengono almeno una parola usata nel documento.
Per questo le librerie salvano solo le celle diverse da 0 (matrici sparse): fit_transform di scikit-learn restituisce proprio una matrice sparsa.
Due modi per migliorare il sacchetto
1 · Bigrammi: contare coppie di parole vicine
Al posto delle singole parole mettiamo nel vocabolario anche le coppie consecutive. Un po' di ordine ritorna.
2 · TF-IDF: pesare meno le parole ovunque presenti
Una parola presente in tutti i documenti non distingue nulla. TF-IDF moltiplica il conteggio per ln(N / df), dove df è il numero di documenti che la contengono.
Con N = 3 frasi: «il» compare in 3 su 3, ln(3/3) = 0. Scikit-learn usa una formula leggermente smussata, ma l'idea è la stessa.
Lo stesso, in Python
# tre frasi sulla VR a scuola frasi = [ "Il visore rende la lezione coinvolgente e divertente", "Il visore non rende la lezione divertente", "Il visore rende la lezione immersiva e coinvolgente", ] # 1) minuscolo e divisione in parole documenti = [f.lower().split() for f in frasi] # 2) vocabolario in ordine di apparizione vocabolario = [] for doc in documenti: for parola in doc: if parola not in vocabolario: vocabolario.append(parola) # 3) un vettore 0/1 per ogni frase for doc in documenti: print([1 if p in doc else 0 for p in vocabolario]) #p-out> [1, 1, 1, 1, 1, 1, 1, 1, 0, 0] #p-out> [1, 1, 1, 1, 1, 0, 0, 1, 1, 0] #p-out> [1, 1, 1, 1, 1, 1, 1, 0, 0, 1]
from sklearn.feature_extraction.text import CountVectorizer cv = CountVectorizer() # porta in minuscolo da solo X = cv.fit_transform(frasi) # matrice sparsa print(cv.get_feature_names_out()) #p-out> ['coinvolgente' 'divertente' 'il' 'immersiva' 'la' # 'lezione' 'non' 'rende' 'visore'] print(X.toarray()) #p-out> [[1 1 1 0 1 1 0 1 1] # [0 1 1 0 1 1 1 1 1] # [1 0 1 1 1 1 0 1 1]] # varianti utili CountVectorizer(binary=True) # solo presenza 0/1 CountVectorizer(ngram_range=(1, 2)) # parole + bigrammi
Il risultato coincide con la tabella costruita a mano. La parte difficile non è il codice: è decidere cosa mettere nel vocabolario.
Due differenze rispetto alla nostra tabella: le colonne sono in ordine alfabetico e la parola «e» sparisce, perché per impostazione predefinita CountVectorizer ignora i token di un solo carattere.
Per provare senza installare nulla: Google Colab o Jupyter. scikit-learn è già disponibile in Colab.
Il filtro antispam, rifatto con il bag-of-words
Il modello vede 8 messaggi già etichettati, li trasforma in bag-of-words e conta quanto spesso ogni parola compare negli spam e nei messaggi normali. Per un nuovo messaggio somma gli indizi di ogni parola (classificatore Naive Bayes).
Portarlo in classe, nelle diverse materie
Vettori e coseno
Gli studenti costruiscono a mano i vettori di 3 frasi e calcolano prodotto scalare, norme e coseno. Ottimo esercizio su spazi a molte dimensioni.
Lemmi e morfologia
Confrontare stem e lemmi di un passo dei Promessi Sposi o di Cesare. Perché lo stemming funziona peggio in latino?
Il BoW in 12 righe
Scrivere da zero il codice della slide precedente, poi confrontarlo con CountVectorizer e spiegare le differenze.
Classificare abstract
Raccogliere 20 brevi testi di biologia e fisica e far indovinare la materia a un classificatore BoW.
Contare è capire?
Il BoW «sa» di cosa parla un testo senza capirlo. Discussione su significato, uso e la stanza cinese di Searle.
Il sacchetto di carta
Parole su cartoncini, una busta per frase, una tabella alla lavagna. In 20 minuti si arriva alla matrice.
Una colonna, due significati
| … | rete | … | |
|---|---|---|---|
| Frase 1 | … | 1 | … |
| Frase 2 | … | 1 | … |
Il word embedding codifica anche la semantica: parole con significato simile diventano vettori simili. L'idea chiave è la rappresentazione distribuita: il significato di una parola si ricava dalle parole che le stanno intorno.
Due differenze dal BoW
1. Il vettore non è lungo quanto il vocabolario, ma ha un numero fisso di dimensioni (ad esempio 100 o 300).
2. I valori non sono 0 e 1 scelti da noi: sono numeri reali appresi da una rete neurale su molti testi.
Word embedding: la definizione formale
Dato un vocabolario V = {w1, …, w|V|}, un word embedding è una funzione
che associa a ogni parola un vettore denso di numeri reali. I valori sono appresi da un corpus in modo che parole che compaiono in contesti simili abbiano vettori vicini, cioè con similarità del coseno alta.
quasi tutti i valori sono diversi da zero, al contrario del BoW
d = 100–300 invece di |V| = decine di migliaia
nessuno sceglie i numeri: li trova l'addestramento
Alla base c'è un'idea della linguistica degli anni '50 (Harris, 1954; Firth, 1957): il significato di una parola dipende dalle parole che la accompagnano. «You shall know a word by the company it keeps.»
In pratica la funzione è una matrice E ∈ ℝd×|V|, con una colonna per ogni parola. Per trovare il vettore di una parola si moltiplica E per il suo one-hot xw: il risultato è proprio la colonna di quella parola.
Come si impara un embedding: la finestra di contesto
Word2vec (2013) scorre il testo con una finestra di ampiezza m. Per ogni parola centrale wt raccoglie le coppie (parola centrale, parola di contesto) e allena una piccola rete a prevedere il contesto partendo dalla parola.
P(o | c) = exp(uo · vc) / Σw∈V exp(uw · vc)
In parole: il prodotto scalare tra il vettore della parola centrale e quello del contesto deve essere alto per le coppie viste nel testo. I vettori v che ne escono sono l'embedding.
Perché parole diverse finiscono vicine
Due parole mai viste insieme, ma usate negli stessi contesti, ricevono gli stessi «compagni» durante l'addestramento. La rete le spinge quindi verso la stessa zona dello spazio.
Il BoW avrebbe messo «visore» e «caschetto» in due colonne indipendenti, con similarità 0. L'embedding le avvicina senza che nessuno gli abbia detto che sono sinonimi.
La matrice di embedding
Ogni colonna è una parola, ogni riga una «caratteristica». Nel nostro esempio, con valori inventati, usiamo 4 righe con un nome per capirci: Digitale, Immersivo, Scolastico, Tascabile.
Nella realtà le dimensioni non hanno un nome. All'inizio i valori sono casuali; una rete neurale li aggiusta leggendo milioni di frasi, finché le parole usate in contesti simili hanno colonne simili.
Le parole come punti nello spazio
Usiamo due dimensioni: digitale in orizzontale e tascabile in verticale. Le parole simili stanno vicine.
In 2D Tablet e Smartphone sono quasi sovrapposti: a separarli è la dimensione Scolastico, che qui non vediamo. È il motivo per cui gli embedding reali hanno centinaia di dimensioni.
Tutti i valori sono inventati a scopo didattico.
Bag-of-words e word embedding a confronto
| Bag-of-words | Word embedding | |
|---|---|---|
| Lunghezza del vettore | quante parole ha il vocabolario | numero fisso di dimensioni scelto da noi |
| Valori | 0/1 o conteggi, quasi tutti zero | numeri reali, tutti diversi da zero |
| Da dove vengono | si contano, anche a mano | si imparano con una rete neurale e molti dati |
| Significato | nessuno: «visore» e «caschetto» sono estranee | parole simili, vettori vicini |
| Trasparenza | ogni numero si spiega | le dimensioni non hanno un nome |
| Quando usarlo | pochi dati, classificazione, didattica | ricerca semantica, traduzione, modelli linguistici |
I grandi modelli linguistici di oggi partono proprio da qui: il primo strato trasforma ogni pezzo di testo in un vettore di embedding.
Sei domande per verificare
Tre idee da portare a casa
1 · Prima si pulisce
Rumore, minuscolo, token, stopword, stem o lemmi. Ogni passaggio è una scelta che dipende dal compito.
2 · Poi si conta
Vocabolario + presenza o conteggio = una matrice frasi × parole. Da lì possiamo misurare somiglianze e addestrare classificatori.
3 · Contare non è capire
Il BoW perde l'ordine e il significato. Gli embedding recuperano il significato imparandolo dal contesto.
Per approfondire
nltk.org · la libreria Python più usata per iniziare
scikit-learn · Text feature extraction · CountVectorizer e TF-IDF
Google Colab · Python nel browser, senza installare nulla
spaCy · lemmatizzazione e analisi dell'italiano
Grazie. Domande?