本頁面由 Cloud Translation API 翻譯而成。
Switch to English

使用合成圖進行情感分類的圖正則化

在TensorFlow.org上查看 在Google Colab中運行 在GitHub上查看源代碼

總覽

該筆記本使用評論文本將電影評論分為正面評論或負面評論。這是二進制分類的示例, 二進制分類是一種重要且廣泛適用的機器學習問題。

我們將通過從給定的輸入中構建一個圖形來演示在此筆記本中圖形正則化的用法。當輸入不包含顯式圖時,使用神經結構學習(NSL)框架構建圖正則化模型的一般方法如下:

  1. 為輸入中的每個文本樣本創建嵌入。這可以使用預先訓練的模型來完成,例如word2vecSwivelBERT等。
  2. 通過使用“ L2”距離,“餘弦”距離等相似性度量,基於這些嵌入構建圖。圖中的節點對應於樣本,圖中的邊對應於成對樣本之間的相似性。
  3. 從上面的合成圖和样本特徵生成訓練數據。所得的訓練數據除原始節點特徵外還將包含鄰居特徵。
  4. 使用Keras順序,功能或子類API將神經網絡創建為基本模型。
  5. 使用由NSL框架提供的GraphRegularization包裝器類包裝基本模型,以創建新的圖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數據集包含來自Internet電影數據庫的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個最頻繁出現的單詞保留num_words=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

創建樣本嵌入

我們將使用預訓練的Swivel嵌入在tf.train.Example格式中為輸入中的每個樣本創建嵌入。我們將以TFRecord格式存儲生成的嵌入TFRecord以及代表每個樣本ID的其他功能。這很重要,這將使我們稍後可以將樣本嵌入與圖中的相應節點進行匹配。

 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格式為問題創建示例功能,並將其保留為TFRecord格式。每個樣本將包括以下三個功能:

  1. id :樣本的節點ID。
  2. words :一個包含單詞ID的int64列表。
  3. label :一個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用於訓練和評估的各種超參數和常量。我們在下面簡要介紹它們:

  • num_classes :分為2類- 正數負數

  • max_seq_length :在此示例中,這是每個電影評論考慮的最大單詞數。

  • vocab_size :這是此示例考慮的詞彙表的大小。

  • distance_type :這是用於將樣本與其鄰居進行正則化的距離度量。

  • graph_regularization_multiplier :這控製圖規則化項在整體損失函數中的相對權重。

  • num_neighbors :用於圖正則化的鄰居數。調用nsl.tools.pack_nbrs時,此值必須小於或等於上面使用的max_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 s和1 s向量,類似於一鍵編碼。例如,序列[3, 5]將成為一個10000維向量,除了索引35都是1之外,它們全為零。然後,使其成為我們網絡中的第一層- 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. 該固定長度的輸出矢量通過具有64個隱藏單元的完全連接( Dense )層進行管道傳輸。
  5. 最後一層與單個輸出節點緊密連接。使用sigmoid激活函數,此值是0到1之間的浮點數,表示概率或置信度。

隱藏的單位

上面的模型在輸入和輸出之間有兩個中間層或“隱藏”層,不包括Embedding層。輸出(單元,節點或神經元)的數量是該圖層的表示空間的尺寸。換句話說,學習內部表示時允許網絡自由度。

如果模型具有更多的隱藏單元(較高維的表示空間)和/或更多的層,則網絡可以學習更多複雜的表示。但是,這會使網絡的計算成本更高,並且可能導致學習不必要的模式,這些模式可以提高訓練數據的性能,但不能提高測試數據的性能。這稱為過擬合

損失函數和優化器

模型需要損失函數和用於訓練的優化器。由於這是一個二進制分類問題,並且該模型輸出概率(具有S形激活的單單元層),因此我們將使用binary_crossentropy損失函數。

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

創建一個驗證集

訓練時,我們要根據以前從未見過的數據檢查模型的準確性。通過分開一部分原始訓練數據來創建驗證集 。 (為什麼現在不使用測試集?我們的目標是僅使用訓練數據來開發和調整模型,然後僅使用測試數據一次來評估我們的準確性)。

在本教程中,我們將大約10%的初始訓練樣本(25000中的10%)作為標記數據進行訓練,其餘作為驗證數據。由於初始訓練/測試劃分為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 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()
 

png

 plt.clf()   # clear figure

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

plt.show()
 

png

請注意,訓練損失在每個時期都會減少 ,而訓練精度在每個時期都增加 。當使用梯度下降優化時,這是可以預期的-它應該在每次迭代中將所需的數量最小化。

圖正則化

現在,我們準備使用上面構建的基本模型嘗試圖形正則化。我們將使用神經結構學習框架提供的GraphRegularization包裝器類包裝基本(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()
 

png

 plt.clf()   # clear figure

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

plt.show()
 

png

半監督學習的力量

當訓練數據量很少時,半監督學習,更具體地說,在本教程的上下文中的圖正則化可能非常強大。缺乏訓練數據可通過利用訓練樣本之間的相似性來彌補,這在傳統的有監督學習中是不可能的。

我們將監督比率定義為訓練樣本與樣本總數之比,其中包括訓練樣本,驗證樣本和測試樣本。在此筆記本中,我們使用了0.05的監管比率(即,標記數據的5%)來訓練基本模型和圖形正則化模型。我們在下面的單元格中說明監管比率對模型準確性的影響。

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

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

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

plt.clf()  # clear figure

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

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

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

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

png

可以看出,隨著監督比率的降低,模型的準確性也會降低。無論使用哪種模型體系結構,基本模型和圖正則化模型都是如此。但是,請注意,對於這兩種架構,圖規則化模型的性能均優於基本模型。特別是,對於Bi-LSTM模型,當監督比率為0.01時,圖形正則化模型的精度比基本模型的精度高約20% 。這主要是由於針對圖正則化模型的半監督學習,其中除了訓練樣本本身之外,還使用了訓練樣本之間的結構相似性。

結論

我們已經證明了使用神經結構化學習(NSL)框架進行圖正則化的使用,即使輸入不包含顯式圖也是如此。我們考慮了IMDB電影評論的情感分類任務,為此我們基於評論嵌入合成了相似度圖。我們鼓勵用戶通過更改超參數,監控量以及使用不同的模型架構來進一步進行實驗。