Visualizza su TensorFlow.org | Esegui in Google Colab | Visualizza su GitHub | Scarica taccuino |
Panoramica
La preelaborazione del testo è la trasformazione end-to-end del testo non elaborato negli input interi di un modello. I modelli NLP sono spesso accompagnati da diverse centinaia (se non migliaia) di righe di codice Python per la preelaborazione del testo. La pre-elaborazione del testo è spesso una sfida per i modelli perché:
Inclinazione al servizio della formazione. Diventa sempre più difficile garantire che la logica di pre-elaborazione degli input del modello sia coerente in tutte le fasi dello sviluppo del modello (ad es. pre-addestramento, messa a punto, valutazione, inferenza). L'utilizzo di diversi iperparametri, tokenizzazione, algoritmi di pre-elaborazione delle stringhe o semplicemente impacchettare gli input del modello in modo incoerente in fasi diverse potrebbe produrre effetti disastrosi e difficili da debug per il modello.
Efficienza e flessibilità. Sebbene la preelaborazione possa essere eseguita offline (ad esempio scrivendo gli output elaborati su file su disco e quindi riutilizzando i dati preelaborati nella pipeline di input), questo metodo comporta un costo aggiuntivo di lettura e scrittura del file. Anche la pre-elaborazione offline è scomoda se sono presenti decisioni di pre-elaborazione che devono essere eseguite in modo dinamico. Sperimentare con un'opzione diversa richiederebbe di rigenerare nuovamente il set di dati.
Interfaccia del modello complesso. I modelli di testo sono molto più comprensibili quando i loro input sono puro testo. È difficile comprendere un modello quando i suoi input richiedono un passaggio di codifica indiretto aggiuntivo. La riduzione della complessità della pre-elaborazione è particolarmente apprezzata per il debug, il servizio e la valutazione del modello.
Inoltre, le interfacce del modello più semplici rendono anche più conveniente provare il modello (ad es. inferenza o addestramento) su insiemi di dati diversi e inesplorati.
Pre-elaborazione del testo con TF.Text
Utilizzando le API di preelaborazione del testo di TF.Text, possiamo costruire una funzione di preelaborazione in grado di trasformare il set di dati di testo di un utente negli input interi del modello. Gli utenti possono impacchettare la pre-elaborazione direttamente come parte del loro modello per alleviare i problemi sopra menzionati.
Questo tutorial vi mostrerà come utilizzare tf.text ops pre-elaborazione per trasformare i dati di testo in input per il modello BERT e ingressi per la lingua di mascheramento pretraining compito descritto in "Masked LM e procedura mascheramento" del BERT: Pre-training di profonda bidirezionale Trasformatori di Lingua comprensione . Il processo prevede la tokenizzazione del testo in unità di sottoparole, la combinazione di frasi, il taglio del contenuto a una dimensione fissa e l'estrazione di etichette per l'attività di modellazione del linguaggio mascherato.
Impostare
Importiamo prima i pacchetti e le librerie di cui abbiamo bisogno.
pip install -q -U tensorflow-text
import tensorflow as tf
import tensorflow_text as text
import functools
I nostri dati contiene due caratteristiche del testo e siamo in grado di creare un esempio tf.data.Dataset
. Il nostro obiettivo è quello di creare una funzione che possiamo fornire Dataset.map()
con da utilizzare in allenamento.
examples = {
"text_a": [
b"Sponge bob Squarepants is an Avenger",
b"Marvel Avengers"
],
"text_b": [
b"Barack Obama is the President.",
b"President is the highest office"
],
}
dataset = tf.data.Dataset.from_tensor_slices(examples)
next(iter(dataset))
{'text_a': <tf.Tensor: shape=(), dtype=string, numpy=b'Sponge bob Squarepants is an Avenger'>, 'text_b': <tf.Tensor: shape=(), dtype=string, numpy=b'Barack Obama is the President.'>}
Tokenizzazione
Il nostro primo passo è eseguire qualsiasi pre-elaborazione di stringhe e tokenizzare il nostro set di dati. Questo può essere fatto utilizzando la text.BertTokenizer
, che è un text.Splitter
che può tokenize frasi in sottoparole o wordpieces per il modello di BERT dato un vocabolario generato dal algoritmo di Wordpiece . È possibile saperne di più su altri tokenizers disponibili in tf.text Subword da qui .
Il vocabolario può provenire da un checkpoint BERT generato in precedenza oppure puoi generarne uno tu stesso sui tuoi dati. Ai fini di questo esempio, creiamo un vocabolario giocattolo:
_VOCAB = [
# Special tokens
b"[UNK]", b"[MASK]", b"[RANDOM]", b"[CLS]", b"[SEP]",
# Suffixes
b"##ack", b"##ama", b"##ger", b"##gers", b"##onge", b"##pants", b"##uare",
b"##vel", b"##ven", b"an", b"A", b"Bar", b"Hates", b"Mar", b"Ob",
b"Patrick", b"President", b"Sp", b"Sq", b"bob", b"box", b"has", b"highest",
b"is", b"office", b"the",
]
_START_TOKEN = _VOCAB.index(b"[CLS]")
_END_TOKEN = _VOCAB.index(b"[SEP]")
_MASK_TOKEN = _VOCAB.index(b"[MASK]")
_RANDOM_TOKEN = _VOCAB.index(b"[RANDOM]")
_UNK_TOKEN = _VOCAB.index(b"[UNK]")
_MAX_SEQ_LEN = 8
_MAX_PREDICTIONS_PER_BATCH = 5
_VOCAB_SIZE = len(_VOCAB)
lookup_table = tf.lookup.StaticVocabularyTable(
tf.lookup.KeyValueTensorInitializer(
keys=_VOCAB,
key_dtype=tf.string,
values=tf.range(
tf.size(_VOCAB, out_type=tf.int64), dtype=tf.int64),
value_dtype=tf.int64),
num_oov_buckets=1
)
Costrutto di Let un text.BertTokenizer
utilizzando il vocabolario sopra e tokenize ingressi di testo in un RaggedTensor
.`.
bert_tokenizer = text.BertTokenizer(lookup_table, token_out_type=tf.string)
bert_tokenizer.tokenize(examples["text_a"])
<tf.RaggedTensor [[[b'Sp', b'##onge'], [b'bob'], [b'Sq', b'##uare', b'##pants'], [b'is'], [b'an'], [b'A', b'##ven', b'##ger']], [[b'Mar', b'##vel'], [b'A', b'##ven', b'##gers']]]>
bert_tokenizer.tokenize(examples["text_b"])
<tf.RaggedTensor [[[b'Bar', b'##ack'], [b'Ob', b'##ama'], [b'is'], [b'the'], [b'President'], [b'[UNK]']], [[b'President'], [b'is'], [b'the'], [b'highest'], [b'office']]]>
Output di testo da text.BertTokenizer
ci permette di vedere come il testo viene tokenizzato, ma il modello richiede interi ID. Possiamo impostare il token_out_type
param per tf.int64
avere integer ID (che sono gli indici nella vocabolario).
bert_tokenizer = text.BertTokenizer(lookup_table, token_out_type=tf.int64)
segment_a = bert_tokenizer.tokenize(examples["text_a"])
segment_a
<tf.RaggedTensor [[[22, 9], [24], [23, 11, 10], [28], [14], [15, 13, 7]], [[18, 12], [15, 13, 8]]]>
segment_b = bert_tokenizer.tokenize(examples["text_b"])
segment_b
<tf.RaggedTensor [[[16, 5], [19, 6], [28], [30], [21], [0]], [[21], [28], [30], [27], [29]]]>
text.BertTokenizer
restituisce un RaggedTensor
di forma [batch, num_tokens, num_wordpieces]
. Perché non abbiamo bisogno l'extra num_tokens
dimensioni per il nostro caso d'uso corrente, siamo in grado di unire le ultime due dimensioni per ottenere un RaggedTensor
di forma [batch, num_wordpieces]
:
segment_a = segment_a.merge_dims(-2, -1)
segment_a
<tf.RaggedTensor [[22, 9, 24, 23, 11, 10, 28, 14, 15, 13, 7], [18, 12, 15, 13, 8]]>
segment_b = segment_b.merge_dims(-2, -1)
segment_b
<tf.RaggedTensor [[16, 5, 19, 6, 28, 30, 21, 0], [21, 28, 30, 27, 29]]>
Ritaglio del contenuto
L'input principale a BERT è una concatenazione di due frasi. Tuttavia, BERT richiede che gli input siano di dimensioni e forma fisse e potremmo avere contenuti che superano il nostro budget.
Siamo in grado di affrontare questo utilizzando un text.Trimmer
per tagliare il nostro basso contenuto di una dimensione predeterminata (una volta concatenato lungo l'ultimo asse). Ci sono diversi text.Trimmer
tipi che selezionano i contenuti di preservare utilizzando algoritmi differenti. text.RoundRobinTrimmer
per esempio assegnerà contingente ugualmente per ogni segmento, ma può tagliare le estremità di frasi. text.WaterfallTrimmer
taglierà a partire dalla fine dell'ultima frase.
Per il nostro esempio, useremo RoundRobinTrimmer
che seleziona articoli da ciascun segmento in modo da sinistra a destra.
trimmer = text.RoundRobinTrimmer(max_seq_length=[_MAX_SEQ_LEN])
trimmed = trimmer.trim([segment_a, segment_b])
trimmed
[<tf.RaggedTensor [[22, 9, 24, 23], [18, 12, 15, 13]]>, <tf.RaggedTensor [[16, 5, 19, 6], [21, 28, 30, 27]]>]
trimmed
ora contiene i segmenti in cui il numero di elementi attraverso un batch è 8 elementi (quando concatenate lungo l'asse = -1).
Combinazione di segmenti
Ora che abbiamo segmenti rifilato, li possiamo combinare insieme per ottenere un unico RaggedTensor
. BERT utilizza gettoni speciali per indicare l'inizio ( [CLS]
) e alla fine di un segmento ( [SEP]
). Abbiamo anche bisogno di un RaggedTensor
indicando quali elementi nella combinata Tensor
appartenere a quale segmento. Possiamo usare text.combine_segments()
per ottenere entrambi questi Tensor
con gettoni speciali inseriti.
segments_combined, segments_ids = text.combine_segments(
[segment_a, segment_b],
start_of_sequence_id=_START_TOKEN, end_of_segment_id=_END_TOKEN)
segments_combined, segments_ids
(<tf.RaggedTensor [[3, 22, 9, 24, 23, 11, 10, 28, 14, 15, 13, 7, 4, 16, 5, 19, 6, 28, 30, 21, 0, 4], [3, 18, 12, 15, 13, 8, 4, 21, 28, 30, 27, 29, 4]]>, <tf.RaggedTensor [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1]]>)
Compito del modello di linguaggio mascherato
Ora che abbiamo i nostri ingressi di base, possiamo cominciare a estrarre gli input necessari per la "Masked LM e procedura di mascheratura" incarico di cui BERT: Pre-training di profonda bidirezionale Transformers per la comprensione del linguaggio
L'attività del modello di linguaggio mascherato ha due problemi secondari a cui pensare: (1) quali elementi selezionare per il mascheramento e (2) quali valori vengono assegnati?
Selezione articolo
Perché noi sceglieremo di selezionare gli elementi in modo casuale per il mascheramento, useremo un text.RandomItemSelector
. RandomItemSelector
seleziona casualmente gli articoli di un lotto soggetta a limitazioni poste ( max_selections_per_batch
, selection_rate
e unselectable_ids
) e ritorna una maschera booleano che indica che sono stati selezionati elementi.
random_selector = text.RandomItemSelector(
max_selections_per_batch=_MAX_PREDICTIONS_PER_BATCH,
selection_rate=0.2,
unselectable_ids=[_START_TOKEN, _END_TOKEN, _UNK_TOKEN]
)
selected = random_selector.get_selection_mask(
segments_combined, axis=1)
selected
<tf.RaggedTensor [[False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, True, True, True, False, False], [False, False, False, False, False, True, False, False, False, False, False, True, False]]>
La scelta del valore mascherato
La metodologia descritta nel documento BERT originale per la scelta del valore per il mascheramento è la seguente:
Per mask_token_rate
del tempo, sostituire l'articolo con la [MASK]
token:
"my dog is hairy" -> "my dog is [MASK]"
Per random_token_rate
del tempo, sostituire l'articolo con una parola a caso:
"my dog is hairy" -> "my dog is apple"
Per 1 - mask_token_rate - random_token_rate
del tempo, mantenere invariata la voce:
"my dog is hairy" -> "my dog is hairy."
text.MaskedValuesChooser
incapsula questa logica e può essere utilizzato per la nostra funzione di pre-elaborazione. Ecco un esempio di ciò che MaskValuesChooser
rendimenti dato un mask_token_rate
del 80% e di default random_token_rate
:
input_ids = tf.ragged.constant([[19, 7, 21, 20, 9, 8], [13, 4, 16, 5], [15, 10, 12, 11, 6]])
mask_values_chooser = text.MaskValuesChooser(_VOCAB_SIZE, _MASK_TOKEN, 0.8)
mask_values_chooser.get_mask_values(input_ids)
<tf.RaggedTensor [[1, 1, 1, 1, 1, 1], [1, 1, 1, 1], [1, 10, 1, 1, 6]]>
Quando alimentato con una RaggedTensor
ingresso, text.MaskValuesChooser
restituisce un RaggedTensor
della stessa forma sia con _MASK_VALUE
(0), un ID casuale, o lo stesso ID invariato.
Generazione di input per l'attività del modello di linguaggio mascherato
Ora che abbiamo un RandomItemSelector
di aiuto a selezionare gli elementi per mascheramento e text.MaskValuesChooser
per assegnare i valori, possiamo usare text.mask_language_model()
per assemblare tutti gli ingressi di questo compito per il nostro modello BERT.
masked_token_ids, masked_pos, masked_lm_ids = text.mask_language_model(
segments_combined,
item_selector=random_selector, mask_values_chooser=mask_values_chooser)
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/util/dispatch.py:206: batch_gather (from tensorflow.python.ops.array_ops) is deprecated and will be removed after 2017-10-25. Instructions for updating: `tf.batch_gather` is deprecated, please use `tf.gather` with `batch_dims=-1` instead.
Dive di Let più profondo ed esaminare le uscite di mask_language_model()
. L'uscita di masked_token_ids
è:
masked_token_ids
<tf.RaggedTensor [[3, 22, 1, 24, 23, 1, 10, 28, 1, 15, 1, 7, 4, 16, 5, 19, 6, 28, 30, 21, 0, 4], [3, 18, 12, 15, 13, 1, 4, 21, 28, 30, 27, 1, 4]]>
Ricorda che il nostro input è codificato usando un vocabolario. Se noi decodificare masked_token_ids
utilizzando il nostro vocabolario, otteniamo:
tf.gather(_VOCAB, masked_token_ids)
<tf.RaggedTensor [[b'[CLS]', b'Sp', b'[MASK]', b'bob', b'Sq', b'[MASK]', b'##pants', b'is', b'[MASK]', b'A', b'[MASK]', b'##ger', b'[SEP]', b'Bar', b'##ack', b'Ob', b'##ama', b'is', b'the', b'President', b'[UNK]', b'[SEP]'], [b'[CLS]', b'Mar', b'##vel', b'A', b'##ven', b'[MASK]', b'[SEP]', b'President', b'is', b'the', b'highest', b'[MASK]', b'[SEP]']]>
Si noti che alcuni gettoni wordpiece sono stati sostituiti con uno [MASK]
, [RANDOM]
o un valore ID diverso. masked_pos
uscita ci dà gli indici (nel rispettivo lotto) dei gettoni che sono stati sostituiti.
masked_pos
<tf.RaggedTensor [[2, 5, 8, 10], [5, 11]]>
masked_lm_ids
ci dà il valore originale del token.
masked_lm_ids
<tf.RaggedTensor [[9, 11, 14, 13], [8, 29]]>
Possiamo nuovamente decodificare gli ID qui per ottenere valori leggibili dall'uomo.
tf.gather(_VOCAB, masked_lm_ids)
<tf.RaggedTensor [[b'##onge', b'##uare', b'an', b'##ven'], [b'##gers', b'office']]>
Ingressi modello di riempimento
Ora che abbiamo tutti gli ingressi per il nostro modello, l'ultimo passo nel nostro preelaborazione è imballarli in fisso 2-dimensionale Tensor
s con imbottitura e anche generare una maschera Tensor
indicando i valori che sono valori pad. Possiamo usare text.pad_model_inputs()
per aiutarci con questo compito.
# Prepare and pad combined segment inputs
input_word_ids, input_mask = text.pad_model_inputs(
masked_token_ids, max_seq_length=_MAX_SEQ_LEN)
input_type_ids, _ = text.pad_model_inputs(
masked_token_ids, max_seq_length=_MAX_SEQ_LEN)
# Prepare and pad masking task inputs
masked_lm_positions, masked_lm_weights = text.pad_model_inputs(
masked_token_ids, max_seq_length=_MAX_PREDICTIONS_PER_BATCH)
masked_lm_ids, _ = text.pad_model_inputs(
masked_lm_ids, max_seq_length=_MAX_PREDICTIONS_PER_BATCH)
model_inputs = {
"input_word_ids": input_word_ids,
"input_mask": input_mask,
"input_type_ids": input_type_ids,
"masked_lm_ids": masked_lm_ids,
"masked_lm_positions": masked_lm_positions,
"masked_lm_weights": masked_lm_weights,
}
model_inputs
{'input_word_ids': <tf.Tensor: shape=(2, 8), dtype=int64, numpy= array([[ 3, 22, 1, 24, 23, 1, 10, 28], [ 3, 18, 12, 15, 13, 1, 4, 21]])>, 'input_mask': <tf.Tensor: shape=(2, 8), dtype=int64, numpy= array([[1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1]])>, 'input_type_ids': <tf.Tensor: shape=(2, 8), dtype=int64, numpy= array([[ 3, 22, 1, 24, 23, 1, 10, 28], [ 3, 18, 12, 15, 13, 1, 4, 21]])>, 'masked_lm_ids': <tf.Tensor: shape=(2, 5), dtype=int64, numpy= array([[ 9, 11, 14, 13, 0], [ 8, 29, 0, 0, 0]])>, 'masked_lm_positions': <tf.Tensor: shape=(2, 5), dtype=int64, numpy= array([[ 3, 22, 1, 24, 23], [ 3, 18, 12, 15, 13]])>, 'masked_lm_weights': <tf.Tensor: shape=(2, 5), dtype=int64, numpy= array([[1, 1, 1, 1, 1], [1, 1, 1, 1, 1]])>}
Revisione
Rivediamo ciò che abbiamo finora e assembliamo la nostra funzione di pre-elaborazione. Ecco cosa abbiamo:
def bert_pretrain_preprocess(vocab_table, features):
# Input is a string Tensor of documents, shape [batch, 1].
text_a = features["text_a"]
text_b = features["text_b"]
# Tokenize segments to shape [num_sentences, (num_words)] each.
tokenizer = text.BertTokenizer(
vocab_table,
token_out_type=tf.int64)
segments = [tokenizer.tokenize(text).merge_dims(
1, -1) for text in (text_a, text_b)]
# Truncate inputs to a maximum length.
trimmer = text.RoundRobinTrimmer(max_seq_length=6)
trimmed_segments = trimmer.trim(segments)
# Combine segments, get segment ids and add special tokens.
segments_combined, segment_ids = text.combine_segments(
trimmed_segments,
start_of_sequence_id=_START_TOKEN,
end_of_segment_id=_END_TOKEN)
# Apply dynamic masking task.
masked_input_ids, masked_lm_positions, masked_lm_ids = (
text.mask_language_model(
segments_combined,
random_selector,
mask_values_chooser,
)
)
# Prepare and pad combined segment inputs
input_word_ids, input_mask = text.pad_model_inputs(
masked_input_ids, max_seq_length=_MAX_SEQ_LEN)
input_type_ids, _ = text.pad_model_inputs(
masked_input_ids, max_seq_length=_MAX_SEQ_LEN)
# Prepare and pad masking task inputs
masked_lm_positions, masked_lm_weights = text.pad_model_inputs(
masked_input_ids, max_seq_length=_MAX_PREDICTIONS_PER_BATCH)
masked_lm_ids, _ = text.pad_model_inputs(
masked_lm_ids, max_seq_length=_MAX_PREDICTIONS_PER_BATCH)
model_inputs = {
"input_word_ids": input_word_ids,
"input_mask": input_mask,
"input_type_ids": input_type_ids,
"masked_lm_ids": masked_lm_ids,
"masked_lm_positions": masked_lm_positions,
"masked_lm_weights": masked_lm_weights,
}
return model_inputs
Abbiamo già costruito un tf.data.Dataset
e ora possiamo usare il nostro assemblati pre-elaborazione funzione bert_pretrain_preprocess()
in Dataset.map()
. Questo ci consente di creare una pipeline di input per trasformare i nostri dati di stringa non elaborati in input interi e alimentarli direttamente nel nostro modello.
dataset = tf.data.Dataset.from_tensors(examples)
dataset = dataset.map(functools.partial(
bert_pretrain_preprocess, lookup_table))
next(iter(dataset))
{'input_word_ids': <tf.Tensor: shape=(2, 8), dtype=int64, numpy= array([[ 3, 22, 9, 1, 4, 16, 5, 19], [ 3, 18, 1, 15, 4, 1, 28, 30]])>, 'input_mask': <tf.Tensor: shape=(2, 8), dtype=int64, numpy= array([[1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1]])>, 'input_type_ids': <tf.Tensor: shape=(2, 8), dtype=int64, numpy= array([[ 3, 22, 9, 1, 4, 16, 5, 19], [ 3, 18, 1, 15, 4, 1, 28, 30]])>, 'masked_lm_ids': <tf.Tensor: shape=(2, 5), dtype=int64, numpy= array([[24, 19, 0, 0, 0], [12, 21, 0, 0, 0]])>, 'masked_lm_positions': <tf.Tensor: shape=(2, 5), dtype=int64, numpy= array([[ 3, 22, 9, 1, 4], [ 3, 18, 1, 15, 4]])>, 'masked_lm_weights': <tf.Tensor: shape=(2, 5), dtype=int64, numpy= array([[1, 1, 1, 1, 1], [1, 1, 1, 1, 1]])>}
Tutorial correlati
Il testo adesso con BERT - Un tutorial su come utilizzare un modello BERT preaddestrato in testo classificare. Questo è un buon seguito ora che hai familiarità con come preelaborare gli input utilizzati dal modello BERT.
Creazione di token con il TF testo - Tutorial dettaglio le diverse tipologie di tokenizers che esistono in tf.text.
Manipolazione Testo
RaggedTensor
- guida dettagliate su come creare, utilizzare e manipolareRaggedTensor
s.