![]() | ![]() | ![]() |
סקירה כללית
מחברת זו מסווגת ביקורות על סרטים כחיוביות או שליליות באמצעות טקסט הביקורת. זוהי דוגמה לסיווג בינארי , סוג חשוב ויישום נרחב של בעיית למידת מכונה.
נדגים את השימוש ברגולציה של גרפים במחברת זו על ידי בניית גרף מהקלט הנתון. המתכון הכללי לבניית מודל מוסדר גרף תוך שימוש במסגרת למידה מובנית עצבית (NSL) כאשר הקלט אינו מכיל גרף מפורש הוא כדלקמן:
- צור טבלאות לכל דוגמת טקסט בכניסה. ניתן לעשות זאת באמצעות מודלים שהוכשרו מראש כגון word2vec , Swivel , BERT וכו '.
- בנה גרף המבוסס על השיבוצים הללו על ידי שימוש במדד דמיון כגון מרחק 'L2', מרחק 'קוסינוס' וכו '. הצמתים בתרשים תואמים לדגימות והקצוות בתרשים תואמים לדמיון בין זוגות הדגימות.
- צור נתוני אימון מהגרף המסונתז לעיל ומאפייני דוגמה. נתוני האימון המתקבלים יכילו תכונות שכנות בנוסף לתכונות הצומת המקוריות.
- צור רשת עצבית כמודל בסיס באמצעות ממשק ה- API הרציף, הפונקציונלי או תת-הקלאסי של Keras.
- עטוף את המודל הבסיסי עם מחלקת העטיפה GraphRegularization, המסופקת על ידי מסגרת NSL, כדי ליצור מודל גרף חדש של Keras. מודל חדש זה יכלול הפסד רגולציה של גרף כמונח ההסדרה במטרת ההכשרה שלו.
- התאמן והעריך את מודל הגרף Keras.
דרישות
- התקן את חבילת הלמידה המובנית העצבית.
- התקן את רכזת 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
. כל מדגם יכלול את שלוש התכונות הבאות:
- id : מזהה הצומת של המדגם.
- מילים : רשימה int64 המכילה מזהי מילים.
- תווית : סינגלטון 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 _________________________________________________________________
השכבות נערמות למעשה ברצף כדי לבנות את המסווג:
- השכבה הראשונה היא שכבת
Input
שלוקחת את אוצר המילים המקודד לשלמות. - השכבה הבאה היא שכבת
Embedding
, הלוקחת את אוצר המילים המקודד לשלמות ומחפשת את וקטור ההטבעה עבור כל אינדקס מילים. וקטורים אלה נלמדים כאשר הרכבות המודל. הווקטורים מוסיפים מימד למערך הפלט. הממדים שהתקבלו הם:(batch, sequence, embedding)
. - לאחר מכן, שכבת LSTM דו כיוונית מחזירה וקטור פלט באורך קבוע לכל דוגמה.
- וקטור הפלט באורך קבוע זה מועבר דרך שכבה מחוברת לחלוטין (
Dense
) עם 64 יחידות נסתרות. - השכבה האחרונה מחוברת בצפיפות עם צומת פלט יחיד. באמצעות פונקציית ההפעלה
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()
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
המסופקת על ידי מסגרת הלמידה 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()
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 שבגינן סינתזנו גרף דמיון על סמך טבילות ביקורת. אנו ממליצים למשתמשים להתנסות בהמשך על ידי שינוי היפרפרמטרים, כמות הפיקוח ושימוש בארכיטקטורות מודל שונות.