Con il termine logica fuzzy (fuzzy logic) si intendono tutte quelle regole e funzioni che vanno ad applicare una euristica basata sulla approssimazione della verità - la parola "fuzzy" in inglese si traduce come poco chiaro, sfocato. Questa parola non è scelta a caso e fa proprio riferimento al modo in cui funzionano questi ragionamenti.

Nell'informatica, la fuzzy logic vuole aiutare a fornire un grado di verità invece che la verità vera e propria. È un insieme di ragionamenti che permettono di rispondere in maniera imprecisa a domande altrettanto imprecise.

Un esempio di fuzzy logic

Poniamo che ci venga chiesto quanti anni abbiamo, ma nella forma

Sei anziano?

Guardiamo questa domanda dal punto di vista logico:

  • se abbiamo 5 anni, probabilmente non possiamo essere considerati anziani
  • se abbiamo 70 anni, probabilmente possiamo essere considerati anziani
  • ...ma cosa rispondiamo se invece abbiamo 50 anni?

La logica fuzzy risponde in questo modo:

Si e no.

Mentre la logica tradizionale si esprime solo con due stati, vero e falso, la logica fuzzy vuole riempire quello che c'è in mezzo, andando proprio a fornire dei gradi di verità invece che dell'espressione booleana.

Tornando all'esempio appena fatto, e scomponendo il quesito per tipo di logica, avremmo queste risposte alla domanda sei anziano?

Ponendo che possiamo essere considerati anziani all'età di 75 anni,

  • Logica tradizionale: VERO se ETÀ > 75; altrimenti FALSO
  • Logica fuzzy:  ETÀ / 75 % anziano.

Quindi se abbiamo 70 anni, allora secondo la logica fuzzy noi saremmo 93% anziani.

Per noi analisti, la logica fuzzy diventa uno strumento per associare, distinguere o raggruppare elementi che sono definiti da una metrica.

Usare la logica fuzzy per il tagging / categorizzazione

Quello che mostrerò in questo articolo è il tagging o categorizzazione di contenuto testuale (articoli di questo blog) usando la logica fuzzy in Python.

Usando la distanza di Levenshtein come sistema di valutazione, useremo la logica fuzzy per associare un articolo ad una o più categorie predefinite da noi.

La distanza di Levenshtein è una misura usata spesso per valutare la differenza tra due stringhe. Essa spiega quanti caratteri occorrono alla stringa A per diventare stringa B.

Un esempio:
Stringa A: foo | Stringa B: fii

Distanza di Levenshtein: 2

Occorrono infatti due cambiamenti ad A per trasformarla in B (oo -> ii).

Se il lettore è interessato al tema delle manipolazioni di stringhe e alla sua applicazione principale, che è quella dell'autocorrezione, suggerisco di leggere questo articolo che spiega come funziona un modello di autocorrezione.

Il nostro sistema di logica fuzzy si baserà su una libreria chiamata The Fuzz che è stata creata proprio per il nostro intento.

Cosa faremo?

I lettori assidui di questo blog conosceranno lo scraper di articoli che ho pubblicato del tempo fa. Userò quel piccolo software per recuperare gli articoli di Diario Di Un Analista.it e applicare su di loro dei tag predefiniti.

Con questo piccolo progetto, voglio permettere al lettore di poter taggare qualsiasi contenuto testuale in base a dei tag predefiniti.  
Poiché questo approccio non si basa su dei modelli predittivi, bisogna fornire al software dei tag che vogliamo applicare ai nostri documenti.

Questo approccio ha i suoi vantaggi rispetto a quelli basati su modelli predittivi. Un modello potrebbe dedurre dei tag imprecisi, che non soddisfano i criteri da noi in mente.

Definendo i tag a monte, possiamo esser sicuri che il tagging sia coerente con la nostra cernita. Certo, possiamo e incorreremo sicuramente in errori, ma saranno più controllabili da parte nostra.

I requisiti

I requisiti saranno solo due: Pandas e The Fuzz.

pip install thefuzz pandas in terminale.

Iniziamo a importare le nostre librerie come di consueto

from thefuzz import process
import pandas as pd

Creazione dei tag

La logica che andremo ad applicare ha bisogno di un riferimento per funzionare. Per questo motivo, creeremo una lista chiamata tags con le etichette che vogliamo applicare ai documenti.

# queste sono i tag che vogliamo applicare ai nostri documenti.
# cambiate questa lista a vostra discrezione
tags = [
    "machine learning",
    "clustering",
    "carriera",
    "progetto",
    "consigli",
    "analytics",
    "deep learning",
    'nlp',
]

Caricamento del dataset

Utilizzando lo scraper di blog linkato sopra, creiamo un dataset che avrà due colonne: url e article.

# carichiamo un dataset e isoliamo i post
df = pd.read_csv('dataset.csv')
posts = df[df.url.str.contains('post')]
posts.reset_index(inplace=True, drop=True)
articles = list(posts.article)

Abbiamo il nostro corpus. Al momento di scrittura di questo pezzo stiamo intorno a 30 articoli - si tratta quindi di un corpus molto piccolo. Andrà comunque bene per il nostro esempio.

Funzione di tagging

Ora scriveremo il codice per applicare il tagging usando la logica fuzzy. L'algoritmo funziona così:

  1. ciclare tra i tag
  2. per ogni tag, usare process.extract di The Fuzz per estrarre un gruppo di articoli rappresentativi del tag, ordinati per punteggio
  3. per ogni elemento dell'output precedente, strutturare il dato in un dizionario Python e creare un dataframe Pandas
  4. dopo aver raccolto tutti i dataframe, unirli tutti con una concatenazione

Scriviamo la funzione

def fuzzy_tagging(tags, articles):
	"""
	Questa funzione riceve in input una lista di tag predefiniti e la lista di contenuto testuale da taggare.
	Restituisce un dataframe Pandas con gli articoli taggati
	"""
	results = []
	# ciclo nei tag
	for i, tag in enumerate(tags):
	    d = {}
	    ranking = process.extract(tag, articles, limit=4)
	    for r in ranking:
	        d = {"tag": tag, "index": articles.index(r[0]), "confidence": r[1]}
	        results.append(d)
	# organizziamo tutto in un df pandas
	raw_tags = pd.DataFrame(results)
	raw_tags.set_index('index', inplace=True, drop=True)

	d = {}
	for i, row in raw_tags.iterrows():
	    if d.get(i):
	        if row['confidence'] >= 55: # se la soglia supera il valore di 55, aggiungere il tag a quelli esistenti se è già presente un tag
	            d[i] += ', ' + str(row['category'])
	    else:
	        d[i] = str(row['category'])
	        
	# creiamo il dataset finale
	tags = pd.Series(d, name='tag')
	tagged_df = pd.concat([posts, tags], axis=1)
	
	return tagged_df

Applicazione del tagging

Andiamo a vedere i risultati del tagging andando ad eseguire il codice scritto

tagged_df = fuzzy_tagging(tags=tags, articles=articles)
tagged_df

Come possiamo vedere, il tagging non è perfetto ma la maggior parte dei risultati sono in effetti coerenti.

Quando una pagina appartiene a più categorie, come ad esempio la riga 3 "Clustering delle serie temporali per la previsione del mercato azionario" che appartiene ai tag clustering e progetto.

Ci sono anche dei NaN. Questo è normale. Un articolo potrebbe non essere associato con una soglia maggiore a quella stabilita (55 nel nostro caso) ad un certo tag. In questo caso, nessun tag viene associato al contenuto e quindi viene assegnato NaN. Modificare la soglia di confidence aiuterà ad avere più o meno risultati.

Conclusioni

Abbiamo visto come fare tagging dei contenuti in Python, usando la logica fuzzy e i dataframe di Pandas.

Questo è un piccolo progetto che però può avere ripercussioni importanti sul lavoro. Un dataset già di 200 righe può richiedere delle ore per essere taggato a mano. Questo vuole essere un approccio semplice ma efficace all'automazione di questi task.

Next step

In questo esempio ho utilizzato il body dell'articolo. È stata una scelta arbitraria, ma ad esempio si può fare la stessa cosa con i title degli articoli o con le meta description. Se questi testi sono rappresentativi dell'argomento, il nostro ragionamento potrebbe funzionare anche meglio! Provate voi :)

A presto,

Template del codice

Ecco qui l'intera codebase

from thefuzz import fuzz, process
import pandas as pd

# definiamo le categorie che vogliamo applicare
tags = [
    "machine learning",
    "clustering",
    "carriera",
    "progetto",
    "consigli",
    "analytics",
    "deep learning",
    'nlp',
]

# carichiamo un dataset e isoliamo i post
df = pd.read_csv('dataset.csv')
posts = df[df.url.str.contains('post')]
posts.reset_index(inplace=True, drop=True)
articles = list(posts.article)

def fuzzy_tagging(tags, articles):
	"""
	Questa funzione riceve in input una lista di tag predefiniti e la lista di contenuto testuale da taggare.
	Restituisce un dataframe Pandas con gli articoli taggati
	"""
	results = []
	# ciclo nei tag
	for i, tag in enumerate(tags):
	    d = {}
	    ranking = process.extract(tag, articles, limit=4)
	    for r in ranking:
	        d = {"tag": tag, "index": articles.index(r[0]), "confidence": r[1]}
	        results.append(d)
	# organizziamo tutto in un df pandas
	raw_tags = pd.DataFrame(results)
	raw_tags.set_index('index', inplace=True, drop=True)

	d = {}
	for i, row in raw_tags.iterrows():
	    if d.get(i):
	        if row['confidence'] >= 55: # se la soglia supera il valore di 55, aggiungere il tag a quelli esistenti se è già presente un tag
	            d[i] += ', ' + str(row['category'])
	    else:
	        d[i] = str(row['category'])
	        
	# creiamo il dataset finale
	tags = pd.Series(d, name='tag')
	tagged_df = pd.concat([posts, tags], axis=1)
	
	return tagged_df

tagged_df = fuzzy_tagging(tags=tags, articles=articles)
tagged_df