1. 函數式 API(Function API)
-
模型是層的簡單堆疊,無法表示任意模型。tf.keras.Sequential
inputs = tf.keras.Input(shape=(32,)) # 建構一個輸入張量
# 層layer的執行個體對象是callable的,他接受一個tensor,并傳回一個處理之後的tensor
x = layers.Dense(64, activation='relu')(inputs)
x = layers.Dense(64, activation='relu')(x)
predictions = layers.Dense(10, activation='softmax')(x)
# 在給定輸入和輸出的情況下執行個體化模型。
model = tf.keras.Model(inputs=inputs, outputs=predictions)
# 編譯模型
model.compile(optimizer=tf.train.RMSPropOptimizer(0.001),
loss='categorical_crossentropy',
metrics=['accuracy'])
##訓練 5 個 epochs
model.fit(data, labels, batch_size=32, epochs=5)
2. 模型子類化(Model SubClass)—實作自定義模型
其實所謂的"模型子類化"就是自己實作一個類來繼承
Model
類,建構一個
Model
類的子類,需要實作兩個方法,即:
__init__()
call()
在類繼承模型中,模型的拓撲結構是由 Python 代碼定義的(而不是網絡層的靜态圖)。這意味着該模型的拓撲結構不能被檢查或序列化。是以,以下方法和屬性不适用于類繼承模型:
-
和model.inputs
。model.outputs
-
和model.to_yaml()
。model.to_json()
-
和model.get_config()
。model.save()
通過對
tf.keras.Model
進行子類化并定義自己的前向傳播來建構完全可自定義的模型。
- 在
方法中建立層并将它們設定為類執行個體的屬性;__init__
- 在
方法中定義前向傳播;call
下面給出典型的ResNet網絡代碼
import os
import tensorflow as tf
import numpy as np
from tensorflow import keras
# 3x3 convolution
def conv3x3(channels, stride=1, kernel=(3, 3)):
return keras.layers.Conv2D(channels, kernel, strides=stride, padding='same',
use_bias=False,
kernel_initializer=tf.random_normal_initializer())
class ResnetBlock(keras.Model):
def __init__(self, channels, strides=1, residual_path=False):
super().__init__()
self.channels = channels
self.strides = strides
self.residual_path = residual_path
self.conv1 = conv3x3(channels, strides)
self.bn1 = keras.layers.BatchNormalization()
self.conv2 = conv3x3(channels)
self.bn2 = keras.layers.BatchNormalization()
if residual_path:
self.down_conv = conv3x3(channels, strides, kernel=(1, 1))
self.down_bn = tf.keras.layers.BatchNormalization()
def call(self, inputs, training=None):
residual = inputs
x = self.bn1(inputs, training=training)
x = tf.nn.relu(x)
x = self.conv1(x)
x = self.bn2(x, training=training)
x = tf.nn.relu(x)
x = self.conv2(x)
# this module can be added into self.
# however, module in for can not be added.
if self.residual_path:
residual = self.down_bn(inputs, training=training)
residual = tf.nn.relu(residual)
residual = self.down_conv(residual)
x = x + residual
return x
class ResNet(keras.Model):
def __init__(self, block_list, num_classes, initial_filters=16, **kwargs):
super().__init__(**kwargs)
self.num_blocks = len(block_list)
self.block_list = block_list
self.in_channels = initial_filters
self.out_channels = initial_filters
self.conv_initial = conv3x3(self.out_channels)
self.blocks = keras.models.Sequential(name='dynamic-blocks')
# build all the blocks
for block_id in range(len(block_list)):
for layer_id in range(block_list[block_id]):
if block_id != 0 and layer_id == 0:
block = ResnetBlock(self.out_channels,
strides=2, residual_path=True)
else:
if self.in_channels != self.out_channels:
residual_path = True
else:
residual_path = False
block = ResnetBlock(self.out_channels,
residual_path=residual_path)
self.in_channels = self.out_channels
self.blocks.add(block)
self.out_channels *= 2
self.final_bn = keras.layers.BatchNormalization()
self.avg_pool = keras.layers.GlobalAveragePooling2D()
self.fc = keras.layers.Dense(num_classes)
def call(self, inputs, training=None):
out = self.conv_initial(inputs)
out = self.blocks(out, training=training)
out = self.final_bn(out, training=training)
out = tf.nn.relu(out)
out = self.avg_pool(out)
out = self.fc(out)
return out
if __name__ == "__main__":
model = ResNet([2, 2, 2], 10)
model.build(input_shape=(None, 28, 28, 1))
model.summary()
3. 總結
一般情況下,簡單的應用可以直接使用函數式API程式設計,對于複雜的網絡的定義和訓練可以使用類繼承的方式,這樣的代碼邏輯較好,封裝性較好。