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))
text author
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:
- https://nannibassetti.blogspot.com/2021/01/machine-learning-costruiamo-un.html
- https://nannibassetti.blogspot.com/2020/05/come-funziona-un-neurone-di-una-rete.html
- https://www.cybersecurity360.it/soluzioni-aziendali/algoritmi-genetici-e-machine-learning-nella-digital-forensics-applicazioni-pratiche/
Autore: Nanni Bassetti - nannibassetti.com
Commenti