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

Mendirikan

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, lapisan bersama, dan bahkan beberapa masukan atau keluaran.

Ide utamanya adalah bahwa model pembelajaran dalam biasanya berupa grafik asiklik berarah (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 masukan:

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

Bentuk data ditetapkan sebagai vektor berdimensi 784. Ukuran tumpukan selalu dihilangkan karena hanya bentuk setiap 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])

Inilah dtype-nya:

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 "masukan" ke lapisan yang Anda buat ini. Anda "meneruskan" masukan ke lapisan dense , dan Anda mendapatkan x sebagai keluaran.

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 outputnya dalam grafik lapisan:

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

Mari kita lihat seperti apa ringkasan model itu:

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

Sebuah "grafik lapisan" adalah gambaran mental intuitif untuk model pembelajaran mendalam, dan API fungsional adalah cara untuk membuat model yang sangat mirip dengan ini.

Pelatihan, evaluasi, dan inferensi

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

Di sini, muat data gambar MNIST, bentuk kembali menjadi vektor, paskan model pada data (sambil memantau performa 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 [==============================] - 2s 2ms/step - loss: 0.3388 - accuracy: 0.9032 - val_loss: 0.1877 - val_accuracy: 0.9457
Epoch 2/2
750/750 [==============================] - 2s 2ms/step - loss: 0.1636 - accuracy: 0.9521 - val_loss: 0.1559 - val_accuracy: 0.9543
313/313 - 0s - loss: 0.1454 - accuracy: 0.9551
Test loss: 0.14535436034202576
Test accuracy: 0.9550999999046326

Untuk bacaan lebih lanjut, lihat panduan pelatihan dan evaluasi .

Simpan dan buat serial

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

File yang disimpan ini mencakup:

  • model arsitektur
  • nilai bobot model (yang dipelajari selama pelatihan)
  • konfigurasi pelatihan model, jika ada (seperti yang diteruskan ke compile )
  • pengoptimal dan statusnya, jika ada (untuk memulai kembali pelatihan yang Anda tinggalkan sebelumnya)
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/training/tracking/tracking.py:111: Model.state_updates (from tensorflow.python.keras.engine.training) is deprecated and will be removed in a future version.
Instructions for updating:
This property should not be used in TensorFlow 2.0, as updates are applied automatically.
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/training/tracking/tracking.py:111: Layer.updates (from tensorflow.python.keras.engine.base_layer) is deprecated and will be removed in a future version.
Instructions for updating:
This property should not be used in TensorFlow 2.0, as updates are applied automatically.
INFO:tensorflow:Assets written to: path_to_my_model/assets

Untuk detailnya, baca serialisasi model & panduan penyimpanan .

Gunakan grafik lapisan yang sama untuk menentukan beberapa model

Dalam API fungsional, model dibuat dengan menentukan masukan dan keluarannya dalam grafik lapisan. Artinya, 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 benar-benar simetris dengan arsitektur encoding, sehingga bentuk keluarannya sama dengan bentuk masukan (28, 28, 1) .

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

Semua model dapat dipanggil, seperti halnya lapisan

Anda dapat memperlakukan model apa pun seolah-olah itu adalah lapisan dengan memanggilnya 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 contoh 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 disarangkan: model dapat berisi sub-model (karena model seperti lapisan). Kasus penggunaan umum 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 masukan dan keluaran

API fungsional memudahkan untuk memanipulasi banyak masukan dan keluaran. Ini tidak dapat ditangani dengan Sequential API.

Misalnya, jika Anda membangun sistem untuk memberi peringkat tiket terbitan pelanggan berdasarkan prioritas dan mengarahkannya ke departemen yang benar, model tersebut akan memiliki tiga masukan:

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

Model ini akan memiliki dua keluaran:

  • skor prioritas antara 0 dan 1 (keluaran sigmoid skalar), 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 keluaran. Anda bahkan dapat menetapkan bobot yang berbeda untuk setiap kerugian - 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 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 meneruskan daftar array NumPy input dan target:

# 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 11ms/step - loss: 1.2819 - priority_loss: 0.7088 - department_loss: 2.8653
Epoch 2/2
40/40 [==============================] - 0s 10ms/step - loss: 1.2723 - priority_loss: 0.6992 - department_loss: 2.8655

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

Saat memanggil fit dengan objek Dataset , itu harus menghasilkan tuple daftar seperti ([title_data, body_data, tags_data], [priority_targets, dept_targets]) atau tuple 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 beberapa input dan output, API fungsional memudahkan untuk memanipulasi topologi konektivitas non-linier - ini adalah model dengan lapisan yang tidak terhubung secara berurutan, yang tidak dapat ditangani oleh Sequential API.

Kasus penggunaan umum untuk ini adalah koneksi sisa. Mari kita buat model mainan ResNet untuk CIFAR10 untuk mendemonstrasikan 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 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)
13/13 [==============================] - 0s 21ms/step - loss: 2.3574 - acc: 0.1037 - val_loss: 2.3011 - val_acc: 0.1000

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

Lapisan bersama

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

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

Untuk berbagi lapisan di API fungsional, panggil instance lapisan yang sama beberapa kali. Misalnya, berikut ini adalah lapisan Embedding dibagikan 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, maka dapat diakses dan diperiksa. Dan inilah cara Anda dapat memplot model fungsional sebagai gambar.

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

Mari kita lihat contohnya. 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 [==============================] - 20s 0us/step

Dan ini adalah aktivasi menengah 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)

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

Perluas API menggunakan lapisan khusus

tf.keras menyertakan berbagai macam 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 memperluas API dengan membuat lapisan Anda sendiri. Semua lapisan membuat subkelas kelas Layer dan menerapkan:

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

Untuk mempelajari lebih lanjut tentang membuat lapisan dari awal, baca lapisan kustom dan panduan 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 lapisan kustom Anda, tentukan metode get_config yang mengembalikan argumen konstruktor dari instance 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})

Secara opsional, implementasikan metode kelas from_config(cls, config) yang digunakan saat membuat ulang instance lapisan dengan kamus from_config(cls, config) . Implementasi default from_config adalah:

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

Kapan menggunakan API fungsional

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

Namun, subclass model memberikan fleksibilitas yang lebih besar saat membangun model yang tidak mudah diekspresikan seperti grafik asiklik lapisan yang diarahkan. Misalnya, Anda tidak dapat mengimplementasikan Tree-RNN dengan API fungsional dan harus membuat Model subclass secara langsung.

Untuk melihat lebih dalam tentang perbedaan antara API fungsional dan subclass model, baca Apa itu Symbolic and Imperative APIs di TensorFlow 2.0? .

Kekuatan API fungsional:

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

Lebih sedikit 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 menentukan grafik konektivitasnya

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

Ini menjamin bahwa model apa pun yang 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 direncanakan dan 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 potongan kode, model ini dapat diserialkan dengan aman dan dapat disimpan sebagai file tunggal yang memungkinkan Anda membuat ulang model yang sama persis tanpa memiliki akses ke kode asli mana pun. Lihat serialisasi & panduan penyimpanan .

Untuk membuat serial model subclass, 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 sebagian besar arsitektur deep learning, tetapi tidak semua - misalnya, jaringan rekursif atau Tree RNN tidak mengikuti asumsi ini dan tidak dapat diterapkan di API fungsional.

Padu padan gaya API

Memilih antara API fungsional atau subclass Model bukanlah keputusan biner yang membatasi Anda ke dalam satu kategori model. Semua model di tf.keras API dapat berinteraksi satu sama lain, baik itu model Sequential , model fungsional, atau model subclass 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 dapat menggunakan lapisan atau model subclass apa pun di API fungsional selama API tersebut mengimplementasikan metode call yang mengikuti salah satu pola berikut:

  • call(self, inputs, **kwargs) - Di mana inputs adalah tensor atau struktur tensor bersarang (misalnya 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 lapisan harus berperilaku dalam mode pelatihan dan mode inferensi.
  • call(self, inputs, mask=None, **kwargs) - Dimana mask adalah boolean mask tensor (berguna untuk RNN, misalnya).
  • call(self, inputs, training=None, mask=None, **kwargs) - Tentu saja, Anda dapat melakukan masking dan perilaku khusus pelatihan pada saat yang bersamaan.

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

Berikut adalah contoh cepat 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)))