Tutorial NLP per la classificazione del testo in Python
I dati non strutturati sotto forma di testo: chat, e-mail, social media, risposte a sondaggi sono presenti ovunque oggi. Il testo può essere una ricca fonte di informazioni, ma a causa della sua natura non strutturata può essere difficile estrarre informazioni da esso.
La classificazione del testo è uno dei compiti più importanti dell’apprendimento automatico supervisionato (ML). Si tratta di un processo di assegnazione di tag/categorie ai documenti che ci aiuta a strutturare e analizzare il testo in modo automatico e rapido e conveniente. È uno dei compiti fondamentali dell’elaborazione del linguaggio naturale, con ampie applicazioni come l’analisi dei sentimenti, l’individuazione dello spam, l’etichettatura dei temi, l’individuazione delle intenzioni, ecc.
In questo articolo, vorrei illustrarvi passo per passo come effettuare la classificazione del testo utilizzando Python. Ho caricato il codice completo su GitHub: https://github.com/vijayaiitk/NLP-text-classification-model
Dividiamo il problema della classificazione nei seguenti passi:
- Impostazione: Importazione delle librerie
- Caricamento del set di dati e analisi esplorativa dei dati
- Pre-elaborazione del testo
- Estrazione di vettori dal testo (vettorizzazione)
- Esecuzione di algoritmi di ML
- Conclusione
Passo 1: Importare le librerie
Il primo passo consiste nell’importare il seguente elenco di librerie:
import pandas as pd
import numpy as np#for text pre-processing
import re, string
import nltk
from nltk.tokenize import word_tokenize
from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize
from nltk.stem import SnowballStemmer
from nltk.corpus import wordnet
from nltk.stem import WordNetLemmatizernltk.download('punkt')
nltk.download('averaged_perceptron_tagger')
nltk.download('wordnet')#for model-building
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.naive_bayes import MultinomialNB
from sklearn.metrics import classification_report, f1_score, accuracy_score, confusion_matrix
from sklearn.metrics import roc_curve, auc, roc_auc_score# bag of words
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.feature_extraction.text import CountVectorizer#for word embedding
import gensim
from gensim.models import Word2Vec
Passo 2: Caricamento del set di dati e EDA
Il set di dati che utilizzeremo per questo articolo è il famoso set di dati “Natural Language Processing with Disaster Tweets”, in cui dovremo prevedere se un determinato tweet riguarda un vero disastro (target=1) o meno (target=0).
In questa gara, dovrete costruire un modello di apprendimento automatico che preveda quali tweet parlano di disastri reali e quali no. Avrete accesso a un set di dati di 10.000 tweet classificati a mano.
Caricamento del set di dati in Kaggle Notebook:
Abbiamo 7.613 tweet nel dataset di allenamento (etichettato) e 3.263 nel dataset di test (non etichettato). Ecco un’istantanea del dataset di addestramento/etichettato che utilizzeremo per costruire il nostro modello
df_train= pd.read_csv('../input/nlp-getting-started/train.csv')
df_test=pd.read_csv('../input/nlp-getting-started/test.csv')
Analisi esplorativa dei dati (EDA)
1. Distribuzione delle classi: Ci sono più tweet di classe 0 (nessun disastro) che di classe 1 (tweet di disastro). Possiamo dire che il dataset è relativamente bilanciato con 4342 tweet non catastrofici (57%) e 3271 tweet catastrofici (43%). Poiché i dati sono bilanciati, non applicheremo tecniche di bilanciamento dei dati come SMOTE durante la costruzione del modello.
2. Valori mancanti: Abbiamo ~2,5k valori mancanti nel campo della località e 61 valori mancanti nella colonna delle parole chiave.df_train.isna().sum()
3. Numero di parole in un tweet: I tweet relativi alle catastrofi sono più prolissi di quelli non relativi alle catastrofi.
Il numero medio di parole in un tweet di una catastrofe è di 15,17 rispetto a una media di 14,7 parole in un tweet non di una catastrofe.
4. Numero di caratteri in un tweet: I tweet relativi alle catastrofi sono più lunghi di quelli non relativi alle catastrofi.
La media dei caratteri di un tweet in caso di catastrofe è di 108,1 caratteri rispetto alla media di 95,7 caratteri di un tweet senza catastrofe.
x=df_train['target'].value_counts()
print(x)
sns.barplot(x.index,x)
# WORD-COUNT df_train['word_count'] = df_train['text'].apply(lambda x: len(str(x).split())) print(df_train[df_train['target']==1]['word_count'].mean()) #Disaster tweets print(df_train[df_train['target']==0]['word_count'].mean()) #Non-Disaster tweets
# PLOTTING WORD-COUNT fig,(ax1,ax2)=plt.subplots(1,2,figsize=(10,4)) train_words=df_train[df_train['target']==1]['word_count'] ax1.hist(train_words,color='red') ax1.set_title('Disaster tweets') train_words=df_train[df_train['target']==0]['word_count'] ax2.hist(train_words,color='green') ax2.set_title('Non-disaster tweets') fig.suptitle('Words per tweet') plt.show()
# CHARACTER-COUNT
df_train['char_count'] = df_train['text'].apply(lambda x: len(str(x)))
print(df_train[df_train['target']==1]['char_count'].mean()) #Disaster tweets
print(df_train[df_train['target']==0]['char_count'].mean()) #Non-Disaster tweets
Fase 3: Pre-elaborazione del testo
Prima di passare alla costruzione del modello, dobbiamo preprocessare il nostro set di dati rimuovendo punteggiature e caratteri speciali, pulendo i testi, rimuovendo le stop words e applicando la lemmatizzazione.
Semplici processi di pulizia del testo: Alcuni dei più comuni processi di pulizia del testo prevedono:
– rimozione di punteggiatura, caratteri speciali, URL e hashtag
– Rimozione di spazi bianchi e tabulati in testa e in coda.
– Correzione degli errori di battitura e degli slang, scrittura delle abbreviazioni nella loro forma lunga.
Rimozione delle stop-word: Con nltk possiamo rimuovere dal vocabolario inglese un elenco di stop words generiche. Alcune di queste parole sono ‘i’, ‘you’, ‘a’, ‘the’, ‘he’, ‘which’ ecc.
Stemmatizzazione: Si riferisce al processo di taglio della fine o dell’inizio delle parole con l’intento di rimuovere gli affissi (prefisso/suffisso).
Lemmatizzazione: È il processo di riduzione della parola alla sua forma base.
Ecco il codice per la pre-elaborazione del testo:
#convert to lowercase, strip and remove punctuations
def preprocess(text):
text = text.lower()
text=text.strip()
text=re.compile('<.*?>').sub('', text)
text = re.compile('[%s]' % re.escape(string.punctuation)).sub(' ', text)
text = re.sub('\s+', ' ', text)
text = re.sub(r'\[[0-9]*\]',' ',text)
text=re.sub(r'[^\w\s]', '', str(text).lower().strip())
text = re.sub(r'\d',' ',text)
text = re.sub(r'\s+',' ',text)
return text
# STOPWORD REMOVAL
def stopword(string):
a= [i for i in string.split() if i not in stopwords.words('english')]
return ' '.join(a)#LEMMATIZATION
# Initialize the lemmatizer
wl = WordNetLemmatizer()
# This is a helper function to map NTLK position tags
def get_wordnet_pos(tag):
if tag.startswith('J'):
return wordnet.ADJ
elif tag.startswith('V'):
return wordnet.VERB
elif tag.startswith('N'):
return wordnet.NOUN
elif tag.startswith('R'):
return wordnet.ADV
else:
return wordnet.NOUN# Tokenize the sentence
def lemmatizer(string):
word_pos_tags = nltk.pos_tag(word_tokenize(string)) # Get position tags
a=[wl.lemmatize(tag[0], get_wordnet_pos(tag[1])) for idx, tag in enumerate(word_pos_tags)] # Map the position tag and lemmatize the word/token
return " ".join(a)
Pre-elaborazione finale
def finalpreprocess(string):
return lemmatizer(stopword(preprocess(string)))df_train['clean_text'] = df_train['text'].apply(lambda x: finalpreprocess(x))
df_train.head()
Fase 4: Estrazione di vettori dal testo (vettorizzazione)
È difficile lavorare con i dati di testo quando si costruiscono modelli di apprendimento automatico, poiché questi modelli hanno bisogno di dati numerici ben definiti. Il processo di conversione dei dati testuali in dati numerici/vettoriali è chiamato vettorizzazione o, nel mondo della PNL, word embedding. Bag-of-Words (BoW) e Word Embedding (con Word2Vec) sono due metodi ben noti per convertire i dati di testo in dati numerici.
Esistono diverse versioni di Bag of Words, che corrispondono a diversi metodi di scoring delle parole. Utilizziamo la libreria Sklearn per calcolare i valori numerici di BoW utilizzando questi approcci:
1. Vettori di conteggio: Costruisce un vocabolario a partire da un corpus di documenti e conta quante volte le parole appaiono in ogni documento.
2. Frequenza dei termini – Frequenze inverse dei documenti (tf-Idf): I vettori di conteggio potrebbero non essere la rappresentazione migliore per convertire i dati di testo in dati numerici. Quindi, invece del semplice conteggio, possiamo usare una variante avanzata del Bag-of-Words che utilizza la frequenza di termine inversa alla frequenza del documento (o Tf-Idf). In pratica, il valore di una parola aumenta proporzionalmente al conteggio nel documento, ma è inversamente proporzionale alla frequenza della parola nel corpus.
Word2Vec: Uno dei principali svantaggi dell’uso delle tecniche Bag-of-words è che non è in grado di catturare il significato o la relazione delle parole dai vettori. Word2Vec è una delle tecniche più diffuse per apprendere gli incorporamenti di parole utilizzando una rete neurale superficiale che è in grado di catturare il contesto di una parola in un documento, la somiglianza semantica e sintattica, la relazione con altre parole, ecc.
Possiamo utilizzare uno qualsiasi di questi approcci per convertire i nostri dati testuali in forma numerica che verrà utilizzata per costruire il modello di classificazione. Tenendo conto di ciò, per prima cosa suddividerò il set di dati in un set di allenamento (80%) e in un set di test (20%) utilizzando il codice seguente
#SPLITTING THE TRAINING DATASET INTO TRAIN AND TEST
X_train, X_test, y_train, y_test = train_test_split(df_train["clean_text"],df_train["target"],test_size=0.2,shuffle=True)#Word2Vec
# Word2Vec runs on tokenized sentences
X_train_tok= [nltk.word_tokenize(i) for i in X_train]
X_test_tok= [nltk.word_tokenize(i) for i in X_test]
Ecco il codice per la vettorizzazione utilizzando Bag-of-Words (con Tf-Idf ) e Word2Vec
#Tf-Idf
tfidf_vectorizer = TfidfVectorizer(use_idf=True)
X_train_vectors_tfidf = tfidf_vectorizer.fit_transform(X_train)
X_test_vectors_tfidf = tfidf_vectorizer.transform(X_test)#building Word2Vec model
class MeanEmbeddingVectorizer(object):
def __init__(self, word2vec):
self.word2vec = word2vec
# if a text is empty we should return a vector of zeros
# with the same dimensionality as all the other vectors
self.dim = len(next(iter(word2vec.values())))def fit(self, X, y):
return selfdef transform(self, X):
return np.array([
np.mean([self.word2vec[w] for w in words if w in self.word2vec]
or [np.zeros(self.dim)], axis=0)
for words in X
])w2v = dict(zip(model.wv.index2word, model.wv.syn0)) df['clean_text_tok']=[nltk.word_tokenize(i) for i in df['clean_text']]
model = Word2Vec(df['clean_text_tok'],min_count=1)
modelw = MeanEmbeddingVectorizer(w2v)# converting text to numerical data using Word2Vec
X_train_vectors_w2v = modelw.transform(X_train_tok)
X_val_vectors_w2v = modelw.transform(X_test_tok)
Fase 5. Esecuzione degli algoritmi di ML
È il momento di addestrare un modello di apprendimento automatico sul set di dati vettoriali e di testarlo. Ora che abbiamo convertito i dati testuali in dati numerici, possiamo eseguire modelli di ML su X_train_vector_tfidf e y_train. Testiamo questo modello su X_test_vectors_tfidf per ottenere y_predict e valutare ulteriormente le prestazioni del modello.
1. Regressione logistica: Inizieremo con il modello più semplice, la Regressione logistica. È possibile costruire facilmente una Regressione logistica in scikit utilizzando le seguenti righe di codice
2. Naive Bayes: è un classificatore probabilistico che fa uso del Teorema di Bayes, una regola che utilizza la probabilità per fare previsioni basate sulla conoscenza preventiva di condizioni che potrebbero essere collegate
È ora possibile selezionare il modello migliore (lr_tfidf nel nostro caso) per stimare i valori “target” per il set di dati non etichettati (df_test). Ecco il codice per farlo
Predicted target value for unlabelled dataset
#FITTING THE CLASSIFICATION MODEL using Logistic Regression(tf-idf)lr_tfidf=LogisticRegression(solver = 'liblinear', C=10, penalty = 'l2')
lr_tfidf.fit(X_train_vectors_tfidf, y_train) #Predict y value for test dataset
y_predict = lr_tfidf.predict(X_test_vectors_tfidf)
y_prob = lr_tfidf.predict_proba(X_test_vectors_tfidf)[:,1]print(classification_report(y_test,y_predict))
print('Confusion Matrix:',confusion_matrix(y_test, y_predict))
fpr, tpr, thresholds = roc_curve(y_test, y_prob)
roc_auc = auc(fpr, tpr)
print('AUC:', roc_auc)
#FITTING THE CLASSIFICATION MODEL using Logistic Regression (W2v)
lr_w2v=LogisticRegression(solver = 'liblinear', C=10, penalty = 'l2')
lr_w2v.fit(X_train_vectors_w2v, y_train) #model#Predict y value for test dataset
y_predict = lr_w2v.predict(X_test_vectors_w2v)
y_prob = lr_w2v.predict_proba(X_test_vectors_w2v)[:,1]print(classification_report(y_test,y_predict))
print('Confusion Matrix:',confusion_matrix(y_test, y_predict))
fpr, tpr, thresholds = roc_curve(y_test, y_prob)
roc_auc = auc(fpr, tpr)
print('AUC:', roc_auc)
#FITTING THE CLASSIFICATION MODEL using Naive Bayes(tf-idf)nb_tfidf = MultinomialNB()
nb_tfidf.fit(X_train_vectors_tfidf, y_train) #Predict y value for test dataset
y_predict = nb_tfidf.predict(X_test_vectors_tfidf)
y_prob = nb_tfidf.predict_proba(X_test_vectors_tfidf)[:,1]print(classification_report(y_test,y_predict))
print('Confusion Matrix:',confusion_matrix(y_test, y_predict))
fpr, tpr, thresholds = roc_curve(y_test, y_prob)
roc_auc = auc(fpr, tpr)
print('AUC:', roc_auc)
#Pre-processing the new dataset
df_test['clean_text'] = df_test['text'].apply(lambda x: finalpreprocess(x)) #preprocess the data
X_test=df_test['clean_text'] #converting words to numerical data using tf-idf
X_vector=tfidf_vectorizer.transform(X_test)#use the best model to predict 'target' value for the new dataset
y_predict = lr_tfidf.predict(X_vector)
y_prob = lr_tfidf.predict_proba(X_vector)[:,1]
df_test['predict_prob']= y_prob
df_test['target']= y_predict
final=df_test[['clean_text','target']].reset_index(drop=True)
print(final.head())
Conclusione
In questo articolo ho mostrato le basi della costruzione di un modello di classificazione del testo confrontando Bag-of-Words (con Tf-Idf) e Word Embedding con Word2Vec. È possibile migliorare ulteriormente le prestazioni del modello utilizzando questo codice
– utilizzando altri algoritmi di classificazione come Support Vector Machines (SVM), XgBoost, modelli Ensemble, reti neurali, ecc.
– utilizzando Gridsearch per mettere a punto gli iperparametri del modello
– utilizzando metodi avanzati di word-embedding come GloVe e BERT.
Se questo articolo vi è piaciuto, mettete un like, commentate e condividete. Il vostro feedback è ben accetto!
Articolo originale di Vijaya Rani
https://medium.com/analytics-vidhya/nlp-tutorial-for-text-classification-in-python-8f19cd17b49e