บันทึกวันที่! Google I / O ส่งคืนวันที่ 18-20 พฤษภาคม ลงทะเบียนตอนนี้
หน้านี้ได้รับการแปลโดย Cloud Translation API
Switch to English

สร้างเลเยอร์และโมเดลใหม่ผ่านคลาสย่อย

ดูใน TensorFlow.org เรียกใช้ใน Google Colab ดูแหล่งที่มาบน GitHub ดาวน์โหลดสมุดบันทึก

ติดตั้ง

import tensorflow as tf
from tensorflow import keras

คลาส Layer : การรวมกันของสถานะ (น้ำหนัก) และการคำนวณบางอย่าง

หนึ่งในสิ่งที่เป็นนามธรรมหลักใน Keras คือคลาส Layer เลเยอร์จะห่อหุ้มทั้งสถานะ ("น้ำหนัก" ของเลเยอร์) และการแปลงจากอินพุตเป็นเอาต์พุต ("การโทร" ซึ่งเป็นฟอร์เวิร์ดพาสของเลเยอร์)

นี่คือเลเยอร์ที่เชื่อมต่อกันอย่างหนาแน่น มีสถานะ: ตัวแปร w และ b

class Linear(keras.layers.Layer):
    def __init__(self, units=32, input_dim=32):
        super(Linear, self).__init__()
        w_init = tf.random_normal_initializer()
        self.w = tf.Variable(
            initial_value=w_init(shape=(input_dim, units), dtype="float32"),
            trainable=True,
        )
        b_init = tf.zeros_initializer()
        self.b = tf.Variable(
            initial_value=b_init(shape=(units,), dtype="float32"), trainable=True
        )

    def call(self, inputs):
        return tf.matmul(inputs, self.w) + self.b

คุณจะใช้เลเยอร์โดยเรียกมันในอินพุตเทนเซอร์บางตัวเหมือนกับฟังก์ชัน Python

x = tf.ones((2, 2))
linear_layer = Linear(4, 2)
y = linear_layer(x)
print(y)
tf.Tensor(
[[ 8.9605771e-02 -5.8132906e-02 -2.3558782e-02 -5.9220940e-05]
 [ 8.9605771e-02 -5.8132906e-02 -2.3558782e-02 -5.9220940e-05]], shape=(2, 4), dtype=float32)

โปรดทราบว่าน้ำหนัก w และ b จะถูกติดตามโดยอัตโนมัติโดยเลเยอร์เมื่อถูกตั้งค่าเป็นแอตทริบิวต์ของเลเยอร์:

assert linear_layer.weights == [linear_layer.w, linear_layer.b]

โปรดทราบว่าคุณสามารถเข้าถึงทางลัดที่เร็วกว่าสำหรับการเพิ่มน้ำหนักให้กับเลเยอร์: วิธีการ add_weight() :

class Linear(keras.layers.Layer):
    def __init__(self, units=32, input_dim=32):
        super(Linear, self).__init__()
        self.w = self.add_weight(
            shape=(input_dim, units), initializer="random_normal", trainable=True
        )
        self.b = self.add_weight(shape=(units,), initializer="zeros", trainable=True)

    def call(self, inputs):
        return tf.matmul(inputs, self.w) + self.b


x = tf.ones((2, 2))
linear_layer = Linear(4, 2)
y = linear_layer(x)
print(y)
tf.Tensor(
[[-0.00164011 -0.02662886  0.00616307  0.0370644 ]
 [-0.00164011 -0.02662886  0.00616307  0.0370644 ]], shape=(2, 4), dtype=float32)

เลเยอร์สามารถมีน้ำหนักที่ไม่สามารถฝึกได้

นอกจากน้ำหนักที่ฝึกได้แล้วคุณยังเพิ่มน้ำหนักที่ไม่สามารถฝึกได้ให้กับเลเยอร์ได้อีกด้วย น้ำหนักดังกล่าวไม่ได้ตั้งใจที่จะนำมาพิจารณาในระหว่างการขยายภาพย้อนกลับเมื่อคุณกำลังฝึกเลเยอร์

วิธีเพิ่มและใช้น้ำหนักที่ไม่สามารถฝึกได้มีดังนี้

class ComputeSum(keras.layers.Layer):
    def __init__(self, input_dim):
        super(ComputeSum, self).__init__()
        self.total = tf.Variable(initial_value=tf.zeros((input_dim,)), trainable=False)

    def call(self, inputs):
        self.total.assign_add(tf.reduce_sum(inputs, axis=0))
        return self.total


x = tf.ones((2, 2))
my_sum = ComputeSum(2)
y = my_sum(x)
print(y.numpy())
y = my_sum(x)
print(y.numpy())
[2. 2.]
[4. 4.]

เป็นส่วนหนึ่งของ layer.weights แต่ถูกจัดประเภทเป็นน้ำหนักที่ไม่สามารถฝึกได้:

print("weights:", len(my_sum.weights))
print("non-trainable weights:", len(my_sum.non_trainable_weights))

# It's not included in the trainable weights:
print("trainable_weights:", my_sum.trainable_weights)
weights: 1
non-trainable weights: 1
trainable_weights: []

แนวทางปฏิบัติที่ดีที่สุด: เลื่อนการสร้างน้ำหนักออกไปจนกว่าจะทราบรูปร่างของปัจจัยการผลิต

เลเยอร์ Linear ของเราด้านบนรับอาร์กิวเมนต์ input_dim ที่ใช้ในการคำนวณรูปร่างของน้ำหนัก w และ b ใน __init__() :

class Linear(keras.layers.Layer):
    def __init__(self, units=32, input_dim=32):
        super(Linear, self).__init__()
        self.w = self.add_weight(
            shape=(input_dim, units), initializer="random_normal", trainable=True
        )
        self.b = self.add_weight(shape=(units,), initializer="zeros", trainable=True)

    def call(self, inputs):
        return tf.matmul(inputs, self.w) + self.b

ในหลาย ๆ กรณีคุณอาจไม่ทราบขนาดอินพุตของคุณล่วงหน้าและคุณต้องการสร้างน้ำหนักอย่างเกียจคร้านเมื่อทราบค่านั้นบางครั้งหลังจากสร้างอินสแตนซ์เลเยอร์

ใน Keras API เราขอแนะนำให้สร้างน้ำหนักเลเยอร์ในเมธอด build(self, inputs_shape) ของเลเยอร์ของคุณ แบบนี้:

class Linear(keras.layers.Layer):
    def __init__(self, units=32):
        super(Linear, 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

เมธอด __call__() ของเลเยอร์ของคุณจะรัน __call__() โดยอัตโนมัติในครั้งแรกที่เรียก ตอนนี้คุณมีเลเยอร์ที่ขี้เกียจและใช้งานง่ายกว่า:

# At instantiation, we don't know on what inputs this is going to get called
linear_layer = Linear(32)

# The layer's weights are created dynamically the first time the layer is called
y = linear_layer(x)

เลเยอร์สามารถประกอบซ้ำได้

หากคุณกำหนดอินสแตนซ์ Layer เป็นแอตทริบิวต์ของ Layer อื่นชั้นนอกจะเริ่มติดตามน้ำหนักของชั้นใน

เราขอแนะนำให้สร้างชั้นย่อยดังกล่าวใน __init__() (เนื่องจากโดยทั่วไปแล้วชั้นย่อยจะมีวิธีการสร้างพวกเขาจะถูกสร้างขึ้นเมื่อสร้างชั้นนอก)

# Let's assume we are reusing the Linear class
# with a `build` method that we defined above.


class MLPBlock(keras.layers.Layer):
    def __init__(self):
        super(MLPBlock, self).__init__()
        self.linear_1 = Linear(32)
        self.linear_2 = Linear(32)
        self.linear_3 = Linear(1)

    def call(self, inputs):
        x = self.linear_1(inputs)
        x = tf.nn.relu(x)
        x = self.linear_2(x)
        x = tf.nn.relu(x)
        return self.linear_3(x)


mlp = MLPBlock()
y = mlp(tf.ones(shape=(3, 64)))  # The first call to the `mlp` will create the weights
print("weights:", len(mlp.weights))
print("trainable weights:", len(mlp.trainable_weights))
weights: 6
trainable weights: 6

add_loss() วิธีการ

เมื่อเขียนเมธอด call() ของเลเยอร์คุณสามารถสร้างเทนเซอร์การสูญเสียที่คุณต้องการใช้ในภายหลังเมื่อเขียนลูปการฝึกของคุณ สิ่งนี้ทำได้โดยเรียก self.add_loss(value) :

# A layer that creates an activity regularization loss
class ActivityRegularizationLayer(keras.layers.Layer):
    def __init__(self, rate=1e-2):
        super(ActivityRegularizationLayer, self).__init__()
        self.rate = rate

    def call(self, inputs):
        self.add_loss(self.rate * tf.reduce_sum(inputs))
        return inputs

การสูญเสียเหล่านี้ (รวมถึงสิ่งที่สร้างขึ้นโดยชั้นในใด ๆ ) สามารถเรียกคืนได้ผ่าน layer.losses คุณสมบัตินี้จะถูกรีเซ็ตเมื่อเริ่มต้นของทุก ๆ __call__() ไปยังเลเยอร์ระดับบนสุดดังนั้น layer.losses จะมีค่าการสูญเสียที่สร้างขึ้นระหว่างการส่งต่อครั้งสุดท้ายเสมอ

class OuterLayer(keras.layers.Layer):
    def __init__(self):
        super(OuterLayer, self).__init__()
        self.activity_reg = ActivityRegularizationLayer(1e-2)

    def call(self, inputs):
        return self.activity_reg(inputs)


layer = OuterLayer()
assert len(layer.losses) == 0  # No losses yet since the layer has never been called

_ = layer(tf.zeros(1, 1))
assert len(layer.losses) == 1  # We created one loss value

# `layer.losses` gets reset at the start of each __call__
_ = layer(tf.zeros(1, 1))
assert len(layer.losses) == 1  # This is the loss created during the call above

นอกจากนี้คุณสมบัติ loss ยังมีการสูญเสียการทำให้เป็นมาตรฐานที่สร้างขึ้นสำหรับน้ำหนักของชั้นในใด ๆ :

class OuterLayerWithKernelRegularizer(keras.layers.Layer):
    def __init__(self):
        super(OuterLayerWithKernelRegularizer, self).__init__()
        self.dense = keras.layers.Dense(
            32, kernel_regularizer=tf.keras.regularizers.l2(1e-3)
        )

    def call(self, inputs):
        return self.dense(inputs)


layer = OuterLayerWithKernelRegularizer()
_ = layer(tf.zeros((1, 1)))

# This is `1e-3 * sum(layer.dense.kernel ** 2)`,
# created by the `kernel_regularizer` above.
print(layer.losses)
[<tf.Tensor: shape=(), dtype=float32, numpy=0.001774033>]

การสูญเสียเหล่านี้ควรนำมาพิจารณาเมื่อเขียนลูปการฝึกอบรมเช่นนี้:

# Instantiate an optimizer.
optimizer = tf.keras.optimizers.SGD(learning_rate=1e-3)
loss_fn = keras.losses.SparseCategoricalCrossentropy(from_logits=True)

# Iterate over the batches of a dataset.
for x_batch_train, y_batch_train in train_dataset:
  with tf.GradientTape() as tape:
    logits = layer(x_batch_train)  # Logits for this minibatch
    # Loss value for this minibatch
    loss_value = loss_fn(y_batch_train, logits)
    # Add extra losses created during this forward pass:
    loss_value += sum(model.losses)

  grads = tape.gradient(loss_value, model.trainable_weights)
  optimizer.apply_gradients(zip(grads, model.trainable_weights))

สำหรับคำแนะนำโดยละเอียดเกี่ยวกับการเขียนลูปการฝึกอบรมโปรดดู คำแนะนำในการเขียนลูปการฝึกตั้งแต่เริ่มต้น

การสูญเสียเหล่านี้ยังทำงานได้อย่างราบรื่นด้วย fit() (จะได้รับการสรุปและรวมเข้ากับการสูญเสียหลักโดยอัตโนมัติถ้ามี)

import numpy as np

inputs = keras.Input(shape=(3,))
outputs = ActivityRegularizationLayer()(inputs)
model = keras.Model(inputs, outputs)

# If there is a loss passed in `compile`, the regularization
# losses get added to it
model.compile(optimizer="adam", loss="mse")
model.fit(np.random.random((2, 3)), np.random.random((2, 3)))

# It's also possible not to pass any loss in `compile`,
# since the model already has a loss to minimize, via the `add_loss`
# call during the forward pass!
model.compile(optimizer="adam")
model.fit(np.random.random((2, 3)), np.random.random((2, 3)))
1/1 [==============================] - 0s 111ms/step - loss: 0.3057
1/1 [==============================] - 0s 44ms/step - loss: 0.0246
<tensorflow.python.keras.callbacks.History at 0x7f557862c4e0>

add_metric() วิธีการ

ในทำนองเดียวกันกับ add_loss() เลเยอร์ยังมีวิธีการ add_metric() สำหรับติดตามค่าเฉลี่ยเคลื่อนที่ของปริมาณระหว่างการฝึก

พิจารณาเลเยอร์ต่อไปนี้: เลเยอร์ "ปลายทางโลจิสติก" ใช้เป็นอินพุตการคาดการณ์และเป้าหมายโดยจะคำนวณการสูญเสียที่ติดตามผ่าน add_loss() และคำนวณสเกลาร์ความแม่นยำซึ่งติดตามผ่าน add_metric()

class LogisticEndpoint(keras.layers.Layer):
    def __init__(self, name=None):
        super(LogisticEndpoint, self).__init__(name=name)
        self.loss_fn = keras.losses.BinaryCrossentropy(from_logits=True)
        self.accuracy_fn = keras.metrics.BinaryAccuracy()

    def call(self, targets, logits, sample_weights=None):
        # Compute the training-time loss value and add it
        # to the layer using `self.add_loss()`.
        loss = self.loss_fn(targets, logits, sample_weights)
        self.add_loss(loss)

        # Log accuracy as a metric and add it
        # to the layer using `self.add_metric()`.
        acc = self.accuracy_fn(targets, logits, sample_weights)
        self.add_metric(acc, name="accuracy")

        # Return the inference-time prediction tensor (for `.predict()`).
        return tf.nn.softmax(logits)

เมตริกที่ติดตามด้วยวิธีนี้สามารถเข้าถึงได้ผ่าน layer.metrics :

layer = LogisticEndpoint()

targets = tf.ones((2, 2))
logits = tf.ones((2, 2))
y = layer(targets, logits)

print("layer.metrics:", layer.metrics)
print("current accuracy value:", float(layer.metrics[0].result()))
layer.metrics: [<tensorflow.python.keras.metrics.BinaryAccuracy object at 0x7f5578831518>]
current accuracy value: 1.0

เช่นเดียวกับ add_loss() เมตริกเหล่านี้จะถูกติดตามโดย fit() :

inputs = keras.Input(shape=(3,), name="inputs")
targets = keras.Input(shape=(10,), name="targets")
logits = keras.layers.Dense(10)(inputs)
predictions = LogisticEndpoint(name="predictions")(logits, targets)

model = keras.Model(inputs=[inputs, targets], outputs=predictions)
model.compile(optimizer="adam")

data = {
    "inputs": np.random.random((3, 3)),
    "targets": np.random.random((3, 10)),
}
model.fit(data)
1/1 [==============================] - 0s 248ms/step - loss: 0.8016 - binary_accuracy: 0.0000e+00
<tensorflow.python.keras.callbacks.History at 0x7f557860d470>

คุณสามารถเลือกเปิดใช้งานการทำให้เป็นอนุกรมบนเลเยอร์ของคุณได้

หากคุณต้องการให้เลเยอร์ที่กำหนดเองของคุณสามารถทำให้เป็นอนุกรมได้โดยเป็นส่วนหนึ่งของ โมเดลการทำงาน คุณสามารถเลือกใช้ get_config() :

class Linear(keras.layers.Layer):
    def __init__(self, units=32):
        super(Linear, 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}


# Now you can recreate the layer from its config:
layer = Linear(64)
config = layer.get_config()
print(config)
new_layer = Linear.from_config(config)
{'units': 64}

โปรดสังเกตว่า __init__() ของคลาส Layer ฐานใช้อาร์กิวเมนต์คำสำคัญบางส่วนโดยเฉพาะ name และ dtype เป็นแนวทางปฏิบัติที่ดีในการส่งอาร์กิวเมนต์เหล่านี้ไปยังคลาส __init__() ใน __init__() และรวมไว้ในการกำหนดค่าเลเยอร์:

class Linear(keras.layers.Layer):
    def __init__(self, units=32, **kwargs):
        super(Linear, self).__init__(**kwargs)
        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):
        config = super(Linear, self).get_config()
        config.update({"units": self.units})
        return config


layer = Linear(64)
config = layer.get_config()
print(config)
new_layer = Linear.from_config(config)
{'name': 'linear_8', 'trainable': True, 'dtype': 'float32', 'units': 64}

หากคุณต้องการความยืดหยุ่นมากขึ้นในการแยกส่วนของเลเยอร์ออกจากการกำหนดค่าคุณยังสามารถแทนที่ from_config() ได้ นี่คือการใช้งานพื้นฐานของ from_config() :

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

หากต้องการเรียนรู้เพิ่มเติมเกี่ยวกับการทำให้เป็นอนุกรมและการบันทึกโปรดดู คู่มือ ฉบับสมบูรณ์ เกี่ยวกับการบันทึกและการทำให้เป็นอนุกรม

อาร์กิวเมนต์ training สิทธิ์ในเมธอด call()

บางเลเยอร์โดยเฉพาะอย่างยิ่งชั้น BatchNormalization และเลเยอร์ Dropout มีพฤติกรรมที่แตกต่างกันระหว่างการฝึกอบรมและการอนุมาน สำหรับเลเยอร์ดังกล่าวเป็นวิธีปฏิบัติมาตรฐานในการแสดงอาร์กิวเมนต์ training (บูลีน) ในเมธอด call()

ด้วยการเปิดเผยอาร์กิวเมนต์นี้ใน call() คุณจะเปิดใช้งานลูปการฝึกอบรมและการประเมินผลในตัว (เช่น fit() ) เพื่อใช้เลเยอร์อย่างถูกต้องในการฝึกอบรมและการอนุมาน

class CustomDropout(keras.layers.Layer):
    def __init__(self, rate, **kwargs):
        super(CustomDropout, self).__init__(**kwargs)
        self.rate = rate

    def call(self, inputs, training=None):
        if training:
            return tf.nn.dropout(inputs, rate=self.rate)
        return inputs

อาร์กิวเมนต์มาส mask สิทธิ์ในเมธอด call()

อาร์กิวเมนต์ที่มีสิทธิพิเศษอื่น ๆ ที่สนับสนุนโดย call() คืออาร์กิวเมนต์มาส mask

คุณจะพบได้ในเลเยอร์ Keras RNN ทั้งหมด มาสก์คือเทนเซอร์บูลีน (ค่าบูลีนหนึ่งค่าต่อการประทับเวลาในอินพุต) ที่ใช้เพื่อข้ามเวลาป้อนข้อมูลบางอย่างเมื่อประมวลผลข้อมูลลำดับเวลา

Keras จะส่งผ่านอาร์กิวเมนต์มาส mask ถูกต้องโดยอัตโนมัติไปยัง __call__() สำหรับเลเยอร์ที่รองรับเมื่อมาสก์ถูกสร้างขึ้นโดยเลเยอร์ก่อนหน้า ชั้นหน้ากากสร้างเป็น Embedding ชั้นกำหนดค่าด้วย mask_zero=True และ Masking ชั้น

หากต้องการเรียนรู้เพิ่มเติมเกี่ยวกับการมาสก์และวิธีการเขียนเลเยอร์ที่เปิดใช้งานการมาสก์โปรดดูคู่มือ "ทำความเข้าใจช่องว่างภายในและการมาสก์"

คลาส Model

โดยทั่วไปคุณจะใช้คลาส Layer เพื่อกำหนดบล็อกการคำนวณภายในและจะใช้คลาส Model เพื่อกำหนดโมเดลด้านนอก - อ็อบเจ็กต์ที่คุณจะฝึก

ตัวอย่างเช่นในโมเดล ResNet50 คุณจะมีคลาสย่อยของ ResNet บล็อกหลาย Layer และ Model เดียวที่ครอบคลุมเครือข่าย ResNet50 ทั้งหมด

คลาส Model มี API เหมือนกับ Layer โดยมีความแตกต่างดังต่อไปนี้:

  • จะแสดงลูปการฝึกอบรมการประเมินผลและการคาดคะเนในตัว ( model.fit() , model.evaluate() , model.predict() )
  • มันแสดงรายการของเลเยอร์ด้านในผ่านคุณสมบัติ model.layers
  • มันแสดง API การบันทึกและการทำให้เป็นอนุกรม ( save() , save_weights() ... )

อย่างมีประสิทธิภาพคลาส Layer จะสอดคล้องกับสิ่งที่เราอ้างถึงในวรรณกรรมว่าเป็น "เลเยอร์" (เช่นเดียวกับ "เลเยอร์การแปลง" หรือ "เลเยอร์ที่เกิดซ้ำ") หรือเป็น "บล็อก" (เช่นเดียวกับใน "ResNet block" หรือ "Inception block" ).

ในขณะเดียวกันคลาส Model จะสอดคล้องกับสิ่งที่อ้างถึงในวรรณกรรมว่าเป็น "โมเดล" (เช่นเดียวกับ "แบบจำลองการเรียนรู้เชิงลึก") หรือเป็น "เครือข่าย" (เช่นเดียวกับ "โครงข่ายประสาทส่วนลึก")

ดังนั้นหากคุณสงสัยว่า "ฉันควรใช้คลาส Layer หรือคลาส Model " ให้ถามตัวเองว่า: ฉันจะต้องเรียก fit() หรือไม่? ฉันจะต้องเรียก save() หรือไม่ ถ้าเป็นเช่นนั้นไปกับ Model ถ้าไม่ (เนื่องจากชั้นเรียนของคุณเป็นเพียงบล็อกในระบบที่ใหญ่กว่าหรือเพราะคุณกำลังเขียนการฝึกอบรมและการประหยัดรหัสด้วยตัวเอง) ให้ใช้ Layer

ตัวอย่างเช่นเราสามารถใช้ตัวอย่าง mini-resnet ของเราด้านบนและใช้เพื่อสร้าง Model ที่เราสามารถฝึกได้ด้วย fit() และเราสามารถบันทึกได้ด้วย save_weights() :

class ResNet(tf.keras.Model):

    def __init__(self, num_classes=1000):
        super(ResNet, self).__init__()
        self.block_1 = ResNetBlock()
        self.block_2 = ResNetBlock()
        self.global_pool = layers.GlobalAveragePooling2D()
        self.classifier = Dense(num_classes)

    def call(self, inputs):
        x = self.block_1(inputs)
        x = self.block_2(x)
        x = self.global_pool(x)
        return self.classifier(x)


resnet = ResNet()
dataset = ...
resnet.fit(dataset, epochs=10)
resnet.save(filepath)

รวมทุกอย่างเข้าด้วยกัน: ตัวอย่าง end-to-end

นี่คือสิ่งที่คุณได้เรียนรู้จนถึงตอนนี้:

  • Layer ห่อหุ้มสถานะ (สร้างขึ้นใน __init__() หรือ build() ) และการคำนวณบางอย่าง (กำหนดไว้ใน call() )
  • เลเยอร์สามารถซ้อนซ้ำเพื่อสร้างบล็อกการคำนวณใหม่ที่ใหญ่ขึ้น
  • เลเยอร์สามารถสร้างและติดตามการสูญเสีย (โดยทั่วไปคือการสูญเสียการทำให้เป็นมาตรฐาน) ตลอดจนเมตริกผ่านทาง add_loss() และ add_metric()
  • ตู้คอนเทนเนอร์ด้านนอกสิ่งที่คุณต้องการฝึกคือ Model Model ก็เหมือนกับ Layer แต่มียูทิลิตี้การฝึกอบรมและการทำให้เป็นอนุกรมเพิ่มเข้ามา

ลองรวบรวมสิ่งเหล่านี้ทั้งหมดเข้าด้วยกันเป็นตัวอย่าง end-to-end: เรากำลังจะใช้ Variational AutoEncoder (VAE) เราจะฝึกมันโดยใช้หลัก MNIST

VAE ของเราจะเป็นคลาสย่อยของ Model ซึ่งสร้างขึ้นจากองค์ประกอบที่ซ้อนกันของเลเยอร์ที่ Subclass Layer มันจะมีการสูญเสียการทำให้เป็นมาตรฐาน (KL divergence)

from tensorflow.keras import layers


class Sampling(layers.Layer):
    """Uses (z_mean, z_log_var) to sample z, the vector encoding a digit."""

    def call(self, inputs):
        z_mean, z_log_var = inputs
        batch = tf.shape(z_mean)[0]
        dim = tf.shape(z_mean)[1]
        epsilon = tf.keras.backend.random_normal(shape=(batch, dim))
        return z_mean + tf.exp(0.5 * z_log_var) * epsilon


class Encoder(layers.Layer):
    """Maps MNIST digits to a triplet (z_mean, z_log_var, z)."""

    def __init__(self, latent_dim=32, intermediate_dim=64, name="encoder", **kwargs):
        super(Encoder, self).__init__(name=name, **kwargs)
        self.dense_proj = layers.Dense(intermediate_dim, activation="relu")
        self.dense_mean = layers.Dense(latent_dim)
        self.dense_log_var = layers.Dense(latent_dim)
        self.sampling = Sampling()

    def call(self, inputs):
        x = self.dense_proj(inputs)
        z_mean = self.dense_mean(x)
        z_log_var = self.dense_log_var(x)
        z = self.sampling((z_mean, z_log_var))
        return z_mean, z_log_var, z


class Decoder(layers.Layer):
    """Converts z, the encoded digit vector, back into a readable digit."""

    def __init__(self, original_dim, intermediate_dim=64, name="decoder", **kwargs):
        super(Decoder, self).__init__(name=name, **kwargs)
        self.dense_proj = layers.Dense(intermediate_dim, activation="relu")
        self.dense_output = layers.Dense(original_dim, activation="sigmoid")

    def call(self, inputs):
        x = self.dense_proj(inputs)
        return self.dense_output(x)


class VariationalAutoEncoder(keras.Model):
    """Combines the encoder and decoder into an end-to-end model for training."""

    def __init__(
        self,
        original_dim,
        intermediate_dim=64,
        latent_dim=32,
        name="autoencoder",
        **kwargs
    ):
        super(VariationalAutoEncoder, self).__init__(name=name, **kwargs)
        self.original_dim = original_dim
        self.encoder = Encoder(latent_dim=latent_dim, intermediate_dim=intermediate_dim)
        self.decoder = Decoder(original_dim, intermediate_dim=intermediate_dim)

    def call(self, inputs):
        z_mean, z_log_var, z = self.encoder(inputs)
        reconstructed = self.decoder(z)
        # Add KL divergence regularization loss.
        kl_loss = -0.5 * tf.reduce_mean(
            z_log_var - tf.square(z_mean) - tf.exp(z_log_var) + 1
        )
        self.add_loss(kl_loss)
        return reconstructed

มาเขียนลูปการฝึกง่ายๆใน MNIST:

original_dim = 784
vae = VariationalAutoEncoder(original_dim, 64, 32)

optimizer = tf.keras.optimizers.Adam(learning_rate=1e-3)
mse_loss_fn = tf.keras.losses.MeanSquaredError()

loss_metric = tf.keras.metrics.Mean()

(x_train, _), _ = tf.keras.datasets.mnist.load_data()
x_train = x_train.reshape(60000, 784).astype("float32") / 255

train_dataset = tf.data.Dataset.from_tensor_slices(x_train)
train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)

epochs = 2

# Iterate over epochs.
for epoch in range(epochs):
    print("Start of epoch %d" % (epoch,))

    # Iterate over the batches of the dataset.
    for step, x_batch_train in enumerate(train_dataset):
        with tf.GradientTape() as tape:
            reconstructed = vae(x_batch_train)
            # Compute reconstruction loss
            loss = mse_loss_fn(x_batch_train, reconstructed)
            loss += sum(vae.losses)  # Add KLD regularization loss

        grads = tape.gradient(loss, vae.trainable_weights)
        optimizer.apply_gradients(zip(grads, vae.trainable_weights))

        loss_metric(loss)

        if step % 100 == 0:
            print("step %d: mean loss = %.4f" % (step, loss_metric.result()))
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz
11493376/11490434 [==============================] - 0s 0us/step
Start of epoch 0
step 0: mean loss = 0.3603
step 100: mean loss = 0.1262
step 200: mean loss = 0.0997
step 300: mean loss = 0.0895
step 400: mean loss = 0.0845
step 500: mean loss = 0.0810
step 600: mean loss = 0.0789
step 700: mean loss = 0.0773
step 800: mean loss = 0.0761
step 900: mean loss = 0.0750
Start of epoch 1
step 0: mean loss = 0.0748
step 100: mean loss = 0.0741
step 200: mean loss = 0.0736
step 300: mean loss = 0.0731
step 400: mean loss = 0.0728
step 500: mean loss = 0.0724
step 600: mean loss = 0.0721
step 700: mean loss = 0.0718
step 800: mean loss = 0.0715
step 900: mean loss = 0.0713

โปรดทราบว่าเนื่องจาก VAE เป็น Model คลาสย่อยจึงมีลูปการฝึกอบรมในตัว ดังนั้นคุณสามารถฝึกได้เช่นนี้:

vae = VariationalAutoEncoder(784, 64, 32)

optimizer = tf.keras.optimizers.Adam(learning_rate=1e-3)

vae.compile(optimizer, loss=tf.keras.losses.MeanSquaredError())
vae.fit(x_train, x_train, epochs=2, batch_size=64)
Epoch 1/2
938/938 [==============================] - 3s 2ms/step - loss: 0.0945
Epoch 2/2
938/938 [==============================] - 2s 2ms/step - loss: 0.0678
<tensorflow.python.keras.callbacks.History at 0x7f54dc15f550>

นอกเหนือจากการพัฒนาเชิงวัตถุ: Functional API

ตัวอย่างนี้มีการพัฒนาเชิงวัตถุมากเกินไปสำหรับคุณหรือไม่? คุณยังสามารถสร้างโมเดลโดยใช้ Functional API ที่สำคัญการเลือกสไตล์ใดสไตล์หนึ่งไม่ได้ป้องกันไม่ให้คุณใช้ประโยชน์จากส่วนประกอบที่เขียนในสไตล์อื่นคุณสามารถมิกซ์แอนด์แมทช์ได้ตลอดเวลา

ตัวอย่างเช่นตัวอย่าง Functional API ด้านล่างนำเลเยอร์ Sampling เดิมที่เรากำหนดไว้ในตัวอย่างด้านบนมาใช้ซ้ำ:

original_dim = 784
intermediate_dim = 64
latent_dim = 32

# Define encoder model.
original_inputs = tf.keras.Input(shape=(original_dim,), name="encoder_input")
x = layers.Dense(intermediate_dim, activation="relu")(original_inputs)
z_mean = layers.Dense(latent_dim, name="z_mean")(x)
z_log_var = layers.Dense(latent_dim, name="z_log_var")(x)
z = Sampling()((z_mean, z_log_var))
encoder = tf.keras.Model(inputs=original_inputs, outputs=z, name="encoder")

# Define decoder model.
latent_inputs = tf.keras.Input(shape=(latent_dim,), name="z_sampling")
x = layers.Dense(intermediate_dim, activation="relu")(latent_inputs)
outputs = layers.Dense(original_dim, activation="sigmoid")(x)
decoder = tf.keras.Model(inputs=latent_inputs, outputs=outputs, name="decoder")

# Define VAE model.
outputs = decoder(z)
vae = tf.keras.Model(inputs=original_inputs, outputs=outputs, name="vae")

# Add KL divergence regularization loss.
kl_loss = -0.5 * tf.reduce_mean(z_log_var - tf.square(z_mean) - tf.exp(z_log_var) + 1)
vae.add_loss(kl_loss)

# Train.
optimizer = tf.keras.optimizers.Adam(learning_rate=1e-3)
vae.compile(optimizer, loss=tf.keras.losses.MeanSquaredError())
vae.fit(x_train, x_train, epochs=3, batch_size=64)
Epoch 1/3
938/938 [==============================] - 3s 2ms/step - loss: 0.0950
Epoch 2/3
938/938 [==============================] - 2s 2ms/step - loss: 0.0677
Epoch 3/3
938/938 [==============================] - 2s 2ms/step - loss: 0.0676
<tensorflow.python.keras.callbacks.History at 0x7f54d00b9630>

สำหรับข้อมูลเพิ่มเติมโปรดอ่านคู่มือ Functional API