ממשקי API נפוצים של SavedModel למשימות טקסט

דף זה מתאר כיצד TF2 SavedModels עבור משימות הקשורות לטקסט צריכים ליישם את ה-API SavedModel שניתן לשימוש חוזר . (זה מחליף ומרחיב את החתימות הנפוצות לטקסט עבור פורמט TF1 Hub שהוצא משימוש כעת.)

סקירה כללית

ישנם מספר ממשקי API לחישוב הטמעות טקסט (ידוע גם בתור ייצוגים צפופים של טקסט, או וקטורים של תכונת טקסט).

  • ה-API להטמעות טקסט מקלט טקסט מיושם על ידי SavedModel הממפה אצווה של מחרוזות לאצווה של וקטורים הטבעה. זה מאוד קל לשימוש, ודגמים רבים ב-TF Hub יישמו את זה. עם זאת, זה לא מאפשר כוונון עדין של הדגם ב-TPU.

  • ה-API להטמעות טקסט עם קלט מעובד מראש פותר את אותה משימה, אך מיושם על ידי שני SavedModels נפרדים:

    • מעבד קדם שיכול לפעול בתוך צינור קלט tf.data ולהמיר מחרוזות ונתונים אחרים באורך משתנה לטנזורים מספריים,
    • מקודד שמקבל את תוצאות המעבד המקדים ומבצע את החלק הניתן לאימון של חישוב ההטמעה.

    פיצול זה מאפשר לעיבוד קלט מראש באופן אסינכרוני לפני הזנה ללולאת האימון. בפרט, זה מאפשר לבנות מקודדים שניתן להפעיל ולכוון עדין על TPU .

  • ה-API להטמעות טקסט עם מקודדי Transformer מרחיב את ה-API להטמעות טקסט מכניסות מעובדות מראש למקרה המסוים של BERT ומקודדי Transformer אחרים.

    • המעבד המקדים מורחב לבניית כניסות מקודד מיותר מקטע אחד של טקסט קלט.
    • מקודד ה-Transformer חושף את ההטמעות המודעות להקשר של אסימונים בודדים.

בכל מקרה, קלט הטקסט הוא מחרוזות מקודדות UTF-8, בדרך כלל של טקסט רגיל, אלא אם תיעוד הדגם קובע אחרת.

ללא קשר ל-API, מודלים שונים הוכשרו מראש על טקסט משפות ותחומים שונים, ומתוך מחשבה על משימות שונות. לכן, לא כל מודל הטמעת טקסט מתאים לכל בעיה.

הטבעת טקסט מקלט טקסט

SavedModel להטמעות טקסט מקלט טקסט מקבל אצווה של קלט במחרוזת Tensor של צורה [batch_size] וממפה אותם ל-float32 Tensor של צורה [batch_size, dim] עם ייצוגים צפופים (וקטורי תכונה) של הקלט.

תקציר שימוש

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

זכור מ- Reusable SavedModel API שהפעלת המודל במצב אימון (למשל, לנשירה) עשויה לדרוש ארגומנט מילת מפתח 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")
    ...

דוגמאות

הטמעות טקסט עם קלט מעובד מראש

הטבעת טקסט עם קלט מעובד מראש מיושמת על ידי שני SavedModels נפרדים:

  • מעבד קדם שממפה מחרוזת Tensor של צורה [batch_size] ל-dict של Tensorים מספריים,
  • מקודד שמקבל dict של Tensors כפי שהוחזר על ידי הקדם-processor, מבצע את החלק הניתן לאימון של חישוב ההטבעה, ומחזיר dict של פלטים. הפלט תחת מפתח "default" הוא float32 Tensor של צורה [batch_size, dim] .

זה מאפשר להפעיל את הפרה-מעבד בצינור קלט אך לכוונן עדין את ההטמעות המחושבות על ידי המקודד כחלק ממודל גדול יותר. בפרט, זה מאפשר לבנות מקודדים שניתן להפעיל ולכוון עדין על TPU .

זהו פרט יישום אילו Tensors כלולים בפלט של המעבד, ואילו (אם יש) Tensors נוספים מלבד "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"]

זכור מממשק ה- Reusable SavedModel API שהפעלת המקודד במצב אימון (למשל, לנשירה) עשויה לדרוש 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():
    ...

על מנת ליצור מחדש את משתני המקודד בצורה המבוזרת.

באופן דומה, אם המעבד המקדים הוא חלק מהמודל המאומן (כמו בדוגמה הפשוטה למעלה), יש לטעון אותו גם תחת היקף אסטרטגיית ההפצה. עם זאת, אם נעשה שימוש במעבד הקדם בצינור קלט (למשל, ב-callable המועבר ל- tf.data.Dataset.map() ), הטעינה שלו חייבת להתרחש מחוץ לתחום אסטרטגיית ההפצה, כדי למקם את המשתנים שלו (אם יש כאלה) ) במעבד המארח.

דוגמאות

הטמעת טקסט עם Transformer Encoders

מקודד שנאי עבור טקסט פועל על אצווה של רצפי קלט, כל רצף כולל n ≥ 1 מקטעים של טקסט אסימון, בתוך איזה דגם ספציפי הקשור ל- n . עבור BERT ורבות מההרחבות שלו, הגבול הזה הוא 2, אז הם מקבלים מקטעים בודדים וצמדי מקטעים.

ה-API להטמעות טקסט עם מקודדי Transformer מרחיב את ה-API להטמעות טקסט עם קלט מעובד מראש להגדרה זו.

מעבד קדם

Preprocessor SavedModel להטמעות טקסט עם מקודדי Transformer מיישם את ה-API של Preprocessor SavedModel להטמעות טקסט עם כניסות מעובדות מראש (ראה לעיל), המספק דרך למפות קלט טקסט מקטע בודד ישירות לכניסות מקודד.

בנוסף, ה-Preprocessor SavedModel מספק תת-אובייקטים הניתנים tokenize לטוקניזציה (בנפרד לכל קטע) ו- bert_pack_inputs לאריזת n מקטעים עם אסימון לרצף קלט אחד עבור המקודד. כל אובייקט משנה עוקב אחר ה- Reusable SavedModel API .

תקציר שימוש

כדוגמה קונקרטית לשני קטעי טקסט, הבה נסתכל על מטלת שילוב משפטים ששואלת האם הנחת יסוד (קטע ראשון) מרמזת או לא מרמזת על השערה (קטע שני).

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 של shape [batch_size, ...] שהערכים שלו הם מזהי int32 token המייצגים את מחרוזות הקלט. יכולות להיות r ≥ 1 ממדים מרופטים אחרי batch_size אבל אין ממד אחיד אחר.

  • אם r =1, הצורה היא [batch_size, (tokens)] , וכל קלט מסומן פשוט לרצף שטוח של אסימונים.
  • אם r >1, יש r -1 רמות נוספות של קיבוץ. לדוגמה, tensorflow_text.BertTokenizer משתמש ב- r =2 כדי לקבץ אסימונים לפי מילים ומניב צורה [batch_size, (words), (tokens_per_word)] . זה תלוי במודל הנדון כמה מהרמות הנוספות הללו קיימות, אם בכלל, ואילו קבוצות הן מייצגות.

המשתמש יכול (אך לא צריך) לשנות קלט עם אסימון, למשל, כדי להתאים את מגבלת seq_length שתיאכף באריזת כניסות מקודד. מימדים נוספים בפלט האסימונים יכולים לעזור כאן (למשל, לכבד את גבולות המילים) אך הופכים חסרי משמעות בשלב הבא.

במונחים של ממשק ה- Reusable SavedModel API , לאובייקט preprocessor.tokenize עשוי להיות .variables אך לא נועד לעבור הכשרה נוספת. טוקניזציה אינה תלוית מצב: אם preprocessor.tokenize() יש ארגומנט training=... בכלל, אין לו השפעה.

פרטים של bert_pack_inputs

קריאה ל- preprocessor.bert_pack_inputs() מקבלת רשימת Python של כניסות אסימונים (באצווה בנפרד עבור כל קטע קלט) ומחזירה dict of Tensors המייצגת אצווה של רצפי קלט באורך קבוע עבור מודל ה-Transformer encoder.

כל קלט אסימון הוא int32 RaggedTensor של צורה [batch_size, ...] , כאשר מספר r של מידות מרופטות אחרי batch_size הוא 1 או זהה לפלט של preprocessor.tokenize(). (האחרון מיועד לנוחות בלבד; המידות הנוספות משטחות לפני האריזה.)

האריזה מוסיפה אסימונים מיוחדים מסביב למקטעי הקלט כפי שמצופה מהמקודד. הקריאה bert_pack_inputs() מיישמת בדיוק את סכימת האריזה המשמשת את דגמי ה-BERT המקוריים ורבים מההרחבות שלהם: הרצף הארוז מתחיל באסימון אחד של התחלה של רצף, ואחריו המקטעים המוסמנים, שכל אחד מהם מסתיים בקצה אחד של קטע אחד. אֲסִימוֹן. המיקומים הנותרים עד seq_length, אם יש כאלה, מתמלאים באסימוני ריפוד.

אם רצף ארוז יחרוג מ-seq_length, bert_pack_inputs() חותך את הקטעים שלו לקידומות בגודל שווה בערך כך שהרצף הארוז יתאים בדיוק בתוך seq_length.

האריזה אינה תלוית מצב: אם preprocessor.bert_pack_inputs() יש ארגומנט training=... בכלל, אין לו השפעה. כמו כן, preprocessor.bert_pack_inputs לא צפויים להיות משתנים, או לתמוך בכוונון עדין.

קוֹדַאִי

המקודד נקרא על פי dict of encoder_inputs באותו אופן כמו ב-API להטמעות טקסט עם כניסות מעובדות מראש (ראה לעיל), כולל ההוראות מ- Reusable SavedModel API .

תקציר שימוש

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

או שווה ערך בקרס:

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

פרטים

ה- encoder_outputs הם הכתבה של Tensors עם המקשים הבאים.

  • "sequence_output" : טנסור float32 של צורה [batch_size, seq_length, dim] עם הטבעה מודעת להקשר של כל אסימון של כל רצף קלט ארוז.
  • "pooled_output" : float32 Tensor של צורה [batch_size, dim] עם הטבעה של כל רצף קלט בכללותו, הנגזר מ-sequent_output בצורה כלשהי שניתן לאמן.
  • "default" , כנדרש על ידי ה-API להטמעות טקסט עם קלט מעובד מראש: float32 Tensor של צורה [batch_size, dim] עם הטבעה של כל רצף קלט. (זה עשוי להיות רק כינוי של pooled_output.)

התוכן של ה- encoder_inputs אינו נדרש בהחלט לפי הגדרת API זו. עם זאת, עבור מקודדים המשתמשים בכניסות בסגנון BERT, מומלץ להשתמש בשמות הבאים (מתוך ערכת הכלים למודלים של NLP של TensorFlow Model Garden ) כדי למזער את החיכוך בהחלפת מקודדים ושימוש חוזר בדגמי קדם-מעבד:

  • "input_word_ids" : טנסור int32 של צורה [batch_size, seq_length] עם מזהי האסימון של רצף הקלט הארוז (כלומר, כולל אסימון התחלה של רצף, אסימוני סוף קטע וריפוד).
  • "input_mask" : טנסור int32 של צורה [batch_size, seq_length] עם ערך 1 במיקום של כל אסימוני הקלט הקיימים לפני הריפוד וערך 0 עבור אסימוני הריפוד.
  • "input_type_ids" : טנסור int32 של צורה [batch_size, seq_length] עם האינדקס של קטע הקלט שהוליד את אסימון הקלט במיקום המתאים. קטע הקלט הראשון (אינדקס 0) כולל את אסימון תחילת הרצף ואת אסימון סוף הקטע שלו. המקטע השני והמאוחר יותר (אם קיימים) כוללים את אסימון סוף המקטע שלהם. אסימוני ריפוד מקבלים שוב אינדקס 0.

הכשרה מבוזרת

לטעינת אובייקטי קדם-מעבד ומקודד בתוך או מחוץ להיקף אסטרטגיית הפצה, חלים אותם כללים כמו ב-API עבור הטבעת טקסט עם קלט מעובד מראש (ראה לעיל).

דוגמאות