الشبكات العصبية المتكررة (RNN) مع Keras

ترجمة دليل الشبكة العصبية العودية من Tensorflow.org. تناقش المادة كلاً من القدرات المدمجة لـ Keras / Tensorflow 2.0 للتشبيك السريع ، بالإضافة إلى إمكانية تخصيص الطبقات والخلايا. كما يتم النظر في حالات وحدود استخدام نواة CuDNN ، مما يسمح بتسريع عملية تعلم الشبكة العصبية.



الشبكات العصبية العودية (RNNs) هي فئة من الشبكات العصبية جيدة لنمذجة البيانات التسلسلية ، مثل السلاسل الزمنية أو اللغة الطبيعية.

إذا كانت الطبقة RNN تستخدم بشكل تخطيطي حلقة forللتكرار عبر تسلسل مرتب حسب الوقت ، أثناء التخزين في حالة داخلية ، يتم ترميز المعلومات حول الخطوات التي شاهدها بالفعل.

تم تصميم Keras RNN API مع التركيز على:

سهولة الاستخدام : المدمج في طبقات tf.keras.layers.RNN، tf.keras.layers.LSTM، tf.keras.layers.GRUتسمح لك لبناء بسرعة نموذجا متكررة دون الحاجة إلى إجراء إعدادات التكوين معقدة.

التخصيص السهل : يمكنك أيضًا تحديد الطبقة الخاصة بك من خلايا RNN (الجزء الداخلي من الحلقةfor) مع السلوك المخصص واستخدامه مع طبقة مشتركة من `tf.keras.layers.RNN` (حلقة` for` نفسها). سيتيح لك ذلك وضع نماذج لأفكار البحث المختلفة بسرعة وبأسلوب مرن ، مع الحد الأدنى من التعليمات البرمجية.

التركيب


from __future__ import absolute_import, division, print_function, unicode_literals

import collections
import matplotlib.pyplot as plt
import numpy as np

import tensorflow as tf

from tensorflow.keras import layers

بناء نموذج بسيط


تحتوي Keras على ثلاث طبقات RNN مضمنة:

  1. tf.keras.layers.SimpleRNN، RNN متصل بالكامل بحيث يتم تمرير ناتج الخطوة الزمنية السابقة إلى الخطوة التالية.
  2. tf.keras.layers.GRU، اقترح لأول مرة في المقالة دراسة العبارات باستخدام برنامج الترميز RNN للترجمة الآلية الإحصائية
  3. tf.keras.layers.LSTM، اقترح لأول مرة في مقال طويل الأمد الذاكرة قصيرة المدى

في أوائل عام 2015 ، قدمت Keras أول تطبيقات مفتوحة المصدر قابلة لإعادة الاستخدام Python و LSTM و GRU.

فيما يلي مثال على Sequentialنموذج يعالج تسلسل الأعداد الصحيحة عن طريق تداخل كل عدد صحيح في ناقل 64-الأبعاد ، ثم معالجة تسلسلات المتجهات باستخدام طبقة LSTM.

model = tf.keras.Sequential()
#   Embedding      1000, 
#     64.
model.add(layers.Embedding(input_dim=1000, output_dim=64))

#   LSTM  128  .
model.add(layers.LSTM(128))

#   Dense  10    softmax.
model.add(layers.Dense(10))

model.summary()

المخرجات والحالات


بشكل افتراضي ، يحتوي إخراج طبقة RNN على متجه واحد لكل عنصر. هذا المتجه هو إخراج آخر خلية RNN تحتوي على معلومات حول تسلسل الإدخال بأكمله. بُعد هذا الإخراج (batch_size, units)، حيث unitsيتوافق مع الوسيطة التي unitsتم تمريرها إلى مُنشئ الطبقة.

يمكن لطبقة RNN أيضًا إرجاع تسلسل الإخراج الكامل لكل عنصر (متجه واحد لكل خطوة) ، إذا حددت return_sequences=True. البعد من هذا الناتج (batch_size, timesteps, units).

model = tf.keras.Sequential()
model.add(layers.Embedding(input_dim=1000, output_dim=64))

#  GRU  3D   (batch_size, timesteps, 256)
model.add(layers.GRU(256, return_sequences=True))

#  SimpleRNN  2D   (batch_size, 128)
model.add(layers.SimpleRNN(128))

model.add(layers.Dense(10))

model.summary()

بالإضافة إلى ذلك ، يمكن لطبقة RNN إرجاع حالتها (حالاتها) الداخلية النهائية.

يمكن استخدام الحالات المرتجعة لاحقًا لاستئناف تنفيذ RNN أو لتهيئة RNN أخرى . يُستخدم هذا الإعداد عادةً في نموذج وحدة التشفير - التشفير ، بالتسلسل إلى التسلسل ، حيث يتم استخدام الحالة النهائية للمشفرة للحالة الأولية لمفكك الشفرة.

لكي تقوم طبقة RNN بإعادة حالتها الداخلية ، قم بتعيين المعلمة return_stateعلى القيمة Trueعند إنشاء الطبقة. لاحظ أن هناك LSTMموتران لحالة الولاية GRUوواحد فقط.

لضبط الحالة الأولية للطبقة ، ما عليك سوى استدعاء الطبقة بحجة إضافية initial_state.

لاحظ أن البعد يجب أن يتطابق مع بُعد عنصر الطبقة ، كما في المثال التالي.

encoder_vocab = 1000
decoder_vocab = 2000

encoder_input = layers.Input(shape=(None, ))
encoder_embedded = layers.Embedding(input_dim=encoder_vocab, output_dim=64)(encoder_input)

#       
output, state_h, state_c = layers.LSTM(
    64, return_state=True, name='encoder')(encoder_embedded)
encoder_state = [state_h, state_c]

decoder_input = layers.Input(shape=(None, ))
decoder_embedded = layers.Embedding(input_dim=decoder_vocab, output_dim=64)(decoder_input)

#  2     LSTM    
decoder_output = layers.LSTM(
    64, name='decoder')(decoder_embedded, initial_state=encoder_state)
output = layers.Dense(10)(decoder_output)

model = tf.keras.Model([encoder_input, decoder_input], output)
model.summary()

طبقات RNN وخلايا RNN


توفر واجهة برمجة تطبيقات RNN ، بالإضافة إلى طبقات RNN المدمجة ، واجهات برمجة تطبيقات على مستوى الخلية. على عكس طبقات RNN ، التي تعالج حزم كاملة من تسلسلات الإدخال ، تعالج خلية RNN خطوة زمنية واحدة فقط.

الخلية داخل دورة forطبقة RNN. tf.keras.layers.RNNيمنحك التفاف الخلية بطبقة طبقة قادرة على معالجة حزم تسلسل ، على سبيل المثال RNN(LSTMCell(10)).

رياضيا ، RNN(LSTMCell(10))يعطي نفس النتيجة LSTM(10). في الواقع ، كان تنفيذ هذه الطبقة داخل TF v1.x فقط لإنشاء خلية RNN المقابلة ولفها في طبقة RNN. ومع ذلك، فإن استخدام طبقات جزءا لا يتجزأ من GRUو LSTMيسمح استخدام CuDNN التي يمكن أن تعطيك أداء أفضل.

هناك ثلاث خلايا RNN مدمجة ، كل منها يتوافق مع طبقة RNN الخاصة به.

  • tf.keras.layers.SimpleRNNCellيطابق الطبقة SimpleRNN.
  • tf.keras.layers.GRUCellيطابق الطبقة GRU.
  • tf.keras.layers.LSTMCellيطابق الطبقة LSTM.

إن استخلاص خلية مع فئة مشتركة tf.keras.layers.RNNيجعل من السهل جدًا تنفيذ بنى RNN المخصصة لبحثك.

مجموعة حفظ عبر الدفعة


عند معالجة تسلسلات طويلة (ربما لا حصر لها) ، قد ترغب في استخدام نمط الحالة عبر الدفعة .

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

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

يمكنك القيام بذلك عن طريق تعيين `stateful = True` في المُنشئ.

إذا كان لديك التسلسل `s = [t0، t1، ... t1546، t1547]` ، يمكنك تقسيمه على سبيل المثال إلى:

s1 = [t0, t1, ... t100]
s2 = [t101, ... t201]
...
s16 = [t1501, ... t1547]

ثم يمكنك معالجتها باستخدام:

lstm_layer = layers.LSTM(64, stateful=True)
for s in sub_sequences:
  output = lstm_layer(s)

عندما تريد تنظيف الحالة ، استخدم layer.reset_states().
ملاحظة: في هذه الحالة ، يُفترض أن المثال iفي هذه الحزمة هو استمرار لمثال iالحزمة السابقة. هذا يعني أن جميع الحزم تحتوي على نفس عدد العناصر (حجم العبوة). على سبيل المثال ، إذا كانت الحزمة تحتوي على [sequence_A_from_t0_to_t100, sequence_B_from_t0_to_t100]، يجب أن تحتوي الحزمة التالية [sequence_A_from_t101_to_t200, sequence_B_from_t101_to_t200].
هنا مثال كامل:

paragraph1 = np.random.random((20, 10, 50)).astype(np.float32)
paragraph2 = np.random.random((20, 10, 50)).astype(np.float32)
paragraph3 = np.random.random((20, 10, 50)).astype(np.float32)

lstm_layer = layers.LSTM(64, stateful=True)
output = lstm_layer(paragraph1)
output = lstm_layer(paragraph2)
output = lstm_layer(paragraph3)

# reset_states()      initial_state.
#  initial_state   ,      .
lstm_layer.reset_states()

RNN ثنائي الاتجاه


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

يوفر Keras واجهة برمجة تطبيقات بسيطة لإنشاء شبكات RNN ثنائية الاتجاه: غلاف tf.keras.layers.Bidirectional.

model = tf.keras.Sequential()

model.add(layers.Bidirectional(layers.LSTM(64, return_sequences=True), 
                               input_shape=(5, 10)))
model.add(layers.Bidirectional(layers.LSTM(32)))
model.add(layers.Dense(10))

model.summary()

تحت غطاء المحرك ، سيتم نسخ Bidirectionalطبقة RNN المنقولة go_backwardsوسيتم قلب حقل الطبقة المنسوخة حديثًا ، وبالتالي سيتم معالجة بيانات الإدخال بترتيب عكسي.

سيكون خرج ` BidirectionalRNN بشكل افتراضي هو مجموع ناتج الطبقة الأمامية وخرج الطبقة العكسية. إذا كنت بحاجة إلى سلوك دمج آخر ، على سبيل المثال تسلسل ، قم بتغيير المعلمة `merge_mode` في مُنشئ المجمّع` ثنائي الاتجاه`.

تحسين الأداء و CuDNN Core في TensorFlow 2.0


في TensorFlow 2.0 ، يمكن استخدام طبقات LSTM و GRU المضمنة بشكل افتراضي نوى CuDNN إذا كان معالج الرسومات متاحًا. مع هذا التغيير ، تكون الطبقات السابقة keras.layers.CuDNNLSTM/CuDNNGRUقديمة ، ويمكنك بناء نموذجك دون القلق بشأن المعدات التي ستعمل عليها.

نظرًا لأن نواة CuDNN مبنية ببعض الافتراضات ، فإن هذا يعني أن الطبقة لن تتمكن من استخدام طبقة نواة CuDNN إذا قمت بتغيير الإعدادات الافتراضية لطبقات LSTM أو GRU المدمجة . على سبيل المثال

  • تغيير وظيفة activationمن tanhشيء آخر.
  • تغيير وظيفة recurrent_activationمن sigmoidشيء آخر.
  • الاستخدام recurrent_dropout> 0.
  • تعيينها unrollإلى True، والذي يسبب LSTM / GRU لتتحلل الداخلية tf.while_loopفي حلقة نشرها for.
  • تعيين use_biasعلى خطأ.
  • استخدام الأقنعة عندما تكون بيانات الإدخال غير مبررة بشكل صحيح (إذا كان القناع يطابق البيانات الصحيحة المحاذاة بدقة ، فلا يزال من الممكن استخدام CuDNN. هذه هي الحالة الأكثر شيوعًا).

عند الإمكان استخدم نوى CuDNN


batch_size = 64
#    MNIST    (batch_size, 28, 28).
#     (28, 28) (   ).
input_dim = 28

units = 64
output_size = 10  #   0  9

#  RNN 
def build_model(allow_cudnn_kernel=True):
  # CuDNN     ,     .
  #   `LSTM(units)`    CuDNN,
  #   RNN(LSTMCell(units))   non-CuDNN .
  if allow_cudnn_kernel:
    #  LSTM      CuDNN.
    lstm_layer = tf.keras.layers.LSTM(units, input_shape=(None, input_dim))
  else:
    #  LSTMCell  RNN    CuDNN.
    lstm_layer = tf.keras.layers.RNN(
        tf.keras.layers.LSTMCell(units),
        input_shape=(None, input_dim))
  model = tf.keras.models.Sequential([
      lstm_layer,
      tf.keras.layers.BatchNormalization(),
      tf.keras.layers.Dense(output_size)]
  )
  return model

تحميل مجموعة بيانات MNIST


mnist = tf.keras.datasets.mnist

(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0
sample, sample_label = x_train[0], y_train[0]

إنشاء مثيل من النموذج وتجميعه


لقد اخترنا sparse_categorical_crossentropyكدالة للخسائر. ناتج النموذج له بعد [batch_size, 10]. إجابة النموذج متجه عدد صحيح ، كل من الأرقام في النطاق من 0 إلى 9.

model = build_model(allow_cudnn_kernel=True)

model.compile(loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True), 
              optimizer='sgd',
              metrics=['accuracy'])

model.fit(x_train, y_train,
          validation_data=(x_test, y_test),
          batch_size=batch_size,
          epochs=5)

قم ببناء نموذج جديد بدون نواة CuDNN


slow_model = build_model(allow_cudnn_kernel=False)
slow_model.set_weights(model.get_weights())
slow_model.compile(loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True), 
                   optimizer='sgd', 
                   metrics=['accuracy'])
slow_model.fit(x_train, y_train, 
               validation_data=(x_test, y_test), 
               batch_size=batch_size,
               epochs=1)  #         .

كما ترى ، فإن النموذج المصمم باستخدام CuDNN أسرع بكثير في التدريب من النموذج باستخدام نواة TensorFlow المعتادة.

يمكن استخدام نفس النموذج مع دعم CuDNN للإخراج في بيئة المعالج الواحد. tf.deviceيشير التعليق التوضيحي ببساطة إلى الجهاز المستخدم. سيتم تشغيل النموذج بشكل افتراضي على وحدة المعالجة المركزية إذا لم يكن GPU متاحًا.

لا داعي للقلق بشأن الأجهزة التي تعمل عليها. أليس هذا رائعا؟

with tf.device('CPU:0'):
  cpu_model = build_model(allow_cudnn_kernel=True)
  cpu_model.set_weights(model.get_weights())
  result = tf.argmax(cpu_model.predict_on_batch(tf.expand_dims(sample, 0)), axis=1)
  print('Predicted result is: %s, target result is: %s' % (result.numpy(), sample_label))
  plt.imshow(sample, cmap=plt.get_cmap('gray'))

RNN مع إدخال قائمة / قاموس ، أو إدخال متداخل


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

[batch, timestep, {\"video\": [height, width, channel], \"audio\": [frequency]}]

في مثال آخر ، يمكن أن تحتوي البيانات المكتوبة بخط اليد على إحداثيات x و y لموضع القلم الحالي ، بالإضافة إلى معلومات الضغط. لذلك يمكن تمثيل البيانات على النحو التالي:

[batch, timestep, {\"location\": [x, y], \"pressure\": [force]}]

ينشئ الكود التالي مثالاً لخلية RNN مخصصة تعمل مع مثل هذه المدخلات المنظمة.

حدد خلية مستخدم تدعم الإدخال / الإخراج المتداخل


NestedInput = collections.namedtuple('NestedInput', ['feature1', 'feature2'])
NestedState = collections.namedtuple('NestedState', ['state1', 'state2'])

class NestedCell(tf.keras.layers.Layer):

  def __init__(self, unit_1, unit_2, unit_3, **kwargs):
    self.unit_1 = unit_1
    self.unit_2 = unit_2
    self.unit_3 = unit_3
    self.state_size = NestedState(state1=unit_1, 
                                  state2=tf.TensorShape([unit_2, unit_3]))
    self.output_size = (unit_1, tf.TensorShape([unit_2, unit_3]))
    super(NestedCell, self).__init__(**kwargs)

  def build(self, input_shapes):
    # #  input_shape  2 , [(batch, i1), (batch, i2, i3)]
    input_1 = input_shapes.feature1[1]
    input_2, input_3 = input_shapes.feature2[1:]

    self.kernel_1 = self.add_weight(
        shape=(input_1, self.unit_1), initializer='uniform', name='kernel_1')
    self.kernel_2_3 = self.add_weight(
        shape=(input_2, input_3, self.unit_2, self.unit_3),
        initializer='uniform',
        name='kernel_2_3')

  def call(self, inputs, states):
    #     [(batch, input_1), (batch, input_2, input_3)]
    #     [(batch, unit_1), (batch, unit_2, unit_3)]
    input_1, input_2 = tf.nest.flatten(inputs)
    s1, s2 = states

    output_1 = tf.matmul(input_1, self.kernel_1)
    output_2_3 = tf.einsum('bij,ijkl->bkl', input_2, self.kernel_2_3)
    state_1 = s1 + output_1
    state_2_3 = s2 + output_2_3

    output = [output_1, output_2_3]
    new_states = NestedState(state1=state_1, state2=state_2_3)

    return output, new_states

قم ببناء نموذج RNN بإدخال / إخراج متداخل


دعونا نبني نموذج Keras يستخدم طبقة tf.keras.layers.RNNوخلية مخصصة حددناها للتو.

unit_1 = 10
unit_2 = 20
unit_3 = 30

input_1 = 32
input_2 = 64
input_3 = 32
batch_size = 64
num_batch = 100
timestep = 50

cell = NestedCell(unit_1, unit_2, unit_3)
rnn = tf.keras.layers.RNN(cell)

inp_1 = tf.keras.Input((None, input_1))
inp_2 = tf.keras.Input((None, input_2, input_3))

outputs = rnn(NestedInput(feature1=inp_1, feature2=inp_2))

model = tf.keras.models.Model([inp_1, inp_2], outputs)

model.compile(optimizer='adam', loss='mse', metrics=['accuracy'])unit_1 = 10
unit_2 = 20
unit_3 = 30

input_1 = 32
input_2 = 64
input_3 = 32
batch_size = 64
num_batch = 100
timestep = 50

cell = NestedCell(unit_1, unit_2, unit_3)
rnn = tf.keras.layers.RNN(cell)

inp_1 = tf.keras.Input((None, input_1))
inp_2 = tf.keras.Input((None, input_2, input_3))

outputs = rnn(NestedInput(feature1=inp_1, feature2=inp_2))

model = tf.keras.models.Model([inp_1, inp_2], outputs)

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

تدريب النموذج على البيانات بشكل عشوائي


نظرًا لعدم وجود مجموعة بيانات جيدة لهذا النموذج ، فإننا نستخدم بيانات عشوائية تم إنشاؤها بواسطة مكتبة Numpy للتوضيح.

input_1_data = np.random.random((batch_size * num_batch, timestep, input_1))
input_2_data = np.random.random((batch_size * num_batch, timestep, input_2, input_3))
target_1_data = np.random.random((batch_size * num_batch, unit_1))
target_2_data = np.random.random((batch_size * num_batch, unit_2, unit_3))
input_data = [input_1_data, input_2_data]
target_data = [target_1_data, target_2_data]

model.fit(input_data, target_data, batch_size=batch_size)

باستخدام الطبقة ، ما tf.keras.layers.RNNعليك سوى تحديد المنطق الرياضي لخطوة واحدة داخل التسلسل ، وستتعامل الطبقة tf.keras.layers.RNNمع تكرار التسلسل نيابة عنك. هذه طريقة قوية بشكل لا يصدق لنموذج سريع لأنواع جديدة من RNNs (مثل متغير LSTM).

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

Source: https://habr.com/ru/post/undefined/


All Articles