compositionality

La scorsa puntata (che potete trovare qui) abbiamo parlato delle difficoltà che ha un computer a interpretare il linguaggio naturale, in particolare il significato (la semantica) delle parole.

Per superare queste difficoltà abbiamo parlato di un approccio chiamato “semantica distribuzionale”, in questo approccio ogni parola viene codificata in un vettore, il quale racchiude l’informazione delle co-occorrenze di tale parola con tutte le altre. In questo modo, come abbiamo visto, calcolare il prodotto scalare tra due vettori ci dà un’idea di quanto due parole appaiano in contesti simili e, per estensione, quanto due parole abbiano sensi simili (o perlomeno correlati).

Purtroppo la realtà è un po’ più complicata di quella che abbiamo esposto: le idee incontrate nello scorso articolo, per quanto intuitive, sono un po’ troppo semplicistiche ed hanno alcune limitazioni, sia teoriche che pratiche. Oggi entreremo quindi un po’ più nel tecnico.

Contenuto informativo.

Il primo problema che affrontiamo è quello di come misurare quanto due parole siano associate. L’approccio che avevamo intrapreso era quello di contare quante volte una parola appare vicino ad un’altra. Avevamo definito quindi la seguente misura di associazione (association feature in inglese) tra due parole $$v$$ e $$w$$:

$$a(v, w) = \text{numero di volte che w appare “vicino” a v}$$

dove ‘vicino’ era definito come l’insieme delle 5 parole precedenti e successive a $$v$$.

Una volta definita questa misura avevamo deciso quindi di codificare parola $$v$$ con il vettore  con componenti $$a(v, w_i)$$, per tutte le $$w_i$$ nel nostro vocabolario.

Questo approccio è problematico perché non tiene conto del fatto che non tutte le parole appaiono con la stessa frequenza! Esistono parole molto comuni, che appaiono quasi in ogni frase, ed esistono parole molto rare -spesso si tratta di vocabolario tecnico- che appaiono pochissime volte in un intero corpus.

Perché questa distinzione è importante? L’idea è questa, se una parola è molto comune, questa tenderà a capitare vicino ad altre parole molto più spesso, non perché le due parole siano necessariamente correlate, ma semplicemente in virtù del fatto che appare più volte vicino ad ogni parola.

Questo significa che due parole molto comuni ma non necessariamente correlate (ad esempio “buono” e “nuovo”) possono capitare vicino molto spesso, semplicemente in virtù della loro maggior frequenza, mentre parole decisamente più rare e decisamente più correlate, come ad esempio “monossido” e “carbonio” appariranno vicino molto più raramente. In un certo senso, il fatto che due parole rare appaiono vicine tra loro ci dà più informazione, è un evento più significativo, e quindi dovremmo trovare il modo di farlo valere di più nei nostri conti.

Ci sono due modi più o meno equivalenti per ovviare a questo problema, entrambi consistono nel definire in maniera diversa la misura di associazione tra due parole.

La misura di cui vi parlo viene chiamata informazione mutua puntuale (abbreviata in PMI dal corrispettivo acronimo inglese) ed è definita come segue:

$$pmi(v, w) = \log(\frac{\text{numero di volte che v e w appaiono vicine}}{(\text{frequenza di v})*(\text{frequenza di w})})$$

Fondamentalmente stiamo quantificando “quanto più spesso queste due parole stanno apparendo vicine, rispetto a quanto apparirerebbero se questo succedesse solo per caso, basate sulla rispettiva frequenza?”

Facciamo un esempio (tradotto da qui): le parole “monossido” e “carbonio” sono globalmente parole rare, appaiono rispettivamente 1353 e 4265 volte in un corpus di 50 milioni di parole, e appaiono vicine 1032 volte.
Se le due parole non fossero correlate ci aspetteremo che appaiono vicine soltanto 0.11 volte (ovvero il prodotto delle rispettive frequenze: $$\frac{1353\cdot 4265}{5000000}$$) e siccome non ha senso usare numeri decimali per contare questo tipo di eventi, potremmo scommettere che non appaiano MAI vicine.

D’altro canto, parole come congiunzioni e preposizioni sono molto frequenti ma portano poca informazione, ad esempio le parole “of” e “and” appaiono rispettivamente 1761436 e 1375396 volte, portandoci ad aspettarci che co-occorrano circa 48000 volte, mentre il vero conteggio è di sole 1190.

La pmi è appunto una misura di questa discrepanza (1032 volte contro le 0.11 attese nel primo caso, e 1190 contro le 48000 nel secondo).

Riduzione della dimensionalità.

Il secondo problema del vecchio approccio è la sua inefficienza, nel senso che ogni parola è rappresentata da un vettore dello stesso numero di dimensioni di tutto il nostro vocabolario, che nelle applicazioni più usate può essere nell’ordine delle centinaia di migliaia di parole.
Non solo, la maggiorparte delle componenti di questo vettore è zero! La stragrande maggioranza di parole non appaiono mai una accanto all’altra, in gergo si dice che questi vettori sono sparsi.
Sarebbe interessante quindi “comprimere” questi vettori in maniera da conservare al più possibile tutta l’informazione che contengono.

Esistono varie tecniche di riduzione della dimensionalità, la tecnica usata per comprimere questi vettori (o meglio, comprimere l’intera matrice di co-occorrenza, le cui righe sono i vettori a cui siamo interessai) è chiamata analisi delle componente principali (PCA).

La PCA è una procedura molto generale che trasforma una matrice di dimensione $$N \times N$$ in una matrice più piccola di dimensione $$N x k$$, dove $$k$$ è un numero molto più piccolo di $$N$$ (come abbiamo già visto N può essere nell’ordine delle centinaia di migliaia, mentre $$k$$ è un numero nell’ordine delle centinaia).

Un esempio di riduzione di dimensionalità: i punti (in 3 dimensioni) vengono proiettati sul piano (2 dimensioni) che "meglio" li rappresenta.

Un esempio di riduzione di dimensionalità: i punti (in 3 dimensioni) vengono proiettati sul piano (2 dimensioni) che “meglio” li rappresenta.

Una spiegazione dettagliata di come funziona la PCA richiederebbe un post a sé stante (e non è da escludere che prima o poi…), ma per il momento ci basta sapere che la matrice risultante da questo approccio è la migliore approssimazione possibile della matrice di partenza (è la miglior approssimazione possibile tra tutte le matrici di un certo rango specifico).

Il vantaggio di questo approccio è evidente: prima ogni parola era rappresentata da un vettore di svariate migliaia di componenti, molto delle quali nulle; ora invece è sufficiente un vettore di circa $$200$$ componenti (tutte diverse da zero, vale a dire che non stiamo “sprecando spazio”).

Lo svantaggio però è che quello che guadagniamo in efficienza lo perdiamo in interpretabilità: una volta che i vettori sono compressi non è più possibile leggere cosa vogliano dire le singole componenti, l’informazione semantica è ora distribuita su tutto il vettore, senza possibilità di essere letta.

Nuovi approcci.

Si conclude quindi (per ora) il nostro viaggio nel mondo della semantica spiegata ai computer. Nella prima parte del viaggio abbiamo introdotto il problema e dato una spiegazione intuitiva di come provare a risolverlo, mentre in questa seconda parte siamo entrati più nel dettaglio tecnico.

Per concludere vorrei sottolineare che la semantica distribuzionale (con alcune sue varianti) è stata per decenni la tecnica principale con cui si insegnava il senso della lingua umana ai computer, negli ultimi anni tuttavia il trend nella ricerca sul trattamento del linguaggio naturale si è spostato da un approccio con radici linguistiche a nuovi approcci molto più incentrati nel mondo del machine learning e delle reti neurali. Anche questi approcci hanno come fine ultimo quello di trasformare una parola in un vettore che ne racchiuda il senso, ma affrontano il problema in maniera totalmente diversa.

In un prossimo articolo vedremo quindi come le reti neurali stanno rivoluzionando il modo di trattare il linguaggio per via algoritmica.

Alla prossima!

CC BY-NC-SA 4.0
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.