Na tej stronie opisano, w jaki sposób TF2 SavedModels do zadań związanych z tekstem powinien implementować interfejs API SavedModel wielokrotnego użytku . (Zastępuje to i rozszerza wspólne podpisy tekstowe dla obecnie przestarzałego formatu TF1 Hub .)
Przegląd
Istnieje kilka interfejsów API do obliczania osadzania tekstu (znanych również jako gęste reprezentacje tekstu lub wektory cech tekstu).
Interfejs API do osadzania tekstu z danych wejściowych jest implementowany przez SavedModel, który mapuje partię ciągów na partię wektorów osadzania. Jest to bardzo łatwe w użyciu i wiele modeli w TF Hub je zaimplementowało. Nie pozwala to jednak na dostrojenie modelu na TPU.
Interfejs API do osadzania tekstu z wstępnie przetworzonymi danymi wejściowymi rozwiązuje to samo zadanie, ale jest implementowany przez dwa osobne modele SavedModels:
- preprocesor , który może działać wewnątrz potoku wejściowego tf.data i konwertuje ciągi znaków i inne dane o zmiennej długości na tensory numeryczne,
- koder , który akceptuje wyniki preprocesora i wykonuje możliwą do wyszkolenia część obliczeń osadzania.
Podział ten umożliwia asynchroniczne wstępne przetwarzanie danych wejściowych przed wprowadzeniem ich do pętli szkoleniowej. W szczególności umożliwia budowanie koderów, które można uruchamiać i dostrajać na TPU .
Interfejs API do osadzania tekstu w koderach Transformer rozszerza interfejs API do osadzania tekstu z wstępnie przetworzonych danych wejściowych na konkretny przypadek BERT i innych koderów Transformer.
- Preprocesor został rozszerzony w celu tworzenia danych wejściowych kodera z więcej niż jednego segmentu tekstu wejściowego.
- Koder Transformer udostępnia kontekstowe osadzanie poszczególnych tokenów.
W każdym przypadku tekstem wejściowym są ciągi znaków zakodowane w formacie UTF-8, zazwyczaj zwykły tekst, chyba że dokumentacja modelu stanowi inaczej.
Niezależnie od interfejsu API różne modele zostały wstępnie przeszkolone na tekstach z różnych języków i domen oraz z myślą o różnych zadaniach. Dlatego nie każdy model osadzania tekstu jest odpowiedni dla każdego problemu.
Osadzanie tekstu z wprowadzonego tekstu
SavedModel do osadzania tekstu z wejść tekstowych akceptuje partię danych wejściowych w tensorze ciągu o kształcie [batch_size]
i odwzorowuje je na tensor float32 o kształcie [batch_size, dim]
z gęstymi reprezentacjami (wektorami cech) wejść.
Podsumowanie użycia
obj = hub.load("path/to/model")
text_input = ["A long sentence.",
"single-word",
"http://example.com"]
embeddings = obj(text_input)
Przypomnij sobie z interfejsu API SavedModel wielokrotnego użytku , że uruchomienie modelu w trybie uczenia (np. w przypadku porzucenia) może wymagać argumentu słowa kluczowego obj(..., training=True)
i że obj
udostępnia atrybuty .variables
, .trainable_variables
i .regularization_losses
, stosownie do przypadku .
W Keras tym wszystkim zajmuje się
embeddings = hub.KerasLayer("path/to/model", trainable=...)(text_input)
Szkolenia rozproszone
Jeśli osadzanie tekstu jest używane jako część modelu szkolonego w ramach strategii dystrybucji, wywołanie metody hub.load("path/to/model")
lub hub.KerasLayer("path/to/model", ...)
, odpowiednio, musi nastąpić w zakresie DistributionStrategy, aby utworzyć zmienne modelu w sposób rozproszony. Na przykład
with strategy.scope():
...
model = hub.load("path/to/model")
...
Przykłady
- Poradnik Colab Klasyfikacja tekstu z recenzjami filmów .
Osadzanie tekstu z wstępnie przetworzonymi danymi wejściowymi
Osadzanie tekstu z wstępnie przetworzonymi danymi wejściowymi jest realizowane przez dwa oddzielne SavedModels:
- preprocesor , który odwzorowuje tensor ciągu o kształcie
[batch_size]
na dyktando tensorów numerycznych, - koder , który akceptuje dykt tensorów zwrócony przez preprocesor, wykonuje możliwą do wyszkolenia część obliczeń osadzania i zwraca dyktando wyników. Dane wyjściowe pod kluczem
"default"
to tensor float32 o kształcie[batch_size, dim]
.
Umożliwia to uruchomienie preprocesora w potoku wejściowym, ale dostrojenie osadzania obliczonego przez koder w ramach większego modelu. W szczególności pozwala budować kodery, które można uruchamiać i dostrajać na TPU .
Jest to szczegół implementacji, które Tensory są zawarte na wyjściu preprocesora i które (jeśli w ogóle) dodatkowe Tensory oprócz "default"
są zawarte na wyjściu kodera.
Dokumentacja kodera musi określać, jakiego preprocesora z nim używać. Zazwyczaj istnieje dokładnie jeden prawidłowy wybór.
Podsumowanie użycia
text_input = tf.constant(["A long sentence.",
"single-word",
"http://example.com"])
preprocessor = hub.load("path/to/preprocessor") # Must match `encoder`.
encoder_inputs = preprocessor(text_input)
encoder = hub.load("path/to/encoder")
encoder_outputs = encoder(encoder_inputs)
embeddings = encoder_outputs["default"]
Przypomnij sobie z interfejsu API SavedModel wielokrotnego użytku , że uruchomienie kodera w trybie uczenia (np. w przypadku porzucenia) może wymagać argumentu słowa kluczowego encoder(..., training=True)
i że encoder
udostępnia atrybuty .variables
, .trainable_variables
i .regularization_losses
, stosownie do przypadku .
Model preprocessor
może mieć .variables
ale nie jest przeznaczony do dalszego uczenia. Przetwarzanie wstępne nie jest zależne od trybu: jeśli preprocessor()
w ogóle ma argument training=...
, nie ma to żadnego efektu.
W Keras tym wszystkim zajmuje się
encoder_inputs = hub.KerasLayer("path/to/preprocessor")(text_input)
encoder_outputs = hub.KerasLayer("path/to/encoder", trainable=True)(encoder_inputs)
embeddings = encoder_outputs["default"]
Szkolenia rozproszone
Jeśli koder jest używany jako część modelu szkolonego w ramach strategii dystrybucji, wywołanie metody hub.load("path/to/encoder")
lub hub.KerasLayer("path/to/encoder", ...)
odpowiednio, musi wydarzyć się wewnątrz
with strategy.scope():
...
w celu odtworzenia zmiennych kodera w sposób rozproszony.
Podobnie, jeśli preprocesor jest częścią wyszkolonego modelu (jak w prostym przykładzie powyżej), należy go również załadować w zakresie strategii dystrybucji. Jeśli jednak preprocesor jest używany w potoku wejściowym (np. w wywołaniu przekazywanym do tf.data.Dataset.map()
), jego ładowanie musi odbywać się poza zakresem strategii dystrybucji, aby umieścić jego zmienne (jeśli takie istnieją) ) na procesorze hosta.
Przykłady
- Samouczek Colab Klasyfikuj tekst za pomocą BERT .
Osadzanie tekstu za pomocą enkoderów Transformer
Transformatorowe kodery tekstu działają na partii sekwencji wejściowych, przy czym każda sekwencja zawiera n ≥ 1 segmentów tekstu tokenizowanego, w ramach pewnego specyficznego dla modelu ograniczenia na n . W przypadku BERT i wielu jego rozszerzeń granica ta wynosi 2, więc akceptowane są pojedyncze segmenty i pary segmentów.
Interfejs API do osadzania tekstu za pomocą koderów Transformer rozszerza interfejs API do osadzania tekstu o wstępnie przetworzone dane wejściowe do tego ustawienia.
Preprocesor
Preprocesor SavedModel do osadzania tekstu w koderach Transformer implementuje interfejs API preprocesora SavedModel do osadzania tekstu z wstępnie przetworzonymi danymi wejściowymi (patrz wyżej), który umożliwia mapowanie jednosegmentowych wejść tekstowych bezpośrednio na wejścia kodera.
Ponadto preprocesor SavedModel zapewnia wywoływalne podobiekty tokenize
do tokenizacji (oddzielnie na segment) i bert_pack_inputs
do pakowania n tokenizowanych segmentów w jedną sekwencję wejściową dla kodera. Każdy podobiekt jest zgodny z interfejsem API SavedModel wielokrotnego użytku .
Podsumowanie użycia
Jako konkretny przykład dwóch segmentów tekstu przyjrzyjmy się zadaniu polegającemu na wyciąganiu zdań, które zadaje pytanie, czy przesłanka (pierwszy segment) implikuje hipotezę, czy też nie (drugi segment).
preprocessor = hub.load("path/to/preprocessor")
# Tokenize batches of both text inputs.
text_premises = tf.constant(["The quick brown fox jumped over the lazy dog.",
"Good day."])
tokenized_premises = preprocessor.tokenize(text_premises)
text_hypotheses = tf.constant(["The dog was lazy.", # Implied.
"Axe handle!"]) # Not implied.
tokenized_hypotheses = preprocessor.tokenize(text_hypotheses)
# Pack input sequences for the Transformer encoder.
seq_length = 128
encoder_inputs = preprocessor.bert_pack_inputs(
[tokenized_premises, tokenized_hypotheses],
seq_length=seq_length) # Optional argument.
W Kerasie obliczenie to można wyrazić jako
tokenize = hub.KerasLayer(preprocessor.tokenize)
tokenized_hypotheses = tokenize(text_hypotheses)
tokenized_premises = tokenize(text_premises)
bert_pack_inputs = hub.KerasLayer(
preprocessor.bert_pack_inputs,
arguments=dict(seq_length=seq_length)) # Optional argument.
encoder_inputs = bert_pack_inputs([tokenized_premises, tokenized_hypotheses])
Szczegóły tokenize
Wywołanie preprocessor.tokenize()
akceptuje tensor ciągu o kształcie [batch_size]
i zwraca tensor RaggedTensor o kształcie [batch_size, ...]
którego wartościami są identyfikatory tokenów int32 reprezentujące ciągi wejściowe. Po batch_size
może występować r ≥ 1 nierównych wymiarów, ale nie może być żadnego innego jednolitego wymiaru.
- Jeśli r =1, kształt to
[batch_size, (tokens)]
, a każde dane wejściowe są po prostu tokenizowane w płaską sekwencję tokenów. - Jeżeli r >1, istnieją r -1 dodatkowe poziomy grupowania. Na przykład tensorflow_text.BertTokenizer używa r =2 do grupowania tokenów według słów i daje kształt
[batch_size, (words), (tokens_per_word)]
. To, ile takich dodatkowych poziomów istnieje, jeśli w ogóle, i jakie grupy reprezentują, zależy od danego modelu.
Użytkownik może (ale nie musi) modyfikować tokenizowanych wejść, np. aby dostosować się do limitu seq_length, który będzie wymuszany przy pakowaniu wejść kodera. Dodatkowe wymiary w wynikach tokenizera mogą być tutaj pomocne (np. przestrzeganie granic słów), ale w następnym kroku staną się bez znaczenia.
Jeśli chodzi o interfejs API SavedModel wielokrotnego użytku , obiekt preprocessor.tokenize
może mieć .variables
, ale nie jest przeznaczony do dalszego szkolenia. Tokenizacja nie jest zależna od trybu: jeśli preprocessor.tokenize()
w ogóle ma argument training=...
, nie ma to żadnego efektu.
Szczegóły bert_pack_inputs
Wywołanie funkcji preprocessor.bert_pack_inputs()
akceptuje listę tokenizowanych danych wejściowych w języku Python (pogrupowanych osobno dla każdego segmentu wejściowego) i zwraca dykt Tensorów reprezentujący partię sekwencji wejściowych o stałej długości dla modelu kodera Transformera.
Każde tokenizowane wejście jest int32 RaggedTensor kształtu [batch_size, ...]
, gdzie liczba r nierównych wymiarów po Batch_size wynosi 1 lub jest taka sama jak na wyjściu preprocessor.tokenize().
(To ostatnie służy wyłącznie wygodzie; dodatkowe wymiary są spłaszczane przed pakowaniem.)
Pakowanie dodaje specjalne tokeny wokół segmentów wejściowych zgodnie z oczekiwaniami kodera. Wywołanie bert_pack_inputs()
implementuje dokładnie schemat pakowania używany w oryginalnych modelach BERT i wielu ich rozszerzeniach: spakowana sekwencja zaczyna się od jednego tokena początku sekwencji, po którym następują tokenizowane segmenty, każdy zakończony jednym końcem segmentu znak. Pozostałe pozycje do seq_length, jeśli istnieją, są wypełniane tokenami dopełniającymi.
Jeśli spakowana sekwencja przekroczyłaby długość seq_length, bert_pack_inputs()
obcina jej segmenty do przedrostków o w przybliżeniu równych rozmiarach, tak aby spakowana sekwencja mieściła się dokładnie w obrębie seq_length.
Pakowanie nie jest zależne od trybu: jeśli preprocessor.bert_pack_inputs()
w ogóle ma argument training=...
, nie ma to żadnego efektu. Ponadto nie oczekuje się, że preprocessor.bert_pack_inputs
będzie zawierał zmienne ani wspierał dostrajania.
Koder
Koder jest wywoływany na podstawie dyktatu encoder_inputs
w taki sam sposób, jak w API do osadzania tekstu z wstępnie przetworzonymi danymi wejściowymi (patrz wyżej), uwzględniając postanowienia z API Reusable SavedModel .
Podsumowanie użycia
encoder = hub.load("path/to/encoder")
encoder_outputs = encoder(encoder_inputs)
lub równoważnie w Keras:
encoder = hub.KerasLayer("path/to/encoder", trainable=True)
encoder_outputs = encoder(encoder_inputs)
Bliższe dane
encoder_outputs
są dyktowane przez Tensory z następującymi kluczami.
-
"sequence_output"
: tensor typu float32 o kształcie[batch_size, seq_length, dim]
z kontekstowym osadzaniem każdego tokena każdej spakowanej sekwencji wejściowej. -
"pooled_output"
: tensor typu float32 o kształcie[batch_size, dim]
z osadzeniem każdej sekwencji wejściowej jako całości, wyprowadzony z sekwencji_wyjściowej w jakiś możliwy do wytrenowania sposób. -
"default"
, zgodnie z wymaganiami API dla osadzania tekstu z wstępnie przetworzonymi danymi wejściowymi: tensor float32 o kształcie[batch_size, dim]
z osadzeniem każdej sekwencji wejściowej. (Może to być po prostu alias Pooled_output.)
Zawartość wejść encoder_inputs
nie jest ściśle wymagana przez tę definicję API. Jednakże w przypadku koderów korzystających z wejść w stylu BERT zaleca się użycie następujących nazw (z zestawu narzędzi do modelowania NLP w TensorFlow Model Garden ), aby zminimalizować tarcia podczas wymiany koderów i ponownego wykorzystania modeli preprocesora:
-
"input_word_ids"
: tensor int32 kształtu[batch_size, seq_length]
z identyfikatorami tokenów spakowanej sekwencji wejściowej (tj. włączając token początku sekwencji, tokeny końca segmentu i dopełnienie). -
"input_mask"
: tensor int32 o kształcie[batch_size, seq_length]
o wartości 1 w pozycji wszystkich tokenów wejściowych obecnych przed dopełnieniem i wartością 0 dla tokenów dopełnienia. -
"input_type_ids"
: tensor int32 o kształcie[batch_size, seq_length]
z indeksem segmentu wejściowego, który dał początek tokenowi wejściowemu w odpowiedniej pozycji. Pierwszy segment wejściowy (indeks 0) zawiera żeton początku sekwencji i jego żeton końca segmentu. Drugi i kolejne segmenty (jeśli są obecne) zawierają odpowiadający im żeton końca segmentu. Tokeny dopełniające ponownie uzyskują indeks 0.
Szkolenia rozproszone
W przypadku ładowania obiektów preprocesora i kodera do zakresu strategii dystrybucji lub poza nią obowiązują te same zasady, co w interfejsie API w przypadku osadzania tekstu z wstępnie przetworzonymi danymi wejściowymi (patrz wyżej).
Przykłady
- Poradnik Colab Rozwiązuj zadania GLUE za pomocą BERT na TPU .