لدي سؤال؟ تواصل مع المجتمع في منتدى زيارة منتدى TensorFlow

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

عرض على 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 الوسيطة 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 تنسيق 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.

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 باستخدام تنسيق tf.train.Example واستمرارها في تنسيق TFRecord . ستتضمن كل عينة الميزات الثلاث التالية:

  1. المعرف : معرف العقدة للعينة.
  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'

Hyperparameters

سنستخدم HParams من HParams العديد من HParams والثوابت المستخدمة في التدريب والتقييم. نصف بإيجاز كل منهم أدناه:

  • num_classes: هناك 2 الطبقات - الإيجابية والسلبية.

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

  • vocab_size : هذا هو حجم المفردات التي تم أخذها في الاعتبار في هذا المثال.

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

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

  • num_neighbours : عدد الجيران المستخدم في تسوية الرسم البياني. يجب أن تكون هذه القيمة أقل من وسيطة max_nbrs المستخدمة أعلاه أو مساوية لها عند استدعاءnsl.tools.pack_nbrs .

  • num_fc_units : عدد الوحدات في الطبقة المتصلة بالكامل من الشبكة العصبية.

  • train_epochs : عدد فترات التدريب.

  • حجم_الدفعة : حجم الدفعة المستخدمة للتدريب والتقييم.

  • Eval_steps : عدد الدُفعات المطلوب معالجتها قبل اعتبار التقييم مكتملًا. إذا تم التعيين إلى None ، 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 s و 1 s تشير إلى حدوث كلمة ، على غرار ترميز واحد ساخن. على سبيل المثال ، سيصبح المتسلسل [3, 5] متجهًا 10000 يمثل جميع الأصفار باستثناء الفهرين 3 و 5 ، وهما واحدان. بعد ذلك ، اجعل هذه الطبقة الأولى في شبكتنا - طبقة Dense - يمكنها التعامل مع بيانات متجه النقطة العائمة. هذا الأسلوب num_words * num_reviews الذاكرة ، على الرغم من أنه يتطلب مصفوفة بحجم 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 طبقة Embedding . عدد النواتج (الوحدات ، العقد ، أو الخلايا العصبية) هو بُعد المساحة التمثيلية للطبقة. بمعنى آخر ، مقدار الحرية المسموح به للشبكة عند تعلم تمثيل داخلي.

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

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

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

بي إن جي

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 فئة غلاف GraphRegularization التي يوفرها إطار عمل Neural Structured Learning لتغطية النموذج الأساسي (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()

بي إن جي

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