Vai al contenuto principale

Modello Bag of Words in Python: guida completa

Scopri tutto ciò che devi sapere su come implementare il modello bag of words in Python.
· 12 min leggi

Scopri con l'IA

Bag of Words (BoW) è una tecnica del Natural Language Processing (NLP). È ampiamente usata per trasformare i dati testuali in un formato leggibile dalle macchine, in particolare in valori numerici, senza considerare grammatica e ordine delle parole. Capire BoW è importante per chiunque lavori con dati testuali. Python offre diversi strumenti e librerie per implementare efficacemente Bag of Words.

In questo tutorial esploreremo BoW, introdurremo i suoi concetti, ne vedremo gli utilizzi e realizzeremo un'implementazione dettagliata in Python. Al termine, saprai applicare il modello Bag of Words a problemi reali. Se sei alle prime armi con l'NLP, dai un'occhiata alla nostra skill track Natural Language Processing in Python per saperne di più. 

Che cos'è Bag of Words?

Bag of Words è una tecnica per estrarre feature dai dati testuali per compiti di machine learning, come classificazione del testo e analisi del sentiment. Questo è importante perché gli algoritmi di machine learning non possono elaborare direttamente dati testuali. Il processo di conversione del testo in numeri è noto come feature extraction o feature encoding.

Un Bag of Words si basa sulla frequenza di occorrenza delle parole in un documento. Il processo inizia individuando il vocabolario nel testo e misurandone le occorrenze. Si chiama "bag" (sacco) perché l'ordine e la struttura delle parole non sono considerati, conta solo la loro presenza. 

Il modello Bag of Words è diverso dal Continuous Bag of Words (CBOW), che apprende word embedding densi usando le parole circostanti per predire una parola target, catturando le relazioni semantiche tra parole. CBOW richiede l'addestramento su un ampio corpus e produce vettori a bassa dimensionalità, utili per applicazioni NLP complesse in cui il contesto delle parole è importante.

Aspetto

BOW

CBOW

Scopo

Conta le occorrenze di ciascuna parola

Predice la parola target in base al contesto

Tipo di output

Vettore sparso ad alta dimensionalità

Embedding denso a bassa dimensionalità

Considera il contesto

No (ignora l'ordine delle parole)

Sì (usa le parole circostanti)

Rappresentazione

Vettore di frequenze sparso

Vettore denso che cattura la semantica

Complessità

Bassa (nessun training richiesto)

Alta (richiede training su un grande corpus)

Applicazioni tipiche

Classificazione del testo, analisi del sentiment

Word embedding, task NLP che richiedono contesto

Perché usare Bag of Words?

Bag of Words è utile in molti task di NLP, ecco alcuni motivi per usarlo:

  • Estrazione di feature: converte dati testuali non strutturati in dati strutturati, utilizzabili come input per vari algoritmi di machine learning.
  • Semplicità ed efficienza: BoW è computazionalmente semplice da implementare e funziona bene per corpora di piccole e medie dimensioni.
  • Similarità tra documenti: può essere usato per calcolare la similarità tra documenti testuali usando tecniche come la similarità del coseno.
  • Classificazione del testo: se combinato con tecniche come Naive Bayes, BoW è efficace per compiti di classificazione del testo come la classificazione dello spam e l'analisi del sentiment.

Ci sono però anche degli svantaggi, come il fatto di non considerare semantica, struttura delle parole o ordine delle parole.

Passaggi per implementare Bag of Words in Python

Per creare un modello bag-of-words, prendiamo tutte le parole in un corpus e creiamo una colonna per ciascuna parola. Le righe rappresentano le frasi. Se una certa parola esiste nella frase, è rappresentata da 1, e se non esiste, è rappresentata da 0. Ogni parola nella colonna rappresenta una singola feature.

Alla fine otteniamo una matrice sparsa. Una matrice sparsa è una matrice con molti zeri. 

Preprocessing dei dati

Per creare un modello Bag of Words in Python, dobbiamo eseguire alcuni passaggi di preprocessing. Questi includono tokenizzazione e rimozione delle stopword.

La tokenizzazione è il processo di scomposizione di un testo in unità più piccole, tipicamente parole. Puoi eseguire la tokenizzazione usando NLTK. 

Le stop word sono parole comuni in inglese, come "the", "that" e "a", che non contribuiscono alla polarità di una frase.

import nltk
from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize

# Download stopwords and tokenizer if you haven't already
nltk.download("punkt")
nltk.download("stopwords")

# Example sentence
sentence = "This is an example showing how to remove stop words from a sentence."

# Tokenize the sentence into words
words = word_tokenize(sentence)

# Get the list of stop words in English
stop_words = set(stopwords.words("english"))

# Remove stop words from the sentence
filtered_sentence = [word for word in words if word.lower() not in stop_words]

# Join the words back into a sentence
filtered_sentence = " ".join(filtered_sentence)
print(filtered_sentence)

Output:

example showing remove stop words sentence.

Creare un vocabolario

Un vocabolario è un insieme di parole uniche trovate in un corpus di testo. Costruire un vocabolario implica raccogliere tutte le parole uniche dal corpus e contarne le occorrenze. Questo vocabolario è utile per vari task di NLP come language modeling, word embedding e classificazione del testo.

Il codice seguente crea una semplice distribuzione di frequenza delle parole nel corpus, utile per task NLP di base come costruire un vocabolario o comprendere il contenuto del testo:

  • La variabile corpus contiene alcune frasi di esempio. Nelle applicazioni reali, conterrà dati testuali più ampi e vari.
  • vocab = defaultdict(int) semplifica il conteggio delle frequenze, inizializzando automaticamente ogni nuova parola con conteggio 0, permettendo l'incremento diretto senza controlli.
  • Ogni frase viene tokenizzata convertendola in minuscolo ed estraendo le parole tramite espressioni regolari. Il pattern \b\w+\b identifica parole con soli caratteri alfanumerici, ignorando punteggiatura e altri simboli.
  • Il conteggio di ciascuna parola viene aggiornato nel dizionario vocab.
  • Il vocabolario è ordinato per frequenza in ordine decrescente, così da mostrare in alto le parole più comuni, ed è visualizzato come riferimento.
import re  
# Import the regular expressions module to help with text processing
from collections import (
    defaultdict,
)  

# Import defaultdict to easily handle word frequency counting

# Sample corpus of text - a small dataset of sentences to analyze
corpus = [
    "Tokenization is the process of breaking text into words.",
    "Vocabulary is the collection of unique words.",
    "The process of tokenizing is essential in NLP.",
]

# Initialize a defaultdict with integer values to store word frequencies

# defaultdict(int) initializes each new key with a default integer value of 0
vocab = defaultdict(int)

# Loop through each sentence in the corpus to tokenize and normalize
for sentence in corpus:
    # Convert the sentence to lowercase to ensure consistency in counting (e.g., 'Tokenization' and 'tokenization' are treated as the same word)
    # Use regular expressions to find words composed of alphanumeric characters only
    words = re.findall(r"\b\w+\b", sentence.lower())
    # For each word found, increment its count in the vocab dictionary
    for word in words:
        vocab[word] += 1

# Convert the defaultdict vocab to a regular dictionary for easier handling and sorting

# Sort the dictionary by word frequency in descending order and convert it to a new dictionary
sorted_vocab = dict(sorted(vocab.items(), key=lambda x: x[1], reverse=True))

# Display the sorted vocabulary with each word and its frequency count
print("Vocabulary with Frequencies:", sorted_vocab)

Output:

Vocabulary with Frequencies: {'is': 3, 'the': 3, 'of': 3, 'process': 2, 'words': 2, 'tokenization': 1, 'breaking': 1, 'text': 1, 'into': 1, 'vocabulary': 1, 'collection': 1, 'unique': 1, 'tokenizing': 1, 'essential': 1, 'in': 1, 'nlp': 1}

Costruire manualmente un vocabolario può richiedere tempo, soprattutto per corpora di grandi dimensioni. CountVectorizer di Scikit-learn automatizza questo processo e consente un'elaborazione del testo più flessibile, come vedremo più avanti. 

Implementazione di Bag of Words in Python (da zero)

Iniziamo con una semplice implementazione di Bag of Words da zero in Python. Questo ti aiuterà a capire i mattoni di base e i meccanismi di come funziona sotto il cofano.

Implementazione manuale

Passo 1: Preprocessare i dati testuali

Partiremo definendo una semplice funzione per processare il testo, includendo tokenizzazione, conversione in minuscolo e rimozione della punteggiatura.

from collections import defaultdict
import string

# Sample text data: sentences
corpus = [
    "Python is amazing and fun.",
    "Python is not just fun but also powerful.",
    "Learning Python is fun!",
]
# Function to preprocess text
def preprocess(text):
    # Convert to lowercase
    text = text.lower()
    # Remove punctuation
    text = text.translate(str.maketrans("", "", string.punctuation))
    # Tokenize: split the text into words
    tokens = text.split()
    return tokens

# Apply preprocessing to the sample corpus
processed_corpus = [preprocess(sentence) for sentence in corpus]
print(processed_corpus)

Output:

[['python', 'is', 'amazing', 'and', 'fun'], ['python', 'is', 'not', 'just', 'fun', 'but', 'also', 'powerful'], ['learning', 'python', 'is', 'fun']]

Passo 2: Costruire il vocabolario

Ora dobbiamo scansionare tutti i documenti e costruire un elenco completo di parole uniche, cioè il nostro vocabolario.

# Initialize an empty set for the vocabulary
vocabulary = set()

# Build the vocabulary
for sentence in processed_corpus:
    vocabulary.update(sentence)

# Convert to a sorted list
vocabulary = sorted(list(vocabulary))
print("Vocabulary:", vocabulary)

Passo 3: Calcolare le frequenze e vettorializzare

Calcoleremo ora la frequenza di ciascuna parola nel vocabolario per ogni documento nel corpus processato.

def create_bow_vector(sentence, vocab):
    vector = [0] * len(vocab)  # Initialize a vector of zeros
    for word in sentence:
        if word in vocab:
            idx = vocab.index(word)  # Find the index of the word in the vocabulary
            vector[idx] += 1  # Increment the count at that index
    return vector

A questo punto, avrai creato una rappresentazione Bag of Words per ogni documento del tuo corpus.

# Create BoW vector for each sentence in the processed corpus
bow_vectors = [create_bow_vector(sentence, vocabulary) for sentence in processed_corpus]
print("Bag of Words Vectors:")
for vector in bow_vectors:
    print(vector)

Output:

Bag of Words Vectors:
[0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1]
[1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1]
[0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1]

Usare CountVectorizer di Scikit-learn

Costruire manualmente un modello Bag of Words è utile per imparare, ma per applicazioni in produzione vorrai usare librerie efficienti e ottimizzate come Scikit-learn.

La funzione Python che usiamo per la tokenizzazione è CountVectorizer, importata da sklearn.feature_extraction.text. Una delle feature di CountVectorizer è max_features, che rappresenta il numero massimo di parole che vuoi avere nel modello bag of words. In questo caso usiamo None, cioè verranno usate tutte le feature. 

Dopo aver creato un'istanza di CountVectorizer, usa il metodo .fit_transform() per creare il modello bag of words. Poi usa .toarray() per convertire il modello bag of words in array numpy da passare a un modello di machine learning.

Una volta adattato, CountVectorizer ha costruito un dizionario di indici delle feature. Il valore di indice di una parola nel vocabolario è collegato alla sua frequenza nell'intero corpus di training.

from sklearn.feature_extraction.text import CountVectorizer
# Original corpus
corpus = [
    "Python is amazing and fun.",
    "Python is not just fun but also powerful.",
    "Learning Python is fun!",
]
# Create a CountVectorizer Object
vectorizer = CountVectorizer()
# Fit and transform the corpus
X = vectorizer.fit_transform(corpus)
# Print the generated vocabulary
print("Vocabulary:", vectorizer.get_feature_names_out())
# Print the Bag-of-Words matrix
print("BoW Representation:")
print(X.toarray())

Output:

markdownVocabulary: ['also' 'amazing' 'and' 'but' 'fun' 'is' 'just' 'learning' 'not'

 'powerful' 'python']

BoW Representation:

[[0 1 1 0 1 1 0 0 0 0 1]

 [1 0 0 1 1 1 1 0 1 1 1]

 [0 0 0 0 1 1 0 1 0 0 1]]

Esempio: applicare Bag of Words

Applichiamo ora il modello BoW a un piccolo corpus di testo composto da tre recensioni di film per illustrare l'intero processo. 

Useremo CountVectorizer di Scikit-learn per applicare il modello BoW a questo piccolo corpus.

Ecco i passaggi che seguiremo:

  • CountVectorizer tokenizza il testo, rimuove la punteggiatura e converte automaticamente le parole in minuscolo.
  • .fit_transform(corpus) converte il corpus in una matrice documento-termine, dove ogni riga rappresenta un documento e ogni colonna rappresenta una parola del vocabolario.
  • X_dense è la matrice densa che rappresenta la frequenza di ciascuna parola in ogni documento.
from sklearn.feature_extraction.text import CountVectorizer
# Sample corpus of movie reviews
corpus = [
    "I loved the movie, it was fantastic!",
    "The movie was okay, but not great.",
    "I hated the movie, it was terrible.",
]

# Initialize the CountVectorizer
vectorizer = CountVectorizer()

# Fit and transform the corpus to a document-term matrix
X = vectorizer.fit_transform(corpus)

# Convert the document-term matrix into a dense format (optional for visualization)
X_dense = X.toarray()

# Get the vocabulary (mapping of words to index positions)
vocab = vectorizer.get_feature_names_out()

# Print the vocabulary and document-term matrix
print("Vocabulary:", vocab)
print("Document-Term Matrix:\n", X_dense)

Output:

Vocabulary: ['but' 'fantastic' 'great' 'hated' 'it' 'loved' 'movie' 'not' 'okay' 'terrible' 'the' 'was']
Document-Term Matrix:
 [[0 1 0 0 1 1 1 0 0 0 1 1]  # First review: "I loved the movie, it was fantastic!"
  [1 0 1 0 1 0 1 1 1 0 1 1]  # Second review: "The movie was okay, but not great."
  [0 0 0 1 1 0 1 0 0 1 1 1]] # Third review: "I hated the movie, it was terrible."

Ecco come possiamo interpretare l'output sopra:

  • A ogni parola unica nel corpus è assegnato un indice, e le parole sono ordinate alfabeticamente. Ad esempio, "but" è all'indice 0, "fantastic" all'indice 1, "movie" all'indice 6 e così via.
  •  Ogni riga nella matrice dei documenti rappresenta una recensione, e ogni colonna corrisponde a una parola del vocabolario. I valori nella matrice rappresentano la frequenza di ciascuna parola in quel particolare documento.
    • Prima recensione: [0 1 0 0 1 1 1 0 0 0 1 1] indica che:
      • La parola "fantastic" appare una volta (1 all'indice 1),
      • La parola "loved" appare una volta (1 all'indice 5),
      • La parola "movie" appare una volta (1 all'indice 6),
      • La parola "it" appare una volta (1 all'indice 4),
      • e così via.

Il vettore BoW può essere interpretato così:

  • Ogni documento è un vettore di numeri che rappresentano i conteggi delle parole. Le dimensioni del vettore sono pari alla dimensione del vocabolario. In questo caso il vocabolario ha 12 parole, quindi ogni recensione è trasformata in un vettore a 12 dimensioni.
  • La maggior parte delle parole in ogni riga sono zeri perché non ogni documento contiene tutte le parole del vocabolario. Per questo i modelli BoW sono spesso sparsi, cioè con molti zeri.

Vantaggi e limiti di Bag of Words

Vediamo ora alcuni vantaggi e limiti del modello Bag of Words.

Vantaggi

  1. Semplice da implementare e interpretare: il modello Bag of Words è una delle tecniche di rappresentazione del testo più immediate, ideale per chi inizia. La sua semplicità consente un'implementazione rapida senza bisogno di preprocessing complesso o modelli specializzati.
  2. Facile da usare per compiti di classificazione del testo: Bag of Words si presta bene a task di base come classificazione del testo, analisi del sentiment e rilevamento dello spam. Questi compiti spesso non richiedono modelli linguistici sofisticati, quindi una rappresentazione BoW è sufficiente ed efficiente.

Limiti

  1. La dimensione del vocabolario influisce sulla sparsità delle rappresentazioni: più grande è il vocabolario, più la rappresentazione diventa sparsa e ad alta dimensionalità. Questa sparsità può rendere più difficile l'apprendimento dei modelli e richiede un'attenta regolazione della dimensione del vocabolario per evitare costi computazionali eccessivi.
  2. Produce matrici sparse costose da elaborare: poiché ogni documento è rappresentato dalla frequenza di ciascuna parola in un vocabolario potenzialmente grande, le matrici risultanti sono spesso per lo più zeri, il che può essere inefficiente da archiviare ed elaborare nelle pipeline di machine learning. Le matrici sparse consumano memoria significativa e spesso richiedono strumenti e librerie specializzati per archiviazione ed elaborazione efficienti, specialmente con dataset di grandi dimensioni.
  3. Perde significato e contesto: BOW ignora l'ordine delle parole e la struttura della frase, con conseguente perdita di relazioni grammaticali e significato. Questo limite lo rende meno adatto per compiti in cui contesto, sfumature e ordine delle parole contano, come la traduzione o il rilevamento del sentiment in frasi complesse.

Le seguenti strategie possono essere usate per ridurre la dimensione del vocabolario in Bag of Words:

  • Ignorare le maiuscole/minuscole. 
  • Rimuovere la punteggiatura.
  • Rimuovere le stopword, cioè parole comuni come the e a. 
  • Assicurarsi che tutte le parole siano scritte correttamente. 
  • Usare tecniche di stemming per ridurre le parole alla loro radice.

Prossimi passi: oltre Bag of Words

Un limite del modello Bag of Words è che tratta tutte le parole allo stesso modo. Purtroppo, questo può portare a dare più importanza ad alcune parole solo perché compaiono spesso. 

TF-IDF (Term Frequency-Inverse Document Frequency) è una soluzione a questo problema, perché aggiusta il peso delle parole in base a quanto frequentemente compaiono in tutti i documenti.

TF-IDF: un'estensione di Bag of Words

La Term Frequency (TF) rappresenta la frequenza di un termine in un documento. L'Inverse Document Frequency (IDF) riduce l'impatto delle parole comunemente presenti in più documenti. Il punteggio TF-IDF si calcola moltiplicando le due metriche. 

Considera un documento contenente 200 parole, in cui la parola love appare 5 volte. La TF per love è quindi (5 / 200) = 0,025. Supponendo di avere un milione di documenti e che la parola love compaia in mille di questi, l'Inverse Document Frequency (cioè IDF) si calcola come log(1000000 / 1000) = 3. Il peso TF-IDF è il prodotto di queste quantità: 0,025 * 3 = 0,075.

In Scikit-learn, questo è relativamente semplice da calcolare usando la classe TfidfVectorizer.

from sklearn.feature_extraction.text import TfidfVectorizer
# Sample corpus
corpus = [
    "Python is amazing and fun.",
    "Python is not just fun but also powerful.",
    "Learning Python is fun!",
]

# Create the Tf-idf vectorizer
tfidf_vectorizer = TfidfVectorizer()

# Fit and transform the corpus
X_tfidf = tfidf_vectorizer.fit_transform(corpus)

# Show the Vocabulary
print("Vocabulary:", tfidf_vectorizer.get_feature_names_out())

# Show the TF-IDF Matrix
print("TF-IDF Representation:")
print(X_tfidf.toarray())

Output:

Vocabulary: ['also' 'amazing' 'and' 'but' 'fun' 'is' 'just' 'learning' 'not'
 'powerful' 'python']
TF-IDF Representation:
[[0.         0.57292883 0.57292883 0.         0.338381   0.338381
  0.         0.         0.         0.         0.338381  ]
 [0.40667606 0.         0.         0.40667606 0.24018943 0.24018943
  0.40667606 0.         0.40667606 0.40667606 0.24018943]
 [0.         0.         0.         0.         0.41285857 0.41285857
  0.         0.69903033 0.         0.         0.41285857]]

La matrice TF-IDF implementata sopra ti fornisce una misura pesata invece delle frequenze grezze.

Sebbene il modello Bag of Words abbia dei limiti, specialmente per dataset più grandi e complessi, resta comunque un elemento fondamentale in molte applicazioni NLP. Comprenderlo ti sarà utile quando esplorerai modelli più avanzati come i word embedding e i Transformer.

Da qui, puoi sperimentare BoW nei tuoi progetti, tra cui rilevamento dello spam, analisi del sentiment, clustering di documenti e altro.

Se vuoi andare oltre Bag of Words, puoi esplorare metodi come Word2Vec e GloVe, o modelli di deep learning come BERT.

Considerazioni finali

La tecnica Bag of Words è una tecnica fondamentale usata nel Natural Language Processing. È un modo semplice ma efficace per convertire testo non strutturato in feature numeriche utilizzabili dagli algoritmi di machine learning. In questo tutorial abbiamo visto:

  • Che cos'è il modello Bag of Words (BoW)?
  • I benefici del modello Bag of Words nella costruzione di modelli di machine learning.
  • Come implementare il modello Bag of Words in Python.
  •  Vantaggi e limiti di Bag of Words.
  • La teoria e la motivazione alla base del modello Bag of Words.
  • L'introduzione di TF-IDF come miglioramento dell'approccio BoW tradizionale.

Dai un'occhiata alla nostra skill track Natural Language Processing in Python per approfondire l'elaborazione del linguaggio naturale. 

Derrick Mwiti

Autore

Derrick Mwiti

Argomenti

Correlato