עיבוד BERT מראש עם טקסט TF

הצג באתר TensorFlow.org הפעל בגוגל קולאב הצג ב-GitHub הורד מחברת

סקירה כללית

עיבוד מקדים של טקסט הוא טרנספורמציה מקצה לקצה של טקסט גולמי לקלט מספרים שלמים של מודל. מודלים של NLP מלווים לרוב בכמה מאות (אם לא אלפי) שורות של קוד Python לעיבוד מוקדם של טקסט. עיבוד מוקדם של טקסט הוא לעתים קרובות אתגר עבור דגמים מכיוון:

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

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

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

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

עיבוד מקדים של טקסט עם TF.Text

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

הדרכה זו תציג כיצד להשתמש ops עיבוד מקדים TF.Text להפוך נתוני טקסט לתוך תשומות עבור המודל ברט תשומות עבור השפה מיסוך pretraining המשימה המתוארת "רעול LM ו מיסוך נוהל" של ברט: טרום הכשרה של רובוטריקים כיוונית עמוק עבור שפה הבנה . התהליך כולל אסימון של טקסט ליחידות של מילות משנה, שילוב משפטים, חיתוך תוכן לגודל קבוע וחילוץ תוויות עבור משימת דוגמנות השפה המסוכה.

להכין

בואו לייבא תחילה את החבילות והספריות שאנו צריכים.

pip install -q -U tensorflow-text
import tensorflow as tf
import tensorflow_text as text
import functools

הנתונים שלנו מכילים שתי תכונות טקסט נוכל ליצור דוגמא tf.data.Dataset . מטרתנו היא ליצור פונקציה כי אנו יכולים לספק Dataset.map() עם שישמש אימונים.

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.'>}

אסימונים

הצעד הראשון שלנו הוא להפעיל כל עיבוד מקדים של מחרוזת וליצור אסימון של מערך הנתונים שלנו. ניתן לעשות זאת באמצעות text.BertTokenizer , שהינה text.Splitter שיכולים tokenize משפטים subwords או wordpieces עבור דגם ברט קיבל אוצר המילים שנוצר מן האלגוריתם Wordpiece . אתה יכול ללמוד עוד על subword האחר tokenizers הזמין TF.Text מ כאן .

אוצר המילים יכול להיות ממחסום BERT שנוצר בעבר, או שאתה יכול ליצור אחד בעצמך על פי הנתונים שלך. למטרות דוגמה זו, בואו ניצור אוצר מילים של צעצוע:

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

המבנה של לתת text.BertTokenizer להשתמש באוצר המילים שלעיל tokenize תשומות הטקסט לתוך 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']]]>

פלט טקסט text.BertTokenizer מאפשר לנו לראות איך הטקסט מתבצע tokenized, אבל המודל דורש מזהים שלמים. אנו יכולים להגדיר את token_out_type param כדי tf.int64 להשיג שלם מזהים (המהווים את המדדים לתוך אוצר המילים).

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 מחזירה RaggedTensor עם הצורה [batch, num_tokens, num_wordpieces] . מכיוון שאנחנו לא צריכים את תוספת num_tokens ממדים המתאימה למקרה השימוש הנוכחי שלנו, אנחנו יכולים למזג את שני הממדים האחרונים להשיג RaggedTensor עם צורה [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]]>

חיתוך תוכן

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

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

לדוגמה שלנו, נשתמש RoundRobinTrimmer אשר בוחר פריטים מכל מגזר באופן שמאל לימין.

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 מכיל כעת המגזרים שבהם מספר אלמנטים פני אצווה הוא 8 אלמנטים (כאשר בשרשור לאורך ציר = -1).

שילוב קטעים

עכשיו יש לנו קטעים גזוז, נוכל לשלב אותם יחד כדי לקבל אחת RaggedTensor . ברט משתמשת אסימונים מיוחדים כדי לציין את תחילתה ( [CLS] ) וסיום של קטע ( [SEP] ). אנחנו גם צריכים RaggedTensor המציין אילו פריטים ב בשילוב Tensor שייכים לאיזו פלח. אנו יכולים להשתמש text.combine_segments() כדי לקבל את שני אלה Tensor עם אסימונים מיוחדים המוכנסים.

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

משימה מודל שפה מסכה

עכשיו שיש לנו התשומות הבסיסיות שלנו, אנו יכולים להתחיל לחלץ התשומות דרוש "LM הרעול הפנים נוהלו מיסוך" משימה מתוארת ברט: טרום הכשרה של רובוטריקים כיוונית העמוקים להבנת שפה

למשימת מודל השפה המסוכה יש שתי בעיות משנה שעליהן עלינו לחשוב: (1) אילו פריטים לבחור למיסוך ו-(2) אילו ערכים מוקצים להם?

בחירת פריט

מכיוון שאנחנו נבחר לבחור פריטים באופן אקראי עבור מיסוך, נשתמש text.RandomItemSelector . RandomItemSelector בוחר פריטים באופן אקראי נושא אצווה למגבלות נתון ( max_selections_per_batch , selection_rate ו unselectable_ids ) וחוזר מסכה בוליאני המציין אילו פריטים נבחרו.

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

בחירת ערך המסכה

המתודולוגיה שתיארה את נייר BERT המקורי לבחירת הערך למיסוך היא כדלקמן:

עבור mask_token_rate הזמן, להחליף את הפריט עם [MASK] האסימון:

"my dog is hairy" -> "my dog is [MASK]"

עבור random_token_rate הזמן, להחליף את הפריט עם מילה אקראית:

"my dog is hairy" -> "my dog is apple"

עבור 1 - mask_token_rate - random_token_rate הזמן, לשמור את הפריט ללא שינוי:

"my dog is hairy" -> "my dog is hairy."

text.MaskedValuesChooser מתמצת ההיגיון הזה יכול לשמש הפונקציה המקדימה שלנו. הנה דוגמה של מה MaskValuesChooser מחזירה נתון mask_token_rate של 80% ו מחדל 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]]>

כאשר מסופק עם RaggedTensor קלט, text.MaskValuesChooser מחזירה RaggedTensor של אותה צורה גם עם _MASK_VALUE (0), מזהה אקראי, או באותו מזהה שינוי.

הפקת קלט עבור מטלת מודל שפה מסכה

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

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.

צלילה בואו עמוק ולבחון את התפוקות של mask_language_model() . הפלט של masked_token_ids הוא:

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

זכור שהקלט שלנו מקודד באמצעות אוצר מילים. אם אנחנו לפענח masked_token_ids באמצעות אוצר המילים שלנו, אנחנו מקבלים:

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

שים לב שחלק אסימוני wordpiece הוחלפו או [MASK] , [RANDOM] או ערך מזהה אחר. masked_pos פלט נותן לנו את המדדים (ב אצווה בהתאמה) של הז'יטונים הוחלפו.

masked_pos
<tf.RaggedTensor [[2, 5, 8, 10], [5, 11]]>

masked_lm_ids נותן לנו את הערך המקורי של האסימון.

masked_lm_ids
<tf.RaggedTensor [[9, 11, 14, 13], [8, 29]]>

אנחנו יכולים שוב לפענח את המזהים כאן כדי לקבל ערכים קריאים אנושיים.

tf.gather(_VOCAB, masked_lm_ids)
<tf.RaggedTensor [[b'##onge', b'##uare', b'an', b'##ven'], [b'##gers', b'office']]>

כניסות דגם ריפוד

עכשיו יש לנו את כול התשומות עבור המודל שלנו, השלב האחרון המקדים שלנו הוא לארוז אותם לתוך 2-ממדי קבוע Tensor ים עם ריפוד וגם ליצור מסכה Tensor המציינת את הערכים שהם ערכי כרית. אנו יכולים להשתמש text.pad_model_inputs() כדי לעזור לנו עם משימה זו.

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

סקירה

בואו נסקור את מה שיש לנו עד כה ונרכיב את פונקציית העיבוד המקדים שלנו. הנה מה שיש לנו:

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

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

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]])>}
  • לסווג טקסט עם ברט - הדרכה על אופן השימוש במודל ברט pretrained לטקסט לסווג. זהו המשך נחמד עכשיו כשאתה מכיר כיצד לעבד מראש את התשומות המשמשות את מודל BERT.

  • Tokenizing עם טקסט TF - מדריך המפרט את הסוגים השונים של tokenizers שקיימים TF.Text.

  • טיפול בטקסט עם RaggedTensor - מדריך מפורט על איך ליצור, שימוש ולטפל RaggedTensor ים.