דף זה תורגם על ידי Cloud Translation API.
Switch to English

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

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

להכין

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

מבוא

ממשק ה- API הפונקציונלי של Keras הוא דרך ליצור מודלים גמישים יותר מאשר ה- API של tf.keras.Sequential . ה- 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 .

כאן, טען את נתוני התמונה 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.5892 - accuracy: 0.8387 - val_loss: 0.1952 - val_accuracy: 0.9424
Epoch 2/2
750/750 [==============================] - 2s 3ms/step - loss: 0.1800 - accuracy: 0.9451 - val_loss: 0.1507 - val_accuracy: 0.9557
313/313 - 0s - loss: 0.1486 - accuracy: 0.9537
Test loss: 0.14858466386795044
Test accuracy: 0.9537000060081482

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

שמור וסידר

שמירת המודל והסידרה פועלים באותה צורה עבור מודלים שנבנו באמצעות ה- API הפונקציונלי כפי שהם עושים עבור מודלים 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

לפרטים, קרא את המדריך לסידור ושמירת המודל.

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

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

בדוגמה שלהלן, אתה משתמש באותה ערימה של שכבות כדי ליצור שני מודלים: מודל encoder שהופך את קלטי התמונה לווקטורים 16 ממדיים, ומודל autoencoder מקצה 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 הפונקציונלי מקל על מניפולציה של מספר כניסות ופלטים. לא ניתן לטפל בזה באמצעות ה- API Sequential .

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

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

למודל זה שתי יציאות:

  • ציון העדיפות בין 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=[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 [==============================] - 5s 14ms/step - loss: 1.3045 - priority_loss: 0.6949 - department_loss: 3.0484
Epoch 2/2
40/40 [==============================] - 1s 13ms/step - loss: 1.3587 - priority_loss: 0.6994 - department_loss: 3.2962

<tensorflow.python.keras.callbacks.History at 0x7faee8e3c1d0>

([title_data, body_data, tags_data], [priority_targets, dept_targets]) להתאמה עם אובייקט Dataset , הוא אמור להניב כפל של רשימות כמו ([title_data, body_data, tags_data], [priority_targets, dept_targets]) או ([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 צעצוע

בנוסף למודלים עם מספר כניסות ופלטים, ה- API הפונקציונלי מקל על מניפולציה של טופולוגיות קישוריות לא ליניאריות - אלה מודלים עם שכבות שאינן מחוברות ברצף, אשר ה- API של 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)

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)
13/13 [==============================] - 2s 38ms/step - loss: 2.3014 - acc: 0.1085 - val_loss: 2.3005 - val_acc: 0.1150

<tensorflow.python.keras.callbacks.History at 0x7faf7c0d5ef0>

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

שימוש טוב נוסף עבור ה- 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 [==============================] - 13s 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 בשכבה המותאמת אישית שלך, הגדר שיטת 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) שיטת המחלקה from_config(cls, config) המשמשת בעת יצירת מופע שכבה מחדש בהינתן מילון התצורה שלו. יישום ברירת המחדל של from_config הוא:

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

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

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

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

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

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

המאפיינים הבאים נכונים גם למודלים רציפים (שהם גם מבני נתונים), אך אינם נכונים למודלים המשויכים לסיווג משנה (שהם קוד bytecode של Python, ולא מבני נתונים).

פחות מילולית

אין 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)

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

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

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

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

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

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

סגנונות API של Mix-and-Match

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

אתה תמיד יכול להשתמש במודל פונקציונלי או במודל 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)

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

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