TensorFlow2 入门指南 | 13 Keras Functional API 官方教程 素颜马尾好姑娘i 2022-10-06 09:53 152阅读 0赞 **前言:** **本专栏在保证内容完整性的基础上,力求简洁,旨在让初学者能够更快地、高效地入门TensorFlow2 深度学习框架。如果觉得本专栏对您有帮助的话,可以给一个小小的三连,各位的支持将是我创作的最大动力!** 系列文章汇总:[TensorFlow2 入门指南][TensorFlow2] Github项目地址:[https://github.com/Keyird/TensorFlow2-for-beginner][https_github.com_Keyird_TensorFlow2-for-beginner] ![在这里插入图片描述][20200607130720698.gif_pic_center] -------------------- ### 文章目录 ### * * 一、Keras Functional API 介绍 * 二、Keras Functional API 网络训练、验证方法 * 三、模型保存以及加载 * 四、使用同一 graph of layers 定义不同模型 * 五、任何 model 可以像层一样被调用 * * (1)模型嵌套 * (2)模型集成 * 六、构建复杂的网络结构 * * (1)多输入多输出模型 * (2)搭建 ResNet 结构 * 七、共享层 * 八、提取并重用网络图中的节点 * 九、使用自定义层扩展API * 十、什么时候使用 Keras Functional API * * (1)Keras Functional API 的优点 * (2)Keras Functional API 的缺点 * 十一、API混搭风格 ## 一、Keras Functional API 介绍 ## 采用 Keras Functional API 相比 tf.keras.Sequential API 能搭建出更复杂的网络模型,比如利用 Keras Functional API 可以搭建出非线性、共享的、甚至是多输入多输出的结构,这些都是 tf.keras.Sequential API 不能完成的。 通过 Keras Functional API 来建立一个简单的三层网络: # 网络搭建 inputs = keras.Input(shape=(784,)) x = layers.Dense(128, activation="relu")(inputs) x = layers.Dense(64, activation="relu")(x) outputs = layers.Dense(10)(x) 基于此,要创建一个model,需要在层图中指定其输入和输出: model = keras.Model(inputs=inputs, outputs=outputs, name="mnist_model") 接下来,就能通过 model.summary() 打印出model的结构和参数了;也能通过 model.fit() 和 model.compile() 完成进一步地训练。 ## 二、Keras Functional API 网络训练、验证方法 ## Keras Functional API 网络训练、验证方法 与 Sequential models 几乎是一样的。下面是使用 Keras Functional API 对 MNIST 数据集进行分类的一个案例: # 数据集准备 (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 # 网络搭建 inputs = keras.Input(shape=(784,)) x = layers.Dense(128, activation="relu")(inputs) x = layers.Dense(64, activation="relu")(x) outputs = layers.Dense(10)(x) model = keras.Model(inputs=inputs, outputs=outputs, name="mnist_model") model.summary() # 网络装配 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=10, 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]) 网络训练结果: ![在这里插入图片描述][20210613133850382.png] ## 三、模型保存以及加载 ## 对于使用 Keras Functional API 构建的模型,保存模型和序列化的工作方式与 Sequential models 相同。保存函数模型的标准方法是调用 model.save() 将整个模型保存为单个文件。您以后可以从这个文件重新创建相同的模型,即使构建模型的代码不再可用。这个保存的文件包括: * 模型结构 * 模型权重值(训练过程中学习到的) * 模型训练配置(as passed to compile) * 优化器以及训练的状态 (to restart training where you left off) 模型保存以及加载方法: 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") ## 四、使用同一 graph of layers 定义不同模型 ## 在 Keras Functional API 中,通过在 graph of layers 中指定它们的输入和输出来创建模型。这意味着一个 graph of layers 可以用来生成多个模型。 在下面的示例中,使用相同的层堆栈实例化两个模型:一个将图像输入转换为16维向量的编码器模型encoder ,以及一个用于训练的端到端自动编码器模型autoencoder。 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 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 autoencoder = keras.Model(encoder_input, decoder_output, name="autoencoder") autoencoder.summary() ## 五、任何 model 可以像层一样被调用 ## ### (1)模型嵌套 ### 你可以将 model 视为一个层,在输入或另一层的输出上调用任何模型。你不仅重用了模型的体系结构,还重用了它的权重。下面是一个不同的 autoencoder 示例,它创建了一个编码器模型,一个解码器模型,并将它们链在两个调用中以获得 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() 将已定义好的 encoder、decoder 模型以层的方式进行调用: autoencoder_input = keras.Input(shape=(28, 28, 1), name="img") # 将 model 当做 layers 的方式,可以被调用 encoded_img = encoder(autoencoder_input) decoded_img = decoder(encoded_img) # 自动编码器模型autoencoder autoencoder = keras.Model(autoencoder_input, decoded_img, name="autoencoder") autoencoder.summary() ### (2)模型集成 ### 如上所见,模型可以嵌套:模型可以包含子模型(因为模型就像一个层)。模型嵌套的一个常见用例是**集成**。例如,下面是如何将一组模型集成到一个单一模型中,对它们的预测进行平均: 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) ## 六、构建复杂的网络结构 ## ### (1)多输入多输出模型 ### Keras Functional API 使操作多个输入和输出变得很容易,这是 Sequential API 不能实现的。 例如,如果您正在构建一个根据优先级对客户问题票据进行排序并将其路由到正确的部门的系统,那么该模型将有三个输入: * 票的标题(文本输入) * 票据的文本正文(文本输入) * 用户添加的任何标记(分类输入) 这个模型将有两个输出: * 优先级得分介于0和1之间(标量sigmoid输出) * 应处理票据的部门(部门集上的softmax输出) 要搭建一个结构图如下图所示的网络模型: ![在这里插入图片描述][20210613182303790.png_pic_center] 对于这个问题,你可以用 Keras Functional API 用几行代码构建这个模型: import numpy as np import tensorflow as tf from tensorflow import keras from tensorflow.keras import layers 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], ) 在装配这个模型时,您可以为每个输出分配不同的损失。你甚至可以为每次损失分配不同的权重——以调整它们对总训练损失的贡献: 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], ) 通过传递输入和目标的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, ) ### (2)搭建 ResNet 结构 ### Keras Functional API 除了能搭建多输入多输出模型之外,还能轻松搭建非线性拓扑结构的模型,而 Sequential API 无法处理这些层。一个常见的用例是Resnet结构中的残差连接,下面是Resnet的网络结构图: ![在这里插入图片描述][20210613183155740.png_pic_center] 采用 Keras Functional API 搭建如上图所示结构图: 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() ## 七、共享层 ## 函数式 API 的另一个很好的用途是使用共享层的模型。共享层是在同一模型中**被多次重用的层实例**——它们学习对应于层图中多个路径的特征。 共享层通常用于从相似的空间(例如,具有相似词汇表的两段不同文本)对输入进行编码。它们可以跨这些不同的输入共享信息,并且可以在更少的数据上训练这样的模型。如果在某个输入中看到给定的单词,那么将有利于处理通过共享层的所有输入。 要在函数式API中共享一个层,需要多次调用同一层实例。例如,这是一个跨两个不同的文本输入共享的嵌入层: # 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) ## 八、提取并重用网络图中的节点 ## graph of layers(网络结构图)是一个静态数据结构,所以可以访问和检查它。这也意味着你可以访问中间层(网络图中的“节点”)的激活,并在其他地方重用它们,这对于特征提取等非常有用。 让我们看一个例子。这是一个在 ImageNet 上预训练权重的VGG19模型: vgg19 = tf.keras.applications.VGG19() 这些是通过查询图数据结构获得的模型的中间激活: features_list = [layer.output for layer in vgg19.layers] 使用这些特性创建一个新的特征提取模型,返回中间层激活的值: 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) ## 九、使用自定义层扩展API ## tf.keras 包括广泛的内置层,例如: * 卷积层: Conv1D, Conv2D, Conv3D, Conv2DTranspose * 池化层: MaxPooling1D, MaxPooling2D, MaxPooling3D, AveragePooling1D * RNN 层:GRU, LSTM, ConvLSTM2D * 其它层:BatchNormalization, Dropout, Embedding, 等等 但如果没有找到所需的内容,可以通过创建自己的层来扩展API。**所有层继承Layer类并实现:** * call():它指定了该层完成的计算; * build():这会创建层的权重(这只是一个样式约定,因为你也可以在\_\_init\_\_中创建权重)。 更多可了解:[https://tensorflow.google.cn/guide/keras/custom\_layers\_and\_models][https_tensorflow.google.cn_guide_keras_custom_layers_and_models] 以下是 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) 为了在你的自定义层中支持序列化,定义一个 get\_config 方法返回层实例的构造函数参数: 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}) 可选地,实现类方法 from\_config(cls, config),当给定配置字典重新创建一个层实例时使用该方法。 def from_config(cls, config): return cls(**config) ## 十、什么时候使用 Keras Functional API ## 你应该使用 Keras 函数式 API 来创建一个新模型,还是只是直接子类化 model 类? 一般来说,函数式API更高级、更简单、更安全,并且具有许多子类模型不支持的特性。 然而,当构建不容易表示为层的有向无环图的模型时,模型子类化提供了更大的灵活性。例如,您不能使用功能性API实现Tree-RNN,并且必须直接子类化Model。 需要深入了解函数式API和模型子类化之间的区别,前往:[https://blog.tensorflow.org/2019/01/what-are-symbolic-and-imperative-apis.html][https_blog.tensorflow.org_2019_01_what-are-symbolic-and-imperative-apis.html] ### (1)Keras Functional API 的优点 ### 下面的属性优点对于 Sequential model (也是数据结构)也是正确的,但是对于自定义子类模型(是Python字节编码,而不是数据结构)不是正确的。 **1、更少的代码冗余**。通常,使用子类模型,还需要定义 `super(MyClass, self).__init__(...), no def call(self, ...):`等等。例如: # Solution1: Keras Functional API inputs = keras.Input(shape=(32,)) x = layers.Dense(64, activation='relu')(inputs) outputs = layers.Dense(10)(x) mlp = keras.Model(inputs, outputs) # Solution2: 子类模型 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() # The model doesn't have a state until it's called at least once. out_mlp = mlp(tf.zeros((1, 32))) **2、在定义连接图时对验证进行建模**。在函数式API中,输入规范(shape和dtype)是预先创建的(使用input)。每次调用一个层时,该层检查传递给它的规范是否与它的假设相匹配,如果不匹配,它将提出一条有用的错误消息。 **3、函数式模型是可标绘和可检查的**。您可以将模型绘制成一个图,并且可以轻松地访问这个图中的中间节点。例如,提取和重用中间层的激活(如前面的例子所示): features_list = [layer.output for layer in vgg19.layers] feat_extraction_model = keras.Model(inputs=vgg19.input, outputs=features_list) **4、函数式模型可以序列化或克隆**。因为函数式模型是一种数据结构,而不是一段代码,所以它是安全的可序列化的,可以保存为单个文件,允许您在没有访问任何原始代码的情况下重新创建完全相同的模型。要序列化一个子类模型,实现者必须在模型级别指定一个 get\_config() 和 from\_config() 方法。 ### (2)Keras Functional API 的缺点 ### **它不支持动态架构**:功能性 API 将模型视为层的 DAGs,这适用于大多数深度学习体系结构,但并非所有。例如,递归网络或树型RNN不遵循这一假设,不能在函数式 API 中实现。 ## 十一、API混搭风格 ## 在函数式API和模型的子类之间进行选择并不是一个二元决策,它将您限制在一个模型类别中。tf.keras API 里的所有模型可以相互交互,无论它们是顺序模型、函数式模型还是从头编写的子类模型。 比如,你可以始终使用功能模型或序列模型作为子类模型或层的一部分: import tensorflow as tf from tensorflow import keras from tensorflow.keras import layers 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_out = rnn_model(tf.zeros((1, timesteps, input_dim))) 下面是一个自定义RNN的快速示例,它是从头编写的,在函数模型中使用: 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))) -------------------- > 本教程所有代码会逐渐上传github仓库:[https://github.com/Keyird/TensorFlow2-for-beginner][https_github.com_Keyird_TensorFlow2-for-beginner] > 如果对你有帮助的话,欢迎star收藏~ 最好的关系是**互相成就**,各位的「三连」就是【AI 菌】创作的最大动力,我们下期见! [TensorFlow2]: https://blog.csdn.net/wjinjie/category_9955855.html [https_github.com_Keyird_TensorFlow2-for-beginner]: https://github.com/Keyird/TensorFlow2-for-beginner [20200607130720698.gif_pic_center]: /images/20221005/4d08a383d99d4ababbbd54ae28499ca6.png [20210613133850382.png]: /images/20221005/fe0338514cfd4f96a246bc87ff533ca2.png [20210613182303790.png_pic_center]: /images/20221005/bd98a70bbace41218c1bd7e0da85e5ff.png [20210613183155740.png_pic_center]: /images/20221005/23b295e68ac640568ccf587cf7267827.png [https_tensorflow.google.cn_guide_keras_custom_layers_and_models]: https://tensorflow.google.cn/guide/keras/custom_layers_and_models [https_blog.tensorflow.org_2019_01_what-are-symbolic-and-imperative-apis.html]: https://blog.tensorflow.org/2019/01/what-are-symbolic-and-imperative-apis.html
还没有评论,来说两句吧...