Misurare la distanza tra due testi con Python

  • Di
  • 2022-01-20 - 6 minuti
banner

Immagina di avere due stringhe e di voler vedere se queste due parlano dello stesso argomento: come fare?

Riuscire a misurare la distanza tra due testi con Python può sembrare difficile, ma non se si hanno gli strumenti giusti: ci sono infatti diverse tecniche che è possibile sfruttare, come la coseno-similarità o il prodotto scalare.

Come farlo? Vediamo un esempio pratico su come quantificare la distanza tra due stringhe!

Intro

Partiamo da un concetto fondamentale: nell’ambito dell’analisi del linguaggio naturale, la vettorializzazione di un testo significa rappresentare un documento come un vettore, in cui gli elementi rappresentano il numero di occorrenze nel testo.

Si tratta di un’attività fondamentale nel campo dell’information retrieval, per topic analysis e sentiment analysis.

Ovviamente, c’è un’assunzione implicita: la vettorializzazione può essere applicata solo quando l’ordine delle parole non è importante; questo vuol dire che il significato del documento deriva dai termini che lo costituiscono e non dal contenuto stesso, come ad esempio nella classificazione o nell’estrazione di informazioni.

In questo caso, convertire le frasi in una matrice di termini ci tornerà molto utile per poter calcolare la similarità tra due frasi e quindi rappresentarne le distanze tramite i valori.

Come funziona

Un’analisi che permetta di misurare la distanza tra due testi si basa su tre fasi: indicizzazione di un documento, dove le parole vengono rappresentate in base all’ordine e vengono rimosse le stopwords, i termini vengono poi pesati in base alla frequenza e viene calcolato il coefficiente di similarità.

Abbiamo già detto che esistono diverse tecniche per farlo: una di queste è la coseno similarità, che si basa su un concetto matematico: misura il coseno dell’angolo tra due vettori proiettati in uno spazio multidimensionale.

Cosa vuol dire? Vuol dire che quanto più piccolo è l’angolo, maggiore è la somiglianza del coseno, e quindi tanto più il valore sarà prossimo all’uno, tanto più i due testi saranno simili; al contrario, se il valore dovesse avvicinarsi allo zero, vuol dire che i due testi saranno molto diversi.

Un approccio comunemente usato per abbinare documenti simili si basa sul conteggio del numero massimo di parole comuni tra i documenti.

Ma questo approccio ha un difetto intrinseco: all’aumentare delle dimensioni del documento, il numero di parole comuni tende ad aumentare anche se i documenti parlano di argomenti totalmente diversi.

Come metrica di somiglianza, in che modo la somiglianza del coseno differisce dal semplice conteggio di parole uguali?

Quando viene tracciata su uno spazio multidimensionale, dove ogni dimensione corrisponde a una parola nel documento, la somiglianza del coseno cattura l’orientamento (l’angolo) dei documenti e non quantifica la somiglianza su un’unica dimensione.

Codice Python

Esistono diverse metodologie per misurare la coseno-similarità, e un’ottima soluzione è quello di utilizzare la libreria NLTK, che fornisce un metodo in grado di calcolare il calcolo tra due array.

Passiamo al codice!

Come primo step, importiamo la funzione dalla libreria NLTK e importiamo numpy, che ci tornerà utile per poter gestire i testi di cui vogliamo misurare la similarità:

from nltk.cluster.util import cosine_distance
import numpy as np

La prima cosa da fare è definire un metodo che ci permetta di fornire due stringhe in ingresso e un elenco di stopwords: questo renderà la nostra funzione trasversale a più situazioni e lingue, potendo fornire una lista di parole che vari all’occorrenza!

def get_cosine_similarity(sentence1, sentence2, stopwords=None):

All’interno di questa funzione, andremo a fare un po’ di attività di pre-processing: renderemo tutte le frasi in minuscolo, così da non creare bias nel calcolo della similarità:

    if stopwords is None:
        stopwords = []

    sentence1 = [w.lower() for w in sentence1]
    sentence2 = [w.lower() for w in sentence2]

Ultimo step per questa funzione è quello di definire una lista con tutte le parole presenti nelle due frasi all’interno di words_all: dopo aver creato due array, andremo a inserire al loro interno le parole presenti nelle due frasi escludendo le stopwords:

   words_all = list(set(sentence1 + sentence2))

    array1 = [0] * len(words_all)
    array2 = [0] * len(words_all)

    for w in sentence1:
        if w in stopwords:
            continue
        array1[words_all.index(w)] += 1

    for w in sentence2:
        if w in stopwords:
            continue
        array2[words_all.index(w)] += 1

Ora è tempo di restituire il valore che verrà calcolato dalla funzione di NLTK: l’ultima istruzione prevede infatti che venga restituita la differenza tra uno e il risultato del metodo, così che XXX

    return 1 - cosine_distance(array1, array2)

La coseno-similarità viene di solito rappresentata tramite una matrice, all’interno della quale i valori presenti sulla diagonale rappresentano la somiglianza tra una frase e sé stessa: ovviamente, questo valore sarà sempre uguale.

Andiamo avanti con il codice: definiamo quindi una funzione che prenda sempre le frasi e le stopwords e che crei una matrice lunga e larga quanto la dimensione delle due frasi:

def cos_sim_matrix(sentences, stop_words):
    matrix = np.zeros((len(sentences), len(sentences)))

Adesso, per ogni frase presente nell’insieme iniziale, andiamo a calcolare la coseno-similarità: un po’ come se giocassimo a battaglia navale, immaginiamo che ad ogni riga e ogni colonna, corrispondano due frasi che, in duello tra loro, cercano di assomigliarsi.

Per farlo, richiamiamo la funzione creata in precedenza e chiamata get_cosine_similarity:

    for idx1 in range(len(sentences)):
        for idx2 in range(len(sentences)):
            if idx1 == idx2:  # ignore if both are same sentences
                continue
            matrix[idx1][idx2] = get_cosine_similarity(sentences[idx1], sentences[idx2], stop_words)

    return matrix

Per provare il codice, andiamo a usare tre frasi: la prima e la seconda si assomiglieranno (cambia una sola parola tra le due), mentre la terza sarà totalmente dissimile.

Per questo, ci aspettiamo che i valori siano molto alti per le prime due colonne e due righe, mentre siano più bassi nell’ultima casella della matrice:

sentence1 = "This is a first sentence"
sentence2 = "This is a second sentence"
sentence3 = "I don't know if it's clear, but pineapple on pizza is oltrageous."

print(cos_sim_matrix([sentence1, sentence2, sentence3], []))

Il risultato che otteniamo è il seguente:

[[0. 0.93675938 0.79159032]
 [0.93675938 0. 0.77740376]
 [0.79159032 0.77740376 0. ]]

Attenzione però: questo è solo uno dei metodi… Dai un’occhiata alla libreria al modulo nltk.cluster.util, dove ci sono altri metodi che permettono di verificare se due frasi sono uguali, come la distanza euclidea!

Codice completo

from nltk.cluster.util import cosine_distance
import numpy as np

def get_cosine_similarity(sentence1, sentence2, stopwords=None):
    if stopwords is None:
        stopwords = []

    sentence1 = [w.lower() for w in sentence1]
    sentence2 = [w.lower() for w in sentence2]

    words_all = list(set(sentence1 + sentence2))

    array1 = [0] * len(words_all)
    array2 = [0] * len(words_all)

    for w in sentence1:
        if w in stopwords:
            continue
        array1[words_all.index(w)] += 1

    for w in sentence2:
        if w in stopwords:
            continue
        array2[words_all.index(w)] += 1

    return 1 - cosine_distance(array1, array2)


def cos_sim_matrix(sentences, stop_words):
    matrix = np.zeros((len(sentences), len(sentences)))

    for idx1 in range(len(sentences)):
        for idx2 in range(len(sentences)):
            if idx1 == idx2:  # ignore if both are same sentences
                continue
            matrix[idx1][idx2] = get_cosine_similarity(sentences[idx1], sentences[idx2], stop_words)

    return matrix

sentence1 = "This is a first sentence"
sentence2 = "This is a second sentence"
sentence3 = "I don't know if it's clear, but pineapple on pizza is oltrageous."

print(cos_sim_matrix([sentence1, sentence2, sentence3], []))

Risorse utili:

- Analisi del linguaggio con Python - Apogeo

- Addestramento NER con spaCy per new entry

- Machine Learning in una settimana

Post correlati

Partners

Community, aziende e persone che supportano attivamente il blog

Logo di Codemotion
Logo di GrUSP
Logo di Python Milano
Logo di Schrodinger Hat
Logo di Python Biella Group
Logo di Fuzzy Brains
Logo di Django Girls
Logo di Improove
Logo del libro open source

Iscriviti alla newsletter

Per non perderti gli ultimi articoli e per vincere biglietti e gadget TheRedCode

Riceverai una volta al mese (o anche meno) gli articoli più interessanti pubblicati sul blog, e potrai provare a vincere un biglietto per uno dei prossimi eventi!

Andiamo!