ترجمت واجهة Cloud Translation API‏ هذه الصفحة.
Switch to English

تسوية الرسم البياني لتصنيف المشاعر باستخدام الرسوم البيانية المركبة

عرض على TensorFlow.org تشغيل في Google Colab عرض المصدر على جيثب

نظرة عامة

يصنف هذا الكمبيوتر الدفتري مراجعات الفيلم على أنها إيجابية أو سلبية باستخدام نص المراجعة. هذا مثال على التصنيف الثنائي ، وهو نوع مهم وقابل للتطبيق على نطاق واسع من مشكلة التعلم الآلي.

سنوضح استخدام تسوية الرسم البياني في هذا الكمبيوتر المحمول من خلال إنشاء رسم بياني من المدخلات المحددة. الوصفة العامة لبناء نموذج منظم للرسم البياني باستخدام إطار التعلم العصبي المنظم (NSL) عندما لا يحتوي الإدخال على رسم بياني صريح كما يلي:

  1. إنشاء التضمينات لكل عينة نصية في الإدخال. يمكن القيام بذلك باستخدام نماذج مدربة مسبقًا مثل word2vec و Swivel و BERT وما إلى ذلك.
  2. أنشئ رسمًا بيانيًا استنادًا إلى هذه التضمينات باستخدام مقياس التشابه مثل مسافة "L2" ، ومسافة "جيب التمام" ، وما إلى ذلك. العقد في الرسم البياني تتوافق مع العينات والحواف في الرسم البياني تتوافق مع التشابه بين أزواج العينات.
  3. إنشاء بيانات تدريب من الرسم البياني المركب أعلاه وميزات العينة. ستحتوي بيانات التدريب الناتجة على ميزات الجوار بالإضافة إلى ميزات العقدة الأصلية.
  4. قم بإنشاء شبكة عصبية كنموذج أساسي باستخدام واجهة برمجة تطبيقات Keras التسلسلية أو الوظيفية أو الفرعية.
  5. قم بلف النموذج الأساسي مع فئة غلاف GraphRegularization ، التي يوفرها إطار عمل NSL ، لإنشاء نموذج رسم بياني جديد Keras. سيتضمن هذا النموذج الجديد خسارة تسوية الرسم البياني كمصطلح تسوية في هدفه التدريبي.
  6. تدريب وتقييم نموذج الرسم البياني Keras.

المتطلبات

  1. تثبيت حزمة التعلم العصبي المنظم.
  2. قم بتثبيت tensorflow-hub.
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 مراجعة للأفلام من قاعدة بيانات أفلام الإنترنت . تنقسم هذه إلى 25000 مراجعة للتدريب و 25000 مراجعة للاختبار. مجموعات التدريب والاختبار متوازنة ، مما يعني أنها تحتوي على عدد متساو من المراجعات الإيجابية والسلبية.

في هذا البرنامج التعليمي ، سنستخدم إصدارًا معالجًا مسبقًا من مجموعة بيانات 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 تحافظ على أفضل 10000 كلمة متكررة في بيانات التدريب. يتم تجاهل الكلمات النادرة للحفاظ على حجم المفردات يمكن التحكم فيها.

استكشف البيانات

دعنا نتوقف لحظة لفهم تنسيق البيانات. تأتي مجموعة البيانات مسبقة المعالجة: كل مثال عبارة عن مجموعة من الأعداد الصحيحة التي تمثل كلمات مراجعة الفيلم. كل تسمية هي قيمة صحيحة إما 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 جنبًا إلى جنب مع ميزة إضافية تمثل معرف كل عينة. هذا مهم وسيسمح لنا بمطابقة نماذج التضمين مع العقد المقابلة في الرسم البياني لاحقًا.

 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 كعتبة التشابه ، ينتهي بنا المطاف برسم بياني يحتوي على 445،327 حواف ثنائية الاتجاه.

 nsl.tools.build_graph(['/tmp/imdb/embeddings.tfr'],
                      '/tmp/imdb/graph_99.tsv',
                      similarity_threshold=0.99)
 

ميزات العينة

ننشئ ميزات نموذجية tf.train.Example باستخدام تنسيق 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 والثوابت المستخدمة للتدريب والتقييم. نحن نصف بإيجاز كل منهم أدناه:

  • num_classes : هناك فئتان - إيجابية وسلبية .

  • max_seq_length : هذا هو الحد الأقصى لعدد الكلمات التي يتم أخذها في الاعتبار من كل مراجعة فيلم في هذا المثال.

  • vocab_size : هذا هو حجم المفردات التي تم النظر فيها لهذا المثال.

  • المسافة_النوع : هذا هو مقياس المسافة المستخدم لتسوية العينة مع جيرانها.

  • graph_regularization_multiplier : يتحكم هذا في الوزن النسبي لمصطلح تسوية الرسم البياني في دالة الخسارة الإجمالية.

  • num_neighbors : عدد الجيران المستخدم لتسوية الرسم البياني. يجب أن تكون هذه القيمة أقل من أو تساوي الوسيطة 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 المحددة أدناه لتوحيد الأطوال.

 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 . عدد المخرجات (الوحدات أو العقد أو الخلايا العصبية) هو البعد من الفضاء التمثيلي للطبقة. بمعنى آخر ، مقدار الحرية المسموح بها للشبكة عند تعلم تمثيل داخلي.

إذا كان النموذج يحتوي على المزيد من الوحدات المخفية (مساحة تمثيل أعلى الأبعاد) و / أو طبقات أكثر ، فيمكن للشبكة معرفة تمثيلات أكثر تعقيدًا. ومع ذلك ، فإنه يجعل الشبكة أكثر تكلفة من الناحية الحسابية وقد يؤدي إلى تعلم الأنماط غير المرغوب فيها - أنماط تحسن الأداء في بيانات التدريب ولكن ليس في بيانات الاختبار. وهذا ما يسمى بالتضميد الزائد .

وظيفة الخسارة والمحسن

يحتاج النموذج إلى وظيفة فقدان ومحسن للتدريب. نظرًا لأن هذه مشكلة تصنيف ثنائية binary_crossentropy النموذج احتمالية (طبقة وحدة واحدة مع تنشيط سيني) ، فسوف نستخدم وظيفة فقدان binary_crossentropy .

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

إنشاء مجموعة التحقق من الصحة

عند التدريب ، نريد التحقق من دقة النموذج على البيانات التي لم يراها من قبل. أنشئ مجموعة تحقق من خلال فصل جزء صغير من بيانات التدريب الأصلية. (لماذا لا تستخدم مجموعة الاختبار الآن؟ هدفنا هو تطوير نموذجنا وضبطه باستخدام بيانات التدريب فقط ، ثم استخدام بيانات الاختبار مرة واحدة فقط لتقييم دقتنا).

في هذا البرنامج التعليمي ، نأخذ ما يقرب من 10٪ من عينات التدريب الأولية (10٪ من 25000) كبيانات مصنفة للتدريب والباقي كبيانات للتحقق. نظرًا لأن تقسيم القطار / الاختبار الأولي كان 50/50 (25000 عينة لكل منهما) ، فإن تقسيم القطار / التحقق / الاختبار الفعال الذي لدينا الآن هو 5/45/50.

لاحظ أن "مجموعة بيانات القطار" قد تم تجميعها وتعديلها بالفعل.

 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 925ms/step - loss: 0.6930 - accuracy: 0.5092 - val_loss: 0.6924 - val_accuracy: 0.5006
Epoch 2/10
21/21 [==============================] - 19s 894ms/step - loss: 0.6890 - accuracy: 0.5465 - val_loss: 0.7294 - val_accuracy: 0.5698
Epoch 3/10
21/21 [==============================] - 19s 883ms/step - loss: 0.6785 - accuracy: 0.6208 - val_loss: 0.6489 - val_accuracy: 0.7043
Epoch 4/10
21/21 [==============================] - 19s 890ms/step - loss: 0.6592 - accuracy: 0.6400 - val_loss: 0.6523 - val_accuracy: 0.6866
Epoch 5/10
21/21 [==============================] - 19s 883ms/step - loss: 0.6413 - accuracy: 0.6923 - val_loss: 0.6335 - val_accuracy: 0.7004
Epoch 6/10
21/21 [==============================] - 21s 982ms/step - loss: 0.6053 - accuracy: 0.7188 - val_loss: 0.5716 - val_accuracy: 0.7183
Epoch 7/10
21/21 [==============================] - 18s 879ms/step - loss: 0.5204 - accuracy: 0.7619 - val_loss: 0.4511 - val_accuracy: 0.7930
Epoch 8/10
21/21 [==============================] - 19s 882ms/step - loss: 0.4719 - accuracy: 0.7758 - val_loss: 0.4244 - val_accuracy: 0.8094
Epoch 9/10
21/21 [==============================] - 18s 880ms/step - loss: 0.3695 - accuracy: 0.8431 - val_loss: 0.3567 - val_accuracy: 0.8487
Epoch 10/10
21/21 [==============================] - 19s 891ms/step - loss: 0.3504 - accuracy: 0.8500 - val_loss: 0.3219 - val_accuracy: 0.8652

قم بتقييم النموذج

الآن ، دعنا نرى كيف يعمل النموذج. سيتم إرجاع قيمتين. الخسارة (رقم يمثل خطأنا ، والقيم الأقل أفضل) ، والدقة.

 results = model.evaluate(test_dataset, steps=HPARAMS.eval_steps)
print(results)
 
196/196 [==============================] - 17s 85ms/step - loss: 0.4116 - accuracy: 0.8221
[0.4116455018520355, 0.8221200108528137]

إنشاء رسم بياني للدقة / الخسارة بمرور الوقت

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

بي إن جي

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

بي إن جي

لاحظ أن فقدان التدريب ينخفض مع كل حقبة وتزيد دقة التدريب مع كل حقبة. هذا أمر متوقع عند استخدام تحسين نزول التدرج - يجب أن يقلل الكمية المطلوبة في كل تكرار.

تسوية الرسم البياني

نحن الآن جاهزون لمحاولة تسوية الرسم البياني باستخدام النموذج الأساسي الذي بنيناه أعلاه. سوف نستخدم فئة غلاف 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.6930 - accuracy: 0.5246 - scaled_graph_loss: 2.9800e-06 - val_loss: 0.6929 - val_accuracy: 0.4998
Epoch 2/10
21/21 [==============================] - 21s 988ms/step - loss: 0.6909 - accuracy: 0.5200 - scaled_graph_loss: 7.8452e-06 - val_loss: 0.6838 - val_accuracy: 0.5917
Epoch 3/10
21/21 [==============================] - 21s 980ms/step - loss: 0.6656 - accuracy: 0.6277 - scaled_graph_loss: 6.1205e-04 - val_loss: 0.6591 - val_accuracy: 0.6905
Epoch 4/10
21/21 [==============================] - 21s 981ms/step - loss: 0.6395 - accuracy: 0.6846 - scaled_graph_loss: 0.0016 - val_loss: 0.5860 - val_accuracy: 0.7171
Epoch 5/10
21/21 [==============================] - 21s 980ms/step - loss: 0.5388 - accuracy: 0.7573 - scaled_graph_loss: 0.0043 - val_loss: 0.4910 - val_accuracy: 0.7844
Epoch 6/10
21/21 [==============================] - 21s 989ms/step - loss: 0.4105 - accuracy: 0.8281 - scaled_graph_loss: 0.0146 - val_loss: 0.3353 - val_accuracy: 0.8612
Epoch 7/10
21/21 [==============================] - 21s 986ms/step - loss: 0.3416 - accuracy: 0.8681 - scaled_graph_loss: 0.0203 - val_loss: 0.4134 - val_accuracy: 0.8209
Epoch 8/10
21/21 [==============================] - 21s 981ms/step - loss: 0.4230 - accuracy: 0.8273 - scaled_graph_loss: 0.0144 - val_loss: 0.4755 - val_accuracy: 0.7696
Epoch 9/10
21/21 [==============================] - 22s 1s/step - loss: 0.4905 - accuracy: 0.7950 - scaled_graph_loss: 0.0080 - val_loss: 0.3862 - val_accuracy: 0.8382
Epoch 10/10
21/21 [==============================] - 21s 978ms/step - loss: 0.3384 - accuracy: 0.8754 - scaled_graph_loss: 0.0215 - val_loss: 0.3002 - val_accuracy: 0.8811

قم بتقييم النموذج

 graph_reg_results = graph_reg_model.evaluate(test_dataset, steps=HPARAMS.eval_steps)
print(graph_reg_results)
 
196/196 [==============================] - 16s 84ms/step - loss: 0.3852 - accuracy: 0.8301
[0.385225385427475, 0.830079972743988]

إنشاء رسم بياني للدقة / الخسارة بمرور الوقت

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

بي إن جي

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

بي إن جي

قوة التعلم شبه الخاضع للإشراف

يمكن أن يكون التعلم شبه الخاضع للإشراف ، وبشكل أكثر تحديدًا ، تسوية الرسم البياني في سياق هذا البرنامج التعليمي قوية حقًا عندما تكون كمية بيانات التدريب صغيرة. يتم تعويض نقص بيانات التدريب من خلال زيادة التشابه بين عينات التدريب ، وهو أمر غير ممكن في التعلم تحت الإشراف التقليدي.

نحدد نسبة الإشراف على أنها نسبة عينات التدريب إلى العدد الإجمالي للعينات التي تشمل عينات التدريب والتحقق والاختبار. لقد استخدمنا في هذا الكمبيوتر الدفتري نسبة إشراف 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>

بي إن جي

يمكن ملاحظة أنه مع انخفاض نسبة الدقة الفائقة ، تنخفض أيضًا دقة النموذج. وينطبق ذلك على كل من النموذج الأساسي والنموذج المنظم للرسم البياني ، بغض النظر عن بنية النموذج المستخدمة. ومع ذلك ، لاحظ أن أداء النموذج المرتبط بالرسم البياني أفضل من النموذج الأساسي لكلا المعماريين. على وجه الخصوص ، بالنسبة لنموذج Bi-LSTM ، عندما تكون نسبة الإشراف 0.01 ، فإن دقة النموذج المعدل للرسم البياني أعلى بنسبة 20٪ تقريبًا من دقة النموذج الأساسي. ويرجع ذلك في المقام الأول إلى التعلم شبه الخاضع للنموذج المنظم للرسم البياني ، حيث يتم استخدام التشابه الهيكلي بين عينات التدريب بالإضافة إلى عينات التدريب نفسها.

خاتمة

لقد أثبتنا استخدام تنظيم الرسم البياني باستخدام إطار التعلم العصبي المنظم (NSL) حتى عندما لا يحتوي الإدخال على رسم بياني صريح. لقد أخذنا في الاعتبار مهمة تصنيف المشاعر لمراجعات أفلام IMDB التي قمنا بتجميع رسم بياني للتشابه يعتمد على تضمين المراجعة. نحن نشجع المستخدمين على إجراء المزيد من التجارب عن طريق تنوع المعلمات ، ومقدار الإشراف ، وباستخدام نماذج مختلفة.