Giúp bảo vệ Great Barrier Reef với TensorFlow trên Kaggle Tham Challenge

API chức năng

Xem trên TensorFlow.org Chạy trong Google Colab Xem nguồn trên GitHub Tải xuống sổ ghi chép

Thành lập

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

Giới thiệu

Các Keras chức năng API là một cách để tạo ra các mô hình linh hoạt hơn so với tf.keras.Sequential API. API chức năng có thể xử lý các mô hình có cấu trúc liên kết phi tuyến tính, các lớp chia sẻ và thậm chí nhiều đầu vào hoặc đầu ra.

Ý tưởng chính là mô hình học sâu thường là một đồ thị xoay chiều có hướng (DAG) của các lớp. Vì vậy, các API chức năng là một cách để xây dựng đồ thị của lớp.

Hãy xem xét mô hình sau:

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

Đây là một đồ thị cơ bản có ba lớp. Để xây dựng mô hình này bằng cách sử dụng API chức năng, hãy bắt đầu bằng cách tạo một nút đầu vào:

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

Hình dạng của dữ liệu được đặt dưới dạng vectơ 784 chiều. Kích thước lô luôn bị bỏ qua vì chỉ hình dạng của từng mẫu được chỉ định.

Nếu, ví dụ, bạn có một đầu vào hình ảnh với một hình dạng của (32, 32, 3) , bạn sẽ sử dụng:

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

Các inputs được trả về chứa thông tin về hình dạng và dtype của dữ liệu đầu vào mà bạn ăn vào mô hình của bạn. Đây là hình dạng:

inputs.shape
TensorShape([None, 784])

Đây là loại dtype:

inputs.dtype
tf.float32

Bạn tạo một nút mới trong đồ thị của các lớp bằng cách gọi một lớp về vấn đề này inputs đối tượng:

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

Hành động "gọi lớp" giống như vẽ một mũi tên từ "đầu vào" đến lớp này mà bạn đã tạo. Bạn đang "đi qua" các yếu tố đầu vào dense lớp, và bạn sẽ có được x như đầu ra.

Hãy thêm một vài lớp nữa vào biểu đồ của các lớp:

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

Tại thời điểm này, bạn có thể tạo một Model bằng cách xác định đầu vào và đầu ra của nó trong đồ thị dưới các lớp:

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

Hãy cùng xem tóm tắt mô hình trông như thế nào:

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
_________________________________________________________________

Bạn cũng có thể vẽ mô hình dưới dạng đồ thị:

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

png

Và, theo tùy chọn, hiển thị hình dạng đầu vào và đầu ra của mỗi lớp trong biểu đồ được vẽ:

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

png

Hình này và mã gần như giống hệt nhau. Trong phiên bản mã, các mũi tên kết nối được thay thế bằng thao tác gọi.

"Biểu đồ các lớp" là một hình ảnh tinh thần trực quan cho mô hình học sâu và API chức năng là một cách để tạo ra các mô hình phản ánh chặt chẽ điều này.

Đào tạo, đánh giá và suy luận

Đào tạo, đánh giá, và việc suy luận chính xác trong cùng một cách cho các mô hình được xây dựng bằng cách sử dụng API chức năng như đối với Sequential các mô hình.

Các Model lớp Mời một built-in loop đào tạo (các fit() phương pháp) và một built-in loop đánh giá (các evaluate() phương pháp). Lưu ý rằng bạn có thể dễ dàng tùy chỉnh các vòng để thực hiện thói quen đào tạo ngoài học có giám sát (ví dụ Gans ).

Tại đây, tải dữ liệu hình ảnh MNIST, định hình lại thành vectơ, phù hợp với mô hình trên dữ liệu (trong khi theo dõi hiệu suất trên một phân tách xác thực), sau đó đánh giá mô hình trên dữ liệu thử nghiệm:

(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

Để đọc thêm, xem đào tạo và đánh giá hướng dẫn.

Lưu và tuần tự hóa

Lưu mô hình và serialization làm việc cùng một cách cho các mô hình được xây dựng bằng cách sử dụng API chức năng như họ làm cho Sequential các mô hình. Cách thông thường để tiết kiệm một mô hình chức năng là để gọi model.save() để lưu toàn bộ mô hình như một tập tin duy nhất. Sau đó, bạn có thể tạo lại cùng một mô hình từ tệp này, ngay cả khi mã đã tạo mô hình đó không còn nữa.

Tệp đã lưu này bao gồm:

  • kiến trúc mô hình
  • giá trị trọng lượng mô hình (đã học được trong quá trình đào tạo)
  • mô hình đào tạo config, nếu có (như thông qua với compile )
  • trình tối ưu hóa và trạng thái của nó, nếu có (để bắt đầu lại quá trình đào tạo mà bạn đã dừng lại)
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

Để biết chi tiết, hãy đọc những mô hình serialization & tiết kiệm dẫn.

Sử dụng cùng một đồ thị của các lớp để xác định nhiều mô hình

Trong API chức năng, các mô hình được tạo bằng cách chỉ định đầu vào và đầu ra của chúng trong biểu đồ các lớp. Điều đó có nghĩa là một biểu đồ duy nhất của các lớp có thể được sử dụng để tạo ra nhiều mô hình.

Trong ví dụ dưới đây, bạn sử dụng cùng một ngăn xếp của các lớp để nhanh chóng hai mô hình: một encoder mô hình đầu vào lần lượt hình ảnh vào vectơ 16 chiều, và một end-to-end autoencoder mô hình đào tạo.

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
_________________________________________________________________

Ở đây, kiến trúc giải mã là chọi nhau với kiến trúc mã hóa, vì vậy hình dạng đầu ra là giống như hình dạng đầu vào (28, 28, 1) .

Điều ngược lại của một Conv2D lớp là một Conv2DTranspose lớp, và ngược lại của một MaxPooling2D lớp là một UpSampling2D lớp.

Tất cả các mô hình đều có thể gọi được, giống như các lớp

Bạn có thể đối xử với bất kỳ mô hình như thể nó là một lớp bằng cách gọi nó trên một Input hoặc đầu ra của lớp khác. Bằng cách gọi một mô hình, bạn không chỉ sử dụng lại kiến ​​trúc của mô hình mà bạn còn đang sử dụng lại trọng lượng của nó.

Để thấy điều này trong thực tế, đây là một cách khác về ví dụ tự động mã tạo mô hình bộ mã hóa, mô hình bộ giải mã và xâu chuỗi chúng thành hai lệnh gọi để có được mô hình tự động mã hóa:

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
_________________________________________________________________

Như bạn thấy, mô hình có thể được lồng vào nhau: một mô hình có thể chứa các mô hình con (vì một mô hình chỉ giống như một lớp). Một trường hợp sử dụng phổ biến cho các mô hình làm tổ được ensembling. Ví dụ: đây là cách tập hợp một tập hợp các mô hình thành một mô hình duy nhất tính trung bình các dự đoán của chúng:

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)

Thao tác các cấu trúc liên kết đồ thị phức tạp

Các mô hình có nhiều đầu vào và đầu ra

API chức năng giúp bạn dễ dàng thao tác với nhiều đầu vào và đầu ra. Điều này không thể được xử lý với Sequential API.

Ví dụ: nếu bạn đang xây dựng một hệ thống để xếp hạng vé phát hành của khách hàng theo mức độ ưu tiên và định tuyến chúng đến đúng bộ phận, thì mô hình sẽ có ba đầu vào:

  • tiêu đề của vé (nhập văn bản),
  • nội dung văn bản của vé (nhập văn bản) và
  • bất kỳ thẻ nào được thêm bởi người dùng (đầu vào phân loại)

Mô hình này sẽ có hai đầu ra:

  • điểm ưu tiên từ 0 đến 1 (đầu ra sigmoid vô hướng) và
  • bộ phận cần xử lý phiếu (đầu ra softmax trên bộ bộ phận).

Bạn có thể xây dựng mô hình này trong một vài dòng với API chức năng:

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

Bây giờ vẽ mô hình:

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

png

Khi biên dịch mô hình này, bạn có thể ấn định các tổn thất khác nhau cho mỗi đầu ra. Bạn thậm chí có thể chỉ định trọng lượng khác nhau cho mỗi lần giảm - để điều chỉnh mức đóng góp của chúng vào tổng số lần mất tập luyện.

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

Vì các lớp đầu ra có tên khác nhau, bạn cũng có thể chỉ định tổn thất và trọng số mất mát bằng các tên lớp tương ứng:

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

Huấn luyện mô hình bằng cách chuyển danh sách các đầu vào và mục tiêu của mảng 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>

Khi gọi phù hợp với một Dataset đối tượng, nó nên năng suất hoặc là một tuple của danh sách như ([title_data, body_data, tags_data], [priority_targets, dept_targets]) hoặc một tuple của bộ từ điển như ({'title': title_data, 'body': body_data, 'tags': tags_data}, {'priority': priority_targets, 'department': dept_targets}) .

Để xem giải thích chi tiết hơn, hãy tham khảo đào tạo và đánh giá hướng dẫn.

Mô hình ResNet đồ chơi

Ngoài các mô hình với nhiều đầu vào và đầu ra, API chức năng giúp bạn dễ dàng thao tác cấu trúc liên kết kết nối phi tuyến tính - đây là những mô hình với các lớp không được kết nối liên tục, mà Sequential API không thể xử lý.

Một trường hợp sử dụng phổ biến cho điều này là kết nối dư. Hãy xây dựng một mô hình ResNet đồ chơi cho CIFAR10 để chứng minh điều này:

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
__________________________________________________________________________________________________

Vẽ mô hình:

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

png

Bây giờ đào tạo mô hình:

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

Các lớp được chia sẻ

Một sử dụng tốt cho các API chức năng là mô hình sử dụng các lớp chia sẻ. Các lớp được chia sẻ là các bản sao của lớp được sử dụng lại nhiều lần trong cùng một mô hình - chúng học các tính năng tương ứng với nhiều đường dẫn trong biểu đồ của lớp.

Các lớp dùng chung thường được sử dụng để mã hóa đầu vào từ các không gian tương tự (giả sử, hai đoạn văn bản khác nhau có từ vựng tương tự). Chúng cho phép chia sẻ thông tin trên các đầu vào khác nhau này và chúng giúp đào tạo một mô hình như vậy trên ít dữ liệu hơn. Nếu một từ nhất định được nhìn thấy ở một trong các đầu vào, điều đó sẽ có lợi cho việc xử lý tất cả các đầu vào đi qua lớp được chia sẻ.

Để chia sẻ một lớp trong API chức năng, hãy gọi phiên bản lớp đó nhiều lần. Ví dụ, đây là một Embedding lớp chia sẻ trên hai đầu vào văn bản khác nhau:

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

Trích xuất và sử dụng lại các nút trong biểu đồ các lớp

Bởi vì đồ thị của các lớp bạn đang thao tác là một cấu trúc dữ liệu tĩnh, nó có thể được truy cập và kiểm tra. Và đây là cách bạn có thể vẽ các mô hình chức năng dưới dạng hình ảnh.

Điều này cũng có nghĩa là bạn có thể truy cập kích hoạt của các lớp trung gian ("nút" trong biểu đồ) và sử dụng lại chúng ở những nơi khác - điều này rất hữu ích cho những thứ như trích xuất tính năng.

Hãy xem một ví dụ. Đây là mô hình VGG19 với các trọng số được huấn luyện trước trên 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

Và đây là những kích hoạt trung gian của mô hình, thu được bằng cách truy vấn cấu trúc dữ liệu biểu đồ:

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

Sử dụng các tính năng này để tạo mô hình trích xuất tính năng mới trả về giá trị của các kích hoạt lớp trung gian:

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)

Điều này có ích cho công việc như chuyển kiểu thần kinh , trong số những thứ khác.

Mở rộng API bằng cách sử dụng các lớp tùy chỉnh

tf.keras bao gồm một loạt các built-in lớp, ví dụ:

  • Lớp xoắn: Conv1D , Conv2D , Conv3D , Conv2DTranspose
  • Lớp tổng hợp: MaxPooling1D , MaxPooling2D , MaxPooling3D , AveragePooling1D
  • RNN lớp: GRU , LSTM , ConvLSTM2D
  • BatchNormalization , Dropout , Embedding vv

Nhưng nếu bạn không tìm thấy thứ mình cần, thật dễ dàng để mở rộng API bằng cách tạo các lớp của riêng bạn. Tất cả các lớp phân lớp các Layer lớp và thực hiện:

  • call phương pháp, mà quy định cụ thể việc tính toán được thực hiện bởi lớp.
  • build phương pháp, tạo ra trọng lượng của lớp (đây chỉ là một quy ước phong cách kể từ khi bạn có thể tạo trọng trong __init__ , cũng).

Để tìm hiểu thêm về việc tạo lớp từ đầu, đọc lớp tùy chỉnh và các mô hình hướng dẫn.

Sau đây là một việc thực hiện cơ bản của 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)

Để được hỗ trợ serialization trong lớp tùy chỉnh của bạn, xác định một get_config phương thức trả về các đối số constructor của lớp dụ:

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

Tùy chọn, thực hiện các phương thức lớp from_config(cls, config) được sử dụng khi tái tạo một trường lớp cho từ điển cấu hình của nó. Việc thực hiện mặc định của from_config là:

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

Khi nào sử dụng API chức năng

Bạn nên sử dụng các API chức năng Keras để tạo ra một mô hình mới, hoặc chỉ phân lớp các Model lớp học trực tiếp? Nói chung, API chức năng ở mức cao hơn, dễ dàng hơn và an toàn hơn, đồng thời có một số tính năng mà các mô hình phân lớp không hỗ trợ.

Tuy nhiên, phân lớp mô hình cung cấp tính linh hoạt cao hơn khi xây dựng các mô hình không dễ biểu diễn như đồ thị vòng có hướng của các lớp. Ví dụ, bạn không thể thực hiện một Tree-RNN với các API chức năng và sẽ phải phân lớp Model trực tiếp.

Đối với một cái nhìn sâu sắc vào sự khác biệt giữa các API chức năng và mô hình subclassing, đọc API tượng trưng và bắt buộc trong TensorFlow 2.0 là gì? .

Điểm mạnh của API chức năng:

Các thuộc tính sau đây cũng đúng với mô hình Tuần tự (cũng là cấu trúc dữ liệu), nhưng không đúng với mô hình phân lớp (là mã byte Python, không phải cấu trúc dữ liệu).

Ít dài dòng hơn

Không có super(MyClass, self).__init__(...) , không có def call(self, ...): vv

Đối chiếu:

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

Với phiên bản phân lớp:

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

Xác thực mô hình trong khi xác định đồ thị kết nối của nó

Trong API chức năng, thông số kỹ thuật đầu vào (hình dạng và dtype) được tạo ra trước (sử dụng Input ). Mỗi khi bạn gọi một lớp, lớp sẽ kiểm tra xem thông số kỹ thuật được chuyển cho nó có khớp với các giả định của nó hay không và nó sẽ đưa ra một thông báo lỗi hữu ích nếu không.

Điều này đảm bảo rằng bất kỳ mô hình nào bạn có thể xây dựng bằng API chức năng sẽ chạy. Tất cả các gỡ lỗi - ngoài gỡ lỗi liên quan đến hội tụ - xảy ra tĩnh trong quá trình xây dựng mô hình và không phải tại thời điểm thực thi. Điều này tương tự như kiểm tra kiểu trong trình biên dịch.

Một mô hình chức năng có thể vẽ và kiểm tra được

Bạn có thể vẽ mô hình dưới dạng đồ thị và bạn có thể dễ dàng truy cập các nút trung gian trong đồ thị này. Ví dụ: để trích xuất và sử dụng lại các kích hoạt của các lớp trung gian (như đã thấy trong ví dụ trước):

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

Một mô hình chức năng có thể được tuần tự hóa hoặc nhân bản

Bởi vì mô hình chức năng là một cấu trúc dữ liệu chứ không phải là một đoạn mã, nó có thể tuần tự hóa một cách an toàn và có thể được lưu thành một tệp duy nhất cho phép bạn tạo lại chính xác cùng một mô hình mà không cần truy cập vào bất kỳ mã gốc nào. Xem hướng dẫn serialization & tiết kiệm .

Để serialize một mô hình subclassed, nó là cần thiết cho đơn vị thực hiện để xác định một get_config()from_config() phương pháp ở cấp mô hình.

Điểm yếu của API chức năng:

Nó không hỗ trợ kiến ​​trúc động

API chức năng coi các mô hình như DAG của các lớp. Điều này đúng với hầu hết các kiến ​​trúc học sâu, nhưng không phải tất cả - ví dụ: mạng đệ quy hoặc RNN dạng cây không tuân theo giả định này và không thể được triển khai trong API chức năng.

Kiểu API kết hợp và kết hợp

Lựa chọn giữa API chức năng hoặc phân lớp Mô hình không phải là một quyết định nhị phân hạn chế bạn vào một danh mục mô hình. Tất cả các model trong tf.keras API có thể tương tác với nhau, cho dù họ đang Sequential các mô hình, mô hình chức năng, hoặc các mô hình subclassed được viết từ đầu.

Bạn luôn có thể sử dụng một mô hình chức năng hoặc Sequential mô hình như là một phần của một mô hình subclassed hoặc lớp:

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)

Bạn có thể sử dụng bất kỳ lớp subclassed hoặc mô hình trong API chức năng miễn là nó thực hiện một call phương pháp mà sau một trong những mô hình sau:

  • call(self, inputs, **kwargs) - Trường hợp inputs là một tensor hoặc một cấu trúc lồng nhau của tensors (ví dụ như một danh sách các tensors), và nơi **kwargs là lập luận phi tensor (phi đầu vào).
  • call(self, inputs, training=None, **kwargs) - Nơi training là một boolean nêu rõ lớp nên cư xử trong chế độ đào tạo và phương thức suy luận.
  • call(self, inputs, mask=None, **kwargs) - Trường hợp mask là một mặt nạ tensor boolean (hữu dụng cho RNNs, ví dụ).
  • call(self, inputs, training=None, mask=None, **kwargs) - Tất nhiên, bạn có thể có cả mặt nạ và hành vi đào tạo cụ thể cùng một lúc.

Ngoài ra, nếu bạn thực hiện get_config phương pháp trên lớp tùy chỉnh của bạn hoặc mô hình, các mô hình chức năng bạn tạo vẫn sẽ serializable và cloneable.

Dưới đây là một ví dụ nhanh về RNN tùy chỉnh, được viết từ đầu, đang được sử dụng trong một mô hình chức năng:

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