Halaman ini diterjemahkan oleh Cloud Translation API.
Switch to English

API Fungsional

Lihat di TensorFlow.org Jalankan di Google Colab Lihat sumber di GitHub Unduh buku catatan

Mempersiapkan

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

pengantar

API fungsional Keras adalah cara untuk membuat model yang lebih fleksibel daripada API tf.keras.Sequential . API fungsional dapat menangani model dengan topologi non-linier, model dengan lapisan bersama, dan model dengan banyak input atau output.

Gagasan utama bahwa model pembelajaran dalam biasanya adalah grafik asiklik terarah (DAG) dari lapisan. Jadi API fungsional adalah cara untuk membangun grafik lapisan .

Pertimbangkan model berikut:

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

Ini adalah grafik dasar dengan tiga lapisan. Untuk membangun model ini menggunakan API fungsional, mulailah dengan membuat simpul input:

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

Bentuk data ditetapkan sebagai vektor 784 dimensi. Ukuran bets selalu dihilangkan karena hanya bentuk setiap sampel yang ditentukan.

Jika, misalnya, Anda memiliki input gambar dengan bentuk (32, 32, 3) , Anda akan menggunakan:

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

The inputs yang dikembalikan berisi informasi tentang bentuk dan dtype dari input data yang Anda makan untuk model Anda. Inilah bentuknya:

 inputs.shape
 
TensorShape([None, 784])

Inilah dtype:

 inputs.dtype
 
tf.float32

Anda membuat simpul baru dalam grafik lapisan dengan memanggil lapisan pada objek inputs ini:

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

Tindakan "panggilan lapisan" seperti menggambar panah dari "input" ke lapisan yang Anda buat. Anda "melewatkan" input ke lapisan dense , dan Anda mendapatkan x .

Mari kita tambahkan beberapa layer lagi ke grafik layer:

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

Pada titik ini, Anda dapat membuat Model dengan menentukan input dan outputnya dalam grafik lapisan:

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

Mari kita lihat seperti apa ringkasan 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
_________________________________________________________________

Anda juga dapat memplot model sebagai grafik:

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

png

Dan, secara opsional, menampilkan bentuk input dan output dari setiap lapisan dalam grafik yang diplot:

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

png

Angka dan kode ini hampir identik. Dalam versi kode, panah koneksi digantikan oleh operasi panggilan.

"Grafik lapisan" adalah gambar mental yang intuitif untuk model pembelajaran yang mendalam, dan API fungsional adalah cara untuk membuat model yang mencerminkan hal ini.

Pelatihan, evaluasi, dan inferensi

Pelatihan, evaluasi, dan inferensi bekerja persis dengan cara yang sama untuk model yang dibangun menggunakan API fungsional seperti untuk model Sequential .

Di sini, muat data gambar MNIST, bentuk kembali menjadi vektor, sesuaikan model pada data (sambil memantau kinerja pada pemisahan validasi), kemudian evaluasi model pada data uji:

 (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 [==============================] - 2s 3ms/step - loss: 0.3458 - accuracy: 0.9013 - val_loss: 0.1860 - val_accuracy: 0.9463
Epoch 2/2
750/750 [==============================] - 2s 2ms/step - loss: 0.1588 - accuracy: 0.9524 - val_loss: 0.1242 - val_accuracy: 0.9645
313/313 - 1s - loss: 0.1297 - accuracy: 0.9604
Test loss: 0.12967276573181152
Test accuracy: 0.9603999853134155

Untuk bacaan lebih lanjut, lihat panduan pelatihan dan evaluasi .

Simpan dan cerita bersambung

Menyimpan model dan serialisasi bekerja dengan cara yang sama untuk model yang dibuat menggunakan API fungsional seperti halnya untuk model Sequential . Untuk cara standar untuk menyimpan model fungsional adalah dengan memanggil model.save() untuk menyimpan seluruh model sebagai satu file. Anda nanti dapat membuat kembali model yang sama dari file ini, bahkan jika kode yang membangun model tidak lagi tersedia.

File yang disimpan ini termasuk:

  • arsitektur model
  • model nilai berat (yang dipelajari selama pelatihan)
  • config pelatihan model, jika ada (sebagaimana diteruskan ke compile )
  • pengoptimal dan statusnya, jika ada (untuk memulai kembali pelatihan di mana Anda tinggalkan)
 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")
 
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/ops/resource_variable_ops.py:1817: calling BaseResourceVariable.__init__ (from tensorflow.python.ops.resource_variable_ops) with constraint is deprecated and will be removed in a future version.
Instructions for updating:
If using Keras pass *_constraint arguments to layers.
INFO:tensorflow:Assets written to: path_to_my_model/assets

Untuk detailnya, baca panduan serialisasi & penghematan model.

Gunakan grafik lapisan yang sama untuk mendefinisikan beberapa model

Dalam API fungsional, model dibuat dengan menentukan input dan output mereka dalam grafik lapisan. Itu berarti bahwa satu grafik lapisan dapat digunakan untuk menghasilkan banyak model.

Dalam contoh di bawah ini, Anda menggunakan tumpukan lapisan yang sama untuk instantiate dua model: model encoder yang mengubah input gambar menjadi vektor 16-dimensi, dan model autoencoder ujung ke ujung untuk pelatihan.

 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
_________________________________________________________________

Di sini, arsitektur decoding sangat simetris dengan arsitektur pengkodean, sehingga bentuk output sama dengan bentuk input (28, 28, 1) .

Kebalikan dari lapisan Conv2D adalah lapisan Conv2DTranspose , dan kebalikan dari lapisan MaxPooling2D adalah lapisan UpSampling2D .

Semua model bisa dipanggil, sama seperti lapisan

Anda dapat memperlakukan model apa saja seolah-olah itu adalah lapisan dengan menjalankannya pada Input atau pada output dari lapisan lain. Dengan memanggil model Anda tidak hanya menggunakan kembali arsitektur model, Anda juga menggunakan kembali bobotnya.

Untuk melihat ini dalam tindakan, berikut ini contoh berbeda pada contoh autoencoder yang membuat model encoder, model decoder, dan rantai mereka dalam dua panggilan untuk mendapatkan model autoencoder:

 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 (Model)              (None, 16)                18672     
_________________________________________________________________
decoder (Model)              (None, 28, 28, 1)         9569      
=================================================================
Total params: 28,241
Trainable params: 28,241
Non-trainable params: 0
_________________________________________________________________

Seperti yang Anda lihat, modelnya dapat disarangkan: sebuah model dapat berisi sub-model (karena sebuah model seperti layer). Kasus penggunaan umum untuk model bersarang adalah ensembling . Sebagai contoh, berikut ini cara membuat rangkaian model menjadi model tunggal yang rata-rata prediksi mereka:

 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)
 

Memanipulasi topologi grafik yang kompleks

Model dengan banyak input dan output

API fungsional memudahkan untuk memanipulasi banyak input dan output. Ini tidak dapat ditangani dengan Sequential API.

Misalnya, jika Anda membangun sistem untuk menentukan peringkat tiket edisi khusus berdasarkan prioritas dan merutekannya ke departemen yang benar, maka model akan memiliki tiga input:

  • judul tiket (input teks),
  • badan teks tiket (input teks), dan
  • tag apa pun yang ditambahkan oleh pengguna (input kategorikal)

Model ini akan memiliki dua output:

  • skor prioritas antara 0 dan 1 (skalar output sigmoid), dan
  • departemen yang harus menangani tiket (output softmax di atas set departemen).

Anda dapat membangun model ini dalam beberapa baris dengan API fungsional:

 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],
)
 

Sekarang plot modelnya:

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

png

Saat menyusun model ini, Anda dapat menetapkan kerugian yang berbeda untuk setiap output. Anda bahkan dapat menetapkan bobot yang berbeda untuk setiap kerugian - untuk memodulasi kontribusi mereka terhadap total kehilangan pelatihan.

 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],
)
 

Karena layer output memiliki nama yang berbeda, Anda juga bisa menentukan kerugian seperti ini:

 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],
)
 

Latih model dengan melewati daftar array input dan target 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 [==============================] - 0s 12ms/step - loss: 1.3372 - priority_loss: 0.7055 - department_loss: 3.1586
Epoch 2/2
40/40 [==============================] - 0s 11ms/step - loss: 1.3285 - priority_loss: 0.6998 - department_loss: 3.1438

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

Ketika memanggil cocok dengan objek Dataset , ia harus menghasilkan tupel daftar seperti ([title_data, body_data, tags_data], [priority_targets, dept_targets]) atau tupel kamus seperti ({'title': title_data, 'body': body_data, 'tags': tags_data}, {'priority': priority_targets, 'department': dept_targets}) .

Untuk penjelasan lebih rinci, lihat panduan pelatihan dan evaluasi .

Model mainan ResNet

Selain model dengan banyak input dan output, API fungsional memudahkan untuk memanipulasi topologi konektivitas non-linear - ini adalah model dengan lapisan yang tidak terhubung secara berurutan. Sesuatu yang Sequential API tidak bisa tangani.

Kasus penggunaan umum untuk ini adalah koneksi residual. Mari kita membangun model mainan ResNet untuk CIFAR10 untuk menunjukkan ini:

 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
__________________________________________________________________________________________________

Plot model:

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

png

Sekarang latih modelnya:

 (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 [==============================] - 0s 25ms/step - loss: 2.3091 - acc: 0.1037 - val_loss: 2.2991 - val_acc: 0.1300

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

Lapisan bersama

Penggunaan lain yang baik untuk API fungsional adalah untuk model yang menggunakan lapisan bersama . Layer bersama adalah instance layer yang digunakan kembali beberapa kali dalam model yang sama - mereka mempelajari fitur yang berhubungan dengan banyak jalur dalam grafik lapisan.

Lapisan bersama sering digunakan untuk menyandikan input dari ruang yang sama (katakanlah, dua bagian berbeda dari teks yang menampilkan kosakata serupa). Mereka memungkinkan berbagi informasi di berbagai input yang berbeda ini, dan mereka memungkinkan untuk melatih model seperti itu pada data yang lebih sedikit. Jika kata yang diberikan terlihat di salah satu input, itu akan menguntungkan pemrosesan semua input yang melewati lapisan bersama.

Untuk berbagi lapisan dalam API fungsional, panggil instance lapisan yang sama beberapa kali. Misalnya, inilah lapisan Embedding dibagikan di dua input teks berbeda:

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

Ekstrak dan gunakan kembali node dalam grafik layer

Karena grafik lapisan yang Anda manipulasi adalah struktur data statis, itu dapat diakses dan diperiksa. Dan ini adalah bagaimana Anda dapat memplot model fungsional sebagai gambar.

Ini juga berarti bahwa Anda dapat mengakses aktivasi lapisan menengah ("node" dalam grafik) dan menggunakannya kembali di tempat lain - yang sangat berguna untuk sesuatu seperti ekstraksi fitur.

Mari kita lihat sebuah contoh. Ini adalah model VGG19 dengan bobot yang dipra-pra-pakai di 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 [==============================] - 5s 0us/step

Dan ini adalah aktivasi menengah dari model, diperoleh dengan menanyakan struktur data grafik:

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

Gunakan fitur ini untuk membuat model ekstraksi fitur baru yang mengembalikan nilai-nilai aktivasi lapisan menengah:

 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)
 

Ini sangat berguna untuk tugas-tugas seperti transfer gaya saraf , antara lain.

Perpanjang API menggunakan lapisan khusus

tf.keras mencakup berbagai lapisan tf.keras , misalnya:

  • Lapisan konvolusional: Conv1D , Conv2D , Conv3D , Conv2DTranspose
  • Pooling lapisan: MaxPooling1D , MaxPooling2D , MaxPooling3D , AveragePooling1D
  • Lapisan RNN: GRU , LSTM , ConvLSTM2D
  • BatchNormalization , Dropout , Embedding , dll.

Tetapi jika Anda tidak menemukan apa yang Anda butuhkan, mudah untuk memperpanjang API dengan membuat layer Anda sendiri. Semua lapisan subkelas kelas Layer dan mengimplementasikan:

  • metode call , yang menentukan perhitungan yang dilakukan oleh layer.
  • metode build , yang menciptakan bobot lapisan (ini hanya konvensi gaya karena Anda dapat membuat bobot di __init__ , juga).

Untuk mempelajari lebih lanjut tentang cara membuat lapisan dari awal, baca panduan lapisan khusus dan model .

Berikut ini adalah implementasi dasar dari 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)
 

Untuk dukungan serialisasi di layer kustom Anda, tentukan metode get_config yang mengembalikan argumen konstruktor dari instance layer:

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

Secara opsional, terapkan classmethod from_config(cls, config) yang digunakan saat membuat ulang instance layer dengan kamus konfigurasi. Implementasi default from_config adalah:

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

Kapan menggunakan API fungsional

Kapan Anda harus menggunakan API fungsional Keras untuk membuat model baru, atau hanya subkelas kelas Model secara langsung? Secara umum, API fungsional adalah tingkat yang lebih tinggi, lebih mudah dan lebih aman, dan memiliki sejumlah fitur yang tidak didukung model subclass.

Namun, model subclassing memberikan fleksibilitas yang lebih besar ketika membangun model yang tidak mudah diungkapkan sebagai grafik lapisan asiklik yang diarahkan. Misalnya, Anda tidak dapat mengimplementasikan Tree-RNN dengan API fungsional dan harus langsung mensubklas Model .

Untuk melihat lebih mendalam perbedaan antara API fungsional dan subklas model, baca Apa itu API Simbolik dan Imperatif di TensorFlow 2.0? .

Kekuatan API fungsional:

Properti berikut ini juga berlaku untuk model Sequential (yang juga merupakan struktur data), tetapi tidak berlaku untuk model subkelas (yang merupakan bytecode Python, bukan struktur data).

Kurang bertele-tele

Tidak ada super(MyClass, self).__init__(...) , tidak ada def call(self, ...): :, dll.

Membandingkan:

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

Dengan versi subclass:

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

Validasi model saat mendefinisikan grafik konektivitasnya

Dalam API fungsional, spesifikasi input (bentuk dan tipe) dibuat terlebih dahulu (menggunakan Input ). Setiap kali Anda memanggil sebuah layer, layer tersebut memeriksa bahwa spesifikasi yang diteruskan kepadanya cocok dengan asumsinya, dan itu akan memunculkan pesan kesalahan yang membantu jika tidak.

Ini menjamin bahwa model apa pun yang Anda bangun dengan API fungsional akan berjalan. Semua debugging - selain debugging yang berhubungan dengan konvergensi - terjadi secara statis selama konstruksi model dan bukan pada waktu pelaksanaan. Ini mirip dengan mengetikkan pengecekan di kompiler.

Model fungsional mudah dipahami dan dapat diperiksa

Anda dapat memplot model sebagai grafik, dan Anda dapat dengan mudah mengakses node perantara dalam grafik ini. Misalnya, untuk mengekstrak dan menggunakan kembali aktivasi lapisan menengah (seperti yang terlihat pada contoh sebelumnya):

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

Model fungsional dapat diserialisasi atau dikloning

Karena model fungsional adalah struktur data dan bukan sepotong kode, maka serializable dengan aman dan dapat disimpan sebagai file tunggal yang memungkinkan Anda untuk membuat kembali model yang sama persis tanpa memiliki akses ke salah satu kode asli. Lihat panduan serialisasi & penyimpanan .

Untuk membuat serialisasi model subkelas, pelaksana perlu menentukan metode get_config() dan from_config() di tingkat model.

Kelemahan API fungsional:

Itu tidak mendukung arsitektur dinamis

API fungsional memperlakukan model sebagai DAG lapisan. Ini berlaku untuk kebanyakan arsitektur pembelajaran yang mendalam, tetapi tidak semua - misalnya, jaringan rekursif atau RNN Pohon tidak mengikuti asumsi ini dan tidak dapat diimplementasikan dalam API fungsional.

Gaya campuran-dan-cocokkan API

Memilih antara fungsional API atau subklasifikasi Model bukanlah keputusan biner yang membatasi Anda ke dalam satu kategori model. Semua model di API tf.keras dapat berinteraksi satu sama lain, apakah itu model Sequential , model fungsional, atau model subklas yang ditulis dari awal.

Anda selalu dapat menggunakan model fungsional atau model Sequential sebagai bagian dari model atau lapisan subclass:

 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)

Anda bisa menggunakan lapisan atau model subkelas apa pun di API fungsional selama menerapkan metode call yang mengikuti salah satu pola berikut:

  • call(self, inputs, **kwargs) - Di mana inputs adalah struktur tensor atau bersarang dari tensor (mis. daftar tensor), dan di mana **kwargs adalah argumen non-tensor (non-input).
  • call(self, inputs, training=None, **kwargs) - Di mana training adalah boolean yang menunjukkan apakah layer harus berperilaku dalam mode pelatihan dan mode inferensi.
  • call(self, inputs, mask=None, **kwargs) - Di mana mask adalah tensor mask boolean (berguna untuk RNNs, misalnya).
  • call(self, inputs, training=None, mask=None, **kwargs) - Tentu saja, Anda dapat memiliki perilaku masking dan pelatihan khusus pada saat yang sama.

Selain itu, jika Anda menerapkan metode get_config pada Layer atau model kustom Anda, model fungsional yang Anda buat masih dapat serial dan cloneable.

Berikut adalah contoh cepat RNN khusus, yang ditulis dari awal, yang digunakan dalam model fungsional:

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