احفظ التاريخ! يعود مؤتمر Google I / O من 18 إلى 20 مايو. سجل الآن
ترجمت واجهة Cloud Translation API‏ هذه الصفحة.
Switch to English

واجهة برمجة التطبيقات الوظيفية

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

يثبت

import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

مقدمة

تعد واجهة برمجة تطبيقات Keras الوظيفية طريقة لإنشاء نماذج أكثر مرونة من واجهة برمجة تطبيقات tf.keras.Sequential . يمكن لواجهة برمجة التطبيقات الوظيفية التعامل مع النماذج ذات الهيكل غير الخطي والطبقات المشتركة وحتى المدخلات أو المخرجات المتعددة.

الفكرة الرئيسية هي أن نموذج التعلم العميق عادة ما يكون رسمًا بيانيًا لا دوريًا موجهًا (DAG) من الطبقات. لذا فإن واجهة برمجة التطبيقات الوظيفية هي طريقة لبناء الرسوم البيانية للطبقات .

ضع في اعتبارك النموذج التالي:

(input: 784-dimensional vectors)
       ↧
[Dense (64 units, relu activation)]
       ↧
[Dense (64 units, relu activation)]
       ↧
[Dense (10 units, softmax activation)]
       ↧
(output: logits of a probability distribution over 10 classes)

هذا رسم بياني أساسي بثلاث طبقات. لإنشاء هذا النموذج باستخدام واجهة برمجة التطبيقات الوظيفية ، ابدأ بإنشاء عقدة إدخال:

inputs = keras.Input(shape=(784,))

يتم تعيين شكل البيانات كمتجه 784 الأبعاد. يتم دائمًا حذف حجم الدُفعة نظرًا لأنه يتم تحديد شكل كل عينة فقط.

على سبيل المثال ، إذا كان لديك إدخال صورة بالشكل (32, 32, 3) ، يمكنك استخدام:

# Just for demonstration purposes.
img_inputs = keras.Input(shape=(32, 32, 3))

تحتوي inputs التي تم إرجاعها على معلومات حول شكل dtype بيانات الإدخال التي تقوم بتغذية النموذج الخاص بك. هذا هو الشكل:

inputs.shape
TensorShape([None, 784])

ها هو النوع dtype:

inputs.dtype
tf.float32

يمكنك إنشاء عقدة جديدة في الرسم البياني للطبقات عن طريق استدعاء طبقة على كائن inputs هذا:

dense = layers.Dense(64, activation="relu")
x = dense(inputs)

يشبه إجراء "استدعاء الطبقة" رسم سهم من "المدخلات" إلى هذه الطبقة التي قمت بإنشائها. أنت "تمرر" المدخلات إلى الطبقة dense ، وتحصل على x كناتج.

دعنا نضيف بضع طبقات أخرى إلى الرسم البياني للطبقات:

x = layers.Dense(64, activation="relu")(x)
outputs = layers.Dense(10)(x)

في هذه المرحلة ، يمكنك إنشاء Model عن طريق تحديد مدخلاته ومخرجاته في الرسم البياني للطبقات:

model = keras.Model(inputs=inputs, outputs=outputs, name="mnist_model")

دعنا نتحقق من شكل ملخص النموذج:

model.summary()
Model: "mnist_model"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_1 (InputLayer)         [(None, 784)]             0         
_________________________________________________________________
dense (Dense)                (None, 64)                50240     
_________________________________________________________________
dense_1 (Dense)              (None, 64)                4160      
_________________________________________________________________
dense_2 (Dense)              (None, 10)                650       
=================================================================
Total params: 55,050
Trainable params: 55,050
Non-trainable params: 0
_________________________________________________________________

يمكنك أيضًا رسم النموذج كرسم بياني:

keras.utils.plot_model(model, "my_first_model.png")

بي إن جي

ويمكنك ، اختياريًا ، عرض أشكال الإدخال والإخراج لكل طبقة في الرسم البياني المرسوم:

keras.utils.plot_model(model, "my_first_model_with_shape_info.png", show_shapes=True)

بي إن جي

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

"الرسم البياني للطبقات" هو صورة ذهنية بديهية لنموذج التعلم العميق ، وواجهة برمجة التطبيقات الوظيفية هي طريقة لإنشاء نماذج تعكس ذلك عن كثب.

التدريب والتقييم والاستدلال

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

تقدم فئة Model حلقة تدريب مضمنة fit() طريقة fit() وحلقة evaluate() مضمنة evaluate() طريقة evaluate() ). لاحظ أنه يمكنك بسهولة تخصيص هذه الحلقات لتنفيذ إجراءات التدريب التي تتجاوز التعلم الخاضع للإشراف (مثل شبكات GAN ).

هنا ، قم بتحميل بيانات صورة MNIST ، وأعد تشكيلها في متجهات ، وتناسب النموذج مع البيانات (أثناء مراقبة الأداء عند تقسيم التحقق من الصحة) ، ثم قم بتقييم النموذج على بيانات الاختبار:

(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()

x_train = x_train.reshape(60000, 784).astype("float32") / 255
x_test = x_test.reshape(10000, 784).astype("float32") / 255

model.compile(
    loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
    optimizer=keras.optimizers.RMSprop(),
    metrics=["accuracy"],
)

history = model.fit(x_train, y_train, batch_size=64, epochs=2, validation_split=0.2)

test_scores = model.evaluate(x_test, y_test, verbose=2)
print("Test loss:", test_scores[0])
print("Test accuracy:", test_scores[1])
Epoch 1/2
750/750 [==============================] - 3s 3ms/step - loss: 0.5848 - accuracy: 0.8332 - val_loss: 0.1880 - val_accuracy: 0.9480
Epoch 2/2
750/750 [==============================] - 2s 2ms/step - loss: 0.1699 - accuracy: 0.9503 - val_loss: 0.1490 - val_accuracy: 0.9563
313/313 - 0s - loss: 0.1463 - accuracy: 0.9563
Test loss: 0.14626088738441467
Test accuracy: 0.9563000202178955

لمزيد من القراءة ، راجع دليل التدريب والتقييم .

احفظ وسلسل

يعمل حفظ النموذج والتسلسل بالطريقة نفسها للنماذج التي تم إنشاؤها باستخدام واجهة برمجة التطبيقات الوظيفية كما هو الحال مع النماذج Sequential . الطريقة القياسية لحفظ نموذج وظيفي هي استدعاء model.save() لحفظ النموذج بأكمله كملف واحد. يمكنك لاحقًا إعادة إنشاء نفس النموذج من هذا الملف ، حتى إذا لم يعد الرمز الذي أنشأ النموذج متاحًا.

يتضمن هذا الملف المحفوظ ما يلي:

  • العمارة النموذجية
  • قيم الوزن النموذجية (التي تم تعلمها أثناء التدريب)
  • تكوين نموذج التدريب ، إن وجد (كما تم تمريره compile )
  • المُحسِّن وحالته ، إن وجدت (لإعادة التدريب من حيث توقفت)
model.save("path_to_my_model")
del model
# Recreate the exact same model purely from the file:
model = keras.models.load_model("path_to_my_model")
INFO:tensorflow:Assets written to: path_to_my_model/assets

للحصول على التفاصيل ، اقرأ دليل تسلسل النموذج وحفظه .

استخدم نفس الرسم البياني للطبقات لتحديد نماذج متعددة

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

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

encoder_input = keras.Input(shape=(28, 28, 1), name="img")
x = layers.Conv2D(16, 3, activation="relu")(encoder_input)
x = layers.Conv2D(32, 3, activation="relu")(x)
x = layers.MaxPooling2D(3)(x)
x = layers.Conv2D(32, 3, activation="relu")(x)
x = layers.Conv2D(16, 3, activation="relu")(x)
encoder_output = layers.GlobalMaxPooling2D()(x)

encoder = keras.Model(encoder_input, encoder_output, name="encoder")
encoder.summary()

x = layers.Reshape((4, 4, 1))(encoder_output)
x = layers.Conv2DTranspose(16, 3, activation="relu")(x)
x = layers.Conv2DTranspose(32, 3, activation="relu")(x)
x = layers.UpSampling2D(3)(x)
x = layers.Conv2DTranspose(16, 3, activation="relu")(x)
decoder_output = layers.Conv2DTranspose(1, 3, activation="relu")(x)

autoencoder = keras.Model(encoder_input, decoder_output, name="autoencoder")
autoencoder.summary()
Model: "encoder"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
img (InputLayer)             [(None, 28, 28, 1)]       0         
_________________________________________________________________
conv2d (Conv2D)              (None, 26, 26, 16)        160       
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 24, 24, 32)        4640      
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 8, 8, 32)          0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 6, 6, 32)          9248      
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 4, 4, 16)          4624      
_________________________________________________________________
global_max_pooling2d (Global (None, 16)                0         
=================================================================
Total params: 18,672
Trainable params: 18,672
Non-trainable params: 0
_________________________________________________________________
Model: "autoencoder"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
img (InputLayer)             [(None, 28, 28, 1)]       0         
_________________________________________________________________
conv2d (Conv2D)              (None, 26, 26, 16)        160       
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 24, 24, 32)        4640      
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 8, 8, 32)          0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 6, 6, 32)          9248      
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 4, 4, 16)          4624      
_________________________________________________________________
global_max_pooling2d (Global (None, 16)                0         
_________________________________________________________________
reshape (Reshape)            (None, 4, 4, 1)           0         
_________________________________________________________________
conv2d_transpose (Conv2DTran (None, 6, 6, 16)          160       
_________________________________________________________________
conv2d_transpose_1 (Conv2DTr (None, 8, 8, 32)          4640      
_________________________________________________________________
up_sampling2d (UpSampling2D) (None, 24, 24, 32)        0         
_________________________________________________________________
conv2d_transpose_2 (Conv2DTr (None, 26, 26, 16)        4624      
_________________________________________________________________
conv2d_transpose_3 (Conv2DTr (None, 28, 28, 1)         145       
=================================================================
Total params: 28,241
Trainable params: 28,241
Non-trainable params: 0
_________________________________________________________________

هنا ، بنية فك التشفير متناظرة تمامًا مع بنية التشفير ، وبالتالي فإن شكل الإخراج هو نفسه شكل الإدخال (28, 28, 1) .

عكس طبقة Conv2D هو طبقة Conv2DTranspose ، وعكس طبقة MaxPooling2D هو طبقة UpSampling2D .

جميع النماذج قابلة للاستدعاء ، تمامًا مثل الطبقات

يمكنك معاملة أي نموذج كما لو كان طبقة عن طريق استدعائه في Input أو إخراج طبقة أخرى. من خلال استدعاء نموذج ، فأنت لا تقوم فقط بإعادة استخدام بنية النموذج ، بل تقوم أيضًا بإعادة استخدام أوزانه.

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

encoder_input = keras.Input(shape=(28, 28, 1), name="original_img")
x = layers.Conv2D(16, 3, activation="relu")(encoder_input)
x = layers.Conv2D(32, 3, activation="relu")(x)
x = layers.MaxPooling2D(3)(x)
x = layers.Conv2D(32, 3, activation="relu")(x)
x = layers.Conv2D(16, 3, activation="relu")(x)
encoder_output = layers.GlobalMaxPooling2D()(x)

encoder = keras.Model(encoder_input, encoder_output, name="encoder")
encoder.summary()

decoder_input = keras.Input(shape=(16,), name="encoded_img")
x = layers.Reshape((4, 4, 1))(decoder_input)
x = layers.Conv2DTranspose(16, 3, activation="relu")(x)
x = layers.Conv2DTranspose(32, 3, activation="relu")(x)
x = layers.UpSampling2D(3)(x)
x = layers.Conv2DTranspose(16, 3, activation="relu")(x)
decoder_output = layers.Conv2DTranspose(1, 3, activation="relu")(x)

decoder = keras.Model(decoder_input, decoder_output, name="decoder")
decoder.summary()

autoencoder_input = keras.Input(shape=(28, 28, 1), name="img")
encoded_img = encoder(autoencoder_input)
decoded_img = decoder(encoded_img)
autoencoder = keras.Model(autoencoder_input, decoded_img, name="autoencoder")
autoencoder.summary()
Model: "encoder"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
original_img (InputLayer)    [(None, 28, 28, 1)]       0         
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 26, 26, 16)        160       
_________________________________________________________________
conv2d_5 (Conv2D)            (None, 24, 24, 32)        4640      
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 8, 8, 32)          0         
_________________________________________________________________
conv2d_6 (Conv2D)            (None, 6, 6, 32)          9248      
_________________________________________________________________
conv2d_7 (Conv2D)            (None, 4, 4, 16)          4624      
_________________________________________________________________
global_max_pooling2d_1 (Glob (None, 16)                0         
=================================================================
Total params: 18,672
Trainable params: 18,672
Non-trainable params: 0
_________________________________________________________________
Model: "decoder"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
encoded_img (InputLayer)     [(None, 16)]              0         
_________________________________________________________________
reshape_1 (Reshape)          (None, 4, 4, 1)           0         
_________________________________________________________________
conv2d_transpose_4 (Conv2DTr (None, 6, 6, 16)          160       
_________________________________________________________________
conv2d_transpose_5 (Conv2DTr (None, 8, 8, 32)          4640      
_________________________________________________________________
up_sampling2d_1 (UpSampling2 (None, 24, 24, 32)        0         
_________________________________________________________________
conv2d_transpose_6 (Conv2DTr (None, 26, 26, 16)        4624      
_________________________________________________________________
conv2d_transpose_7 (Conv2DTr (None, 28, 28, 1)         145       
=================================================================
Total params: 9,569
Trainable params: 9,569
Non-trainable params: 0
_________________________________________________________________
Model: "autoencoder"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
img (InputLayer)             [(None, 28, 28, 1)]       0         
_________________________________________________________________
encoder (Functional)         (None, 16)                18672     
_________________________________________________________________
decoder (Functional)         (None, 28, 28, 1)         9569      
=================================================================
Total params: 28,241
Trainable params: 28,241
Non-trainable params: 0
_________________________________________________________________

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

def get_model():
    inputs = keras.Input(shape=(128,))
    outputs = layers.Dense(1)(inputs)
    return keras.Model(inputs, outputs)


model1 = get_model()
model2 = get_model()
model3 = get_model()

inputs = keras.Input(shape=(128,))
y1 = model1(inputs)
y2 = model2(inputs)
y3 = model3(inputs)
outputs = layers.average([y1, y2, y3])
ensemble_model = keras.Model(inputs=inputs, outputs=outputs)

معالجة طبولوجيا الرسم البياني المعقدة

النماذج ذات المدخلات والمخرجات المتعددة

تجعل واجهة برمجة التطبيقات الوظيفية من السهل التعامل مع المدخلات والمخرجات المتعددة. لا يمكن التعامل مع هذا مع API Sequential .

على سبيل المثال ، إذا كنت تقوم ببناء نظام لترتيب تذاكر إصدار العملاء حسب الأولوية وتوجيهها إلى القسم الصحيح ، فسيكون للنموذج ثلاثة مدخلات:

  • عنوان التذكرة (إدخال نص) ،
  • نص التذكرة (إدخال نص) ، و
  • أي علامات أضافها المستخدم (إدخال فئوي)

سيكون لهذا النموذج ناتجين:

  • درجة الأولوية بين 0 و 1 (الناتج السيني القياسي) ، و
  • القسم الذي يجب أن يتعامل مع التذكرة (إخراج softmax عبر مجموعة الأقسام).

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

num_tags = 12  # Number of unique issue tags
num_words = 10000  # Size of vocabulary obtained when preprocessing text data
num_departments = 4  # Number of departments for predictions

title_input = keras.Input(
    shape=(None,), name="title"
)  # Variable-length sequence of ints
body_input = keras.Input(shape=(None,), name="body")  # Variable-length sequence of ints
tags_input = keras.Input(
    shape=(num_tags,), name="tags"
)  # Binary vectors of size `num_tags`

# Embed each word in the title into a 64-dimensional vector
title_features = layers.Embedding(num_words, 64)(title_input)
# Embed each word in the text into a 64-dimensional vector
body_features = layers.Embedding(num_words, 64)(body_input)

# Reduce sequence of embedded words in the title into a single 128-dimensional vector
title_features = layers.LSTM(128)(title_features)
# Reduce sequence of embedded words in the body into a single 32-dimensional vector
body_features = layers.LSTM(32)(body_features)

# Merge all available features into a single large vector via concatenation
x = layers.concatenate([title_features, body_features, tags_input])

# Stick a logistic regression for priority prediction on top of the features
priority_pred = layers.Dense(1, name="priority")(x)
# Stick a department classifier on top of the features
department_pred = layers.Dense(num_departments, name="department")(x)

# Instantiate an end-to-end model predicting both priority and department
model = keras.Model(
    inputs=[title_input, body_input, tags_input],
    outputs=[priority_pred, department_pred],
)

الآن ارسم النموذج:

keras.utils.plot_model(model, "multi_input_and_output_model.png", show_shapes=True)

بي إن جي

عند تجميع هذا النموذج ، يمكنك تعيين خسائر مختلفة لكل ناتج. يمكنك حتى تعيين أوزان مختلفة لكل خسارة - لتعديل مساهمتها في إجمالي خسارة التدريب.

model.compile(
    optimizer=keras.optimizers.RMSprop(1e-3),
    loss=[
        keras.losses.BinaryCrossentropy(from_logits=True),
        keras.losses.CategoricalCrossentropy(from_logits=True),
    ],
    loss_weights=[1.0, 0.2],
)

نظرًا لأن طبقات الإخراج لها أسماء مختلفة ، يمكنك أيضًا تحديد الخسارة كما يلي:

model.compile(
    optimizer=keras.optimizers.RMSprop(1e-3),
    loss={
        "priority": keras.losses.BinaryCrossentropy(from_logits=True),
        "department": keras.losses.CategoricalCrossentropy(from_logits=True),
    },
    loss_weights=[1.0, 0.2],
)

تدريب النموذج عن طريق تمرير قوائم مصفوفات NumPy للمدخلات والأهداف:

# Dummy input data
title_data = np.random.randint(num_words, size=(1280, 10))
body_data = np.random.randint(num_words, size=(1280, 100))
tags_data = np.random.randint(2, size=(1280, num_tags)).astype("float32")

# Dummy target data
priority_targets = np.random.random(size=(1280, 1))
dept_targets = np.random.randint(2, size=(1280, num_departments))

model.fit(
    {"title": title_data, "body": body_data, "tags": tags_data},
    {"priority": priority_targets, "department": dept_targets},
    epochs=2,
    batch_size=32,
)
Epoch 1/2
40/40 [==============================] - 4s 11ms/step - loss: 1.2978 - priority_loss: 0.7067 - department_loss: 2.9554
Epoch 2/2
40/40 [==============================] - 0s 11ms/step - loss: 1.2947 - priority_loss: 0.7023 - department_loss: 2.9621
<tensorflow.python.keras.callbacks.History at 0x7fe18923e6a0>

عند استدعاء fit مع كائن Dataset ، يجب أن ينتج إما مجموعة قوائم مثل ([title_data, body_data, tags_data], [priority_targets, dept_targets]) أو مجموعة من القواميس مثل ({'title': title_data, 'body': body_data, 'tags': tags_data}, {'priority': priority_targets, 'department': dept_targets}) .

لمزيد من الشرح التفصيلي ، يرجى الرجوع إلى دليل التدريب والتقييم .

نموذج لعبة ResNet

بالإضافة إلى النماذج ذات المدخلات والمخرجات المتعددة ، فإن واجهة برمجة التطبيقات الوظيفية تجعل من السهل معالجة طبولوجيا الاتصال غير الخطي - وهي نماذج ذات طبقات غير متصلة بالتسلسل ، والتي لا تستطيع واجهة برمجة التطبيقات Sequential معالجتها.

حالة الاستخدام الشائعة لهذا هي الوصلات المتبقية. لنقم ببناء نموذج لعبة ResNet لـ CIFAR10 لتوضيح ذلك:

inputs = keras.Input(shape=(32, 32, 3), name="img")
x = layers.Conv2D(32, 3, activation="relu")(inputs)
x = layers.Conv2D(64, 3, activation="relu")(x)
block_1_output = layers.MaxPooling2D(3)(x)

x = layers.Conv2D(64, 3, activation="relu", padding="same")(block_1_output)
x = layers.Conv2D(64, 3, activation="relu", padding="same")(x)
block_2_output = layers.add([x, block_1_output])

x = layers.Conv2D(64, 3, activation="relu", padding="same")(block_2_output)
x = layers.Conv2D(64, 3, activation="relu", padding="same")(x)
block_3_output = layers.add([x, block_2_output])

x = layers.Conv2D(64, 3, activation="relu")(block_3_output)
x = layers.GlobalAveragePooling2D()(x)
x = layers.Dense(256, activation="relu")(x)
x = layers.Dropout(0.5)(x)
outputs = layers.Dense(10)(x)

model = keras.Model(inputs, outputs, name="toy_resnet")
model.summary()
Model: "toy_resnet"
__________________________________________________________________________________________________
Layer (type)                    Output Shape         Param #     Connected to                     
==================================================================================================
img (InputLayer)                [(None, 32, 32, 3)]  0                                            
__________________________________________________________________________________________________
conv2d_8 (Conv2D)               (None, 30, 30, 32)   896         img[0][0]                        
__________________________________________________________________________________________________
conv2d_9 (Conv2D)               (None, 28, 28, 64)   18496       conv2d_8[0][0]                   
__________________________________________________________________________________________________
max_pooling2d_2 (MaxPooling2D)  (None, 9, 9, 64)     0           conv2d_9[0][0]                   
__________________________________________________________________________________________________
conv2d_10 (Conv2D)              (None, 9, 9, 64)     36928       max_pooling2d_2[0][0]            
__________________________________________________________________________________________________
conv2d_11 (Conv2D)              (None, 9, 9, 64)     36928       conv2d_10[0][0]                  
__________________________________________________________________________________________________
add (Add)                       (None, 9, 9, 64)     0           conv2d_11[0][0]                  
                                                                 max_pooling2d_2[0][0]            
__________________________________________________________________________________________________
conv2d_12 (Conv2D)              (None, 9, 9, 64)     36928       add[0][0]                        
__________________________________________________________________________________________________
conv2d_13 (Conv2D)              (None, 9, 9, 64)     36928       conv2d_12[0][0]                  
__________________________________________________________________________________________________
add_1 (Add)                     (None, 9, 9, 64)     0           conv2d_13[0][0]                  
                                                                 add[0][0]                        
__________________________________________________________________________________________________
conv2d_14 (Conv2D)              (None, 7, 7, 64)     36928       add_1[0][0]                      
__________________________________________________________________________________________________
global_average_pooling2d (Globa (None, 64)           0           conv2d_14[0][0]                  
__________________________________________________________________________________________________
dense_6 (Dense)                 (None, 256)          16640       global_average_pooling2d[0][0]   
__________________________________________________________________________________________________
dropout (Dropout)               (None, 256)          0           dense_6[0][0]                    
__________________________________________________________________________________________________
dense_7 (Dense)                 (None, 10)           2570        dropout[0][0]                    
==================================================================================================
Total params: 223,242
Trainable params: 223,242
Non-trainable params: 0
__________________________________________________________________________________________________

ارسم النموذج:

keras.utils.plot_model(model, "mini_resnet.png", show_shapes=True)

بي إن جي

الآن قم بتدريب النموذج:

(x_train, y_train), (x_test, y_test) = keras.datasets.cifar10.load_data()

x_train = x_train.astype("float32") / 255.0
x_test = x_test.astype("float32") / 255.0
y_train = keras.utils.to_categorical(y_train, 10)
y_test = keras.utils.to_categorical(y_test, 10)

model.compile(
    optimizer=keras.optimizers.RMSprop(1e-3),
    loss=keras.losses.CategoricalCrossentropy(from_logits=True),
    metrics=["acc"],
)
# We restrict the data to the first 1000 samples so as to limit execution time
# on Colab. Try to train on the entire dataset until convergence!
model.fit(x_train[:1000], y_train[:1000], batch_size=64, epochs=1, validation_split=0.2)
Downloading data from https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz
170500096/170498071 [==============================] - 6s 0us/step
13/13 [==============================] - 2s 35ms/step - loss: 2.2992 - acc: 0.1273 - val_loss: 2.2629 - val_acc: 0.1850
<tensorflow.python.keras.callbacks.History at 0x7fe210396ef0>

الطبقات المشتركة

استخدام جيد آخر لواجهة برمجة التطبيقات الوظيفية هو النماذج التي تستخدم الطبقات المشتركة . الطبقات المشتركة هي مثيلات الطبقة التي يتم إعادة استخدامها عدة مرات في نفس النموذج - تتعلم الميزات التي تتوافق مع مسارات متعددة في الرسم البياني للطبقات.

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

لمشاركة طبقة في API الوظيفية ، قم باستدعاء مثيل الطبقة نفسه عدة مرات. على سبيل المثال ، إليك طبقة Embedding المشتركة عبر مدخلين مختلفين للنص:

# Embedding for 1000 unique words mapped to 128-dimensional vectors
shared_embedding = layers.Embedding(1000, 128)

# Variable-length sequence of integers
text_input_a = keras.Input(shape=(None,), dtype="int32")

# Variable-length sequence of integers
text_input_b = keras.Input(shape=(None,), dtype="int32")

# Reuse the same layer to encode both inputs
encoded_input_a = shared_embedding(text_input_a)
encoded_input_b = shared_embedding(text_input_b)

استخراج وإعادة استخدام العقد في الرسم البياني للطبقات

نظرًا لأن الرسم البياني للطبقات التي تتعامل معها عبارة عن بنية بيانات ثابتة ، فيمكن الوصول إليها وفحصها. وهذه هي الطريقة التي يمكنك بها رسم النماذج الوظيفية كصور.

هذا يعني أيضًا أنه يمكنك الوصول إلى عمليات تنشيط الطبقات الوسيطة ("العقد" في الرسم البياني) وإعادة استخدامها في مكان آخر - وهو أمر مفيد جدًا لشيء مثل استخراج الميزات.

لنلقي نظرة على مثال. هذا نموذج VGG19 بأوزان تم اختبارها مسبقًا على ImageNet:

vgg19 = tf.keras.applications.VGG19()
Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/vgg19/vgg19_weights_tf_dim_ordering_tf_kernels.h5
574717952/574710816 [==============================] - 7s 0us/step

وهذه هي عمليات التنشيط الوسيطة للنموذج ، والتي تم الحصول عليها عن طريق الاستعلام عن بنية بيانات الرسم البياني:

features_list = [layer.output for layer in vgg19.layers]

استخدم هذه الميزات لإنشاء نموذج استخراج معالم جديد يُرجع قيم عمليات تنشيط الطبقة المتوسطة:

feat_extraction_model = keras.Model(inputs=vgg19.input, outputs=features_list)

img = np.random.random((1, 224, 224, 3)).astype("float32")
extracted_features = feat_extraction_model(img)

يكون هذا مفيدًا لمهام مثل نقل النمط العصبي ، من بين أشياء أخرى.

قم بتوسيع API باستخدام طبقات مخصصة

يتضمن tf.keras نطاقًا واسعًا من الطبقات المضمنة ، على سبيل المثال:

  • الطبقات التلافيفية: Conv1D ، Conv2D ، Conv3D ، Conv2DTranspose
  • طبقات التجميع: MaxPooling1D ، MaxPooling2D ، MaxPooling3D ، AveragePooling1D
  • طبقات RNN: GRU ، LSTM ، ConvLSTM2D
  • BatchNormalization ، Dropout ، Embedding ، إلخ.

ولكن إذا لم تجد ما تحتاج إليه ، فمن السهل توسيع واجهة برمجة التطبيقات عن طريق إنشاء طبقاتك الخاصة. كل الطبقات تصنف فئة Layer الفرعية وتنفذ:

  • طريقة call ، التي تحدد الحساب الذي تقوم به الطبقة.
  • طريقة build تُنشئ أوزان الطبقة (هذه مجرد اصطلاح نمط حيث يمكنك إنشاء أوزان في __init__ أيضًا).

لمعرفة المزيد حول إنشاء طبقات من البداية ، اقرأ دليل النماذج والطبقات المخصصة .

فيما يلي تطبيق أساسي لـ tf.keras.layers.Dense :

class CustomDense(layers.Layer):
    def __init__(self, units=32):
        super(CustomDense, self).__init__()
        self.units = units

    def build(self, input_shape):
        self.w = self.add_weight(
            shape=(input_shape[-1], self.units),
            initializer="random_normal",
            trainable=True,
        )
        self.b = self.add_weight(
            shape=(self.units,), initializer="random_normal", trainable=True
        )

    def call(self, inputs):
        return tf.matmul(inputs, self.w) + self.b


inputs = keras.Input((4,))
outputs = CustomDense(10)(inputs)

model = keras.Model(inputs, outputs)

لدعم التسلسل في الطبقة المخصصة الخاصة بك ، حدد طريقة get_config التي تُرجع وسيطات المُنشئ لمثيل الطبقة:

class CustomDense(layers.Layer):
    def __init__(self, units=32):
        super(CustomDense, self).__init__()
        self.units = units

    def build(self, input_shape):
        self.w = self.add_weight(
            shape=(input_shape[-1], self.units),
            initializer="random_normal",
            trainable=True,
        )
        self.b = self.add_weight(
            shape=(self.units,), initializer="random_normal", trainable=True
        )

    def call(self, inputs):
        return tf.matmul(inputs, self.w) + self.b

    def get_config(self):
        return {"units": self.units}


inputs = keras.Input((4,))
outputs = CustomDense(10)(inputs)

model = keras.Model(inputs, outputs)
config = model.get_config()

new_model = keras.Model.from_config(config, custom_objects={"CustomDense": CustomDense})

اختياريًا ، قم بتنفيذ طريقة class from_config(cls, config) والتي تُستخدم عند إعادة إنشاء مثيل طبقة وفقًا لقاموس التكوين الخاص بها. التنفيذ الافتراضي لـ from_config هو:

def from_config(cls, config):
  return cls(**config)

متى تستخدم API الوظيفية

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

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

لإلقاء نظرة متعمقة على الاختلافات بين واجهة برمجة التطبيقات الوظيفية والتصنيف الفرعي للنموذج ، اقرأ ما هي واجهات برمجة التطبيقات الرمزية والحتمية في TensorFlow 2.0؟ .

نقاط القوة الوظيفية لواجهة برمجة التطبيقات:

الخصائص التالية صحيحة أيضًا للنماذج التسلسلية (وهي أيضًا هياكل بيانات) ، ولكنها ليست صحيحة بالنسبة للنماذج المصنفة تحت التصنيف (وهي Python bytecode ، وليس هياكل البيانات).

أقل إسهابًا

لا يوجد super(MyClass, self).__init__(...) ، لا يوجد def call(self, ...): ، إلخ.

قارن:

inputs = keras.Input(shape=(32,))
x = layers.Dense(64, activation='relu')(inputs)
outputs = layers.Dense(10)(x)
mlp = keras.Model(inputs, outputs)

مع النسخة المصنفة:

class MLP(keras.Model):

  def __init__(self, **kwargs):
    super(MLP, self).__init__(**kwargs)
    self.dense_1 = layers.Dense(64, activation='relu')
    self.dense_2 = layers.Dense(10)

  def call(self, inputs):
    x = self.dense_1(inputs)
    return self.dense_2(x)

# Instantiate the model.
mlp = MLP()
# Necessary to create the model's state.
# The model doesn't have a state until it's called at least once.
_ = mlp(tf.zeros((1, 32)))

التحقق من صحة النموذج أثناء تحديد الرسم البياني للاتصال الخاص به

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

يضمن هذا تشغيل أي نموذج يمكنك إنشاؤه باستخدام واجهة برمجة التطبيقات الوظيفية. تحدث جميع عمليات التصحيح - بخلاف التصحيح المرتبط بالتقارب - بشكل ثابت أثناء إنشاء النموذج وليس في وقت التنفيذ. هذا مشابه لفحص النوع في المترجم.

النموذج الوظيفي قابل للتخطيط والتفتيش

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

features_list = [layer.output for layer in vgg19.layers]
feat_extraction_model = keras.Model(inputs=vgg19.input, outputs=features_list)

يمكن إجراء تسلسل أو استنساخ نموذج وظيفي

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

لإجراء تسلسل لنموذج get_config() ، من الضروري get_config() تحديد أسلوب get_config() و from_config() على مستوى النموذج.

ضعف API الوظيفي:

لا يدعم البنى الديناميكية

تعامل API الوظيفية النماذج على أنها DAGs من الطبقات. هذا صحيح بالنسبة لمعظم بنيات التعلم العميق ، ولكن ليس كلها - على سبيل المثال ، لا تتبع الشبكات العودية أو Tree RNNs هذا الافتراض ولا يمكن تنفيذها في API الوظيفية.

مزيج وتطابق أنماط API

لا يعد الاختيار بين API الوظيفي أو التصنيف الفرعي للنموذج قرارًا ثنائيًا يقيدك في فئة واحدة من النماذج. يمكن لجميع النماذج في tf.keras API التفاعل مع بعضها البعض ، سواء كانت نماذج Sequential نماذج وظيفية أو نماذج فرعية مكتوبة من البداية.

يمكنك دائمًا استخدام نموذج وظيفي أو نموذج Sequential كجزء من طبقة أو نموذج مصنف فرعي:

units = 32
timesteps = 10
input_dim = 5

# Define a Functional model
inputs = keras.Input((None, units))
x = layers.GlobalAveragePooling1D()(inputs)
outputs = layers.Dense(1)(x)
model = keras.Model(inputs, outputs)


class CustomRNN(layers.Layer):
    def __init__(self):
        super(CustomRNN, self).__init__()
        self.units = units
        self.projection_1 = layers.Dense(units=units, activation="tanh")
        self.projection_2 = layers.Dense(units=units, activation="tanh")
        # Our previously-defined Functional model
        self.classifier = model

    def call(self, inputs):
        outputs = []
        state = tf.zeros(shape=(inputs.shape[0], self.units))
        for t in range(inputs.shape[1]):
            x = inputs[:, t, :]
            h = self.projection_1(x)
            y = h + self.projection_2(state)
            state = y
            outputs.append(y)
        features = tf.stack(outputs, axis=1)
        print(features.shape)
        return self.classifier(features)


rnn_model = CustomRNN()
_ = rnn_model(tf.zeros((1, timesteps, input_dim)))
(1, 10, 32)

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

  • call(self, inputs, **kwargs) - حيث تكون inputs call(self, inputs, **kwargs) أو بنية متداخلة من الموترات (مثل قائمة الموترات) ، وحيث **kwargs هي **kwargs غير **kwargs (غير مدخلات).
  • call(self, inputs, training=None, **kwargs) - حيث training هو منطقي يشير إلى ما إذا كان يجب أن تتصرف الطبقة في وضع التدريب ووضع الاستدلال.
  • call(self, inputs, mask=None, **kwargs) - أين mask هو موتر قناع منطقية (مفيدة لRNNs، على سبيل المثال).
  • call(self, inputs, training=None, mask=None, **kwargs) - بالطبع ، يمكن أن يكون لديك كل من الإخفاء والسلوك الخاص بالتدريب في نفس الوقت.

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

فيما يلي مثال سريع على RNN مخصص ، مكتوب من البداية ، يتم استخدامه في نموذج وظيفي:

units = 32
timesteps = 10
input_dim = 5
batch_size = 16


class CustomRNN(layers.Layer):
    def __init__(self):
        super(CustomRNN, self).__init__()
        self.units = units
        self.projection_1 = layers.Dense(units=units, activation="tanh")
        self.projection_2 = layers.Dense(units=units, activation="tanh")
        self.classifier = layers.Dense(1)

    def call(self, inputs):
        outputs = []
        state = tf.zeros(shape=(inputs.shape[0], self.units))
        for t in range(inputs.shape[1]):
            x = inputs[:, t, :]
            h = self.projection_1(x)
            y = h + self.projection_2(state)
            state = y
            outputs.append(y)
        features = tf.stack(outputs, axis=1)
        return self.classifier(features)


# Note that you specify a static batch size for the inputs with the `batch_shape`
# arg, because the inner computation of `CustomRNN` requires a static batch size
# (when you create the `state` zeros tensor).
inputs = keras.Input(batch_shape=(batch_size, timesteps, input_dim))
x = layers.Conv1D(32, 3)(inputs)
outputs = CustomRNN()(x)

model = keras.Model(inputs, outputs)

rnn_model = CustomRNN()
_ = rnn_model(tf.zeros((1, 10, 5)))