לשמור את התאריך! קלט / פלט של Google חוזר 18-20 במאי הירשם עכשיו
דף זה תורגם על ידי Cloud Translation API.
Switch to English

ויסות גרף לסיווג סנטימנטים באמצעות גרפים מסונתזים

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

סקירה כללית

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

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

  1. צור טבלאות לכל דוגמת טקסט בכניסה. ניתן לעשות זאת באמצעות מודלים שהוכשרו מראש כגון word2vec , Swivel , BERT וכו '.
  2. בנה גרף המבוסס על השיבוצים הללו על ידי שימוש במדד דמיון כגון מרחק 'L2', מרחק 'קוסינוס' וכו '. הצמתים בתרשים תואמים לדגימות והקצוות בתרשים תואמים לדמיון בין זוגות הדגימות.
  3. צור נתוני אימון מהגרף המסונתז לעיל ומאפייני דוגמה. נתוני האימון המתקבלים יכילו תכונות שכנות בנוסף לתכונות הצומת המקוריות.
  4. צור רשת עצבית כמודל בסיס באמצעות ממשק ה- API הרציף, הפונקציונלי או תת-הקלאסי של Keras.
  5. עטוף את המודל הבסיסי עם מחלקת העטיפה GraphRegularization, המסופקת על ידי מסגרת NSL, כדי ליצור מודל גרף חדש של Keras. מודל חדש זה יכלול הפסד רגולציה של גרף כמונח ההסדרה במטרת ההכשרה שלו.
  6. התאמן והעריך את מודל הגרף Keras.

דרישות

  1. התקן את חבילת הלמידה המובנית העצבית.
  2. התקן את רכזת tensorflow.
pip install --quiet neural-structured-learning
pip install --quiet tensorflow-hub

תלות ויבוא

import matplotlib.pyplot as plt
import numpy as np

import neural_structured_learning as nsl

import tensorflow as tf
import tensorflow_hub as hub

# Resets notebook state
tf.keras.backend.clear_session()

print("Version: ", tf.__version__)
print("Eager mode: ", tf.executing_eagerly())
print("Hub version: ", hub.__version__)
print(
    "GPU is",
    "available" if tf.config.list_physical_devices("GPU") else "NOT AVAILABLE")
Version:  2.3.0
Eager mode:  True
Hub version:  0.8.0
GPU is NOT AVAILABLE

מערך IMDB

מערך הנתונים IMDB מכיל את הטקסט של 50,000 ביקורות על סרטים ממאגר הסרטים באינטרנט . אלה מחולקים ל 25,000 ביקורות לאימונים ו- 25,000 ביקורות לבדיקה. מערכי האימון והבדיקה מאוזנים , כלומר מכילים מספר שווה של ביקורות חיוביות ושליליות.

במדריך זה נשתמש בגרסה מעובדת מראש של מערך הנתונים IMDB.

הורד מערך IMDB שעובד מראש

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

הקוד הבא מוריד את מערך IMDB (או משתמש בעותק במטמון אם הוא כבר הורד):

imdb = tf.keras.datasets.imdb
(pp_train_data, pp_train_labels), (pp_test_data, pp_test_labels) = (
    imdb.load_data(num_words=10000))
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/imdb.npz
17465344/17464789 [==============================] - 0s 0us/step

הארגומנט num_words=10000 שומר על 10,000 המילים המופיעות בתדירות הגבוהה ביותר. המילים הנדירות מושלכות בכדי לשמור על גודל אוצר המילים.

חקור את הנתונים

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

print('Training entries: {}, labels: {}'.format(
    len(pp_train_data), len(pp_train_labels)))
training_samples_count = len(pp_train_data)
Training entries: 25000, labels: 25000

טקסט הביקורות הומר למספרים שלמים, כאשר כל מספר שלם מייצג מילה ספציפית במילון. כך נראית הסקירה הראשונה:

print(pp_train_data[0])
[1, 14, 22, 16, 43, 530, 973, 1622, 1385, 65, 458, 4468, 66, 3941, 4, 173, 36, 256, 5, 25, 100, 43, 838, 112, 50, 670, 2, 9, 35, 480, 284, 5, 150, 4, 172, 112, 167, 2, 336, 385, 39, 4, 172, 4536, 1111, 17, 546, 38, 13, 447, 4, 192, 50, 16, 6, 147, 2025, 19, 14, 22, 4, 1920, 4613, 469, 4, 22, 71, 87, 12, 16, 43, 530, 38, 76, 15, 13, 1247, 4, 22, 17, 515, 17, 12, 16, 626, 18, 2, 5, 62, 386, 12, 8, 316, 8, 106, 5, 4, 2223, 5244, 16, 480, 66, 3785, 33, 4, 130, 12, 16, 38, 619, 5, 25, 124, 51, 36, 135, 48, 25, 1415, 33, 6, 22, 12, 215, 28, 77, 52, 5, 14, 407, 16, 82, 2, 8, 4, 107, 117, 5952, 15, 256, 4, 2, 7, 3766, 5, 723, 36, 71, 43, 530, 476, 26, 400, 317, 46, 7, 4, 2, 1029, 13, 104, 88, 4, 381, 15, 297, 98, 32, 2071, 56, 26, 141, 6, 194, 7486, 18, 4, 226, 22, 21, 134, 476, 26, 480, 5, 144, 30, 5535, 18, 51, 36, 28, 224, 92, 25, 104, 4, 226, 65, 16, 38, 1334, 88, 12, 16, 283, 5, 16, 4472, 113, 103, 32, 15, 16, 5345, 19, 178, 32]

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

len(pp_train_data[0]), len(pp_train_data[1])
(218, 189)

להמיר את המספרים השלמים בחזרה למילים

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

def build_reverse_word_index():
  # A dictionary mapping words to an integer index
  word_index = imdb.get_word_index()

  # The first indices are reserved
  word_index = {k: (v + 3) for k, v in word_index.items()}
  word_index['<PAD>'] = 0
  word_index['<START>'] = 1
  word_index['<UNK>'] = 2  # unknown
  word_index['<UNUSED>'] = 3
  return dict((value, key) for (key, value) in word_index.items())

reverse_word_index = build_reverse_word_index()

def decode_review(text):
  return ' '.join([reverse_word_index.get(i, '?') for i in text])
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/imdb_word_index.json
1646592/1641221 [==============================] - 0s 0us/step

כעת נוכל להשתמש בפונקציה decode_review כדי להציג את הטקסט לבדיקה הראשונה:

decode_review(pp_train_data[0])
"<START> this film was just brilliant casting location scenery story direction everyone's really suited the part they played and you could just imagine being there robert <UNK> is an amazing actor and now the same being director <UNK> father came from the same scottish island as myself so i loved the fact there was a real connection with this film the witty remarks throughout the film were great it was just brilliant so much that i bought the film as soon as it was released for <UNK> and would recommend it to everyone to watch and the fly fishing was amazing really cried at the end it was so sad and you know what they say if you cry at a film it must have been good and this definitely was also <UNK> to the two little boy's that played the <UNK> of norman and paul they were just brilliant children are often left out of the <UNK> list i think because the stars that play them all grown up are such a big profile for the whole film but these children are amazing and should be praised for what they have done don't you think the whole story was so lovely because it was true and was someone's life after all that was shared with us all"

בניית גרפים

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

לפני שנמשיך הלאה, ראשית ניצור ספריה לאחסון חפצים שנוצרו על ידי מדריך זה.

mkdir -p /tmp/imdb

צור טבילות לדוגמא

נשתמש בהטבעות סיבוב מוכנות מראש כדי ליצור tf.train.Example בפורמט tf.train.Example דוגמה לכל דוגמא בכניסה. אנו נאחסן את TFRecord בפורמט TFRecord יחד עם תכונה נוספת המייצגת את המזהה של כל מדגם. זה חשוב ויאפשר לנו להתאים טבלאות לדוגמא לצמתים המתאימים בגרף בהמשך.

pretrained_embedding = 'https://tfhub.dev/google/tf2-preview/gnews-swivel-20dim/1'

hub_layer = hub.KerasLayer(
    pretrained_embedding, input_shape=[], dtype=tf.string, trainable=True)
def _int64_feature(value):
  """Returns int64 tf.train.Feature."""
  return tf.train.Feature(int64_list=tf.train.Int64List(value=value.tolist()))


def _bytes_feature(value):
  """Returns bytes tf.train.Feature."""
  return tf.train.Feature(
      bytes_list=tf.train.BytesList(value=[value.encode('utf-8')]))


def _float_feature(value):
  """Returns float tf.train.Feature."""
  return tf.train.Feature(float_list=tf.train.FloatList(value=value.tolist()))


def create_embedding_example(word_vector, record_id):
  """Create tf.Example containing the sample's embedding and its ID."""

  text = decode_review(word_vector)

  # Shape = [batch_size,].
  sentence_embedding = hub_layer(tf.reshape(text, shape=[-1,]))

  # Flatten the sentence embedding back to 1-D.
  sentence_embedding = tf.reshape(sentence_embedding, shape=[-1])

  features = {
      'id': _bytes_feature(str(record_id)),
      'embedding': _float_feature(sentence_embedding.numpy())
  }
  return tf.train.Example(features=tf.train.Features(feature=features))


def create_embeddings(word_vectors, output_path, starting_record_id):
  record_id = int(starting_record_id)
  with tf.io.TFRecordWriter(output_path) as writer:
    for word_vector in word_vectors:
      example = create_embedding_example(word_vector, record_id)
      record_id = record_id + 1
      writer.write(example.SerializeToString())
  return record_id


# Persist TF.Example features containing embeddings for training data in
# TFRecord format.
create_embeddings(pp_train_data, '/tmp/imdb/embeddings.tfr', 0)
25000

בנה גרף

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

למידה מובנית עצבית מספקת ספריית בניית גרפים לבניית גרף המבוסס על טבלאות לדוגמא. הוא משתמש בדמיון של קוסינוס כמדד הדמיון כדי להשוות בין הטביעות ולבנות קצוות ביניהן. זה גם מאפשר לנו לציין סף דמיון, שבאמצעותו ניתן להשליך קצוות שונים מהגרף הסופי. בדוגמה זו, שימוש ב- 0.99 כסף הדמיון וב- 12345 כזרע אקראי, בסופו של דבר גרף שיש לו 429,415 קצוות דו כיווניים. כאן אנו משתמשים בתמיכה של בונה הגרפים בחשיש רגיש ליישוב (LSH) כדי להאיץ את בניית הגרפים. לפרטים על השימוש בתמיכה של LSH של בונה הגרפים, עיין build_graph_from_config ה- API של build_graph_from_config .

graph_builder_config = nsl.configs.GraphBuilderConfig(
    similarity_threshold=0.99, lsh_splits=32, lsh_rounds=15, random_seed=12345)
nsl.tools.build_graph_from_config(['/tmp/imdb/embeddings.tfr'],
                                  '/tmp/imdb/graph_99.tsv',
                                  graph_builder_config)

כל קצה דו-כיווני מיוצג על ידי שני קצוות מכוונים בקובץ TSV הפלט, כך שהקובץ מכיל 429,415 * 2 = 858,830 שורות סה"כ:

wc -l /tmp/imdb/graph_99.tsv
858830 /tmp/imdb/graph_99.tsv

תכונות לדוגמא

אנו יוצרים תכונות לדוגמא לבעיה שלנו בעזרת פורמט tf.train.Example אותם בפורמט TFRecord . כל מדגם יכלול את שלוש התכונות הבאות:

  1. id : מזהה הצומת של המדגם.
  2. מילים : רשימה int64 המכילה מזהי מילים.
  3. תווית : סינגלטון int64 המזהה את מחלקת היעד של הסקירה.
def create_example(word_vector, label, record_id):
  """Create tf.Example containing the sample's word vector, label, and ID."""
  features = {
      'id': _bytes_feature(str(record_id)),
      'words': _int64_feature(np.asarray(word_vector)),
      'label': _int64_feature(np.asarray([label])),
  }
  return tf.train.Example(features=tf.train.Features(feature=features))

def create_records(word_vectors, labels, record_path, starting_record_id):
  record_id = int(starting_record_id)
  with tf.io.TFRecordWriter(record_path) as writer:
    for word_vector, label in zip(word_vectors, labels):
      example = create_example(word_vector, label, record_id)
      record_id = record_id + 1
      writer.write(example.SerializeToString())
  return record_id

# Persist TF.Example features (word vectors and labels) for training and test
# data in TFRecord format.
next_record_id = create_records(pp_train_data, pp_train_labels,
                                '/tmp/imdb/train_data.tfr', 0)
create_records(pp_test_data, pp_test_labels, '/tmp/imdb/test_data.tfr',
               next_record_id)
50000

נתוני אימוני הגדלה עם שכנים גרפים

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

במדריך זה אנו רואים קצוות לא מכוונים ומשתמשים במקסימום 3 שכנים לדגימה כדי להגדיל את נתוני האימון עם שכני הגרף.

nsl.tools.pack_nbrs(
    '/tmp/imdb/train_data.tfr',
    '',
    '/tmp/imdb/graph_99.tsv',
    '/tmp/imdb/nsl_train_data.tfr',
    add_undirected_edges=True,
    max_nbrs=3)

דגם בסיס

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

משתנים גלובליים

NBR_FEATURE_PREFIX = 'NL_nbr_'
NBR_WEIGHT_SUFFIX = '_weight'

היפרפרמטרים

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

  • מספר_קלאסים : ישנם 2 כיתות - חיוביות ושליליות .

  • max_seq_length : זהו המספר המרבי של מילים הנחשבות מכל ביקורת סרט בדוגמה זו.

  • vocab_size : זהו גודל אוצר המילים שנחשב לדוגמא זו.

  • distance_type : זהו מדד המרחק המשמש להסדרת המדגם עם שכניו.

  • graph_regularization_multiplier : זה שולט במשקל היחסי של מונח ויסות הגרף בפונקציית האובדן הכללי.

  • מספר_שכנים : מספר השכנים המשמשים להסדרת גרפים. ערך זה צריך להיות קטן או שווה לארגומנט max_nbrs המשמש לעיל בעת הפעלתnsl.tools.pack_nbrs .

  • num_fc_units : מספר היחידות בשכבה המחוברת במלואה של הרשת העצבית.

  • train_epochs : מספר תקופות האימון.

  • batch_size : גודל האצווה המשמש לאימון והערכה.

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

class HParams(object):
  """Hyperparameters used for training."""
  def __init__(self):
    ### dataset parameters
    self.num_classes = 2
    self.max_seq_length = 256
    self.vocab_size = 10000
    ### neural graph learning parameters
    self.distance_type = nsl.configs.DistanceType.L2
    self.graph_regularization_multiplier = 0.1
    self.num_neighbors = 2
    ### model architecture
    self.num_embedding_dims = 16
    self.num_lstm_dims = 64
    self.num_fc_units = 64
    ### training parameters
    self.train_epochs = 10
    self.batch_size = 128
    ### eval parameters
    self.eval_steps = None  # All instances in the test set are evaluated.

HPARAMS = HParams()

הכן את הנתונים

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

  • המר את המערכים לווקטורים של 0 שניות ו- 1 שניות המציינים התרחשות מילים, בדומה לקידוד חם אחד. לדוגמא, הרצף [3, 5] יהפוך לווקטור ממדי של 10000 שכולו אפסים למעט מדדים 3 ו 5 , שהם אחד. לאחר מכן, הפוך זאת לשכבה הראשונה ברשת שלנו - שכבה Dense - שיכולה להתמודד עם נתוני וקטור נקודה צפה. גישה זו היא אמנם אינטנסיבית בזיכרון, ודורשת מטריצת גודל num_words * num_reviews .

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

במדריך זה נשתמש בגישה השנייה.

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

def make_dataset(file_path, training=False):
  """Creates a `tf.data.TFRecordDataset`.

  Args:
    file_path: Name of the file in the `.tfrecord` format containing
      `tf.train.Example` objects.
    training: Boolean indicating if we are in training mode.

  Returns:
    An instance of `tf.data.TFRecordDataset` containing the `tf.train.Example`
    objects.
  """

  def pad_sequence(sequence, max_seq_length):
    """Pads the input sequence (a `tf.SparseTensor`) to `max_seq_length`."""
    pad_size = tf.maximum([0], max_seq_length - tf.shape(sequence)[0])
    padded = tf.concat(
        [sequence.values,
         tf.fill((pad_size), tf.cast(0, sequence.dtype))],
        axis=0)
    # The input sequence may be larger than max_seq_length. Truncate down if
    # necessary.
    return tf.slice(padded, [0], [max_seq_length])

  def parse_example(example_proto):
    """Extracts relevant fields from the `example_proto`.

    Args:
      example_proto: An instance of `tf.train.Example`.

    Returns:
      A pair whose first value is a dictionary containing relevant features
      and whose second value contains the ground truth labels.
    """
    # The 'words' feature is a variable length word ID vector.
    feature_spec = {
        'words': tf.io.VarLenFeature(tf.int64),
        'label': tf.io.FixedLenFeature((), tf.int64, default_value=-1),
    }
    # We also extract corresponding neighbor features in a similar manner to
    # the features above during training.
    if training:
      for i in range(HPARAMS.num_neighbors):
        nbr_feature_key = '{}{}_{}'.format(NBR_FEATURE_PREFIX, i, 'words')
        nbr_weight_key = '{}{}{}'.format(NBR_FEATURE_PREFIX, i,
                                         NBR_WEIGHT_SUFFIX)
        feature_spec[nbr_feature_key] = tf.io.VarLenFeature(tf.int64)

        # We assign a default value of 0.0 for the neighbor weight so that
        # graph regularization is done on samples based on their exact number
        # of neighbors. In other words, non-existent neighbors are discounted.
        feature_spec[nbr_weight_key] = tf.io.FixedLenFeature(
            [1], tf.float32, default_value=tf.constant([0.0]))

    features = tf.io.parse_single_example(example_proto, feature_spec)

    # Since the 'words' feature is a variable length word vector, we pad it to a
    # constant maximum length based on HPARAMS.max_seq_length
    features['words'] = pad_sequence(features['words'], HPARAMS.max_seq_length)
    if training:
      for i in range(HPARAMS.num_neighbors):
        nbr_feature_key = '{}{}_{}'.format(NBR_FEATURE_PREFIX, i, 'words')
        features[nbr_feature_key] = pad_sequence(features[nbr_feature_key],
                                                 HPARAMS.max_seq_length)

    labels = features.pop('label')
    return features, labels

  dataset = tf.data.TFRecordDataset([file_path])
  if training:
    dataset = dataset.shuffle(10000)
  dataset = dataset.map(parse_example)
  dataset = dataset.batch(HPARAMS.batch_size)
  return dataset


train_dataset = make_dataset('/tmp/imdb/nsl_train_data.tfr', True)
test_dataset = make_dataset('/tmp/imdb/test_data.tfr')

בנה את המודל

רשת עצבית נוצרת על ידי ערימת שכבות - לשם כך נדרשות שתי החלטות אדריכליות עיקריות:

  • בכמה שכבות להשתמש במודל?
  • בכמה יחידות נסתרות להשתמש לכל שכבה?

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

נשתמש ב- LSTM דו כיווני כמודל הבסיס שלנו במדריך זה.

# This function exists as an alternative to the bi-LSTM model used in this
# notebook.
def make_feed_forward_model():
  """Builds a simple 2 layer feed forward neural network."""
  inputs = tf.keras.Input(
      shape=(HPARAMS.max_seq_length,), dtype='int64', name='words')
  embedding_layer = tf.keras.layers.Embedding(HPARAMS.vocab_size, 16)(inputs)
  pooling_layer = tf.keras.layers.GlobalAveragePooling1D()(embedding_layer)
  dense_layer = tf.keras.layers.Dense(16, activation='relu')(pooling_layer)
  outputs = tf.keras.layers.Dense(1, activation='sigmoid')(dense_layer)
  return tf.keras.Model(inputs=inputs, outputs=outputs)


def make_bilstm_model():
  """Builds a bi-directional LSTM model."""
  inputs = tf.keras.Input(
      shape=(HPARAMS.max_seq_length,), dtype='int64', name='words')
  embedding_layer = tf.keras.layers.Embedding(HPARAMS.vocab_size,
                                              HPARAMS.num_embedding_dims)(
                                                  inputs)
  lstm_layer = tf.keras.layers.Bidirectional(
      tf.keras.layers.LSTM(HPARAMS.num_lstm_dims))(
          embedding_layer)
  dense_layer = tf.keras.layers.Dense(
      HPARAMS.num_fc_units, activation='relu')(
          lstm_layer)
  outputs = tf.keras.layers.Dense(1, activation='sigmoid')(dense_layer)
  return tf.keras.Model(inputs=inputs, outputs=outputs)


# Feel free to use an architecture of your choice.
model = make_bilstm_model()
model.summary()
Model: "functional_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
words (InputLayer)           [(None, 256)]             0         
_________________________________________________________________
embedding (Embedding)        (None, 256, 16)           160000    
_________________________________________________________________
bidirectional (Bidirectional (None, 128)               41472     
_________________________________________________________________
dense (Dense)                (None, 64)                8256      
_________________________________________________________________
dense_1 (Dense)              (None, 1)                 65        
=================================================================
Total params: 209,793
Trainable params: 209,793
Non-trainable params: 0
_________________________________________________________________

השכבות נערמות למעשה ברצף כדי לבנות את המסווג:

  1. השכבה הראשונה היא שכבת Input שלוקחת את אוצר המילים המקודד לשלמות.
  2. השכבה הבאה היא שכבת Embedding , הלוקחת את אוצר המילים המקודד לשלמות ומחפשת את וקטור ההטבעה עבור כל אינדקס מילים. וקטורים אלה נלמדים כאשר הרכבות המודל. הווקטורים מוסיפים מימד למערך הפלט. הממדים שהתקבלו הם: (batch, sequence, embedding) .
  3. לאחר מכן, שכבת LSTM דו כיוונית מחזירה וקטור פלט באורך קבוע לכל דוגמה.
  4. וקטור הפלט באורך קבוע זה מועבר דרך שכבה מחוברת לחלוטין ( Dense ) עם 64 יחידות נסתרות.
  5. השכבה האחרונה מחוברת בצפיפות עם צומת פלט יחיד. באמצעות פונקציית ההפעלה sigmoid , ערך זה הוא צף בין 0 ל -1, המייצג סבירות או רמת ביטחון.

יחידות נסתרות

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

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

תפקוד אובדן ומייעל

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

model.compile(
    optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])

צור ערכת אימות

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

במדריך זה, אנו לוקחים בערך 10% מדגימות האימון הראשוניות (10% מ- 25000) כנתונים שכותרתו לאימון והנותרים כנתוני אימות. מכיוון שפיצול הרכבת / המבחן הראשוני היה 50/50 (25000 דגימות כל אחד), פיצול הרכבת / אימות / טסט האפקטיבי שיש לנו כעת הוא 5/45/50.

שים לב ש- 'train_dataset' כבר זכה לאצווה ודשדוש.

validation_fraction = 0.9
validation_size = int(validation_fraction *
                      int(training_samples_count / HPARAMS.batch_size))
print(validation_size)
validation_dataset = train_dataset.take(validation_size)
train_dataset = train_dataset.skip(validation_size)
175

תאמן את המודל

אימן את הדגם במיני קבוצות. במהלך האימון, עקוב אחר אובדן המודל ודיוקו במערכת האימות:

history = model.fit(
    train_dataset,
    validation_data=validation_dataset,
    epochs=HPARAMS.train_epochs,
    verbose=1)
Epoch 1/10
/tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/keras/engine/functional.py:543: UserWarning: Input dict contained keys ['NL_nbr_0_words', 'NL_nbr_1_words', 'NL_nbr_0_weight', 'NL_nbr_1_weight'] which did not match any model input. They will be ignored by the model.
  [n for n in tensors.keys() if n not in ref_input_names])
21/21 [==============================] - 19s 917ms/step - loss: 0.6930 - accuracy: 0.5081 - val_loss: 0.6924 - val_accuracy: 0.5518
Epoch 2/10
21/21 [==============================] - 18s 878ms/step - loss: 0.6902 - accuracy: 0.5319 - val_loss: 0.6587 - val_accuracy: 0.6465
Epoch 3/10
21/21 [==============================] - 18s 879ms/step - loss: 0.6338 - accuracy: 0.6731 - val_loss: 0.5882 - val_accuracy: 0.7310
Epoch 4/10
21/21 [==============================] - 18s 872ms/step - loss: 0.4889 - accuracy: 0.7854 - val_loss: 0.4445 - val_accuracy: 0.8047
Epoch 5/10
21/21 [==============================] - 18s 872ms/step - loss: 0.3911 - accuracy: 0.8369 - val_loss: 0.3870 - val_accuracy: 0.8352
Epoch 6/10
21/21 [==============================] - 18s 877ms/step - loss: 0.3544 - accuracy: 0.8542 - val_loss: 0.3420 - val_accuracy: 0.8571
Epoch 7/10
21/21 [==============================] - 19s 900ms/step - loss: 0.3262 - accuracy: 0.8700 - val_loss: 0.3135 - val_accuracy: 0.8762
Epoch 8/10
21/21 [==============================] - 18s 871ms/step - loss: 0.2770 - accuracy: 0.8977 - val_loss: 0.2739 - val_accuracy: 0.8923
Epoch 9/10
21/21 [==============================] - 18s 872ms/step - loss: 0.2863 - accuracy: 0.8958 - val_loss: 0.2703 - val_accuracy: 0.8942
Epoch 10/10
21/21 [==============================] - 18s 875ms/step - loss: 0.2232 - accuracy: 0.9150 - val_loss: 0.2543 - val_accuracy: 0.9037

הערך את המודל

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

results = model.evaluate(test_dataset, steps=HPARAMS.eval_steps)
print(results)
196/196 [==============================] - 16s 82ms/step - loss: 0.3748 - accuracy: 0.8500
[0.37483155727386475, 0.8500000238418579]

צור גרף של דיוק / אובדן לאורך זמן

model.fit() מחזיר אובייקט History שמכיל מילון עם כל מה שקרה במהלך האימון:

history_dict = history.history
history_dict.keys()
dict_keys(['loss', 'accuracy', 'val_loss', 'val_accuracy'])

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

acc = history_dict['accuracy']
val_acc = history_dict['val_accuracy']
loss = history_dict['loss']
val_loss = history_dict['val_loss']

epochs = range(1, len(acc) + 1)

# "-r^" is for solid red line with triangle markers.
plt.plot(epochs, loss, '-r^', label='Training loss')
# "-b0" is for solid blue line with circle markers.
plt.plot(epochs, val_loss, '-bo', label='Validation loss')
plt.title('Training and validation loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend(loc='best')

plt.show()

png

plt.clf()   # clear figure

plt.plot(epochs, acc, '-r^', label='Training acc')
plt.plot(epochs, val_acc, '-bo', label='Validation acc')
plt.title('Training and validation accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend(loc='best')

plt.show()

png

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

רגולציה של גרפים

כעת אנו מוכנים לנסות להסדיר גרפים באמצעות מודל הבסיס שבנינו לעיל. נשתמש GraphRegularization העטיפה GraphRegularization המסופקת על ידי מסגרת הלמידה GraphRegularization העצבית כדי לעטוף את מודל הבסיס (bi-LSTM) כדי לכלול רגולציה של גרפים. שאר השלבים לאימון והערכת המודל המוסדר בגרף דומים לזה של מודל הבסיס.

צור מודל מוסדר גרפי

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

# Build a new base LSTM model.
base_reg_model = make_bilstm_model()
# Wrap the base model with graph regularization.
graph_reg_config = nsl.configs.make_graph_reg_config(
    max_neighbors=HPARAMS.num_neighbors,
    multiplier=HPARAMS.graph_regularization_multiplier,
    distance_type=HPARAMS.distance_type,
    sum_over_axis=-1)
graph_reg_model = nsl.keras.GraphRegularization(base_reg_model,
                                                graph_reg_config)
graph_reg_model.compile(
    optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])

תאמן את המודל

graph_reg_history = graph_reg_model.fit(
    train_dataset,
    validation_data=validation_dataset,
    epochs=HPARAMS.train_epochs,
    verbose=1)
Epoch 1/10
/tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/framework/indexed_slices.py:432: UserWarning: Converting sparse IndexedSlices to a dense Tensor of unknown shape. This may consume a large amount of memory.
  "Converting sparse IndexedSlices to a dense Tensor of unknown shape. "
21/21 [==============================] - 22s 1s/step - loss: 0.6925 - accuracy: 0.5135 - scaled_graph_loss: 7.8682e-06 - val_loss: 0.6925 - val_accuracy: 0.5207
Epoch 2/10
21/21 [==============================] - 22s 1s/step - loss: 0.6902 - accuracy: 0.5373 - scaled_graph_loss: 2.3502e-05 - val_loss: 0.6591 - val_accuracy: 0.6627
Epoch 3/10
21/21 [==============================] - 21s 981ms/step - loss: 0.6376 - accuracy: 0.6942 - scaled_graph_loss: 0.0028 - val_loss: 0.6867 - val_accuracy: 0.5343
Epoch 4/10
21/21 [==============================] - 20s 975ms/step - loss: 0.6240 - accuracy: 0.7031 - scaled_graph_loss: 9.6606e-04 - val_loss: 0.5891 - val_accuracy: 0.7572
Epoch 5/10
21/21 [==============================] - 20s 973ms/step - loss: 0.5111 - accuracy: 0.7896 - scaled_graph_loss: 0.0059 - val_loss: 0.4260 - val_accuracy: 0.8207
Epoch 6/10
21/21 [==============================] - 21s 981ms/step - loss: 0.3816 - accuracy: 0.8508 - scaled_graph_loss: 0.0157 - val_loss: 0.3182 - val_accuracy: 0.8682
Epoch 7/10
21/21 [==============================] - 20s 976ms/step - loss: 0.3488 - accuracy: 0.8704 - scaled_graph_loss: 0.0202 - val_loss: 0.3156 - val_accuracy: 0.8749
Epoch 8/10
21/21 [==============================] - 20s 973ms/step - loss: 0.3227 - accuracy: 0.8815 - scaled_graph_loss: 0.0198 - val_loss: 0.2746 - val_accuracy: 0.8932
Epoch 9/10
21/21 [==============================] - 21s 1s/step - loss: 0.3058 - accuracy: 0.8958 - scaled_graph_loss: 0.0220 - val_loss: 0.2938 - val_accuracy: 0.8833
Epoch 10/10
21/21 [==============================] - 21s 979ms/step - loss: 0.2789 - accuracy: 0.9008 - scaled_graph_loss: 0.0233 - val_loss: 0.2622 - val_accuracy: 0.8981

הערך את המודל

graph_reg_results = graph_reg_model.evaluate(test_dataset, steps=HPARAMS.eval_steps)
print(graph_reg_results)
196/196 [==============================] - 16s 82ms/step - loss: 0.3543 - accuracy: 0.8508
[0.354336142539978, 0.8507599830627441]

צור גרף של דיוק / אובדן לאורך זמן

graph_reg_history_dict = graph_reg_history.history
graph_reg_history_dict.keys()
dict_keys(['loss', 'accuracy', 'scaled_graph_loss', 'val_loss', 'val_accuracy'])

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

acc = graph_reg_history_dict['accuracy']
val_acc = graph_reg_history_dict['val_accuracy']
loss = graph_reg_history_dict['loss']
graph_loss = graph_reg_history_dict['scaled_graph_loss']
val_loss = graph_reg_history_dict['val_loss']

epochs = range(1, len(acc) + 1)

plt.clf()   # clear figure

# "-r^" is for solid red line with triangle markers.
plt.plot(epochs, loss, '-r^', label='Training loss')
# "-gD" is for solid green line with diamond markers.
plt.plot(epochs, graph_loss, '-gD', label='Training graph loss')
# "-b0" is for solid blue line with circle markers.
plt.plot(epochs, val_loss, '-bo', label='Validation loss')
plt.title('Training and validation loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend(loc='best')

plt.show()

png

plt.clf()   # clear figure

plt.plot(epochs, acc, '-r^', label='Training acc')
plt.plot(epochs, val_acc, '-bo', label='Validation acc')
plt.title('Training and validation accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend(loc='best')

plt.show()

png

כוח הלמידה המפוקחת למחצה

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

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

# Accuracy values for both the Bi-LSTM model and the feed forward NN model have
# been precomputed for the following supervision ratios.

supervision_ratios = [0.3, 0.15, 0.05, 0.03, 0.02, 0.01, 0.005]

model_tags = ['Bi-LSTM model', 'Feed Forward NN model']
base_model_accs = [[84, 84, 83, 80, 65, 52, 50], [87, 86, 76, 74, 67, 52, 51]]
graph_reg_model_accs = [[84, 84, 83, 83, 65, 63, 50],
                        [87, 86, 80, 75, 67, 52, 50]]

plt.clf()  # clear figure

fig, axes = plt.subplots(1, 2)
fig.set_size_inches((12, 5))

for ax, model_tag, base_model_acc, graph_reg_model_acc in zip(
    axes, model_tags, base_model_accs, graph_reg_model_accs):

  # "-r^" is for solid red line with triangle markers.
  ax.plot(base_model_acc, '-r^', label='Base model')
  # "-gD" is for solid green line with diamond markers.
  ax.plot(graph_reg_model_acc, '-gD', label='Graph-regularized model')
  ax.set_title(model_tag)
  ax.set_xlabel('Supervision ratio')
  ax.set_ylabel('Accuracy(%)')
  ax.set_ylim((25, 100))
  ax.set_xticks(range(len(supervision_ratios)))
  ax.set_xticklabels(supervision_ratios)
  ax.legend(loc='best')

plt.show()
<Figure size 432x288 with 0 Axes>

png

ניתן לראות שככל שיחס הראייה יורד, גם דיוק המודל פוחת. זה נכון גם עבור מודל הבסיס וגם עבור המודל המוסדר בגרף, ללא קשר לארכיטקטורת המודל בה נעשה שימוש. עם זאת, שים לב שהמודל המוסדר בגרף מבצע ביצועים טובים יותר מאשר מודל הבסיס עבור שני האדריכלות. בפרט, עבור מודל Bi-LSTM, כאשר יחס הפיקוח הוא 0.01, הדיוק של המודל המוסדר בגרף הוא ~ 20% יותר מזה של מודל הבסיס. הסיבה לכך היא בעיקר למידה מפוקחת למחצה עבור המודל המסודר בגרף, כאשר נעשה שימוש בדמיון מבני בין דגימות אימון בנוסף לדגימות האימון עצמן.

סיכום

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