Machine Learning - attribuire la paternità di un testo - NLP

Come potrebbe un algoritmo di machine learning imparare e riconoscere se un testo può appartenere ad un autore?


Il meccanismo è lo stesso adottato per riconoscere se un messaggio contenuto in una e-mail è di Spam, che è ormai ampiamente documentato ed utilizzato, pertanto partendo da quella base, si può anche immaginare di creare un database contenente dei testi appartenenti a vari autori e poi dare in pasto un testo nuovo, al fine di far classificare all'algoritmo la paternità del testo.

Ma vediamo come agire.

Dato un testo, bisogna elaborarlo in modo da renderlo più facile da classificare, vediamo come procedere.

Occorre fornire al computer una rappresentazione numerica del testo e per far questo vi sono tecniche del NLP (Natural Language Processing).
L'approccio più semplice da utilizzare è il Bag Of Words, ossia il sacco di parole, è una tecnica che conta il numero di parole e quante volte compaiono in un testo, non considerandone l'ordine o la semantica, per esempio:

"ciao ciao Pippo come stai?" diventa

ciao: 2 Pippo: 1 come: 1 stai: 1 ?: 1

"Pippo come ciao ? stai ciao"

avrà lo stesso bag of words perchè conta solo le parole a prescindere dal loro ordine.

Una prima tecnica per verificare la somiglianza tra due testi è quella di calcolare la distanza totale tra due testi, sommando i valori assoluti delle distanze tra le singole parole contenute, ma facciamo un esempio:

Testo 1:
ciao Pippo sono al mare

Testo 2:
hola Pluto siamo al mare

Testo 3:
ciao Mario sono con Pippo

Parole: ciao    Pippo    Sono    Al    Mare    Hola    Pluto    Siamo    Mario    Con

Testo1:    1        1        1        1    1            0        0        0            0        0

Testo2:    0        0        0        1    1            1        1        1            0        0

Testo3:    1        1        1        0    0            0        0        0            1        1


Distanza Testo1-Testo2:
|1-0| + |1-0| + |1-0| + |1-1| + |1-1| +|0-1| + |0-1| + |0-1| + |0-0| + |0-0| = 6

Distanza Testo1-Testo3:
|1-1| + |1-1| + |1-1| + |1-0| + |1-0| + |0-0| + |0-0| + |0-0| + |0-1| + |0-1| = 4

Si evince che Testo 1 assomiglia di più a Testo 3 rispetto a Testo 2, infatti la distanza tra Testo 1 e Testo 3 è di 4, mentre quella da Testo 2 è di 6.

Ma questo sistema ha un problema, ossia tiene conto anche di tante parole, che sono molto frequenti ma non hanno importanza nel testo, come le congiunzioni, le preposizioni ed altri elementi grammaticali, quindi bisogna cercare un sistema di "filtraggio" e per far questo si utilizzerà la tecnica del Term Frequency Inverse Document Frequency (TF-IDF).

La TF-IDF è un tecnica basata su un'operazione matematica che serve a calcolare la frequenza di un termine in un testo e assegnare dei pesi maggiori alle parole con minor frequenza, in modo da valorizzarle rispetto alle congiunzioni, preposizioni, ecc..

Il primo termine TF si riferisce alla frequenza di una parola nel singolo testo, quindi nel testo:

"ciao ciao a tutto il mondo"

TF(ciao) = 2/6 ossia appare 2 volte su 6 parole, che poi 2/6=1/3

TF(mondo) = 1/6

e così via....

Il termine DF, si riferisce alla frequenza di una parola in tutti i testi confrontati, quindi:

"ciao ciao a tutto il mondo"

"Ieri Pippo ha detto ciao a Mario"

"Non è il mondo a cambiare"

DF(ciao) = 3/3 
DF(mondo) = 2/3
DF(a)=3/3
DF(il)=2/3
DF(tutto)=1/3

ecc.

Per IDF si intende l'inverso della frequenza della parola nel documento, quindi sarebbe 1/DF.

Adesso bisogna calcolare il punteggio TF-IDF, considerando la formula:

TF-IDF = TF * log(1/DF) 

il logaritmo deve essere in base 10.

Calcoliamo il TF-IDF delle parole del primo testo, ossia:

"ciao ciao a tutto il mondo"

TF-IDF(ciao) = 2/6 * log(1/(3/3)) = 1/3 * log(1/1) = 0

TD-IDF(a) = 1/6 * log (1/1) = 0

TD-IDF(tutto) = 1/6 * log(1/(1/3)) = 1/6 * log(3/2) = 0,055

TD-IDF(il) = 1/6 * log(1/(2/3)) = 0,028

TD-IDF(mondo) = 1/6 * log(3/2) = 0,028

Vediamo che le parole "ciao" ed "a" hanno un TF-IDF pari a 0, inferiore agli altri punteggi, quindi sono parole molto più frequenti rispetto alle altre.

Le ultime operazioni da compiere sul testo, sono quelle di eliminare la punteggiatura e creare dei lemmi o gli stems, che sono, per semplificare, le radici delle singole parole, infine si eliminano le stopwords, ossia le parole che non hanno dei significati importanti (congiunzioni, preposizioni, ecc.).

A questo punto possiamo scatenare il mio consueto e bruttino programmino didattico:

Prima di tutto lanciate l'interprete Python e digitate i comandi:

>>> import nltk
>>> nltk.download()

https://www.nltk.org/data.html 

Poi potete lanciare il programmino

import pandas as pd
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.model_selection import train_test_split
from sklearn.naive_bayes import BernoulliNB
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score
import string
import re
import nltk
from sklearn.feature_extraction.text import TfidfVectorizer

stopwords = nltk.corpus.stopwords.words('italian')

ps = nltk.PorterStemmer()
#ps = nltk.WordNetLemmatizer()

# Qui puliamo dalla punteggiatura, separiamo le parole in tokens (singole parole separate dalla virgola)
# Rimuoviamo le stopwords (o, è, e, a, ecc.)
# infine usiamo gli stems o i lemma della parola

def clean_text(text):

    text = "".join([word.lower() for word in text if word not in string.punctuation])

    tokens = re.split('\W+', text)

    text = [ps.stem(word) for word in tokens if word not in stopwords]

    #text = [ps.lemmatize(word) for word in tokens if word not in stopwords] 

    return text

df = pd.read_csv('oot.csv', sep='\t', names=['text', 'author'], header=None)

#print(df.head())

X = df['text']

y = df['author']

# Possiamo scegliere la vettorizzazione TF-IDF o la CountVectorization, con n-grammi da 1 o 2 parole, per esempio.

#vect = TfidfVectorizer(analyzer=clean_text, ngram_range=(1,2))

vect = CountVectorizer(analyzer=clean_text, ngram_range=(1,2))

X = vect.fit_transform(X)

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.4)

#model = BernoulliNB()

model = KNeighborsClassifier(n_neighbors = 3)

model.fit(X_train, y_train)

p_train = model.predict(X_train)

p_test = model.predict(X_test)

acc_train = accuracy_score(y_train, p_train)

acc_test = accuracy_score(y_test, p_test)

print( f'Train acc. {acc_train}, test acc. {acc_test}' )

testo=vect.transform(['oibò spacchiamo tutto!!'])

#print(testo)

print (model.predict(testo))


Il programma lavorerà su un dataset di questo tipo:

text author
viva la vita e tutto il mondo Mr. X
spacchiamo tutto!! Mr. X
La capra campa ma la gallina muore Mr. X
Ma che cavolo sto scrivendo? non lo so, ma lo devo fare, purtroppo Mr. X
Se ci senti batti un colpo, ma non di pistola! Mr. X
Oibò qual dolce pulzella alberga in questa magione? Aulico
Sebbene orbo, il mio veder è ancora clarissimo Aulico
Non tutto il divenir ruota attorno al fulgido astro, che noi appelliamo Sole Aulico
Lo spaccar dello ligneo bastone risuona fragoroso Aulico

Gli autori sono due Mr. X e Aulico e noi diamo in pasto al programma, la frase:

'oibò spacchiamo tutto!!'

Ottenendo:

Train acc. 0.8333333333333334, test acc. 0.75

['Mr. X']

Quindi il programma attribuisce la frase a Mr. X.

In sintesi, si deve vettorizzare un testo, renderlo numerico ed assegnado dei pesi alle parole, una volta che si ottengono delle matrici numeriche il programma può lavorare e tramite il machine learning può imparare e classificare.

Se ti interessa l'argomento puoi anche leggere:


Autore: Nanni Bassetti - nannibassetti.com 




Commenti