APIهای رایج SavedModel برای وظایف متنی

این صفحه توضیح می دهد که چگونه TF2 SavedModels برای وظایف مرتبط با متن باید API قابل استفاده مجدد SavedModel را پیاده سازی کند. (این جایگزینی و گسترش Common Signatures for Text برای قالب TF1 Hub منسوخ شده است.)

بررسی اجمالی

چندین API برای محاسبه جاسازی متن وجود دارد (همچنین به عنوان نمایش متراکم متن یا بردار ویژگی متن شناخته می شود).

  • API برای جاسازی متن از ورودی های متن توسط SavedModel پیاده سازی می شود که دسته ای از رشته ها را به دسته ای از بردارهای جاسازی شده نگاشت می کند. استفاده از این بسیار آسان است و بسیاری از مدل ها در TF Hub آن را پیاده سازی کرده اند. با این حال، این امکان تنظیم دقیق مدل در TPU را نمی دهد.

  • API برای جاسازی متن با ورودی های از پیش پردازش شده همان کار را حل می کند، اما توسط دو SavedModel جداگانه پیاده سازی می شود:

    • یک پیش پردازنده که می تواند در داخل یک خط لوله ورودی tf.data اجرا شود و رشته ها و سایر داده های با طول متغیر را به تانسورهای عددی تبدیل کند.
    • یک رمزگذار که نتایج پیش پردازنده را می پذیرد و بخش قابل آموزش محاسبات جاسازی را انجام می دهد.

    این تقسیم به ورودی ها اجازه می دهد تا قبل از وارد شدن به حلقه آموزشی، به صورت ناهمزمان پردازش شوند. به ویژه، امکان ساخت رمزگذارهایی را فراهم می کند که می توانند روی TPU اجرا و تنظیم شوند.

  • API برای جاسازی‌های متن با رمزگذارهای ترانسفورماتور، API را برای جاسازی‌های متن از ورودی‌های از پیش پردازش شده به موارد خاص BERT و دیگر رمزگذارهای ترانسفورماتور گسترش می‌دهد.

    • پیش پردازنده برای ساخت ورودی های رمزگذار از بیش از یک بخش از متن ورودی گسترش یافته است.
    • رمزگذار ترانسفورماتور، تعبیه‌های آگاه از زمینه توکن‌های فردی را آشکار می‌کند.

در هر مورد، ورودی‌های متن رشته‌های رمزگذاری شده UTF-8 هستند، معمولاً از متن ساده، مگر اینکه مستندات مدل چیز دیگری ارائه دهد.

صرف نظر از API، مدل‌های مختلف بر روی متن از زبان‌ها و دامنه‌های مختلف و با وظایف متفاوتی از قبل آموزش داده شده‌اند. بنابراین، هر مدل تعبیه متنی برای هر مشکلی مناسب نیست.

جاسازی متن از ورودی های متن

SavedModel برای جاسازی متن از ورودی‌های متن، دسته‌ای از ورودی‌ها را در یک رشته Tensor شکل [batch_size] می‌پذیرد و آن‌ها را به یک تانسور float32 شکل [batch_size, dim] با نمایش‌های متراکم (بردارهای ویژگی) ورودی‌ها نگاشت می‌کند.

خلاصه استفاده

obj = hub.load("path/to/model")
text_input = ["A long sentence.",
              "single-word",
              "http://example.com"]
embeddings = obj(text_input)

از API قابل استفاده مجدد SavedModel به یاد بیاورید که اجرای مدل در حالت آموزشی (مثلاً برای ترک تحصیل) ممکن است به آرگومان کلیدواژه obj(..., training=True) نیاز داشته باشد، و obj ویژگی های .variables ، .trainable_variables و .regularization_losses را در صورت لزوم ارائه می دهد. .

در کراس، همه اینها توسط مراقبت می شود

embeddings = hub.KerasLayer("path/to/model", trainable=...)(text_input)

آموزش توزیع شده

اگر از جاسازی متن به عنوان بخشی از مدلی استفاده شود که با استراتژی توزیع آموزش دیده است، فراخوانی به hub.load("path/to/model") یا hub.KerasLayer("path/to/model", ...) ، به عبارت دیگر، باید در محدوده DistributionStrategy اتفاق بیفتد تا متغیرهای مدل به روش توزیع شده ایجاد شوند. مثلا

  with strategy.scope():
    ...
    model = hub.load("path/to/model")
    ...

مثال ها

جاسازی متن با ورودی های از پیش پردازش شده

یک متن جاسازی شده با ورودی های از پیش پردازش شده توسط دو SavedModel جداگانه پیاده سازی می شود:

  • یک پیش پردازشگر که یک رشته تانسور شکل [batch_size] به دیکته تانسورهای عددی نگاشت می کند،
  • یک رمزگذار که دستوری از تانسورها را که توسط پیش پردازنده برگردانده شده است می پذیرد، بخش قابل آموزش محاسبات جاسازی را انجام می دهد و دیکته ای از خروجی ها را برمی گرداند. خروجی زیر کلید "default" یک تانسور float32 با شکل [batch_size, dim] است.

این اجازه می دهد تا پیش پردازنده را در یک خط لوله ورودی اجرا کنید اما جاسازی های محاسبه شده توسط رمزگذار را به عنوان بخشی از یک مدل بزرگتر تنظیم کنید. به ویژه، امکان ساخت رمزگذارهایی را فراهم می کند که می توانند روی TPU اجرا و تنظیم شوند.

این یک جزئیات پیاده سازی است که تانسورها در خروجی پیش پردازنده وجود دارند و (در صورت وجود) تانسورهای اضافی علاوه بر "default" در خروجی رمزگذار موجود هستند.

مستندات رمزگذار باید مشخص کند که کدام پیش پردازنده با آن استفاده شود. به طور معمول، دقیقاً یک انتخاب صحیح وجود دارد.

خلاصه استفاده

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"]

از API قابل استفاده مجدد SavedModel به یاد بیاورید که اجرای رمزگذار در حالت آموزشی (مثلاً برای ترک تحصیل) ممکن است به encoder(..., training=True) نیاز داشته باشد، و آن encoder ویژگی‌های .variables ، .trainable_variables و .regularization_losses را در صورت لزوم ارائه می‌کند. .

مدل preprocessor ممکن است دارای .variables باشد اما قرار نیست بیشتر آموزش داده شود. پیش پردازش وابسته به حالت نیست: اگر preprocessor() یک آرگومان training=... داشته باشد، هیچ تاثیری ندارد.

در کراس، همه اینها توسط مراقبت می شود

encoder_inputs = hub.KerasLayer("path/to/preprocessor")(text_input)
encoder_outputs = hub.KerasLayer("path/to/encoder", trainable=True)(encoder_inputs)
embeddings = encoder_outputs["default"]

آموزش توزیع شده

اگر رمزگذار به عنوان بخشی از مدلی استفاده می‌شود که با استراتژی توزیع آموزش می‌یابد، فراخوانی به hub.load("path/to/encoder") یا hub.KerasLayer("path/to/encoder", ...) به عبارت دیگر، باید در داخل اتفاق بیفتد

  with strategy.scope():
    ...

به منظور ایجاد مجدد متغیرهای رمزگذار به روش توزیع شده.

به همین ترتیب، اگر پیش پردازنده بخشی از مدل آموزش دیده باشد (مانند مثال ساده بالا)، همچنین باید تحت محدوده استراتژی توزیع بارگذاری شود. با این حال، اگر پیش پردازنده در یک خط لوله ورودی استفاده شود (مثلاً در یک فراخوانی که به tf.data.Dataset.map() ارسال شده است، بارگذاری آن باید خارج از محدوده استراتژی توزیع اتفاق بیفتد تا متغیرهای خود را قرار دهد (در صورت وجود) ) روی CPU میزبان.

مثال ها

جاسازی متن با رمزگذارهای ترانسفورماتور

رمزگذارهای ترانسفورماتور برای متن روی دسته‌ای از دنباله‌های ورودی کار می‌کنند، که هر دنباله شامل n ≥ 1 بخش از متن نشانه‌گذاری شده، در محدوده‌ای از مدل خاص در n است. برای BERT و بسیاری از برنامه های افزودنی آن، این کران 2 است، بنابراین آنها بخش های منفرد و جفت های قطعه را می پذیرند.

API برای جاسازی‌های متن با رمزگذارهای Transformer، API را برای جاسازی‌های متنی با ورودی‌های از پیش پردازش شده به این تنظیم گسترش می‌دهد.

پیش پردازنده

یک SavedModel پیش پردازنده برای جاسازی متن با رمزگذارهای ترانسفورماتور، API یک SavedModel پیش پردازنده را برای جاسازی های متنی با ورودی های از پیش پردازش شده پیاده سازی می کند (به بالا مراجعه کنید)، که راهی برای نگاشت ورودی های متن تک بخش به طور مستقیم به ورودی های رمزگذار ارائه می دهد.

علاوه بر این، SavedModel پیش‌پردازنده tokenize فرعی قابل فراخوانی را برای توکن‌سازی (به‌طور جداگانه در هر بخش) و bert_pack_inputs برای بسته‌بندی n قطعه نشانه‌شده در یک توالی ورودی برای رمزگذار فراهم می‌کند. هر زیر شی از API قابل استفاده مجدد SavedModel پیروی می کند.

خلاصه استفاده

به عنوان یک مثال عینی برای دو بخش از متن، اجازه دهید به یک وظیفه شامل جمله نگاه کنیم که می‌پرسد آیا یک مقدمه (بخش اول) دلالت بر فرضیه دارد یا نه (بخش دوم).

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.

در Keras، این محاسبه را می توان به صورت بیان کرد

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])

جزئیات tokenize

فراخوانی به preprocessor.tokenize() یک رشته Tensor شکل [batch_size] را می پذیرد و یک RaggedTensor شکل [batch_size, ...] را برمی گرداند که مقادیر آن شناسه توکن int32 است که رشته های ورودی را نشان می دهد. بعد از batch_size می توان r ≥ 1 ابعاد ناهموار وجود داشته باشد اما هیچ بعد یکنواخت دیگری وجود نداشته باشد.

  • اگر r =1، شکل [batch_size, (tokens)] است، و هر ورودی به سادگی به یک دنباله مسطح از نشانه‌ها تبدیل می‌شود.
  • اگر r > 1 باشد، r -1 سطوح اضافی گروه بندی وجود دارد. برای مثال tensorflow_text.BertTokenizer از r =2 برای گروه بندی نشانه ها بر اساس کلمات استفاده می کند و شکل [batch_size, (words), (tokens_per_word)] را به دست می دهد. این بستگی به مدل در دست دارد که چه تعداد از این سطوح اضافی وجود دارد، در صورت وجود، و چه گروه‌هایی را نشان می‌دهند.

کاربر می‌تواند (اما نیازی نیست) ورودی‌های توکن‌شده را تغییر دهد، به‌عنوان مثال، محدودیت seq_length را که در بسته‌بندی ورودی‌های رمزگذار اعمال می‌شود، مطابقت دهد. ابعاد اضافی در خروجی توکنایزر می تواند در اینجا کمک کند (مثلاً رعایت مرزهای کلمات) اما در مرحله بعدی بی معنی می شود.

از نظر API قابل استفاده مجدد SavedModel ، شی preprocessor.tokenize ممکن است دارای .variables باشد اما قرار نیست بیشتر آموزش داده شود. Tokenization وابسته به حالت نیست: اگر preprocessor.tokenize() اصلاً آرگومان training=... داشته باشد، تاثیری ندارد.

جزئیات bert_pack_inputs

فراخوانی به preprocessor.bert_pack_inputs() یک لیست پایتون از ورودی های نشانه گذاری شده را می پذیرد (به طور جداگانه برای هر بخش ورودی دسته بندی شده است) و یک دیکته از Tensors را برمی گرداند که مجموعه ای از دنباله های ورودی با طول ثابت را برای مدل رمزگذار ترانسفورماتور نشان می دهد.

هر ورودی توکنیزه شده یک int32 RaggedTensor با شکل [batch_size, ...] است، که در آن تعداد r ابعاد ragged بعد از batch_size یا 1 است یا مانند خروجی preprocessor.tokenize(). (مورد دوم فقط برای راحتی است؛ ابعاد اضافی قبل از بسته بندی صاف می شود.)

بسته بندی نشانه های خاصی را در اطراف بخش های ورودی اضافه می کند همانطور که توسط رمزگذار انتظار می رود. bert_pack_inputs() دقیقاً طرح بسته‌بندی مورد استفاده توسط مدل‌های BERT اصلی و بسیاری از برنامه‌های افزودنی آن را پیاده‌سازی می‌کند: دنباله بسته‌شده با یک نشانه شروع دنباله شروع می‌شود و به دنبال آن بخش‌های نشانه‌گذاری شده، هر کدام با یک قسمت انتهایی خاتمه می‌یابند. نشانه موقعیت‌های باقی‌مانده تا seq_length، در صورت وجود، با توکن‌های padding پر می‌شوند.

اگر دنباله‌ای بسته‌شده از seq_length فراتر رود، bert_pack_inputs() بخش‌های آن را به پیشوندهایی با اندازه‌های تقریباً مساوی کوتاه می‌کند تا دنباله‌ی بسته‌شده دقیقاً در seq_length قرار گیرد.

بسته بندی وابسته به حالت نیست: اگر preprocessor.bert_pack_inputs() آرگومان training=... داشته باشد، هیچ تاثیری ندارد. همچنین انتظار نمی رود preprocessor.bert_pack_inputs دارای متغیر باشد یا از تنظیم دقیق پشتیبانی کند.

رمزگذار

رمزگذار بر اساس دستور encoder_inputs به همان روشی که در API برای جاسازی‌های متنی با ورودی‌های از پیش پردازش شده فراخوانی می‌شود (به بالا مراجعه کنید)، از جمله مفاد API SavedModel قابل استفاده مجدد .

خلاصه استفاده

encoder = hub.load("path/to/encoder")
encoder_outputs = encoder(encoder_inputs)

یا معادل آن در Keras:

encoder = hub.KerasLayer("path/to/encoder", trainable=True)
encoder_outputs = encoder(encoder_inputs)

جزئیات

encoder_outputs یک دیکته از تانسورها با کلیدهای زیر هستند.

  • "sequence_output" : یک تانسور float32 با شکل [batch_size, seq_length, dim] با تعبیه متن آگاه از هر نشانه از هر توالی ورودی بسته شده.
  • "pooled_output" : یک تانسور float32 با شکل [batch_size, dim] با تعبیه هر دنباله ورودی به عنوان یک کل، که از sequence_output به روشی قابل آموزش مشتق شده است.
  • "default" ، همانطور که توسط API برای جاسازی‌های متن با ورودی‌های از پیش پردازش شده لازم است: یک تانسور float32 با شکل [batch_size, dim] با جاسازی هر دنباله ورودی. (این ممکن است فقط نام مستعار pooled_output باشد.)

محتویات encoder_inputs به شدت توسط این تعریف API مورد نیاز نیست. با این حال، برای رمزگذارهایی که از ورودی‌های سبک BERT استفاده می‌کنند، توصیه می‌شود از نام‌های زیر (از NLP Modeling Toolkit of TensorFlow Model Garden ) برای به حداقل رساندن اصطکاک در تبادل رمزگذارها و استفاده مجدد از مدل‌های پیش‌پردازنده استفاده کنید:

  • "input_word_ids" : یک تانسور int32 با شکل [batch_size, seq_length] با شناسه های توکن دنباله ورودی بسته شده (یعنی شامل نشانه شروع دنباله، نشانه های پایان بخش، و بالشتک).
  • "input_mask" : یک تانسور int32 با شکل [batch_size, seq_length] با مقدار 1 در موقعیت همه نشانه‌های ورودی موجود قبل از padding و مقدار 0 برای نشانه‌های padding.
  • "input_type_ids" : یک تانسور int32 شکل [batch_size, seq_length] با شاخص قطعه ورودی که باعث ایجاد نشانه ورودی در موقعیت مربوطه می شود. اولین بخش ورودی (شاخص 0) شامل نشانه شروع دنباله و نشانه پایان بخش آن است. بخش دوم و بعدی (در صورت وجود) شامل نشانه پایان بخش مربوطه می شود. نشانه های پدینگ دوباره شاخص 0 را دریافت می کنند.

آموزش توزیع شده

برای بارگیری اشیاء پیش پردازنده و رمزگذار در داخل یا خارج از محدوده استراتژی توزیع، همان قوانینی که در API برای جاسازی متن با ورودی های از پیش پردازش شده اعمال می شود (به بالا مراجعه کنید).

مثال ها