Voir sur TensorFlow.org | Exécuter dans Google Colab | Voir sur GitHub | Télécharger le cahier |
Aperçu
Le prétraitement de texte est la transformation de bout en bout du texte brut en entrées entières d'un modèle. Les modèles NLP sont souvent accompagnés de plusieurs centaines (voire milliers) de lignes de code Python pour le prétraitement du texte. Le prétraitement du texte est souvent un défi pour les modèles car :
Asymétrie au service de la formation. Il devient de plus en plus difficile de s'assurer que la logique de prétraitement des entrées du modèle est cohérente à toutes les étapes du développement du modèle (par exemple, pré-apprentissage, réglage fin, évaluation, inférence). L'utilisation de différents hyperparamètres, de la tokenisation, d'algorithmes de prétraitement de chaînes ou simplement d'un emballage incohérent des entrées de modèle à différentes étapes pourrait entraîner des effets difficiles à déboguer et désastreux pour le modèle.
Efficacité et flexibilité. Alors que le prétraitement peut être effectué hors ligne (par exemple en écrivant les sorties traitées dans des fichiers sur le disque, puis en réutilisant lesdites données prétraitées dans le pipeline d'entrée), cette méthode entraîne un coût supplémentaire de lecture et d'écriture de fichier. Le prétraitement hors ligne est également gênant si des décisions de prétraitement doivent être prises de manière dynamique. Expérimenter une autre option nécessiterait de régénérer à nouveau l'ensemble de données.
Interface de modèle complexe. Les modèles de texte sont beaucoup plus compréhensibles lorsque leurs entrées sont du texte pur. Il est difficile de comprendre un modèle lorsque ses entrées nécessitent une étape d'encodage indirecte supplémentaire. La réduction de la complexité du prétraitement est particulièrement appréciée pour le débogage, la diffusion et l'évaluation des modèles.
De plus, des interfaces de modèle plus simples facilitent également l'essai du modèle (par exemple, inférence ou formation) sur différents ensembles de données inexplorés.
Prétraitement de texte avec TF.Text
En utilisant les API de prétraitement de texte de TF.Text, nous pouvons construire une fonction de prétraitement qui peut transformer l'ensemble de données de texte d'un utilisateur en entrées entières du modèle. Les utilisateurs peuvent emballer le prétraitement directement dans le cadre de leur modèle pour atténuer les problèmes mentionnés ci-dessus.
Ce tutoriel va vous montrer comment utiliser TF.Text opérations de pré - traitement pour transformer les données de texte en entrées pour le modèle BERT et entrées pour tâche préformation de masquage langage décrit dans « Masked LM et Masking Procédure » de BERT: Pré-formation des transformateurs profonds Bidirectionnel pour la langue comprendre . Le processus implique la segmentation du texte en unités de sous-mots, la combinaison de phrases, le découpage du contenu à une taille fixe et l'extraction d'étiquettes pour la tâche de modélisation du langage masqué.
Installer
Importons d'abord les packages et les bibliothèques dont nous avons besoin.
pip install -q -U tensorflow-text
import tensorflow as tf
import tensorflow_text as text
import functools
Nos données contient deux éléments de texte et nous pouvons créer un exemple tf.data.Dataset
. Notre objectif est de créer une fonction que nous pouvons fournir Dataset.map()
avec à utiliser dans la formation.
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.'>}
Tokenisation
Notre première étape consiste à exécuter n'importe quel prétraitement de chaîne et à tokeniser notre ensemble de données. Cela peut être fait en utilisant la text.BertTokenizer
, qui est un text.Splitter
qui peut tokenizer phrases en sous - mots ou wordpieces pour le modèle BERT donné un vocabulaire généré par l' algorithme Wordpiece . Vous pouvez en savoir plus sur les autres tokenizers de sous - mots disponibles dans TF.Text d' ici .
Le vocabulaire peut provenir d'un point de contrôle BERT précédemment généré, ou vous pouvez en générer un vous-même sur vos propres données. Pour les besoins de cet exemple, créons un vocabulaire de jouets :
_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
)
Regardons les choses en construire un text.BertTokenizer
en utilisant le vocabulaire ci - dessus et tokenizer les entrées de texte dans 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']]]>
Sortie texte de text.BertTokenizer
nous permet de voir comment le texte est segmenté, mais le modèle nécessite entier ID. Nous pouvons définir le token_out_type
param à tf.int64
obtenir entier ID (qui sont les indices dans le vocabulaire).
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
renvoie un RaggedTensor
avec la forme [batch, num_tokens, num_wordpieces]
. Parce que nous ne avons pas besoin les supplémentaires num_tokens
dimensions pour notre cas en cours d'utilisation, nous pouvons fusionner les deux dernières dimensions pour obtenir un RaggedTensor
avec la forme [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]]>
Découpage du contenu
L'entrée principale de BERT est une concaténation de deux phrases. Cependant, BERT exige que les intrants soient de taille et de forme fixes et nous pouvons avoir un contenu qui dépasse notre budget.
Nous pouvons aborder cela en utilisant un text.Trimmer
pour couper notre contenu vers le bas à une taille prédéterminée (une fois concaténé le long du dernier axe). Il existe différents text.Trimmer
types qui choisissent le contenu de préserver en utilisant des algorithmes différents. text.RoundRobinTrimmer
par exemple allouera quota également pour chaque segment , mais peut couper les extrémités des phrases. text.WaterfallTrimmer
taillera à partir de la fin de la dernière phrase.
Pour notre exemple, nous utiliserons RoundRobinTrimmer
qui sélectionne les éléments de chaque segment d'une manière de gauche à droite.
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
contient maintenant les segments où le nombre d'éléments à travers un lot est de 8 éléments (lorsque concaténée le long de l' axe = -1).
Combiner des segments
Maintenant que nous avons des segments parés, nous pouvons les combiner ensemble pour obtenir un seul RaggedTensor
. BERT utilise des jetons spéciaux pour indiquer le début ( [CLS]
) et à la fin d'un segment ( [SEP]
). Nous avons également besoin d' un RaggedTensor
indiquant quels éléments de la combinaison Tensor
appartiennent à quel segment. Nous pouvons utiliser text.combine_segments()
pour obtenir ces deux Tensor
avec des jetons spéciaux insérés.
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]]>)
Tâche de modèle de langage masqué
Maintenant que nous avons nos entrées de base, nous pouvons commencer à extraire les intrants nécessaires à la « Masked LM et Masking procédure » tâche décrite dans BERT: Pré-formation des transformateurs pour la compréhension profonde Bidirectionnel Langue
La tâche du modèle de langage masqué comporte deux sous-problèmes auxquels nous devons réfléchir : (1) quels éléments sélectionner pour le masquage et (2) quelles valeurs leur sont-elles attribuées ?
Sélection d'articles
Parce que nous allons choisir de sélectionner des éléments au hasard pour le masquage, nous utiliserons un text.RandomItemSelector
. RandomItemSelector
sélectionne de manière aléatoire des éléments dans un objet de traitement par lots à des restrictions données ( max_selections_per_batch
, selection_rate
et unselectable_ids
) et renvoie un masque booléenne indiquant quels éléments ont été sélectionnés.
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]]>
Choix de la valeur masquée
La méthodologie décrite dans l'article original du BERT pour choisir la valeur de masquage est la suivante :
Pour mask_token_rate
du temps, remplacer l'élément avec le [MASK]
jeton:
"my dog is hairy" -> "my dog is [MASK]"
Pour random_token_rate
du temps, remplacer l'élément avec un mot au hasard:
"my dog is hairy" -> "my dog is apple"
Pour 1 - mask_token_rate - random_token_rate
du temps, garder l'élément inchangé:
"my dog is hairy" -> "my dog is hairy."
text.MaskedValuesChooser
encapsule cette logique et peut être utilisé pour notre fonction de pré - traitement. Voici un exemple de ce que MaskValuesChooser
rendement donné une mask_token_rate
de 80% et par défaut 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]]>
Lorsqu'il est fourni avec une RaggedTensor
entrée, text.MaskValuesChooser
renvoie un RaggedTensor
de la même forme avec soit _MASK_VALUE
(0), un identifiant aléatoire, ou le même identifiant inchangé.
Génération d'entrées pour la tâche de modèle de langage masqué
Maintenant que nous avons un RandomItemSelector
pour nous aider à sélectionner des éléments pour masquer et text.MaskValuesChooser
pour affecter les valeurs, nous pouvons utiliser text.mask_language_model()
pour assembler toutes les entrées de cette tâche pour notre modèle 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.
La plongée Let profonde et examiner les résultats de mask_language_model()
. La sortie de masked_token_ids
est:
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]]>
N'oubliez pas que notre entrée est codée à l'aide d'un vocabulaire. Si on décode masked_token_ids
en utilisant notre vocabulaire, nous obtenons:
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]']]>
Notez que certains jetons de wordpiece ont été remplacés par deux [MASK]
, [RANDOM]
ou une valeur d'identité différente. masked_pos
sortie nous donne les indices (dans le lot respectif) des jetons qui ont été remplacés.
masked_pos
<tf.RaggedTensor [[2, 5, 8, 10], [5, 11]]>
masked_lm_ids
nous donne la valeur d' origine du jeton.
masked_lm_ids
<tf.RaggedTensor [[9, 11, 14, 13], [8, 29]]>
Nous pouvons à nouveau décoder les identifiants ici pour obtenir des valeurs lisibles par l'homme.
tf.gather(_VOCAB, masked_lm_ids)
<tf.RaggedTensor [[b'##onge', b'##uare', b'an', b'##ven'], [b'##gers', b'office']]>
Remplissage des entrées du modèle
Maintenant que nous avons toutes les entrées pour notre modèle, la dernière étape de notre pré - traitement est de les emballer dans deux dimensions fixe Tensor
s avec rembourrage et générer également un masque Tensor
indiquant les valeurs qui sont des valeurs de pad. Nous pouvons utiliser text.pad_model_inputs()
pour nous aider dans cette tâche.
# 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]])>}
Passer en revue
Passons en revue ce que nous avons jusqu'à présent et assemblons notre fonction de prétraitement. Voici ce que nous avons :
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
Nous avons déjà construit un tf.data.Dataset
et nous pouvons maintenant utiliser notre fonction pré - traitement assemblé bert_pretrain_preprocess()
dans Dataset.map()
. Cela nous permet de créer un pipeline d'entrée pour transformer nos données de chaîne brutes en entrées entières et alimenter directement notre modèle.
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]])>}
Tutoriels associés
Classifier texte avec BERT - Un tutoriel sur la façon d'utiliser un modèle BERT au texte pré - entraîné Classifier. C'est un bon suivi maintenant que vous savez comment prétraiter les entrées utilisées par le modèle BERT.
Tokenizing avec TF Texte - Tutoriel détaillant les différents types de tokenizers qui existent dans TF.Text.
Manipulation Texte
RaggedTensor
- Guide détaillé sur la façon de créer, d' utiliser et de manipulerRaggedTensor
s.