Classificatore di Testi con gli Oggetti – NLP di base in Python [6.3 di 9]

Classificatore di Testi con gli Oggetti

Questo articolo conclude i lavori sul classificatore di testi che abbiamo realizzato insieme qua e migliorato qua. L’obiettivo adesso è di introdurre la Programmazione Orientata agli Oggetti (OOP, dall’inglese Object Oriented Programming). Andremo dunque a creare ed utilizzare il nostro primo “oggetto” in Python.
Ricordo che questo articolo è parte di una serie sul Natural Language Processing (NLP) di base in Python. La serie è composta da 9 argomenti, questo è il terzo articolo per l’argomento numero 6:

  1. Tokenization
  2. Stemming
  3. Lemmatization
  4. Splitting
  5. Bag-of-words
  6. Text classifier
    1. Costruzione del classificatore
    2. Refactoring: funzioni
    3. Refactoring: oggetti <— Questo articolo
  7. Gender classifier
  8. Sentiment analysis
  9. Topic modelling

Piano di azione

Diversamente da come si fa di solito, partiamo dalla “pratica” e poi spieghiamo la “teoria”. Anticipo subito che creeremo 1 classe, contenente 2 metodi e 7 attributi. Tale classe andrà ad arricchire la libreria che stiamo costruendo con gli articoli di questa serie.

Questa classe verrà poi richiamata all’interno del nostro codice del classificatore per creare un oggetto. Questo oggetto dovrà contenere 3 cose:

  1. il corpus da utilizzare per il classificatore (mi riferisco proprio al testo contenuto nei singoli documenti),
  2. l’indice della categoria a cui appartiene ciascun documento del corpus e
  3. i nomi per esteso delle varie categorie.

Da questo possiamo già intuire che quando creiamo l’oggetto dobbiamo passare 3 dati di input.

Prima di metterci al lavoro, tengo a condividere subito con voi che lo scopo di questo articolo non è di spiegare la programmazione ad oggetti! Ricordiamoci infatti che siamo nell’ambito del Machine Learning e che il lettore ideale è un aspirante Data Scientist e non un aspirante Sviluppatore (frontend, backend o di qualsiasi altro tripo). Nella mia personalissima e limitatissima esperienza con il Machine Learning e la Data Science, la programmazione ad oggetti viene comunemente usata in maniera piuttosto passiva. Per questo motivo, in una guida introduttiva come questa, credo che l’obiettivo principale debba essere quello di trasmettere le basi della programmazione ad oggetti, in modo da poter usare classi e oggetti in maniera consapevole.

Benissimo, adesso mettiamoci subito al lavoro! Partiamo con la fase di inizializzazione.

Inizializzazione dell’oggetto

Quando si “istanzia” (crea) un oggetto, vengono eseguite tutte le operazioni indicate nel metodo __init__ all’interno della classe a cui appartiene quell’oggetto (sì, all’inizio ed alla fine del nome del metodo ci sono due underscore).

Procediamo dunque a creare la nostra prima classe ed il suo metodo metodo __init__. Chiamiamo la classe “BaseCorpus” e per adesso lasciamo il metodo di inizializzazione vuoto, ma indichiamo i 3 parametri che ci aspettiamo in input. Per fare questo apriamo il file che contiene la nostra libreria (“NLP_base_library.py”) ed aggiungiamo la nostra prima classe con il seguente codice minimale:

class BaseCorpus:
    def __init__(self, corpus_raw_text, corpus_raw_targets, corpus_raw_target_names):
        pass

Et voilà, la classe è creata!

Come vedete, creare una classe è molto semplice: basta usare il termine class seguito dal nome che vogliamo dare alla classe e poi i due punti. Tutto ciò che sta dentro la classe deve essere indentato al suo interno (4 spazi vuoti), come per le funzioni. Normalmente, per i nomi delle classi si segue la convenzione del CamelCase (tutto unito, con le iniziali tutte in maiuscolo).

I metodi di una classe non sono altro che delle funzioni definite al suo interno. Il contenuto del metodo deve essere a sua volta indentato all’interno del metodo (8 spazi vuoti in tutto). In questo caso, il contenuto del metodo __init__ è semplicemente pass.

Ma chi è questo self? E dov’è il return???

Il metodo __init__ prende in input i 3 parametri che avevamo immaginato prima. In aggiunta c’è anche self, che sta ad indicare “sé stesso”. Non è un parametro che dobbiamo passare quando creiamo l’oggetto, ma deve essere presente nella definizione del metodo nella classe. Sta ad indicare che possiamo usare l’oggetto stesso all’interno del metodo. Questa è una di quelle classiche cose che si capiscono meglio guardando codice, quindi andiamo subito ad utilizzare self dentro il metodo __init__.

class BaseCorpus:
    def __init__(self, corpus_raw_text, corpus_raw_targets, corpus_raw_target_names):
        self.raw_text = corpus_raw_text
        self.raw_targets = corpus_raw_targets
        self.raw_target_names = corpus_raw_target_names

In questo modo abbiamo creato i primi 3 attributi della classe BaseCorpus, che si chiamano rispettivamente raw_text, raw_targets e raw_target_names, ed abbiamo assegnato loro i valori passati in input. Giusto per ulteriore chiarezza, la riga 3 self.raw_text = corpus_raw_text significa “crea l’attributo raw_text dell’oggetto attivo (self) e mettici dentro il contenuto della variabile corpus_raw_text“.

Lasciatemi fare una nota molto semplice e al tempo stesso molto importante. Il metodo non “ritorna” nulla, non c’è nessun return alla fine e non intendiamo aggiungerlo. Perché? Perché l’effetto di questo metodo è quello di aver creato ed inizializzato i 3 attributi.

Questo è un punto importante, che differenzia in maniera fondamentale la programmazione funzionale da quella ad oggetti. Quando si programma ad oggetti i dati fanno parte dell’oggetto stesso, per questo motivo il risultato delle azioni molto spesso è proprio quello di andare ad aggiornare l’oggetto, invece che ritornare qualcosa esterno all’oggetto. Quando si programma con le funzioni, invece, i dati su cui le funzioni operano non fanno parte della funzione.

Creare ed usare un oggetto

Passiamo subito a testare quanto fatto, all’interno di un nostro codice.

Piuttosto che creare nuovo codice da zero, suggerisco di partire dal precedente “6.2_text_classifier.py”. Come al solito, iniziamo con la parte di import, dove questa volta dobbiamo aggiungere la classe che abbiamo appena creato. La parte successiva, chiama “Data” nel codice, può restare uguale a come era prima, mentre interveniamo nella parte ancora dopo, chiamata “Representation”, dove creiamo il nostro primo oggetto a partire dalla nostra prima classe. Se vi siete persi per strada no problem, riporto a seguire tutto il codice che ci serve finora:

from NLP_base_library import get_show_20ng, BaseCorpus

# 1. DATA
# use a function to get the dataset and print basic statistics about it
print('Importing the whole dataset (for all categories):')
input_dataset = get_show_20ng(subset='all')
category_map = {'misc.forsale': 'Sales', 'rec.motorcycles': 'Motorcycles',
                'rec.sport.baseball': 'Baseball', 'sci.crypt': 'Cryptography',
                'sci.space': 'Space'}
print('Importing the train dataset (for 5 categories):')  # 60%
training_data = get_show_20ng(subset='train', categories=category_map.keys(), shuffle=True, random_state=7)

# 2. REPRESENTATION
print('\nInitializing the text object...')
my_first_object = BaseCorpus(training_data.data, training_data.target, training_data.target_names)
print('... done.')
print(f'Attribute values:\n\traw_text (doc 1, 20 char) = "{my_first_object.raw_text[0][:20]} ..."\n'
      f'\traw_targets (list) = "{my_first_object.raw_targets}"\n'
      f'\traw_targets_names (list) = "{my_first_object.raw_target_names}"')

La riga 15 è quella in cui creiamo il nostro oggetto, che non è nient’altro che una variabile chiamata my_first_object. Questa variabile è inizializzata con una chiamata alla classe BaseCorpus, nella quale passiamo i 3 valori di input discussi prima (legati al corpus che abbiamo caricato nella parte 1. Data).

Anche se noi abbiamo semplicemente valorizzato una variabile, la chiamata alla classe ha eseguito in automatico tutte le operazioni dentro il metodo __init__. Per questo motivo il nostro oggetto ha già 3 attributi valorizzati.

Vediamoli dunque!

Stampiamo a schermo il valore dei 3 attributi dell’oggetto. Il primo attributo contiene l’intero corpus, come lista di stringhe (ci limitiamo dunque a mostrare solo i primi 20 caratteri del primo documento), il secondo attributo contiene la lista degli indici ed il terzo il nome delle categorie. Di seguito riporto l’output del codice precedente:

Initializing the text object...
... done.
Attribute values:
    raw_text (doc 1, 20 char) = "From: demers@cs.ucsd ..."
    raw_targets (list) = "[2 1 1 ... 1 2 3]"
    raw_targets_names (list) = "['misc.forsale', 'rec.motorcycles', 'rec.sport.baseball', 'sci.crypt', 'sci.space']"

Creare un metodo

Tecnicamente __init__ è un metodo, però secondo me non conta, perché ce lo hanno tutte le classi e poi abbiamo fatto solo 3 assegnazioni! Non un granché. Passiamo a creare invece un metodo degno di questo nome!

Quali azioni vogliamo che faccia il nostro oggetto? Ricordiamoci che non vogliamo aggiungere nuove funzionalità al codice, vogliamo che il codice faccia le stesse cose che faceva già nell’ultima versione, ma tramite un oggetto. Per questo motivo guardiamo alle operazioni che si fanno attualmente e valutiamo per quali è il caso di inglobarle nel nostro oggetto, invece che in una funzione.

Vi invito a rivedere il codice realizzato in precedenza, a scorrere le tre fasi di Data, Representation e ML model, per vedere, in ciascuna di queste, dove si potrebbe usare un metodo all’interno del nostro oggetto.

Secondo me, possiamo creare un metodo per il calcolo della dtm con la tf-idf come metrica. Mi sembra utile che il nostro oggetto abbia tra i suoi attributi anche la dtm. In questo modo se, ad esempio, volessimo lavorare con diversi corpora, ci basterebbe creare un oggetto per ciascun corpus, e poi ciascun oggetto avrebbe la sua dtm, senza pericolo di confusione.

Creiamo dunque un nuovo metodo, chiamato create_tfidf_dtm, per calcolare la dtm e lo facciamo esattamente come abbiamo fatto in precedenza nella funzione omonima (cioè utilizzando CountVectorizer e TfidfTransformer di sklearn). Riporto di seguito il codice completo, che non include le docstring per facilità di lettura (nel repository trovate tutto ovviamente). Lo commentiamo insieme a seguire.

class BaseCorpus:
    """
    docstring omissis
    """

    def __init__(self, corpus_raw_text, corpus_raw_targets, corpus_raw_target_names):
        """
        docstring omissis
        """
        self.raw_text = corpus_raw_text
        self.raw_targets = corpus_raw_targets
        self.raw_target_names = corpus_raw_target_names
        # initialise other object attributes
        self.features = None
        self.tf = None
        self.tfidf_transformer = None
        self.tfidf = None

    def create_tfidf_dtm(self):
        """
        docstring omissis
        """
        from sklearn.feature_extraction.text import CountVectorizer, TfidfTransformer
        #
        # extract the vocabulary and create a tf matrix
        self.features = CountVectorizer()
        self.tf = self.features.fit_transform(self.raw_text)
        # create the tf-idf matrix
        self.tfidf_transformer = TfidfTransformer()
        self.tfidf = self.tfidf_transformer.fit_transform(self.tf)

Partiamo dal secondo metodo, quello nuovo. Se le 4 righe di codice che contiene (26 – 30) vi sembrano familiari, è perché sono le stesse di quelle che abbiamo usato in precedenza nella funzione con cui calcolavamo la dtm! Ed è giusto che sia così perché, ripeto ancora una volta, non stiamo cambiando funzionalità del codice, stiamo solo organizzando le cose in modo diverso.

Nel creare il nostro metodo, abbiamo anche creato 4 nuovi attributi per la classe, dove abbiamo conservato rispettivamente: le feature, la tf dtm, il tfidf_transformer e la tfidf dtm. Ho dunque aggiunto questi 4 attributi anche nel metodo di inizializzazione, dove imposto il loro valore di partenza a None. Questo significa che quando creiamo l’oggetto, solo i primi 3 attributi sono valorizzati, gli altri 4 invece no.

Piccola nota: il nuovo metodo che abbiamo appena creato ha lo stesso nome della funzione che avevamo creato la scorsa volta. Questo non genera problemi di sorta, in quanto il metodo esiste solo all’interno della classe, quindi non c’è conflitto. Certo, normalmente non li terremmo entrambi e cancellerremo il codice che non usiamo.

Verifichiamo adesso che il metodo funzioni correttamente. Torniamo dunque al nostro driver, nella fase “2. Representation”, ed aggiungiamo il sguente codice:

print('\nInitializing the text object...')
corpus_object = BaseCorpus(training_data.data, training_data.target, training_data.target_names)
print('... done.')
# creat tf-idf dtm matrix:
print('\nCreating the tf-idf dtm matrix...')
corpus_object.create_tfidf_dtm()
print('... done.')

Anche se questo codice di per sé non restituisce niente di speciale a schermo, ha fatto tante cose importanti (è successo tutto dentro l’oggetto). Prima abbiamo inizializzato l’oggetto (riga 2), che come sappiamo esegue il metodo __init__, e poi abbiamo chiamato il metodo create_tfidf_dtm (riga 6). Per vedere cosa è successo, aggiungo un paio di righe di debug:

print('\nInitializing the text object...')
corpus_object = BaseCorpus(training_data.data, training_data.target, training_data.target_names)
print('... done.')
# creat tf-idf dtm matrix:
print('\nCreating the tf-idf dtm matrix...')
print(f'\t DEBUG \tChecking on the tf-idf matrix *before* computing it: \t{type(corpus_object.tfidf)}')
corpus_object.create_tfidf_dtm()
print(f'\t DEBUG \tChecking on the tf-idf matrix *after* computing it: \t{type(corpus_object.tfidf)}')
print('... done.')

Di seguto l’output a schermo di questo codice:

Initializing the text object...
... done.
Creating the tf-idf dtm matrix...
     DEBUG 	Checking on the tf-idf matrix *before* computing it:    <class 'NoneType'>
     DEBUG 	Checking on the tf-idf matrix *after* computing it:    <class 'scipy.sparse.csr.csr_matrix'>
... done.

Come vedete, nella prima riga di debug l’attributo tfidf è ancora None, invece, subito dopo aver chiamato il metodo, l’attributo è stato popolato.

Tutto il resto del codice

Spero che non restiate delusi se vi dico che il resto del codice prosegue esattamente come prima. Dobbiamo solo modificare il nome di alcune variabili, dato che adesso sono conservate all’interno dell’oggetto. Ma per il resto non ho creato altri metodi né attributi.

Ecco il resto del codice del nostro main. Piccola nota: affinché funzioni la parte “3. ML Model” è necessario aggiornare la parte di import del codice, che riporto qui di seguito in riga 1.

from sklearn.naive_bayes import MultinomialNB

# print the tf-idf
words_to_print = 5
first_word_index = 6631  # index of the first word/feature to print (start from 0)
docs_to_print = 6
first_doc_index = 5  # index of the first document to print (start from 0)
print(f'Printing an extract of the dtm (for {words_to_print} words and {docs_to_print} documents)')
print_dtm(corpus_object.features, corpus_object.tfidf, [first_word_index, first_word_index + words_to_print],
          [first_doc_index, first_doc_index + docs_to_print])

# 3. ML MODEL
# - Train the model
# Train a Naive Bayes classifier (on the training data)
our_classifier = MultinomialNB().fit(corpus_object.tfidf, corpus_object.raw_targets)
# - Evaluate the model
print('Importing the test dataset')
test_dataset = get_show_20ng(subset='test', categories=category_map.keys())  # 40%
test_data = test_dataset.data
# Classify all documents in the test dataset
print('Evaluating the classifier ...')
predicted_categories = classify_doclist(test_data, our_classifier, corpus_object.features, corpus_object.tfidf_transformer)
# Compute the classification rate
correct_categories = test_dataset.target
accuracy = compute_list_overlap(predicted_categories, correct_categories)
print(f'Number of correct classifications over total classifications (classifier accuracy) = {100*accuracy:5.2f}%')
# final accuracy = 94.99%

Come anticipato, il codice prosegue come nella versione precedente. Cambia che adesso la dtm non è più conservata nella variabile X_train_tfidf, ma nell’attributo del nostro oggetto, cioè in corpus_object.tfidf. Similmente, le features ed il tfidf_transformer sono adesso in 2 attributi dell’oggetto, rispettivamente in corpus_object.features e corpus_object.tfidf_transformer.

Dato che il codice esegue le stesse operazioni, gli output devono essere identici. Nel nostro caso, gli output che vengono mostrati a schermo sono un estratto della dtm e il classification rate sul dataset di test. Vi invito a far girare entrambi i codici e verificare che i risultati siano identici. Io ho fatto il test ed effettivamente entrambi gli output sono identici a quelli che ottenevamo prima (nel caso del classification rate ho usato fino ad 8 cifre dopo la virgola, ottenendo 94.98734177% in entrambi i casi).

Object Oriented Programming

Adesso è arrivato il momento di passare ad un po’ di teoria degli oggetti, per capire meglio quello che abbiamo fatto.

Definizione ed esempi

La Programmazione Orientata agli Oggetti, Object Oriented Programming OOP in inglese, non è nient’altro che un paradigma di programmazione, cioè uno stile di programmazione che fornisce indicazioni su come strutturare le varie parti di un codice. Altri paradigmi di programmazione comuni sono quello procedurale e quello funzionale. La caratteristica principale della OOP è quella di impacchettare insieme proprietà e comportamenti all’interno di strutture note come “oggetti” (da cui il nome). Ad esempio, se uno dovesse programmare un gatto come un oggetto, allora caratteristiche quali il colore, il peso e la razza sarebbero codificate come proprietà dell’oggetto, mentre comportamenti quali mangiare, saltare, miagolare e fare le fusa sarebbero codificati come metodi dell’oggetto. Quindi un oggetto è un’entità caratterizzata da proprietà ed azioni.

Classi ed oggetti

Per programmare un oggetto si usano le classi, da cui l’oggetto eredita proprietà e comportamenti. Una classe non è nient’altro che l’archetipo dell’oggetto. Tornando ai nostri gatti: esistono tanti gatti e sono tutti unici e diversi tra loro, ma tutti hanno un colore, un peso e una razza e tutti mangiano, saltano, miagolano e fanno le fusa. La classe raccoglie appunto gli elementi comuni a tutti i gatti, ma poi lo specifico gatto è un oggetto, o istanza, di quella classe.

Un altro esempio è quello dei sondaggi. La versione preparata dal creatore, e dunque senza risposte, rappresenta la classe ed è fissa, non cambia. Mentre il sondaggio compilato da una persona è caratterizzato dalle risposte che quella persona ha dato, rappresenta dunque un oggetto (o istanza) di quella classe. Ogni sondaggio compilato è un oggetto diverso, che appartiene però alla stessa classe.

class e __init__

In Python, la classe viene creata usando la keyword class. Il metodo __init__ serve invece ad impostare il valore iniziale degli attributi. Tale metodo può avere un qualsiasi numero di parametri di input, ma il primo deve essere necessariamente self. Gli stessi parametri di input devono poi essere passati quando l’oggetto viene creato, altrimenti Python restituisce un errore. Tornando all’esempio del gatto: se abbiamo deciso che ogni gatto è caratterizzato da un colore, peso e razza, ogni volta che creiamo un nuovo oggetto-gatto dobbiamo fornire un valore per ciascuna di queste proprietà.

Attenzione: non tutti gli attributi di una classe devono essere inseriti nel metodo di inizializzazione. Qualora vi siano degli attributi che hanno lo stesso valore per tutti gli oggetti di una certa istanza, allora questi attributi vanno definiti subito dopo la prima riga della calsse, prima di __init__.

Il fatto che gli attributi di un oggetto siano valorizzati al momento dell’inizializzazione non implica in nessun modo che essi siano statici! Gli attributi di un oggetto possono essere cambiati in qualsiasi momento, con una semplice assegnazione (a meno che non siano attributi privati, di quelli che si indicano con un doppio underscore  prima del nome, ma questo è troppo in dettaglio per noi).

Metodi

Nella definizione di OOP si è detto che una classe è definita da proprietà e comportamenti. Le proprietà sono gli attributi, i comportamenti sono invece chiamati metodi.

Da un punto di vista della programmazione, i metodi non sono nient’altro che funzioni all’interno della classe. Lo stesso __init__ visto in precedenza è un metodo. Proprio come per __init__, tutti i metodi possono accettare un qualsiasi numero di parametri, il primo deve essere self e quando vengono usati bisogna passare gli stessi parametri, altrimenti si riceve un errore.

Nel nostro esempio del gatto, abbiamo immaginato 4 metodi: mangiare, saltare, miagolare e fare le fusa. Chiunque abbia più di un gatto sa per certo che ogni gatto mangia, salta, miagola e fa le fusa a modo suo, anche se l’azione di per sé è sempre la stessa. Allo stesso modo, ogni metodo può fare le stesse azioni ma in modo diverso, grazie ad i parametri passati in input. Ad esempio uno dei parametri per il metodo mangiare potrebbe essere la velocità, oppure il metodo saltare potrebbe accettare in input l’altezza del salto.

Notazione del punto (dot notation)

Per accedere ad un attributo di un oggetto o per eseguire un suo metodo si usa la notazione del punto, o dot notation in inglese. Il nome deriva dal simbolo che si usa per separare il nome dell’oggetto dal nome dell’attributo o del metodo (un punto, appunto :D). Nel caso del metodo, vanno poi aggiunte le parentesi alla fine. Se il metodo accetta dei parametri, bisogna passarli all’interno delle parentesi, altrimenti si terranno le parentesi vuote.

Ad esempio, kiki.colore ci permette di accedere all’attrbituo colore dell’oggetto kiki, mentre kiki.salta(20) ci consente di chiamare il metodo salta dell’oggetto kiki, con un parametro pari a 20.

Qualora non fosse già ovvio, sottolineo che tutto ciò che è creato all’interno di una classe non può essere utilizzato al di fuori. Quindi non possiamo digitare colore o salta(20) per accedere alle relative info o azioni dell’oggetto kiki.

Penso che possiamo interrompere qua la nostra breve panoramica sugli oggetti. Ci sono tante altre cose che si possono studiare riguardo la OOP (ad esempio l’ereditarietà tra classi), ma vanno però aldilà degli scopi di questa breve introduzione. Se però l’argomento vi stimola, allora i link che ho inserito nelle Conclusioni sono un buon punto di partenza per degli approfondimenti.

Oggetti vs Funzioni

Ora che avete fatto esperienza tanto con gli oggetti quanto con le funzioni, secondo voi, quale dei due è migliore?

La risposta in questo caso è semplice: dipende.

Non è assolutamente vero che gli oggetti sono migliori delle funzioni (o viceversa). Si tratta di due paradigmi di programmazione, ciascuno ha i suoi pro ed i suoi contro. Certo può capitare che uno preferisca un modo all’altro, ma questo non implica che quel modo sia giusto e l’altro sbagliato.

Quindi il mio suggerimento è di fare pratica con entrambi i metodi e poi cercare di usare quello più appropriato per il caso in esame. Non so dirivi esattamente quando conviene usare uno o l’altro, ma personalmente trovo utile riflettere in termini di dati e azioni. Quando i dati e le azioni che si compiono su questi dati si muovono insieme, allora è probabile che sia meglio usare gli oggetti, se invece le due cose stanno separate allora è probabile che sia il caso adatto alle funzioni. In ogni caso, imparateli entrambi perché tanto dovrete usarli tutti e due 🙂

Conclusioni

In questo articolo abbiamo fatto il secondo ed ultimo refactoring del nostro codice per il classificatore di testi. Nel primo refactoring avevamo modificato il codice introducendo le funzioni. In questo secondo abbiamo aggiunto anche gli oggetti. Il codice fa ancora le stesse cose che faceva dopo il primo refactoring, ma adesso lo fa utilizzando una combinazione di funzioni e di oggetti. Siamo intervenuti principalmente in due modi:

  1. Abbiamo creato la classe BaseCorpus nella nostra libreria e
    1. abbiamo personalizzato il metodo __init__ per inizializzare 3 attributi con dei valori dinamici e 4 con dei valori statici;
    2. abbiamo creato il metodo create_tfidf_dtm per calcolare la document-term matrix con la metrica tf-idf.
  2. Abbiamo creato un oggetto di tipo BaseCorpus per utilizzarlo in tutto il resto del codice per il classificatore. L’oggetto è creato a partire dal corpus di training ed è utilizzato per:
    1. calcolare la rappresentazione dei documenti
    2. fare il train del modello
    3. fare le classificazioni sui documenti di test

In tutto questo, abbiamo anche mantenuto la buona abitudine di aggiungere la documentazione attraverso le docstring. Alla fine, abbiamo potuto appurare che, da un punto di vista quantitativo, i risultati ottenuti con i due diversi approcci (funzioni e oggetti) sono identici. Spero che siate soddisfatti dei risultati raggiunti!

Sapete una cosa? La prima volta che ho capito come funzionavano gli oggetti mi si è aperto un mondo. E’ stato come un momento di illuminazione 🙂 che mi ha permesso di capire tante delle cose che vedevo negli innumervoli codici e tutorial che leggevo e studiavo. Mi auguro che possiate averlo avuto anche voi con questo articolo!

 

Come al solito, tutto il codice che abbiamo generato è riportato in questo stesso articolo. Inoltre, il codice realizzato in tutta la serie è disponibile in questo repository pubblico su GitHub con licenza Open Source.

Di seguito alcuni link utili sul tema OOP (la rete è piena di risorse):

  1. Guide one-shot, in Python:
    1. tutorial di base con alla fine un piccolo esempio nel contesto di Python in Finance: https://www.datacamp.com/tutorial/python-oop-tutorial;
    2. spiegazione della OOP attraverso 4 argomenti principali (Encapsulation, Inheritance, Polymorphism, Abstraction): https://www.freecodecamp.org/news/object-oriented-programming-in-python/;
    3. una breve guida più concettuale: https://towardsdatascience.com/object-oriented-programming-in-python-what-and-why-d966e9e0fd03.
  2. Per chi vuole “fare sul serio” con qualcosa di più corposo di un semplice tutorial:
    1. questo repository su github introduce i cosiddetti “SOLID principles” della OOP: https://gist.github.com/dmmeteo/f630fa04c7a79d3c132b9e9e5d037bfd, si tratta di 5 indicazioni di base su come utilizzare gli oggetti in modo da costruire del codice con un design robusto (rispetto a bug e future estensioni);
    2. questo sito illustra come usare la programmazione ad oggetti per affrontare alcuni problemi ricorrenti nel design di un codice: http://www.w3sdesign.com/ (include anche un libro gratis da oltre 300 pagine).

 

Buone letture e, se vorrete, ci becchiamo al prossimo articolo sul Gender classifier!