ה- API הפונקציונלי

הצג באתר TensorFlow.org הפעל בגוגל קולאב צפה במקור ב-GitHub הורד מחברת

להכין

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

מבוא

ה- API פונקציונלי Keras היא דרך ליצור מודלים כי הם יותר גמישים מאשר tf.keras.Sequential API. ה-API הפונקציונלי יכול להתמודד עם מודלים עם טופולוגיה לא ליניארית, שכבות משותפות ואפילו כניסות או פלטים מרובים.

הרעיון המרכזי הוא שמודל למידה עמוקה הוא בדרך כלל גרף א-ציקלי מכוון (DAG) של שכבות. אז API התפקודית הוא דרך לבנות גרפים של שכבות.

שקול את הדגם הבא:

(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)

זהו גרף בסיסי עם שלוש שכבות. כדי לבנות מודל זה באמצעות ה-API הפונקציונלי, התחל ביצירת צומת קלט:

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")

png

ובאופן אופציונלי, הצג את צורות הקלט והפלט של כל שכבה בגרף המשורטט:

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

png

נתון זה והקוד כמעט זהים. בגרסת הקוד, חיצי החיבור מוחלפים בפעולת השיחה.

"גרף של שכבות" הוא תמונה מנטלית אינטואיטיבית למודל למידה עמוקה, וה-API הפונקציונלי הוא דרך ליצור מודלים המשקפים זאת היטב.

הדרכה, הערכה והסקת מסקנות

הדרכה, הערכה, ועבודת היקש בדיוק באותו אופן עבור מודלים שנוצרו בעזרת ממשק API פונקציונאלי כמו עבור Sequential דגמים.

Model ההצעות בכיתה מובנה לולאת אימונים (את fit() שיטה) ו מובנית לולאת והערכה ( evaluate() שיטה). שים לב, אתה יכול בקלות להתאים אישית לולאות אלה ליישם לשגרת אימונים מעבר למידה בפיקוח (למשל גנז ).

כאן, טען את נתוני התמונה של 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.3430 - accuracy: 0.9035 - val_loss: 0.1851 - val_accuracy: 0.9463
Epoch 2/2
750/750 [==============================] - 2s 3ms/step - loss: 0.1585 - accuracy: 0.9527 - val_loss: 0.1366 - val_accuracy: 0.9597
313/313 - 0s - loss: 0.1341 - accuracy: 0.9592
Test loss: 0.13414572179317474
Test accuracy: 0.9592000246047974

לקריאה נוספת, ראו הדרכה והערכה מדריך.

שמור והצג בסידרה

מצילים את העבודה המודל בהמשכים באותה דרך עבור מודלים שנוצרו בעזרת ממשק API פונקציונליים כמו שהם עושים עבור Sequential דגמים. הדרך הסטנדרטית להציל מודל פונקציונלי היא להתקשר model.save() כדי להציל את המודל כולו כקובץ יחיד. מאוחר יותר תוכל ליצור מחדש את אותו דגם מקובץ זה, גם אם הקוד שבנה את הדגם אינו זמין יותר.

קובץ שמור זה כולל את:

  • ארכיטקטורת מודל
  • ערכי משקל הדגם (שנלמדו במהלך האימון)
  • config אימון מודל, אם בכלל (כפי שהועבר 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")
2021-08-25 17:50:55.989736: W tensorflow/python/util/util.cc:348] Sets are not currently considered sequences, but this may change in the future, so consider avoiding using them.
INFO:tensorflow:Assets written to: path_to_my_model/assets

לפרטים, קרא את המודל בהמשכים & חיסכון מדריך.

השתמש באותו גרף של שכבות כדי להגדיר מודלים מרובים

ב-API הפונקציונלי, מודלים נוצרים על ידי ציון הקלט והפלטים שלהם בגרף של שכבות. זה אומר שניתן להשתמש בגרף יחיד של שכבות ליצירת מודלים מרובים.

בדוגמא הבאה, תשתמש באותה ערימה של שכבות להפעלה שני דגמים: An 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
_________________________________________________________________

כפי שאתה יכול לראות, המודל יכול להיות מקונן: מודל יכול להכיל תת-מודלים (שכן מודל הוא בדיוק כמו שכבה). שימוש נפוץ עבור קינון מודל ensembling. לדוגמה, הנה איך להרכיב קבוצה של מודלים למודל יחיד שמעמיד ממוצעים של התחזיות שלהם:

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 API.

לדוגמה, אם אתה בונה מערכת לדירוג כרטיסים להנפקת לקוחות לפי עדיפות ולנתב אותם למחלקה הנכונה, אז למודל יהיו שלוש כניסות:

  • כותרת הכרטיס (קלט טקסט),
  • גוף הטקסט של הכרטיס (קלט טקסט), וכן
  • כל תגיות שנוספו על ידי המשתמש (קלט קטגורי)

לדגם זה יהיו שתי יציאות:

  • ציון העדיפות בין 0 ל-1 (פלט סיגמואיד סקלרי), ו
  • המחלקה שאמורה לטפל בכרטיס (פלט softmax על מערך המחלקות).

אתה יכול לבנות את המודל הזה בכמה שורות עם ה-API הפונקציונלי:

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)

png

בעת קומפילציה של מודל זה, אתה יכול להקצות הפסדים שונים לכל פלט. אתה יכול אפילו להקצות משקלים שונים לכל הפסד -- כדי לשנות את תרומתם לאובדן האימון הכולל.

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={"priority": 1.0, "department": 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 [==============================] - 5s 9ms/step - loss: 1.2899 - priority_loss: 0.7186 - department_loss: 2.8564
Epoch 2/2
40/40 [==============================] - 0s 9ms/step - loss: 1.2668 - priority_loss: 0.6991 - department_loss: 2.8389
<keras.callbacks.History at 0x7fc1a66dc790>

בעת התקשרות בכושר עם Dataset אובייקט, זה אמור להניב אחת מעמד tuple של רשימות כמו ([title_data, body_data, tags_data], [priority_targets, dept_targets]) או tuple של מילונים כמו ({'title': title_data, 'body': body_data, 'tags': tags_data}, {'priority': priority_targets, 'department': dept_targets}) .

לקבלת הסבר מפורט יותר, עיינו הכשרה והערכת המדריך.

דגם צעצוע של ResNet

בנוסף למודלים עם כניסות ויציאות מרובות, ה- API הפונקציונלי מקל לתמרן טופולוגיות קישוריות הלא-ליניארית - אלה הם מודלים עם שכבות שאינם מחוברים ברצף, שבה Sequential API לא יכול להתמודד.

מקרה שימוש נפוץ לכך הוא חיבורים שיוריים. בואו נבנה דגם צעצוע של 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)

png

עכשיו תאמן את הדגם:

(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 [==============================] - 11s 0us/step
170508288/170498071 [==============================] - 11s 0us/step
13/13 [==============================] - 2s 29ms/step - loss: 2.3364 - acc: 0.1063 - val_loss: 2.2986 - val_acc: 0.0850
<keras.callbacks.History at 0x7fc19df22610>

שכבות משותפות

עוד שימוש טוב עבור ה- API התפקודית הם מודלים המשתמשים שכבות משותפות. שכבות משותפות הן מופעי שכבות שעושים בהם שימוש חוזר מספר פעמים באותו מודל -- הם לומדים תכונות המתאימות למספר נתיבים בגרף השכבות.

שכבות משותפות משמשות לעתים קרובות כדי לקודד קלט ממרחבים דומים (נניח, שני קטעי טקסט שונים הכוללים אוצר מילים דומה). הם מאפשרים שיתוף מידע על פני התשומות השונות הללו, והם מאפשרים לאמן מודל כזה על פחות נתונים. אם נראית מילה נתונה באחד מהקלטים, הדבר יועיל לעיבוד כל התשומות שעוברות בשכבה המשותפת.

כדי לשתף שכבה ב-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 [==============================] - 15s 0us/step
574726144/574710816 [==============================] - 15s 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 , וכו '

אבל אם אתה לא מוצא את מה שאתה צריך, קל להרחיב את ה-API על ידי יצירת שכבות משלך. כל שכבות מכל מחלקה 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})

לחלופין, ליישם את השיטה בכיתה from_config(cls, config) המשמש בעת מחדש מופע שכבת נתון מילון config שלה. יישום ברירת המחדל של from_config הוא:

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

מתי להשתמש ב-API הפונקציונלי

האם כדאי להשתמש ב- API פונקציונלי Keras ליצור מודל חדש, או סתם מכל מחלקה Model בכיתה ישירות? באופן כללי, ה-API הפונקציונלי הוא ברמה גבוהה יותר, קל יותר ובטוח יותר, ויש לו מספר תכונות שדגמי משנה אינם תומכים בהן.

עם זאת, סיווג משנה של מודלים מספק גמישות רבה יותר בעת בניית מודלים שאינם ניתנים לביטוי בקלות כגרפים א-מחזוריים מכוונים של שכבות. לדוגמה, אתה לא יכול ליישם עץ-RNN עם API פונקציונלי יצטרך מכל מחלקה Model ישירות.

לקבלת מבט מעמיק על ההבדלים בין ה- API פונקציונלי מודל subclassing, לקרוא מה הם APIs סמלי והציווי ב TensorFlow 2.0? .

חוזקות API פונקציונלי:

המאפיינים הבאים נכונים גם עבור מודלים Sequential (שהם גם מבני נתונים), אך אינם נכונים עבור מודלים עם תת-סיווג (שהם 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)))

אימות מודל תוך הגדרת גרף הקישוריות שלו

בשנות ה API פונקציונלי, המפרט קלט (הצורה dtype) נוצרת מראש (באמצעות Input ). בכל פעם שאתה קורא לשכבה, השכבה בודקת שהמפרט שהועבר אליה תואם את ההנחות שלה, והיא תעלה הודעת שגיאה מועילה אם לא.

זה מבטיח שכל מודל שתוכל לבנות עם ה-API הפונקציונלי יפעל. כל איתור באגים -- מלבד איתור באגים הקשור להתכנסות -- מתרחש באופן סטטי במהלך בניית המודל ולא בזמן ביצוע. זה דומה לבדיקת סוג במהדר.

מודל פונקציונלי ניתן לשרטוט וניתן לבדיקה

אתה יכול לשרטט את המודל כגרף, ותוכל לגשת בקלות לצמתי ביניים בגרף זה. לדוגמה, כדי לחלץ ולהשתמש מחדש בהפעלות של שכבות ביניים (כפי שניתן לראות בדוגמה קודמת):

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

מודל פונקציונלי יכול להיות מסודר או לשכפל

מכיוון שמודל פונקציונלי הוא מבנה נתונים ולא פיסת קוד, הוא ניתן לסידרה בבטחה וניתן לשמור אותו כקובץ בודד המאפשר לך ליצור מחדש את אותו מודל בדיוק מבלי שתהיה לך גישה לאף אחד מהקוד המקורי. ראה מדריך בהמשכים & חיסכון .

בהמשכי מודל subclassed, יש צורך עבור המיישם לציין get_config() ו from_config() שיטה ברמת המודל.

חולשת API פונקציונלי:

זה לא תומך בארכיטקטורות דינמיות

ה-API הפונקציונלי מתייחס למודלים כאל DAGs של שכבות. זה נכון לרוב ארכיטקטורות הלמידה העמוקה, אבל לא כולן -- למשל, רשתות רקורסיביות או Tree RNNs לא עוקבות אחר הנחה זו ולא ניתן ליישם אותן ב-API הפונקציונלי.

סגנונות API לערבב והתאמה

הבחירה בין ה-API הפונקציונלי או סיווג המשנה של מודלים אינה החלטה בינארית המגבילה אותך לקטגוריה אחת של מודלים. כל הדגמים של tf.keras API יכולים לתקשר אחד עם השני, אם הם Sequential דגמים, מודלים פונקציונלי, או מודלים subclassed הנכתבות מאפס.

אתה תמיד יכול להשתמש במודל פונקציונלי או Sequential מודל כחלק מודל או שכבת subclassed:

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)

ניתן להשתמש בכל שכבה או מודל subclassed ב- API של תפקודי כל עוד היא מיישמת call שיטת העוקב אחד הדפוסים הבאים:

  • call(self, inputs, **kwargs) - איפה inputs הוא מותח או מבנה מקונן של טנסור (למשל רשימה של טנזורים), והיכן **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 השיט על השכבה המותאמת אישית שלך או מודל, המודלים פונקציונלי לך ליצור עדיין יהיו serializable ו cloneable.

הנה דוגמה מהירה של 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)))