API Fungsional

Tetap teratur dengan koleksi Simpan dan kategorikan konten berdasarkan preferensi Anda.

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

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

Ide utamanya adalah bahwa model pembelajaran mendalam biasanya berupa grafik asiklik terarah (DAG) dari lapisan. Jadi API fungsional adalah cara untuk membangun grafik dari lapisan.

Perhatikan 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 masukan:

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

Bentuk data ditetapkan sebagai vektor 784 dimensi. Ukuran batch selalu dihilangkan karena hanya bentuk masing-masing sampel yang ditentukan.

Jika, misalnya, Anda memiliki masukan 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. Berikut bentuknya:

inputs.shape
TensorShape([None, 784])

Berikut tipe d-nya:

inputs.dtype
tf.float32

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

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

Tindakan "panggilan lapisan" seperti menggambar panah dari "input" ke lapisan yang Anda buat ini. Anda sedang "melewati" input ke dense lapisan, dan Anda mendapatkan x sebagai output.

Mari tambahkan beberapa lapisan lagi ke grafik lapisan:

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

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

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

Mari kita lihat seperti apa ringkasan modelnya:

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, tampilkan 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 ini dan kodenya hampir sama. Dalam versi kode, panah koneksi diganti dengan operasi panggilan.

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

Pelatihan, evaluasi, dan inferensi

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

The Model menawarkan kelas built-in lingkaran pelatihan (yang fit() metode) dan built-in evaluasi lingkaran (yang evaluate() metode). Perhatikan bahwa Anda dapat dengan mudah menyesuaikan loop ini untuk melaksanakan rutinitas pelatihan di luar pembelajaran terawasi (misalnya Gans ).

Di sini, muat data gambar MNIST, bentuk ulang menjadi vektor, sesuaikan model pada data (sambil memantau kinerja pada pemisahan validasi), lalu 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 [==============================] - 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

Untuk bacaan lebih lanjut, lihat pelatihan dan evaluasi panduan.

Simpan dan buat cerita bersambung

Menyimpan model dan serialisasi bekerja dengan cara yang sama untuk model yang dibangun menggunakan API fungsional seperti yang mereka lakukan untuk Sequential model. Cara standar untuk menyimpan model fungsional adalah untuk memanggil model.save() untuk menyimpan seluruh model sebagai file tunggal. Nanti Anda dapat membuat ulang model yang sama dari file ini, meskipun kode yang membuat model tersebut tidak lagi tersedia.

File yang disimpan ini mencakup:

  • arsitektur model
  • nilai bobot model (yang dipelajari selama pelatihan)
  • Model pelatihan config, jika ada (sebagai dilewatkan ke compile )
  • pengoptimal dan statusnya, jika ada (untuk memulai kembali pelatihan yang 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")
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

Untuk detailnya, baca model serialisasi & tabungan panduan.

Gunakan grafik lapisan yang sama untuk mendefinisikan beberapa model

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

Pada contoh di bawah, Anda menggunakan tumpukan yang sama dari lapisan untuk instantiate dua model: sebuah encoder model yang input berubah gambar ke vektor 16-dimensi, dan end-to-end autoencoder model 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 secara ketat simetris dengan arsitektur encoding, sehingga bentuk keluaran adalah sama dengan bentuk masukan (28, 28, 1) .

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

Semua model dapat dipanggil, seperti halnya lapisan

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

Untuk melihat ini beraksi, berikut adalah contoh berbeda dari autoencoder yang membuat model encoder, model decoder, dan menghubungkannya 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 (Functional)         (None, 16)                18672     
_________________________________________________________________
decoder (Functional)         (None, 28, 28, 1)         9569      
=================================================================
Total params: 28,241
Trainable params: 28,241
Non-trainable params: 0
_________________________________________________________________

Seperti yang Anda lihat, model dapat bersarang: model dapat berisi sub-model (karena model seperti layer). Contoh umum penggunaan untuk model bersarang adalah ensembling. Misalnya, berikut ini cara menggabungkan sekumpulan model menjadi satu model yang rata-rata prediksinya:

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 sedang membangun sistem untuk memeringkat tiket yang diterbitkan pelanggan berdasarkan prioritas dan mengarahkannya ke departemen yang benar, maka model tersebut akan memiliki tiga input:

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

Model ini akan memiliki dua output:

  • skor prioritas antara 0 dan 1 (output skalar 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 kehilangan -- untuk memodulasi kontribusinya terhadap total kerugian 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 lapisan keluaran memiliki nama yang berbeda, Anda juga dapat menentukan bobot rugi dan rugi dengan nama lapisan yang sesuai:

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

Latih model dengan meneruskan 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 [==============================] - 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>

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

Untuk penjelasan lebih rinci, lihat pelatihan dan evaluasi panduan.

Model mainan ResNet

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

Kasus penggunaan yang umum untuk ini adalah koneksi sisa. Mari kita buat 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
__________________________________________________________________________________________________

Gambarkan modelnya:

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 [==============================] - 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>

Lapisan bersama

Penggunaan lain yang baik untuk API fungsional adalah model yang menggunakan lapisan bersama. Lapisan bersama adalah contoh lapisan yang digunakan kembali beberapa kali dalam model yang sama -- lapisan tersebut mempelajari fitur yang sesuai dengan beberapa jalur dalam grafik lapisan.

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

Untuk berbagi lapisan di API fungsional, panggil instance lapisan yang sama beberapa kali. Sebagai contoh, di sini adalah Embedding lapisan dibagi di dua input teks yang 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 lapisan

Karena grafik lapisan yang Anda manipulasi adalah struktur data statis, grafik 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 perantara ("simpul" 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 telah dilatih sebelumnya 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 [==============================] - 15s 0us/step
574726144/574710816 [==============================] - 15s 0us/step

Dan ini adalah aktivasi perantara dari model, yang 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 aktivasi lapisan perantara:

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)

Hal ini sangat berguna untuk tugas-tugas seperti gaya saraf mentransfer , antara lain.

Perluas API menggunakan lapisan khusus

tf.keras mencakup berbagai built-in lapisan, misalnya:

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

Tetapi jika Anda tidak menemukan apa yang Anda butuhkan, mudah untuk memperluas API dengan membuat lapisan Anda sendiri. Semua lapisan subclass Layer kelas dan melaksanakan:

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

Untuk mempelajari lebih lanjut tentang membuat lapisan dari awal, membaca lapisan kustom dan model panduan.

Berikut ini adalah implementasi dasar 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 lapisan kustom Anda, menentukan get_config metode yang mengembalikan argumen konstruktor dari contoh lapisan:

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

Opsional, menerapkan metode kelas from_config(cls, config) yang digunakan ketika menciptakan contoh lapisan diberikan kamus config-nya. Implementasi standar dari from_config adalah:

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

Kapan menggunakan API fungsional

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

Namun, subklasifikasi model memberikan fleksibilitas yang lebih besar ketika membangun model yang tidak mudah diekspresikan sebagai grafik asiklik berarah dari lapisan. Misalnya, Anda tidak bisa menerapkan Pohon-RNN dengan API fungsional dan harus subclass Model langsung.

Untuk mendalam melihat perbedaan antara API fungsional dan model subclassing, baca Apa API simbolik dan Imperatif dalam TensorFlow 2.0? .

Kekuatan API fungsional:

Properti berikut 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 dtype) dibuat di muka (menggunakan Input ). Setiap kali Anda memanggil sebuah lapisan, lapisan tersebut memeriksa apakah spesifikasi yang diteruskan ke sana cocok dengan asumsinya, dan itu akan memunculkan pesan kesalahan yang berguna jika tidak.

Ini menjamin bahwa model apa pun yang dapat Anda buat dengan API fungsional akan berjalan. Semua debugging -- selain debugging terkait konvergensi -- terjadi secara statis selama konstruksi model dan bukan pada waktu eksekusi. Ini mirip dengan pemeriksaan tipe di kompiler.

Model fungsional dapat diplot 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 perantara (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 diserialkan atau dikloning

Karena model fungsional adalah struktur data dan bukan sepotong kode, model ini dapat diserialisasi dengan aman dan dapat disimpan sebagai satu file yang memungkinkan Anda membuat ulang model yang sama persis tanpa memiliki akses ke kode asli mana pun. Lihat serialisasi & tabungan panduan .

Untuk cerita bersambung model subclassed, perlu untuk pelaksana untuk menentukan get_config() dan from_config() metode di tingkat model.

Kelemahan API fungsional:

Itu tidak mendukung arsitektur dinamis

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

Padu padankan gaya API

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

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

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 dapat menggunakan lapisan subclassed atau model dalam API fungsional selama itu menerapkan call metode yang mengikuti salah satu pola berikut:

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

Selain itu, jika Anda menerapkan get_config metode pada Layer kustom Anda atau model, model fungsional yang Anda buat akan tetap serializable dan cloneable.

Berikut adalah contoh cepat dari RNN kustom, yang ditulis dari awal, 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)))