Come sviluppare un modello CNN multicanale per la classificazione del testo
Un modello standard di deep learning per la classificazione del testo e l'analisi del sentiment utilizza un livello di incorporamento delle parole e una rete neurale convoluzionale unidimensionale.
Il modello può essere espanso utilizzando più reti neurali convoluzionali parallele che leggono il documento sorgente utilizzando dimensioni del kernel diverse. Questo, in effetti, crea una rete neurale convoluzionale multicanale per il testo che legge testo con diverse dimensioni di n grammi (gruppi di parole).
In questo tutorial scoprirai come sviluppare una rete neurale convoluzionale multicanale per la previsione del sentiment sui dati di recensioni di film di testo.
Dopo aver completato questo tutorial, saprai:
- Come preparare i dati di testo della recensione del film per la modellazione.
- Come sviluppare una rete neurale convoluzionale multicanale per il testo in Keras.
- Come valutare un modello di adattamento sui dati di recensioni di film mai visti.
Avvia il tuo progetto con il mio nuovo libro Deep Learning for Natural Language Processing, che include tutorial passo passo e i file codice sorgente Python per tutti gli esempi.
Cominciamo.
- Aggiornamento febbraio 2018: piccola modifica al codice per riflettere le modifiche nell'API Keras 2.1.3.
- Aggiornamento agosto 2020: collegamento aggiornato al set di dati delle recensioni di film.
Panoramica dell'esercitazione
Questo tutorial è diviso in 4 parti; sono:
- Set di dati di revisione del film
- Preparazione dei dati
- Sviluppare un modello multicanale
- Valutare il modello
Ambiente Python
Questo tutorial presuppone che tu abbia installato un ambiente SciPy Python 3.
È necessario avere Keras (2.0 o versione successiva) installato con il backend TensorFlow o Theano.
Il tutorial presuppone inoltre che tu abbia installato scikit-learn, Pandas, NumPy e Matplotlib.
Se hai bisogno di aiuto con il tuo ambiente, guarda questo post:
- Come configurare un ambiente Python per il machine learning e il deep learning con Anaconda
Set di dati di revisione del film
The Movie Review Data è una raccolta di recensioni di film recuperate dal sito imdb.com all'inizio degli anni 2000 da Bo Pang e Lillian Lee. Le recensioni sono state raccolte e rese disponibili come parte della loro ricerca sull'elaborazione del linguaggio naturale.
Le recensioni sono state originariamente pubblicate nel 2002, ma nel 2004 è stata rilasciata una versione aggiornata e ripulita, denominata “v2.0”.
Il set di dati comprende 1.000 recensioni di film positive e 1.000 negative tratte da un archivio del newsgroup rec.arts.movies.reviews ospitato su imdb.com. Gli autori si riferiscono a questo set di dati come “set di dati sulla polarità”. "
I nostri dati contengono 1.000 recensioni positive e 1.000 negative, tutte scritte prima del 2002, con un limite massimo di 20 recensioni per autore (312 autori in totale) per categoria. Ci riferiamo a questo corpus come set di dati sulla polarità.
- Un'educazione sentimentale: analisi del sentimento utilizzando il riepilogo della soggettività basato su tagli minimi, 2004.
I dati sono stati in qualche modo ripuliti; Per esempio:
- Il set di dati comprende solo recensioni in inglese.
- Tutto il testo è stato convertito in minuscolo.
- C'è uno spazio bianco attorno alla punteggiatura come punti, virgole e parentesi.
- Il testo è stato suddiviso in una frase per riga.
I dati sono stati utilizzati per alcune attività correlate di elaborazione del linguaggio naturale. Per la classificazione, le prestazioni dei modelli di machine learning (come Support Vector Machines) sui dati sono comprese tra il 70% alto e l'80% basso (ad esempio 78%-82%).
Una preparazione dei dati più sofisticata può portare a risultati fino all'86% con una convalida incrociata di 10 volte. Questo ci dà un valore medio-basso degli anni '80 se volessimo utilizzare questo set di dati in esperimenti con metodi moderni.
… a seconda della scelta del classificatore di polarità a valle, possiamo ottenere un miglioramento altamente statisticamente significativo (da 82,8% a 86,4%)
- Un'educazione sentimentale: analisi del sentimento utilizzando il riepilogo della soggettività basato su tagli minimi, 2004.
Puoi scaricare il set di dati da qui:
- Set di dati sulla polarità della recensione del film (review_polarity.tar.gz, 3 MB)
Dopo aver decompresso il file, avrai una directory chiamata “txt_sentoken” con due sottodirectory contenenti il testo “neg” e “pos” per recensioni negative e positive. Le recensioni vengono archiviate una per file con una convenzione di denominazione da cv000 a cv999 per ogni neg e pos.
Successivamente, esaminiamo il caricamento e la preparazione dei dati di testo.
Preparazione dei dati
In questa sezione, esamineremo 3 cose:
- Separazione dei dati in set di training e test.
- Caricamento e pulizia dei dati per rimuovere segni di punteggiatura e numeri.
- Preparare tutte le recensioni e salvarle su file.
Suddiviso in set di training e test
Facciamo finta di sviluppare un sistema in grado di prevedere il sentimento di una recensione testuale di un film come positivo o negativo.
Ciò significa che una volta sviluppato il modello, dovremo fare previsioni sulle nuove revisioni testuali. Ciò richiederà che su queste nuove revisioni venga eseguita la stessa preparazione dei dati eseguita sui dati di training per il modello.
Faremo in modo che questo vincolo sia integrato nella valutazione dei nostri modelli suddividendo i set di dati di training e test prima di qualsiasi preparazione dei dati. Ciò significa che qualsiasi conoscenza nei dati del set di test che potrebbe aiutarci a preparare meglio i dati (ad esempio le parole utilizzate) non è disponibile nella preparazione dei dati utilizzati per l'addestramento del modello.
Detto questo, utilizzeremo le ultime 100 recensioni positive e le ultime 100 recensioni negative come set di test (100 recensioni) e le restanti 1.800 recensioni come set di dati di addestramento.
Questo è un treno al 90%, suddiviso per il 10% dei dati.
La suddivisione può essere imposta facilmente utilizzando i nomi file delle revisioni, dove le revisioni denominate da 000 a 899 sono per i dati di allenamento e le revisioni denominate da 900 in poi sono per i test.
Caricamento e pulizia delle recensioni
I dati di testo sono già abbastanza puliti; non è necessaria molta preparazione.
Senza impantanarci troppo nei dettagli, prepareremo i dati nel modo seguente:
- Gettoni divisi sullo spazio bianco.
- Rimuovi tutta la punteggiatura dalle parole.
- Rimuovi tutte le parole che non sono composte esclusivamente da caratteri alfabetici.
- Rimuovi tutte le parole che sono note come stop word.
- Rimuovi tutte le parole che hanno una lunghezza <= 1 carattere.
Possiamo inserire tutti questi passaggi in una funzione chiamata clean_doc() che prende come argomento il testo grezzo caricato da un file e restituisce un elenco di token puliti. Possiamo anche definire una funzione load_doc() che carica un documento da file pronto per l'uso con la funzione clean_doc(). Di seguito è riportato un esempio di pulizia della prima recensione positiva.
from nltk.corpus import stopwords
import string
# load doc into memory
def load_doc(filename):
# open the file as read only
file = open(filename, 'r')
# read all text
text = file.read()
# close the file
file.close()
return text
# turn a doc into clean tokens
def clean_doc(doc):
# split into tokens by white space
tokens = doc.split()
# remove punctuation from each token
table = str.maketrans('', '', string.punctuation)
tokens = [w.translate(table) for w in tokens]
# remove remaining tokens that are not alphabetic
tokens = [word for word in tokens if word.isalpha()]
# filter out stop words
stop_words = set(stopwords.words('english'))
tokens = [w for w in tokens if not w in stop_words]
# filter out short tokens
tokens = [word for word in tokens if len(word) > 1]
return tokens
# load the document
filename = 'txt_sentoken/pos/cv000_29590.txt'
text = load_doc(filename)
tokens = clean_doc(text)
print(tokens)
L'esecuzione dell'esempio carica e pulisce una recensione di film.
I token della revisione pulita vengono stampati per la revisione.
...
'creepy', 'place', 'even', 'acting', 'hell', 'solid', 'dreamy', 'depp', 'turning', 'typically', 'strong', 'performance', 'deftly', 'handling', 'british', 'accent', 'ians', 'holm', 'joe', 'goulds', 'secret', 'richardson', 'dalmatians', 'log', 'great', 'supporting', 'roles', 'big', 'surprise', 'graham', 'cringed', 'first', 'time', 'opened', 'mouth', 'imagining', 'attempt', 'irish', 'accent', 'actually', 'wasnt', 'half', 'bad', 'film', 'however', 'good', 'strong', 'violencegore', 'sexuality', 'language', 'drug', 'content']
Pulisci tutte le recensioni e salva
Ora possiamo utilizzare la funzione per pulire le recensioni e applicarla a tutte le recensioni.
Per fare ciò, svilupperemo una nuova funzione denominata process_docs() di seguito che esaminerà tutte le recensioni in una directory, le pulirà e le restituirà come elenco.
Aggiungeremo anche un argomento alla funzione per indicare se la funzione sta elaborando revisioni di treni o test, in questo modo i nomi dei file possono essere filtrati (come descritto sopra) e solo le revisioni di treni o test richieste verranno pulite e restituite.
La funzione completa è elencata di seguito.
# load all docs in a directory
def process_docs(directory, is_trian):
documents = list()
# walk through all files in the folder
for filename in listdir(directory):
# skip any reviews in the test set
if is_trian and filename.startswith('cv9'):
continue
if not is_trian and not filename.startswith('cv9'):
continue
# create the full path of the file to open
path = directory + '/' + filename
# load the doc
doc = load_doc(path)
# clean doc
tokens = clean_doc(doc)
# add to list
documents.append(tokens)
return documents
Possiamo chiamare questa funzione con revisioni negative della formazione come segue:
negative_docs = process_docs('txt_sentoken/neg', True)
Successivamente, abbiamo bisogno delle etichette per i documenti di formazione e prova. Sappiamo che disponiamo di 900 documenti di formazione e 100 documenti di test. Possiamo utilizzare una comprensione dell'elenco Python per creare le etichette per le revisioni negative (0) e positive (1) sia per i set di training che per quelli di test.
trainy = [0 for _ in range(900)] + [1 for _ in range(900)]
testY = [0 for _ in range(100)] + [1 for _ in range(100)]
Infine, vogliamo salvare il treno preparato e i set di test su un file in modo da poterli caricare successivamente per la modellazione e la valutazione del modello.
La funzione denominata save_dataset() di seguito salverà un dato set di dati preparato (elementi X e y) in un file utilizzando l'API pickle.
# save a dataset to file
def save_dataset(dataset, filename):
dump(dataset, open(filename, 'wb'))
print('Saved: %s' % filename)
Esempio completo
Possiamo collegare insieme tutte queste fasi di preparazione dei dati.
L'esempio completo è elencato di seguito.
from string import punctuation
from os import listdir
from nltk.corpus import stopwords
from pickle import dump
# load doc into memory
def load_doc(filename):
# open the file as read only
file = open(filename, 'r')
# read all text
text = file.read()
# close the file
file.close()
return text
# turn a doc into clean tokens
def clean_doc(doc):
# split into tokens by white space
tokens = doc.split()
# remove punctuation from each token
table = str.maketrans('', '', punctuation)
tokens = [w.translate(table) for w in tokens]
# remove remaining tokens that are not alphabetic
tokens = [word for word in tokens if word.isalpha()]
# filter out stop words
stop_words = set(stopwords.words('english'))
tokens = [w for w in tokens if not w in stop_words]
# filter out short tokens
tokens = [word for word in tokens if len(word) > 1]
tokens = ' '.join(tokens)
return tokens
# load all docs in a directory
def process_docs(directory, is_trian):
documents = list()
# walk through all files in the folder
for filename in listdir(directory):
# skip any reviews in the test set
if is_trian and filename.startswith('cv9'):
continue
if not is_trian and not filename.startswith('cv9'):
continue
# create the full path of the file to open
path = directory + '/' + filename
# load the doc
doc = load_doc(path)
# clean doc
tokens = clean_doc(doc)
# add to list
documents.append(tokens)
return documents
# save a dataset to file
def save_dataset(dataset, filename):
dump(dataset, open(filename, 'wb'))
print('Saved: %s' % filename)
# load all training reviews
negative_docs = process_docs('txt_sentoken/neg', True)
positive_docs = process_docs('txt_sentoken/pos', True)
trainX = negative_docs + positive_docs
trainy = [0 for _ in range(900)] + [1 for _ in range(900)]
save_dataset([trainX,trainy], 'train.pkl')
# load all test reviews
negative_docs = process_docs('txt_sentoken/neg', False)
positive_docs = process_docs('txt_sentoken/pos', False)
testX = negative_docs + positive_docs
testY = [0 for _ in range(100)] + [1 for _ in range(100)]
save_dataset([testX,testY], 'test.pkl')
L'esecuzione dell'esempio pulisce i documenti di testo della recensione del film, crea etichette e salva i dati preparati per i set di dati di training e test rispettivamente in train.pkl e test.pkl.
Ora siamo pronti per sviluppare il nostro modello.
Sviluppare un modello multicanale
In questa sezione svilupperemo una rete neurale convoluzionale multicanale per il problema di previsione dell'analisi del sentiment.
Questa sezione è divisa in 3 parti:
- Codificare i dati
- Definire il modello.
- Esempio completo.
Codificare i dati
Il primo passaggio consiste nel caricare il set di dati di training pulito.
La funzione denominata load_dataset() di seguito può essere chiamata per caricare il set di dati di addestramento selezionato.
# load a clean dataset
def load_dataset(filename):
return load(open(filename, 'rb'))
trainLines, trainLabels = load_dataset('train.pkl')
Successivamente, dobbiamo inserire un token Keras nel set di dati di addestramento. Utilizzeremo questo tokenizzatore sia per definire il vocabolario per il livello di incorporamento sia per codificare i documenti di revisione come numeri interi.
La funzione create_tokenizer() di seguito creerà un Tokenizer dato un elenco di documenti.
# fit a tokenizer
def create_tokenizer(lines):
tokenizer = Tokenizer()
tokenizer.fit_on_texts(lines)
return tokenizer
Dobbiamo anche conoscere la lunghezza massima delle sequenze di input come input per il modello e riempire tutte le sequenze alla lunghezza fissa.
La funzione max_length() riportata di seguito calcolerà la lunghezza massima (numero di parole) per tutte le revisioni nel set di dati di addestramento.
# calculate the maximum document length
def max_length(lines):
return max([len(s.split()) for s in lines])
Dobbiamo anche conoscere la dimensione del vocabolario per lo strato Incorporamento.
Questo può essere calcolato dal Tokenizer preparato, come segue:
# calculate vocabulary size
vocab_size = len(tokenizer.word_index) + 1
Infine, possiamo codificare con numeri interi e riempire il testo pulito della recensione del film.
La funzione di seguito denominata encode_text() codificherà e riempirà i dati di testo fino alla lunghezza massima della revisione.
# encode a list of lines
def encode_text(tokenizer, lines, length):
# integer encode
encoded = tokenizer.texts_to_sequences(lines)
# pad encoded sequences
padded = pad_sequences(encoded, maxlen=length, padding='post')
return padded
Definire il modello
Un modello standard per la classificazione dei documenti consiste nell'utilizzare un livello di incorporamento come input, seguito da una rete neurale convoluzionale unidimensionale, un livello di pooling e quindi un livello di output di previsione.
La dimensione del kernel nello strato convoluzionale definisce il numero di parole da considerare mentre la convoluzione viene passata attraverso il documento di testo di input, fornendo un parametro di raggruppamento.
Una rete neurale convoluzionale multicanale per la classificazione dei documenti prevede l'utilizzo di più versioni del modello standard con kernel di dimensioni diverse. Ciò consente al documento di essere elaborato a diverse risoluzioni o diversi n-grammi (gruppi di parole) alla volta, mentre il modello impara come integrare al meglio queste interpretazioni.
Questo approccio è stato descritto per la prima volta da Yoon Kim nel suo articolo del 2014 intitolato “Convolutional Neural Networks for Sentence Classification”.
Nel documento, Kim ha sperimentato livelli di incorporamento statici e dinamici (aggiornati), possiamo semplificare l'approccio e concentrarci invece solo sull'uso di kernel di diverse dimensioni.
Questo approccio si comprende meglio con un diagramma tratto dall’articolo di Kim:
In Keras è possibile definire un modello a input multipli utilizzando l'API funzionale.
Definiremo un modello con tre canali di input per l'elaborazione di 4 grammi, 6 grammi e 8 grammi di testo di recensione di film.
Ciascun canale è composto dai seguenti elementi:
- Livello di input che definisce la lunghezza delle sequenze di input.
- Strato di incorporamento impostato sulla dimensione del vocabolario e rappresentazioni a valori reali a 100 dimensioni.
- Strato convoluzionale unidimensionale con 32 filtri e una dimensione del kernel impostata sul numero di parole da leggere contemporaneamente.
- Livello Max Pooling per consolidare l'output dal livello convoluzionale.
- Appiattire il livello per ridurre l'output tridimensionale a bidimensionale per la concatenazione.
L'output dei tre canali viene concatenato in un unico vettore ed elaborato da uno strato Dense e uno strato di output.
La funzione seguente definisce e restituisce il modello. Come parte della definizione del modello, viene stampato un riepilogo del modello definito e viene creato e salvato su file un grafico del grafico del modello.
# define the model
def define_model(length, vocab_size):
# channel 1
inputs1 = Input(shape=(length,))
embedding1 = Embedding(vocab_size, 100)(inputs1)
conv1 = Conv1D(filters=32, kernel_size=4, activation='relu')(embedding1)
drop1 = Dropout(0.5)(conv1)
pool1 = MaxPooling1D(pool_size=2)(drop1)
flat1 = Flatten()(pool1)
# channel 2
inputs2 = Input(shape=(length,))
embedding2 = Embedding(vocab_size, 100)(inputs2)
conv2 = Conv1D(filters=32, kernel_size=6, activation='relu')(embedding2)
drop2 = Dropout(0.5)(conv2)
pool2 = MaxPooling1D(pool_size=2)(drop2)
flat2 = Flatten()(pool2)
# channel 3
inputs3 = Input(shape=(length,))
embedding3 = Embedding(vocab_size, 100)(inputs3)
conv3 = Conv1D(filters=32, kernel_size=8, activation='relu')(embedding3)
drop3 = Dropout(0.5)(conv3)
pool3 = MaxPooling1D(pool_size=2)(drop3)
flat3 = Flatten()(pool3)
# merge
merged = concatenate([flat1, flat2, flat3])
# interpretation
dense1 = Dense(10, activation='relu')(merged)
outputs = Dense(1, activation='sigmoid')(dense1)
model = Model(inputs=[inputs1, inputs2, inputs3], outputs=outputs)
# compile
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
# summarize
print(model.summary())
plot_model(model, show_shapes=True, to_file='multichannel.png')
return model
Esempio completo
Mettendo insieme tutto questo, l'esempio completo è elencato di seguito.
from pickle import load
from numpy import array
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
from keras.utils.vis_utils import plot_model
from keras.models import Model
from keras.layers import Input
from keras.layers import Dense
from keras.layers import Flatten
from keras.layers import Dropout
from keras.layers import Embedding
from keras.layers.convolutional import Conv1D
from keras.layers.convolutional import MaxPooling1D
from keras.layers.merge import concatenate
# load a clean dataset
def load_dataset(filename):
return load(open(filename, 'rb'))
# fit a tokenizer
def create_tokenizer(lines):
tokenizer = Tokenizer()
tokenizer.fit_on_texts(lines)
return tokenizer
# calculate the maximum document length
def max_length(lines):
return max([len(s.split()) for s in lines])
# encode a list of lines
def encode_text(tokenizer, lines, length):
# integer encode
encoded = tokenizer.texts_to_sequences(lines)
# pad encoded sequences
padded = pad_sequences(encoded, maxlen=length, padding='post')
return padded
# define the model
def define_model(length, vocab_size):
# channel 1
inputs1 = Input(shape=(length,))
embedding1 = Embedding(vocab_size, 100)(inputs1)
conv1 = Conv1D(filters=32, kernel_size=4, activation='relu')(embedding1)
drop1 = Dropout(0.5)(conv1)
pool1 = MaxPooling1D(pool_size=2)(drop1)
flat1 = Flatten()(pool1)
# channel 2
inputs2 = Input(shape=(length,))
embedding2 = Embedding(vocab_size, 100)(inputs2)
conv2 = Conv1D(filters=32, kernel_size=6, activation='relu')(embedding2)
drop2 = Dropout(0.5)(conv2)
pool2 = MaxPooling1D(pool_size=2)(drop2)
flat2 = Flatten()(pool2)
# channel 3
inputs3 = Input(shape=(length,))
embedding3 = Embedding(vocab_size, 100)(inputs3)
conv3 = Conv1D(filters=32, kernel_size=8, activation='relu')(embedding3)
drop3 = Dropout(0.5)(conv3)
pool3 = MaxPooling1D(pool_size=2)(drop3)
flat3 = Flatten()(pool3)
# merge
merged = concatenate([flat1, flat2, flat3])
# interpretation
dense1 = Dense(10, activation='relu')(merged)
outputs = Dense(1, activation='sigmoid')(dense1)
model = Model(inputs=[inputs1, inputs2, inputs3], outputs=outputs)
# compile
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
# summarize
print(model.summary())
plot_model(model, show_shapes=True, to_file='multichannel.png')
return model
# load training dataset
trainLines, trainLabels = load_dataset('train.pkl')
# create tokenizer
tokenizer = create_tokenizer(trainLines)
# calculate max document length
length = max_length(trainLines)
# calculate vocabulary size
vocab_size = len(tokenizer.word_index) + 1
print('Max document length: %d' % length)
print('Vocabulary size: %d' % vocab_size)
# encode data
trainX = encode_text(tokenizer, trainLines, length)
print(trainX.shape)
# define model
model = define_model(length, vocab_size)
# fit model
model.fit([trainX,trainX,trainX], array(trainLabels), epochs=10, batch_size=16)
# save the model
model.save('model.h5')
L'esecuzione dell'esempio consente innanzitutto di stampare un riepilogo del set di dati di training preparato.
Max document length: 1380
Vocabulary size: 44277
(1800, 1380)
Successivamente, viene stampato un riepilogo del modello definito.
____________________________________________________________________________________________________
Layer (type) Output Shape Param # Connected to
====================================================================================================
input_1 (InputLayer) (None, 1380) 0
____________________________________________________________________________________________________
input_2 (InputLayer) (None, 1380) 0
____________________________________________________________________________________________________
input_3 (InputLayer) (None, 1380) 0
____________________________________________________________________________________________________
embedding_1 (Embedding) (None, 1380, 100) 4427700 input_1[0][0]
____________________________________________________________________________________________________
embedding_2 (Embedding) (None, 1380, 100) 4427700 input_2[0][0]
____________________________________________________________________________________________________
embedding_3 (Embedding) (None, 1380, 100) 4427700 input_3[0][0]
____________________________________________________________________________________________________
conv1d_1 (Conv1D) (None, 1377, 32) 12832 embedding_1[0][0]
____________________________________________________________________________________________________
conv1d_2 (Conv1D) (None, 1375, 32) 19232 embedding_2[0][0]
____________________________________________________________________________________________________
conv1d_3 (Conv1D) (None, 1373, 32) 25632 embedding_3[0][0]
____________________________________________________________________________________________________
dropout_1 (Dropout) (None, 1377, 32) 0 conv1d_1[0][0]
____________________________________________________________________________________________________
dropout_2 (Dropout) (None, 1375, 32) 0 conv1d_2[0][0]
____________________________________________________________________________________________________
dropout_3 (Dropout) (None, 1373, 32) 0 conv1d_3[0][0]
____________________________________________________________________________________________________
max_pooling1d_1 (MaxPooling1D) (None, 688, 32) 0 dropout_1[0][0]
____________________________________________________________________________________________________
max_pooling1d_2 (MaxPooling1D) (None, 687, 32) 0 dropout_2[0][0]
____________________________________________________________________________________________________
max_pooling1d_3 (MaxPooling1D) (None, 686, 32) 0 dropout_3[0][0]
____________________________________________________________________________________________________
flatten_1 (Flatten) (None, 22016) 0 max_pooling1d_1[0][0]
____________________________________________________________________________________________________
flatten_2 (Flatten) (None, 21984) 0 max_pooling1d_2[0][0]
____________________________________________________________________________________________________
flatten_3 (Flatten) (None, 21952) 0 max_pooling1d_3[0][0]
____________________________________________________________________________________________________
concatenate_1 (Concatenate) (None, 65952) 0 flatten_1[0][0]
flatten_2[0][0]
flatten_3[0][0]
____________________________________________________________________________________________________
dense_1 (Dense) (None, 10) 659530 concatenate_1[0][0]
____________________________________________________________________________________________________
dense_2 (Dense) (None, 1) 11 dense_1[0][0]
====================================================================================================
Total params: 14,000,337
Trainable params: 14,000,337
Non-trainable params: 0
____________________________________________________________________________________________________
Il modello si adatta in tempi relativamente brevi e sembra mostrare buone competenze sul set di dati di addestramento.
...
Epoch 6/10
1800/1800 [==============================] - 30s - loss: 9.9093e-04 - acc: 1.0000
Epoch 7/10
1800/1800 [==============================] - 29s - loss: 5.1899e-04 - acc: 1.0000
Epoch 8/10
1800/1800 [==============================] - 28s - loss: 3.7958e-04 - acc: 1.0000
Epoch 9/10
1800/1800 [==============================] - 29s - loss: 3.0534e-04 - acc: 1.0000
Epoch 10/10
1800/1800 [==============================] - 29s - loss: 2.6234e-04 - acc: 1.0000
Un grafico del modello definito viene salvato su file, mostrando chiaramente i tre canali di input per il modello.
Il modello è adatto per un certo numero di epoche e salvato nel file model.h5 per una valutazione successiva.
Valutare il modello
In questa sezione possiamo valutare il modello di adattamento prevedendo il sentiment su tutte le recensioni nel set di dati di test invisibile.
Utilizzando le funzioni di caricamento dei dati sviluppate nella sezione precedente, possiamo caricare e codificare sia i set di dati di training che quelli di test.
# load datasets
trainLines, trainLabels = load_dataset('train.pkl')
testLines, testLabels = load_dataset('test.pkl')
# create tokenizer
tokenizer = create_tokenizer(trainLines)
# calculate max document length
length = max_length(trainLines)
# calculate vocabulary size
vocab_size = len(tokenizer.word_index) + 1
print('Max document length: %d' % length)
print('Vocabulary size: %d' % vocab_size)
# encode data
trainX = encode_text(tokenizer, trainLines, length)
testX = encode_text(tokenizer, testLines, length)
print(trainX.shape, testX.shape)
Possiamo caricare il modello salvato e valutarlo sia sui set di dati di training che di test.
L'esempio completo è elencato di seguito.
from pickle import load
from numpy import array
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
from keras.models import load_model
# load a clean dataset
def load_dataset(filename):
return load(open(filename, 'rb'))
# fit a tokenizer
def create_tokenizer(lines):
tokenizer = Tokenizer()
tokenizer.fit_on_texts(lines)
return tokenizer
# calculate the maximum document length
def max_length(lines):
return max([len(s.split()) for s in lines])
# encode a list of lines
def encode_text(tokenizer, lines, length):
# integer encode
encoded = tokenizer.texts_to_sequences(lines)
# pad encoded sequences
padded = pad_sequences(encoded, maxlen=length, padding='post')
return padded
# load datasets
trainLines, trainLabels = load_dataset('train.pkl')
testLines, testLabels = load_dataset('test.pkl')
# create tokenizer
tokenizer = create_tokenizer(trainLines)
# calculate max document length
length = max_length(trainLines)
# calculate vocabulary size
vocab_size = len(tokenizer.word_index) + 1
print('Max document length: %d' % length)
print('Vocabulary size: %d' % vocab_size)
# encode data
trainX = encode_text(tokenizer, trainLines, length)
testX = encode_text(tokenizer, testLines, length)
print(trainX.shape, testX.shape)
# load the model
model = load_model('model.h5')
# evaluate model on training dataset
loss, acc = model.evaluate([trainX,trainX,trainX], array(trainLabels), verbose=0)
print('Train Accuracy: %f' % (acc*100))
# evaluate model on test dataset dataset
loss, acc = model.evaluate([testX,testX,testX],array(testLabels), verbose=0)
print('Test Accuracy: %f' % (acc*100))
Nota: i risultati possono variare a causa della natura stocastica dell'algoritmo o della procedura di valutazione o delle differenze nella precisione numerica. Considera l'idea di eseguire l'esempio alcune volte e confrontare il risultato medio.
L'esecuzione dell'esempio stampa le competenze del modello sia sul set di dati di training che su quello di test.
Max document length: 1380
Vocabulary size: 44277
(1800, 1380) (200, 1380)
Train Accuracy: 100.000000
Test Accuracy: 87.500000
Possiamo vedere che, come previsto, la capacità sul dataset di training è eccellente, qui con una precisione del 100%.
Possiamo anche vedere che anche l'abilità del modello sul set di dati di test invisibili è molto impressionante, raggiungendo l'87,5%, che è superiore all'abilità del modello riportata nel documento del 2014 (sebbene non sia un confronto diretto da mele a mele).
Estensioni
In questa sezione sono elencate alcune idee per estendere il tutorial che potresti voler esplorare.
- Diversi n-grammi. Esplora il modello modificando la dimensione del kernel (numero di n grammi) utilizzata dai canali nel modello per vedere come influisce sulle competenze del modello.
- Più o meno canali. Esplora l'utilizzo di più o meno canali nel modello e scopri come influisce sulle competenze del modello.
- Rete più approfondita. Le reti neurali convoluzionali funzionano meglio nella visione artificiale quando sono più profonde. Esplora l'utilizzo di modelli più profondi qui e scopri come influisce sulle abilità del modello.
Ulteriori letture
Questa sezione fornisce più risorse sull'argomento se desideri approfondire.
- Reti neurali convoluzionali per la classificazione delle frasi, 2014.
- Reti neurali convoluzionali per la classificazione delle frasi (codice).
- API funzionale Keras
Riepilogo
In questo tutorial hai scoperto come sviluppare una rete neurale convoluzionale multicanale per la previsione del sentiment sui dati di recensioni di film di testo.
Nello specifico, hai imparato:
- Come preparare i dati di testo della recensione del film per la modellazione.
- Come sviluppare una rete neurale convoluzionale multicanale per il testo in Keras.
- Come valutare un modello di adattamento sui dati di recensioni di film mai visti.
Hai qualche domanda?
Poni le tue domande nei commenti qui sotto e farò del mio meglio per rispondere.