Zobacz na TensorFlow.org | Uruchom w Google Colab | Zobacz na GitHub | Pobierz notatnik |
Przegląd
Wstępne przetwarzanie tekstu to kompleksowe przekształcenie nieprzetworzonego tekstu na dane wejściowe modelu w postaci liczb całkowitych. Modelom NLP często towarzyszy kilkaset (jeśli nie tysiące) wierszy kodu Pythona do wstępnego przetwarzania tekstu. Wstępne przetwarzanie tekstu jest często wyzwaniem dla modeli, ponieważ:
Pochylenie obsługi treningowej. Coraz trudniej jest zapewnić, że logika przetwarzania wstępnego danych wejściowych modelu jest spójna na wszystkich etapach tworzenia modelu (np. uczenie wstępne, dostrajanie, ocena, wnioskowanie). Korzystanie z różnych hiperparametrów, tokenizacja, algorytmy wstępnego przetwarzania ciągów lub po prostu niespójne pakowanie danych wejściowych modelu na różnych etapach może spowodować trudne do debugowania i katastrofalne skutki dla modelu.
Wydajność i elastyczność. Chociaż wstępne przetwarzanie można wykonać w trybie offline (np. poprzez zapisanie przetworzonych danych wyjściowych do plików na dysku, a następnie ponowne wykorzystanie tych wstępnie przetworzonych danych w potoku wejściowym), ta metoda wiąże się z dodatkowym kosztem odczytu i zapisu plików. Wstępne przetwarzanie offline jest również niewygodne, jeśli istnieją decyzje dotyczące wstępnego przetwarzania, które muszą być realizowane dynamicznie. Eksperymentowanie z inną opcją wymagałoby ponownego wygenerowania zestawu danych.
Złożony interfejs modelu. Modele tekstowe są znacznie bardziej zrozumiałe, gdy ich dane wejściowe to czysty tekst. Trudno jest zrozumieć model, którego dane wejściowe wymagają dodatkowego, pośredniego kroku kodowania. Zmniejszenie złożoności przetwarzania wstępnego jest szczególnie doceniane w przypadku debugowania, udostępniania i oceny modeli.
Ponadto prostsze interfejsy modelu ułatwiają również wypróbowanie modelu (np. wnioskowanie lub uczenie) na różnych, niezbadanych zestawach danych.
Wstępne przetwarzanie tekstu za pomocą TF.Text
Korzystając z interfejsów API przetwarzania wstępnego tekstu TF.Text, możemy skonstruować funkcję przetwarzania wstępnego, która może przekształcić zbiór danych tekstowych użytkownika w dane wejściowe w postaci liczb całkowitych. Użytkownicy mogą pakować przetwarzanie wstępne bezpośrednio jako część swojego modelu, aby złagodzić wyżej wymienione problemy.
Ten poradnik pokaże jak korzystać TF.Text przerób ops przekształcić dane tekstowe do wejścia do modelu BERT i wejść do języka maskujących pretraining zadanie opisane w „Zamaskowany LM i maskowanie postępowania” z BERT: pre-szkolenia głębokiego dwukierunkowych Transformers dla języka zrozumienie . Proces obejmuje tokenizację tekstu na jednostki podsłów, łączenie zdań, przycinanie treści do ustalonego rozmiaru i wyodrębnianie etykiet dla zadania modelowania języka maskowanego.
Ustawiać
Najpierw zaimportujmy pakiety i biblioteki, których potrzebujemy.
pip install -q -U tensorflow-text
import tensorflow as tf
import tensorflow_text as text
import functools
Nasze dane zawiera dwie funkcje tekstowe i możemy stworzyć przykład tf.data.Dataset
. Naszym celem jest stworzenie funkcji, które możemy dostarczyć Dataset.map()
z być stosowane w treningu.
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.'>}
Tokenizacja
Naszym pierwszym krokiem jest uruchomienie przetwarzania wstępnego dowolnego ciągu i tokenizacja naszego zestawu danych. Można to zrobić za pomocą text.BertTokenizer
, który jest text.Splitter
że może tokenize zdań na język subwords lub wordpieces dla modelu BERT danego słownictwo generowane przez algorytm Wordpiece . Możesz dowiedzieć się więcej o innych tokenizers podsłowo dostępnych w TF.Text z tutaj .
Słownictwo może pochodzić z wcześniej wygenerowanego punktu kontrolnego BERT lub możesz wygenerować je samodzielnie na podstawie własnych danych. Na potrzeby tego przykładu utwórzmy słownictwo zabawek:
_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
)
Konstrukt Let to text.BertTokenizer
stosując powyższą słownictwo i tokenize wejść tekst do 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']]]>
Wyjście tekst z text.BertTokenizer
pozwala nam zobaczyć, jak tekst jest tokenized, ale model wymaga Integer identyfikatory. Można ustawić token_out_type
param do tf.int64
uzyskać całkowitą identyfikatorów (które są wskaźniki do słownika).
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
zwraca RaggedTensor
w kształcie [batch, num_tokens, num_wordpieces]
. Ponieważ nie potrzebujemy dodatkowych num_tokens
wymiary dla naszego obecnego przypadku użycia, możemy połączyć dwa ostatnie wymiary, aby uzyskać RaggedTensor
z kształtu [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]]>
Przycinanie treści
Głównym wejściem do BERT jest konkatenacja dwóch zdań. Jednak BERT wymaga, aby dane wejściowe miały stałą wielkość i kształt, a my możemy mieć treści przekraczające nasz budżet.
Możemy zająć się tym za pomocą text.Trimmer
przyciąć nasze treści w dół do określonej wielkości (raz łączone wzdłuż ostatniej osi). Istnieją różne text.Trimmer
typy, które Wybierz zawartość do zachowania przy użyciu różnych algorytmów. text.RoundRobinTrimmer
np przeznaczy kwotę równo dla każdego segmentu, ale może przyciąć końce zdań. text.WaterfallTrimmer
będzie przyciąć począwszy od końca ostatniego zdania.
W naszym przykładzie użyjemy RoundRobinTrimmer
elementów, które wybiera z każdego segmentu w lewy-prawy sposób.
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
już zawiera segmenty, w których liczba elementów w partii wynosi 8 elementy (gdy łączone wzdłuż osi = 1).
Łączenie segmentów
Teraz, gdy mamy segmenty przycięte, możemy połączyć je ze sobą, aby uzyskać pojedynczą RaggedTensor
. BERT wykorzystuje specjalne znaki do wskazania początku ( [CLS]
) i koniec segmentu ( [SEP]
). Potrzebujemy też RaggedTensor
wskazujące, które elementy w połączeniu Tensor
należeć do którego segmentu. Możemy użyć text.combine_segments()
, aby uzyskać zarówno tych Tensor
ze specjalnymi tokenami wstawionych.
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]]>)
Zadanie modelu języka maskowanego
Teraz, gdy mamy nasze podstawowe wejść, możemy zacząć, aby wyodrębnić nakładów potrzebnych do „zamaskowane LM i maskowanie postępowania” zadanie opisane w BERT: Wstępne szkolenie Deep dwukierunkowych Transformers dla rozumienia języka
Zadanie zamaskowanego modelu języka ma dwa podproblemy, nad którymi powinniśmy się zastanowić: (1) jakie elementy wybrać do zamaskowania i (2) jakie wartości są im przypisane?
Wybór pozycji
Ponieważ będziemy wybierać, aby wybrać elementy losowo do maskowania, użyjemy text.RandomItemSelector
. RandomItemSelector
losowo wybiera przedmioty u osobnika wsadowym do ograniczeń podanych ( max_selections_per_batch
, selection_rate
i unselectable_ids
) i zwraca wartość logiczną maski wskazujące, które zostały wybrane pozycje.
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]]>
Wybór wartości maskowanej
Metodologia opisana w oryginalnym dokumencie BERT dotyczącym wyboru wartości maskowania jest następująca:
Dla mask_token_rate
czasu, wymienić element z [MASK]
tokena:
"my dog is hairy" -> "my dog is [MASK]"
Dla random_token_rate
czasu, wymienić element z losowym wyrazem:
"my dog is hairy" -> "my dog is apple"
Dla 1 - mask_token_rate - random_token_rate
czasu, utrzymać pozycję na niezmienionym poziomie:
"my dog is hairy" -> "my dog is hairy."
text.MaskedValuesChooser
obudowuje tę logikę i mogą być wykorzystane do naszej funkcji przetwarzania wstępnego. Oto przykład tego, co MaskValuesChooser
powraca dali mask_token_rate
80% i domyślna 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]]>
W przypadku zasilania RaggedTensor
wejściu, text.MaskValuesChooser
zwraca RaggedTensor
tego samego kształtu albo z _MASK_VALUE
(0), a losowy numer identyfikacyjny, lub tej samej niezmienionej id.
Generowanie danych wejściowych dla zadania modelu języka maskowanego
Teraz, gdy mamy RandomItemSelector
pomóc nam wybrać elementy do maskowania i text.MaskValuesChooser
przypisać wartości, możemy użyć text.mask_language_model()
, aby zebrać wszystkie wejścia tego zadania dla naszego modelu 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.
Chodźmy nurkować głębiej i zbadanie wyjścia mask_language_model()
. Wyjście masked_token_ids
jest:
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]]>
Pamiętaj, że nasze wejście jest kodowane przy użyciu słownictwa. Gdybyśmy dekodować masked_token_ids
pomocą naszego słownictwa, otrzymujemy:
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]']]>
Należy zauważyć, że pewne znaczniki wordpiece zastąpiono albo [MASK]
, [RANDOM]
lub różne wartości identyfikatora. masked_pos
wyjście daje nam indeksy (w odpowiedniej partii) z tokenów, które zostały zastąpione.
masked_pos
<tf.RaggedTensor [[2, 5, 8, 10], [5, 11]]>
masked_lm_ids
daje nam pierwotną wartość tokenu.
masked_lm_ids
<tf.RaggedTensor [[9, 11, 14, 13], [8, 29]]>
Możemy ponownie zdekodować identyfikatory tutaj, aby uzyskać wartości czytelne dla człowieka.
tf.gather(_VOCAB, masked_lm_ids)
<tf.RaggedTensor [[b'##onge', b'##uare', b'an', b'##ven'], [b'##gers', b'office']]>
Wejścia modelu dopełniania
Teraz, gdy mamy wszystkie wejścia do naszego modelu, ostatni krok w naszej wyprzedzającym jest pakowanie ich w stałej 2-wymiarowej Tensor
s z wyściółką, a także generowania maski Tensor
wskazujący wartości, które są wartościami kłódek. Możemy użyć text.pad_model_inputs()
, aby pomóc nam w realizacji tego zadania.
# 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]])>}
Recenzja
Przyjrzyjmy się, co mamy do tej pory i zmontujmy naszą funkcję przetwarzania wstępnego. Oto, co mamy:
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
Wcześniej skonstruował tf.data.Dataset
i możemy teraz skorzystać z naszej zmontowany przebiegu wyprzedzającego funkcji bert_pretrain_preprocess()
w Dataset.map()
. Dzięki temu możemy utworzyć potok wejściowy do przekształcania naszych nieprzetworzonych danych łańcuchowych na dane wejściowe liczb całkowitych i przesyłać je bezpośrednio do naszego modelu.
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]])>}
Powiązane samouczki
Tekst klasyfikować z Bertem - tutorial jak korzystać z pretrained modelu BERT tekstu sklasyfikować. Jest to miłe uzupełnienie teraz, gdy znasz już sposób wstępnego przetwarzania danych wejściowych używanych przez model BERT.
Tokenizing z TF Tekst - Tutorial szczegółowo różne rodzaje tokenizers które istnieją w TF.Text.
Obchodzenie Tekst z
RaggedTensor
- szczegółowy przewodnik na temat tworzenia, wykorzystania i manipulowaniaRaggedTensor
s.