Tech Blog

NLP e Named Entity Recognition

Come estrarre informazione dal testo e perchè può essere utile?

Daniele Caldarini
Data Scientist
12 minuti di lettura
machine learning, nlp, named entity recognition, ner, information extraction, spacy e tensorflow
Questo articolo è disponibile anche in English 🇬🇧

Questo è il primo di una serie di articoli che riguardano quella parte del machine learning nota come Natural Language Processing (NLP).

In questo articolo si fa riferimento a diversi concetti chiave in ambito Machine Learning.
Leggere l'articolo Machine learning e applicazioni per l'industria per le principali definizioni
.

Con Natural Language Processing si fa riferimento a quel campo di ricerca interdisciplinare che abbraccia informatica, intelligenza artificiale e linguistica, il cui scopo è quello di sviluppare algoritmi in grado di analizzare, rappresentare e quindi “comprendere” il linguaggio naturale, scritto o parlato, in maniera similare o addirittura più performante rispetto agli esseri umani.

Al giorno d'oggi vengono generati e memorizzati quantità enormi di contenuti testuali e vocali. Si tratta di dati dei quali molto spesso non si fa uso, non consci del fatto che invece rappresentano una fonte inestimabile di valore, grazie ai quali è possibile realizzare strumenti e applicazioni che possono portare un valore aggiunto non indifferente.

Sfruttando i contenuti testuali e vocali a disposizione è possibile realizzare ad esempio strumenti di:

  • Named Entity Recognition: riconoscere ed estrarre entità e informazioni di tipo semantico dal testo.
  • Text Classification: classificare contenuti testuali, ad esempio l'analisi del sentiment di un testo(positivo o negativo).
  • Entity linking: disambiguare le entità individuate nel testo(collegare le entità testuali a concetti identificativi).
  • Topic Modeling: estrarre in modo automatico i topic principali presenti in un corpus testuale.
  • Autocompletion: autocompletamento di una query ad esempio.
  • Machine translation: traduzione di un contenuto testuale da una lingua ad un'altra.
  • Speech Recognition: trasformazione di contenuti vocali in contenuti testuali (tecnologia alla base dei chatbot)

In questo primo articolo verrà trattato il task noto come Named Entity Recognition (NER). Vedremo come è possibile realizzare uno strumento in grado di riconoscere entità nel testo e quali sono alcune delle sue possibili applicazioni.

Named Entity Recognition

La Named Entity Recognition si colloca all'interno di quella sottoclasse di task che in NLP è definita come Information Extraction. Attraverso la NER è possibile identficare entità nel testo e associarle alle corrispondenti categorie semantiche come persone, organizzazioni, entità di tipo geopolitico, geografico, numeri, espressioni temporali e via dicendo.

Figura 1 - Named Entity Recognition
Figura 1 - Named Entity Recognition

Stiamo parlando di un task che negli ultimi tempi ha avuto un forte sviluppo, soprattutto grazie all'avvento del Deep Learning. L'utilizzo di reti neurali molto profonde ha incrementato e migliorato molto l'efficacia di strumenti per il riconoscimento delle entità.
Prima dell'avvento del Deep Learning, lo strumento più utilizzato risultava essere il cosiddetto Hidden Markov Model, un modello statistico basato su Processo di Markov, ma che non garantiva le stesse prestazioni degli odierni modelli basati su Deep Learning.
Più avanti nell'articolo verranno mostrati due esempi di addestramento di modelli per il riconoscimento delle entità, entrambi basati sull'utilzzo di algoritmi realizzati con reti neurali.

Tecniche di annotazione e tool

Il task della Named Entity Recognition viene affrontato tramite approcci di tipo supervisionato, e per questo motivo è necessario avere a disposizione un insieme di dati etichettati per poter addestrare un modello per riconoscere le entità. Ogni contenuto testuale deve essere etichettato con la lista dei token e i relativi tag, per ogni entità che si vuole riconoscere nel testo.

Esistono diversi formati per rappresentare un dataset di addestramento per la Named Entity Recognition. Vediamo i due più utilizzati.

Un primo formato è quello che prende il nome di schema BILUO, dove le diverse parti che costituiscono le entità sono mappate secondo lo schema in Figura 2. Le entità sono etichettate con la categoria semantica precededuta da uno dei prefissi definiti, specificando quindi se si tratta di entità multi-token o no, e la posizione dei diversi token che costituiscono l'entità.

Figura 2 - Schema BILUO
Figura 2 - Schema BILUO

Il mapping tra i token e le entità viene poi salvato in un file csv, utilizzando un'etichetta a parte per tutti i token che non rientrano nelle categorie semantiche di riferimento.

Figura 3 - Mapping con schema BILUO
Figura 3 - Mapping con schema BILUO

Lo schema BILUO è forse il formato più diffuso.

Un secondo formato è una versione semplificata dello schema BILUO. Prende il nome di schema IOB. Si tratta di uno schema a granularità meno fine, dove il prefisso associato all'entità indica solo se si tratta di un token all'inizio o all'interno dell'entità composta da più parole.
Lo schema segue le specifiche definite nella figura sottostante.

Figura 4 - Schema IOB
Figura 4 - Schema IOB

Una terza rappresentazione è in formato jsonl. In questo formato si associa ad ogni contenuto testuale una lista che indica, per ogni entità, la posizione nel testo e la categoria semantica associata.

{   
  "text": ""Sharon flew to Miami last friday"", 
  "entities": [(0, 5, "PERSON"), (15, 20, "LOC"), (26, 32, "DATE")]
}
Source Code 1 - Esempio in formato JSONL

Gli esperti individuano lo schema BILUO come il migliore per realizzare modelli per la Named Entity Recognition il più accurati possibile. Questo perchè, attraverso lo schema a più prefissi, specificano informazioni più dettagliate nel testo, che migliorano le capacità di apprendimento dell'algoritmo di Machine Learning utilizzato.
Ma anche utilizzando lo schema in formato IOB e jsonl, molto spesso si riescono a raggiungere prestazioni degne di nota.
Il passaggio da un formato all'altro è relativamente semplice e può essere eseguito attraverso la definizione di procedure basate su regole di facile intuizione. Inoltre molte librerie di NLP definiscono già funzioni predefinite per trasformare il proprio dataset nel formato desiderato.

Per etichettare e trasformare i dati in uno di questi formati, esistono degli strumenti ad hoc chiamati annotatori. Un annotatore permette di caricare i propri dati come plain text e poi etichettarli in un ambiente grafico che rende più agile il processo di annotazione. Gli annotatori permettono di etichettare dati per la NER, ma anche per task di classificazione testuale piuttosto che task di tipo sequence-to-sequence.

Nel caso della NER, un annotatore presenta una grafica come quella che segue in figura, con delle funzionalità che permettono di svolgere l'attività di annotazione semplicemente evidenziando il testo e specificando l'etichetta da assegnare.

Figura 5 - Schermata di Docccano
Figura 5 - Schermata di Docccano

La schermata di esempio proviene da doccano, un'annotatore ben strutturato e open source. Doccano, una volta etichettati i dati, permette di esportare il dataset in formato jsonl. Da citare invece tra quelli a pagamento Prodigy, un annotatore avanzato e potenziato attraverso il concetto di active learning; è sviluppato da Explosion.ai, creatori della libreria opensource di NLP Spacy.

Dataset

Qualora si voglia addestrare dei modelli per il riconscimento delle entità, un opzione è quella di analizzare prima alcuni dataset etichettati presenti allo stato dell'arte. Si tratta tendenzialmente di dataset generici, con entità etichettate con categorie semantiche relative a nomi di persona, organizzazioni, località, date, entità temporali; è comunque possibile anche trovare dataset etichettati rispetto a domini più specifici. Questi dataset sono quasi sempre in lingua inglese, ma molto spesso questo problema può essere superato utilizzando dei tool di machine translation prima di svolgere l'attività di Named Entity Recognition, traducendo il contenuto dalla lingua italiana alla lingua inglese, e viceversa.

Ecco una lista di alcuni dei dataset etichettati presenti in rete:

  • Annotated Corpus for Named Entity Recognition: dataset in BILUO Schema contenente contenuti testuali etichettati con entità geografiche, geopolitiche, temporali, ecc...
  • CoNLL 2003: dataset in BILUO Schema contenente articoli di news annotati con (LOC) località, ORG (organizzazioni), PER (persone) and MISC (varie).
  • Enron Email Dataset: più di 500.000 email taggate con nomi, date e entità temporali.
  • OntoNotes 5: si tratta di un dataset di news, conversazioni telefoniche, contenuti di blog etichettati con entità di diverso genere.

Panoramica sui tool per la Named Entity Recognition

Allo stato dell'arte sono presenti molti tool e librerie che si occupano della realizzazione e dell'implementazione di strumenti in ambito Natural Language Processing. Questi tool forniscono sia dei modelli preaddestrati, facili da utilizzare e integrare velocemente nel proprio codice, sia la possibilità di addestrarne dei nuovi con i propri dati, sfruttando moduli predefiniti che semplificano l'addestramento di nuovi strumenti di Machine Learning.

Nella sezione successiva vedremo come addestrare modelli custom su dati propri. Vediamo ora, invece, alcuni tool, di facile utilizzo, che forniscono modelli preaddestrati per il riconoscimento delle entità nel testo.

Spacy

Figura 6 - Spacy
Figura 6 - Spacy

Spacy è un framework di Natural Language Processing, che si occupa dello sviluppo e dell'implementazione di tecniche di Machine Learning per molti dei più diffusi task di nlp. Spacy, tool completamente open source, fornisce inoltre una lista di modelli preaddestrati, con supporto per diverse lingue, tramite i quali è possibile eseguire su contenuti testuali processi di elaborazione del linguaggio naturale, come il riconoscimento delle entità.

# installazione di spacy
pip install -U spacy

#download del modello
python -m spacy download it_core_news_sm
# import
import spacy

# loading of choosen model
nlp = spacy.load("en_core_web_sm")

# running model on text and printing recognized entities
doc = nlp("Apple is looking at buying U.K. startup for $1 billion")
for ent in doc.ents:
    print(ent.text, ent.start_char, ent.end_char, ent.label_)
Source Code 2 - Spacy Named Entity Recognition

In questi due snippet di codice vediamo come installare attraverso pip nel proprio ambiente Python la libreria e fare il download del modello scelto. Successivamente, con due semplici righe di codice, è possibile caricare il modello ed eseguirlo sul contenuto testuale a piacere.

Stanford NLP

Figura 7 - Stanford NLP
Figura 7 - Stanford NLP

Stanford NLP è un gruppo di ricerca della Stanford University che si dedica al NLP e che ha realizzato una suite di strumenti per diversi task tra cui anche la Named Entity Recognition.

# import
import nltk
from nltk.tag.stanford import StanfordNERTagger

# text to processing
sentence = "Sharon flew to Miami last friday"

# jar of the model
jar = './stanford-ner-tagger/stanford-ner.jar'
model = './stanford-ner-tagger/ner-model-english.ser.gz'

# preparing ner tagger object with choosen jar
ner_tagger = StanfordNERTagger(model, jar, encoding='utf8')

# tokenization: splitting text
words = nltk.word_tokenize(sentence)

# performing ner tagger on words
print(ner_tagger.tag(words))
Source Code 3 - Stanford NLP Ner

In questo snippet viene utilizzata la libreria nltk per tokenizzare il contenuto testuale e trasformarlo in una lista di parole. Successivamente questa lista viene data in pasto al "ner tagger" che restituisce le entità in output.

Training di modelli custom

Abbiamo visto come allo stato dell'arte siano presenti tool e modelli già addestrati e come questi possono essere utilizzati per individuare le entità sui propri contenuti testuali.
Ma in molti casi si rivelano insufficienti, sia perchè si vogliono individuare entità relative a categorie semantiche diverse, sia perchè i modelli preaddestrati non sono adatti alla sorgente dati sulla quale vuole svolgere l'attività di riconoscimento delle entità. In questi casi risulta più conveniente addestrare dei propri modelli per la Named Entity Recognition, utilizzando i propri dati, che sono stati etichettati tramite l'ausilio di annotatori, come visto nella sezione precedente.

Seguono due esempi di addestramento di modelli custom, attraverso l'utilizzo della libreria Spacy e della libreria di Deep Learning Tensorflow.

Visita il repository Github dove è presente il codice sorgente degli esempi descritto ed eseguibile in jupyter notebook ben documentati.

Spacy

Spacy, come già detto, è una libreria di Machine Learning, dedicata completamente allo sviluppo di strumenti per i diversi task di Natural Language Processing. Si tratta di un framework ben strutturato e con funzionalità avanzate, che fanno si che sia molto adatto anche a contesti di produzione.

Oltre a strumenti già addestrati e rapidamente utilizzabili, come visto nella sezione precedente, fornisce tutta una serie di api e funzioni attraverso le quali è possibile realizzare e addestrare dei propri modelli, in modo rapido ed efficiente, senza doversi preoccupare di dover gestire tutti gli aspetti di più basso livello, che solitamente vanno affrontati durante il training e la creazione di un modello di Machine Learning.

Spacy fornisce delle pipeline già definite e configurabili per molti task di NLP, tra cui la Named Entity Recognition. Andiamo a vedere come addestrare rapidamente un modello per questo task con poche linee di codice, senza dover gestire aspetti che riguardano la definizione dell'architettura del modello, la gestione dei dati, piuttosto che la gestione dei modelli.

In questo primo snippet di codice:

  • vengono importate le librerie necessarie
  • viene definito un sample dei dati di training in formato jsonl, che in un contesto reale dovrebbero essere invece letti da un'apposito file con i dati di training
  • viene inizializzato un nuovo modello o viene caricato un modello già esistente da cui partire per l'addestramento
  • viene inizializzata la pipeline per la "ner" nel modello, se questa già non è presente
from __future__ import unicode_literals, print_function

import random
import warnings
import spacy
from spacy.util import minibatch, compounding


# training data
TRAIN_DATA = [
    ("Sharon flew to Miami last friday", {"entities": [(0, 5, "PERSON"), (15, 20, "LOC"), (26, 32, "DATE")]}),
    ("Daniele works in Rome for SMC Treviso", {"entities": [(0, 6, "PERSON"), (16, 20, "LOC"), (25, 36, "ORG")]}),
]

model=None
output_dir=None
n_iter=100

"""Load the model, set up the pipeline and train the entity recognizer."""
if model is not None:
    nlp = spacy.load(model)  # load existing spaCy model
    print("Loaded model '%s'" % model)
else:
    nlp = spacy.blank("en")  # create blank Language class
    print("Created blank 'en' model")

# create the built-in pipeline components and add them to the pipeline
# nlp.create_pipe works for built-ins that are registered with spaCy
if "ner" not in nlp.pipe_names:
    ner = nlp.create_pipe("ner")
    nlp.add_pipe(ner, last=True)
# otherwise, get it so we can add labels
else:
    ner = nlp.get_pipe("ner")

# add labels
for _, annotations in TRAIN_DATA:
    for ent in annotations.get("entities"):
        ner.add_label(ent[2])
Source Code 4 - Inzializzazione di un modello in Spacy

Nel codice che segue è invece implementata la vera e propria logica di addestramento.
Inizialmente vengono filtrate le pipeline del modello, disabilitando tutte quelle non necessarie per addestrare un modello per il riconoscimento delle entità. Successivamente sulla base del numero iterazioni da svolgere specificato, si itera sui dati, che vengono divisi in minibatch, e si aggiornano i parametri del modello di iterazione in iterazione. Il Dropout può essere configurato a seconda delle esigenze.
Nell'esempio viene stampata la loss del modello al termine di ogni iterazione di addestramento.

# get names of other pipes to disable them during training
pipe_exceptions = ["ner", "trf_wordpiecer", "trf_tok2vec"]
other_pipes = [pipe for pipe in nlp.pipe_names if pipe not in pipe_exceptions]
# only train NER
with nlp.disable_pipes(*other_pipes), warnings.catch_warnings():
    # show warnings for misaligned entity spans once
    warnings.filterwarnings("once", category=UserWarning, module='spacy')

    # reset and initialize the weights randomly – but only if we're
    # training a new model
    if model is None:
        nlp.begin_training()
    for itn in range(n_iter):
        random.shuffle(TRAIN_DATA)
        losses = {}
        # batch up the examples using spaCy's minibatch
        batches = minibatch(TRAIN_DATA, size=compounding(4.0, 32.0, 1.001))
        for batch in batches:
            texts, annotations = zip(*batch)
            nlp.update(
                texts,  # batch of texts
                annotations,  # batch of annotations
                drop=0.5,  # dropout - make it harder to memorise data
                losses=losses,
            )
        print("Losses", losses)
Source Code 5 - Training del modello

Infine, per terminare il processo di addestramento, il modello, in modo molto semplice, può essere salvato su disco, ed in modo altrettanto semplice, caricato per essere essere utilizzato in contesti reali, andando a specificare il path dove il modello è stato salvato.

# save model to output directory
if output_dir is not None:
    output_dir = Path(output_dir)
    if not output_dir.exists():
        output_dir.mkdir()
    nlp.to_disk(output_dir)
    print("Saved model to", output_dir)

# test the saved model
print("Loading from", output_dir)
nlp2 = spacy.load(output_dir)
for text, _ in TRAIN_DATA:
    doc = nlp2(text)
    print("Entities", [(ent.text, ent.label_) for ent in doc.ents])
    print("Tokens", [(t.text, t.ent_type_, t.ent_iob) for t in doc])
Source Code 6 - Salavataggio e caricamento del modello

In questo esempio non viene in alcun modo definita l'architettura del modello, nè tanto meno viene gestito il vocabolario alla base del modello, cosi come il processo di archiviazione e salvataggio di quest'ultimo.
Viene tutto gestito da Spacy, che definisce tutta la logica della Named Entity Recognition nella pipeline di riferimento. Questo permette anche a chi è meno esperto di poter intraprendere l'addestramento di un modello per un task del genere.

In ogni caso, indagando in profondità nell'implementazione della pipeline, troviamo che l'architettura utilizzata è quella di una rete convolutiva con connessioni residue. Si tratta di reti convolutive che, similmente a quanto avviene nelle celle piramidali della nostra corteccia cerebrale, definiscono delle connessioni residue con altre parti della rete neurale; per alcuni task di NLP queste reti dimostrano di performare in modo molto efficace e superare le prestazioni di altri algoritmi di Deep Learning.

Per approfondimenti e maggiori dettagli è presente un'ottima documentazione sul sito di Spacy, sia riguardo la procedura di addestramento, sia riguardo l'architettura utilizzata.
La configurazione utilizzata può in molti casi portare sin da subito a delle ottime prestazioni; rimane sempre consigliato svolgere un'attività di ottimizzazione dei parametri al fine di individuare quella che è la configurazione migiore. Per consigli su come svolgere questa ottimizzazione visita https://spacy.io/usage/training#tips.

Tensorflow

Tensorflow è tra le librerie di Deep Learning più utilizzate sia per le sperimentazioni, sia in contesto di produzione. Definisce una moltitudine di strumenti e tecniche comunemente utilizzate per realizzare modelli di Machine Learning basati su reti neurali molto profonde. Viene gestita da Google ed è completamente open source.

Figura 8 - Tensorflow
Figura 8 - Tensorflow

Tensorflow permette di realizzare qualsiasi modello basato su Deep Learning, dando la possibilità di progettare le reti sia attraverso tecniche molto avanzate che permettono la codifica degli aspetti di più basso livello, sia utilizzando layer predefiniti per creare la propria rete neurale con l'architettura che più fa al caso proprio, configurando i diversi layer e la logica di addestramento a proprio piacere, ricercando le migliori performance.

In Spacy siamo di fatto costretti ad utilizzare l'architettura scelta dagli sviluppatori, potendo agire in alcuni suoi parametri di configurazione, ma senza poter definire nel dettaglio l'algoritmo di addestramento.
L'architettura fornita dallo strumento di Spacy in molti casi si può rivelare la migliore, ma potrebber anche essere che, per una determinare sorgente dati e per la natura delle entità che si vogliono riconoscere, ci sia un algoritmo migliore delle reti convolutive scelte in Spacy.

Con Tensorflow, come detto, si può realizzare qualsiasi algoritmo e modello, basato su Deep Learning. Vediamo quindi come definire un modello per la Named Entity Recognition utilizzando Tensorflow Keras e reti neurali ricorrenti bidirezionali di tipo LSTM.
Le reti neurali ricorrenti si adattano ottimamente laddove risulta rilevante individuare relazioni tra feature per il task da svolgere. E per questo motivo sono molto utilizzate per i task di NLP, dove le relazioni nel testo sono molto importanti, come il riconoscimento delle entità.

# import
import pandas as pd
import numpy as np
import pickle
import tensorflow as tf
from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras.utils import to_categorical
from tensorflow.keras import Model, Input
from tensorflow.keras.layers import LSTM, Embedding, Dense
from tensorflow.keras.layers import TimeDistributed, SpatialDropout1D, Bidirectional
from tensorflow.keras.callbacks import EarlyStopping
from sklearn.model_selection import train_test_split
Source Code 7 - Import delle librerie necessarie

Dovendo definire la logica a più basso livello, anche la gestione dei dati va fatta in modo differente, andando a gestire aspetti che in Spacy vengono gestite dalla libreria stessa.
Nello snippet seguente vengono letti i dati di training, in formato BILUO, in un pandas dataframe. Vengono successivamente create le liste senza duplicati dei token e dei tag presenti nel dataset. Infine, secondo la logica definita dalla classe SentenceGetter, il dataset viene splittato in frasi diverse, e vengono creati dei vocabolari che mappano le liste delle parole e dei tag su degli identificativi numerici.

# class to split dataset in sentences
class SentenceGetter(object):
    def __init__(self, data):
        self.n_sent = 1
        self.data = data

        agg_func = lambda s: [(w, t) for w, t in zip(s["Word"].values.tolist(), s["Tag"].values.tolist())]

        self.grouped = self.data.groupby("Sentence #").apply(agg_func)
        self.sentences = [s for s in self.grouped]

# reading csv in pandas dataframe
data = pd.read_csv("data/biluo_train_data.csv", encoding='latin1')
data.fillna(method='ffill')
data.head(20)

print("Unique words in corpus:", data['Word'].nunique())
print("Unique tags in corpus:", data['Tag'].nunique())

# defining list of words
words = list(set(data["Word"].values))
words.append("ENDPAD")
num_words = len(words)

# defining list of tags
tags = list(set(data["Tag"].values))
num_tags = len(tags)
print (num_words, num_tags)

# splitting dataset in sentences
getter = SentenceGetter(data)
sentences = getter.sentences

# defining dictionaries to mapping words and tags to integers
word2idx = {w: i+1 for i, w in enumerate(words)}
tag2idx = {t: i for i, t in enumerate(tags)}
Source Code 8 - Lettura e trasformazione dei dati

A questo punto i vocabolari creati vengono utilizzati per mappare ogni contenuto testuale su una sequenza di interi, potendo poi così dare queste in input alla rete neurale definita per le epoche di addestramento.
Una volta fatto ciò gli insiemi dei dati e dei tag assiociati vengono splittati in insieme di training e insieme di test, che verrà utilizzato per la valutazione del modello addestrato.

# max len of sentencess
max_len = 400

# for every sentence mapping words to integer id based on dictionary word2idx and padding every sequence to same length
X = [[word2idx[w[0]] for w in s] for s in sentences]
X = pad_sequences(maxlen=max_len, sequences=X, padding='post', value=num_words-1)

# for every sentence mapping tags to integer id based on dictionary tag2idx and padding every sequence to same length
y = [[tag2idx[w[1]] for w in s] for s in sentences]
y = pad_sequences(maxlen=max_len, sequences=y, padding='post', value=tag2idx["O"])
y = [to_categorical(i, num_classes=num_tags) for i in y]

# splitting dataset in train and test set
x_train, x_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=1)
Source Code 9 - Mapping dei dati
Figura 9 - Esempio di mapping dei dati
Figura 9 - Esempio di mapping dei dati

A questo punto viene definita l'architettura del modello. Si tratta di una rete con un layer di Embedding iniziale, un layer LSTM di tipo bidirezionale e infine un layer TimeDistributed, che definisce uno strato fully connected sulle stesse finestre temporali. Per approfondimenti sulla natura dei diversi layer si rimanda alla documentazione ufficiale.

Il modello viene poi addestrato su un certo numero di epoche, utilizzando EarlyStopping come callback che monitora l'addestramento e lo interrompe qualora la rete inizi a soffrire di overfitting, cioè di eccessivo adattamento dei dati training, con conseguente perdita di accuratezza sui dati di test.

input_word = Input(shape=(max_len,))
model = Embedding(input_dim=num_words, output_dim=max_len, input_length=max_len)(input_word)
model = SpatialDropout1D(0.1)(model)
model = Bidirectional(LSTM(units=100, return_sequences=True, recurrent_dropout=0.1))(model)
out = TimeDistributed(Dense(num_tags, activation='softmax'))(model)
model = Model(input_word, out)
model.summary()

model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])

early_stopping = EarlyStopping(monitor='val_loss', min_delta=0, patience=10, verbose=0, mode='auto', baseline=None,
                               restore_best_weights=False)
callbacks = [early_stopping]

history = model.fit(
    x_train, np.array(y_train),
    validation_split=0.2,
    batch_size=32,
    epochs=30,
    verbose=1)
Source Code 10 - Definizione dell'architettura del modello e addestramento

Infine il modello viene salvato su disco, archiviando ogni componente necessaria per i futuri utilizzi. Non viene salvato solo il modello, ma anche tutti i vocabolari necessari per le trasformazioni sui dati.

with open('models/bilstm/word2idx.pickle', 'wb') as handle:
    pickle.dump(word2idx, handle, protocol=pickle.HIGHEST_PROTOCOL)

with open('models/bilstm/tag2idx.pickle', 'wb') as handle:
    pickle.dump(tag2idx, handle, protocol=pickle.HIGHEST_PROTOCOL)

with open('models/bilstm/words.pickle', 'wb') as handle:
    pickle.dump(words, handle, protocol=pickle.HIGHEST_PROTOCOL)

with open('models/bilstm/tags.pickle', 'wb') as handle:
    pickle.dump(tags, handle, protocol=pickle.HIGHEST_PROTOCOL)

# Saving Model Weight
model.save('models/bilstm/bilstm.h5')
Source Code 11 - Esempio in formato JSONL

Per riutilizzare il modello basta caricarlo specificando il path dove è stato archiviato, importando anche i dizionari. Definiamo anche una funzione per svolgere tokenizzazione sul testo sfruttando nltk.

import nltk

# Custom Tokenizer
def tokenize(s): return nltk.word_tokenize(s)

sentence = "Sharon flew to Miami last friday"

max_len = 400

# loading dictionaries
with open(ROOT_FOLDER + '/bilstm_3/word2idx.pickle', 'rb') as handle:
    word2idx = pickle.load(handle)

with open(ROOT_FOLDER + '/bilstm_3/tag2idx.pickle', 'rb') as handle:
    tag2idx = pickle.load(handle)

with open(ROOT_FOLDER + '/bilstm_3/words.pickle', 'rb') as handle:
    words = pickle.load(handle)

with open(ROOT_FOLDER + '/bilstm_3/tags.pickle', 'rb') as handle:
    tags = pickle.load(handle)

# loading model
model = load_model(ROOT_FOLDER + '/bilstm_3/bilstm_3.h5')
Source Code 12 - Caricamento del modello e dei dizionari

A questo punto tokenizziamo il testo, ne facciamo il padding alla lunghezza massima specificata e lo diamo in input al modello, che restituirà per ogni token il tag e la probabilità associata.

# Tokenization
test_sentence = tokenize(sentence)

# Preprocessing
x_test_sent = pad_sequences(sequences=[[word2idx.get(w, 0) for w in test_sentence]],
                            padding="post", value=len(words)-1, maxlen=max_len)

# Predicting tags and probabilities
p_pred = model.predict(np.array([x_test_sent[0]]))
p = np.argmax(p_pred, axis=-1)

prediction = []

# Visualization
for w, pred, probs in zip(test_sentence, p[0], p_pred[0]):
    prediction.append((w, tags[pred], probs[pred]))
Source Code 13 - Utilizzo del modello per la predizione dei tag

Come già detto, utilizzare Tensorflow, piuttosto che un'altra libreria di Deep Learning, per addestrare modelli per task di questo genere, risulta più complesso e richiede maggiore conoscenza di tutti gli aspetti alla base del Machine Learning.
In molti casi strumenti come Spacy permettono di raggiungere livelli di prestazioni molto alti, ma qualora non si riescano a raggiungere prestazioni degne di nota, allora strumenti come Tensorflow, possono essere utilizzati per ricercare la rete neurale e la configurazione che più fa al proprio caso.

Applicazioni delle NER

La capacità di individuare in modo intelligente e efficace entità nel testo, permette di fatto di taggare i contenuti testuali con quelli che possono risultare dei concetti rilevanti per quest'ultimi. Questo si traduce nella possibilità di realizzare o migliorare una serie di possibili applicazioni.

🔍 Sistemi di ricerca

Attraverso l'utilizzo della NER è possibile migliorare sia in efficienza che in efficacia i sistemi di ricerca.
In efficienza, andando di fatto, a svolgere le ricerche direttamente sulla lista di tag o entità associate ai diversi contenuti, invece di svolgere una ricerca full text.
In efficacia, permettendo di svolgere delle ricerche mirate, su determinati tipi di entità.
Ad esempio, se voglio cercare i documenti che fanno riferimento ad una determinata persona o organizzazione, posso riconoscere attraverso la NER questi tipi di entità, e mapparli su appositi campi, andando di fatto a svolgere una ricerca che filtra in base al tipo di categoria e al valore specificato. O ad esempio, taggare le email passsate, con persone e indirizzi email, specificandone anche il contesto.
Un'applicazione di questo genere può risultare molto importante in un contesto enterprise, con lo scopo di cercare in modo rapido ed efficacie, i contenuti di un'azienda, di un dipendente piuttosto che di un cliente.

☎️ Customer Service

Il customer care delle aziende si trova a dover navigare tra centinaia o migliaia di messaggi di assistenza al giorno. La possibilità di taggare i diversi messaggi e ticket in arrivo, può velocizzare di molto la capacità di inviare in modo corretto e rapido i ticket ai reparti specializzati nel prendere in carico una specifica tipologia di problema.

👀 Sistemi di raccomandazione

Nei sistemi di raccomandazione risulta fondamentale analizzare ciò che un utente esplora, e nel caso di un sistema di raccomandazione testuale, come può essere un strumento di raccomandazione di news ad esempio, la capacità di riconoscere entità nel testo permette di analizzare i contenuti che l'utente visita, in modo da poi raccomandarne dei simili.

scritto da
Daniele Caldarini
Data Scientist
Ricercatore e Data Scientist in SMC. Laureato in Ingegneria Informatica all'Università di Roma Tre, ricerca e sviluppa in ambito intelligenza artificiale e machine learning con l'obiettivo di realizzare prodotti moderni e al passo con i tempi.

Potrebbero interessarti anche…