政安晨:【Keras机器学习实践要点】(五)—— 通过子类化创建新层和模型

本文主要是介绍政安晨:【Keras机器学习实践要点】(五)—— 通过子类化创建新层和模型,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

政安晨的个人主页政安晨

欢迎 👍点赞✍评论⭐收藏

收录专栏: TensorFlow与Keras实战演绎机器学习

希望政安晨的博客能够对您有所裨益,如有不足之处,欢迎在评论区提出指正!

介绍

本文将涵盖构建自己的子类化层和模型所需的所有知识。

您将了解以下功能

层类

add_weight()方法

可训练和不可训练的权重

build()方法

确保您的层可以与任何后端一起使用

add_loss()方法

call()中的训练参数

call()中的掩码参数

确保您的层可以序列化

让我们开始吧。


安装

import numpy as np
from tensorflow import keras
from keras import ops
from keras import layers

层级:状态(权重)与某些计算的组合

Keras 的核心抽象之一是层类。

层封装了状态(层的 "权重")和从输入到输出的转换("调用",层的前向传递)。

下面是一个密集连接的层。它有两个状态变量:变量 w 和 b。

class Linear(keras.layers.Layer):def __init__(self, units=32, input_dim=32):super().__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 ops.matmul(inputs, self.w) + self.b

你可以通过调用一个层函数来使用它,就像调用Python函数一样,传入一些张量输入。

x = ops.ones((2, 2))
linear_layer = Linear(4, 2)
y = linear_layer(x)
print(y)

请注意,权重 w 和 b 在被设置为层属性后,会被层自动跟踪

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

层可以有不可训练的重量

除了可训练权重外,还可以向层添加不可训练权重。在反向传播过程中,这些权重在训练层时不会被考虑在内。

下面介绍如何添加和使用不可训练权重

class ComputeSum(keras.layers.Layer):def __init__(self, input_dim):super().__init__()self.total = self.add_weight(initializer="zeros", shape=(input_dim,), trainable=False)def call(self, inputs):self.total.assign_add(ops.sum(inputs, axis=0))return self.totalx = ops.ones((2, 2))
my_sum = ComputeSum(2)
y = my_sum(x)
print(y.numpy())
y = my_sum(x)
print(y.numpy())

它是层权重的一部分,但被归类为不可训练的权重。

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)

最佳实践推迟权重的创建,直到输入的形状已知

我们上面的线性层接受了一个input_dim参数,该参数用于在__init__()函数中计算权重w和偏置b的形状。

class Linear(keras.layers.Layer):def __init__(self, units=32, input_dim=32):super().__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 ops.matmul(inputs, self.w) + self.b

在许多情况下,您可能无法预先知道输入的大小,并且希望在实例化图层后的某个时间,当该值变为已知时,才懒惰地创建权重。

在Keras API中,我们建议在您的图层的build(self, inputs_shape)方法中创建图层权重。

像这样:

class Linear(keras.layers.Layer):def __init__(self, units=32):super().__init__()self.units = unitsdef 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 ops.matmul(inputs, self.w) + self.b

您的层的__call__()方法在第一次被调用时会自动运行build。

现在您有一个懒惰的层,因此更容易使用。

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

将build()单独实现如上所示,很好地将只创建权重一次与在每次调用中使用权重进行了分离。


层可以递归组合

如果你将一个 Layer 实例分配为另一个 Layer 的属性,外层将开始跟踪内层创建的权重。

我们建议在 init() 方法中创建这样的子层,并在第一个 call() 中触发构建它们的权重。

class MLPBlock(keras.layers.Layer):def __init__(self):super().__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 = keras.activations.relu(x)x = self.linear_2(x)x = keras.activations.relu(x)return self.linear_3(x)mlp = MLPBlock()
y = mlp(ops.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))

后端不可知层和特定后端层

只要一个层只使用 keras.ops 命名空间的 API(或者其他 Keras 命名空间,例如 keras.activations、keras.random 或 keras.layers),那么它就可以与任何后端一起使用——TensorFlow、JAX 或 PyTorch。

到目前为止,在本指南中看到的所有层都适用于所有Keras后端。

keras.ops命名空间提供了以下功能

NumPy API,例如ops.matmul,ops.sum,ops.reshape,ops.stack等。

神经网络特定的API,例如ops.softmax,ops.conv,ops.binary_crossentropy,ops.relu等。

您还可以在层中使用本机后端API(例如tf.nn函数),但是如果这样做,您的层只能与特定的后端一起使用。

例如,您可以使用jax.numpy编写以下特定于JAX的层:

import jaxclass Linear(keras.layers.Layer):...def call(self, inputs):return jax.numpy.matmul(inputs, self.w) + self.b

这将是等效的TensorFlow特定层:

import tensorflow as tfclass Linear(keras.layers.Layer):...def call(self, inputs):return tf.matmul(inputs, self.w) + self.b

这将是等效的PyTorch特定层:

import torchclass Linear(keras.layers.Layer):...def call(self, inputs):return torch.matmul(inputs, self.w) + self.b

由于跨后端兼容性是一种非常有用的特性,我们强烈建议您始终通过仅使用Keras APIs来使您的层与后端无关。


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().__init__()self.rate = ratedef call(self, inputs):self.add_loss(self.rate * ops.mean(inputs))return inputs

这些损耗(包括任何内层创建的损耗)可以通过 layer.losses 检索到。

该属性在每次调用顶层层的 __call__() 开始时重置,因此 layer.losses 总是包含上次向前传递时创建的损耗值。

class OuterLayer(keras.layers.Layer):def __init__(self):super().__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(ops.zeros((1, 1)))
assert len(layer.losses) == 1  # We created one loss value# `layer.losses` gets reset at the start of each __call__
_ = layer(ops.zeros((1, 1)))
assert len(layer.losses) == 1  # This is the loss created during the call above

此外,损失属性还包含为任何内层权重创建的正则化损失

class OuterLayerWithKernelRegularizer(keras.layers.Layer):def __init__(self):super().__init__()self.dense = keras.layers.Dense(32, kernel_regularizer=keras.regularizers.l2(1e-3))def call(self, inputs):return self.dense(inputs)layer = OuterLayerWithKernelRegularizer()
_ = layer(ops.zeros((1, 1)))# This is `1e-3 * sum(layer.dense.kernel ** 2)`,
# created by the `kernel_regularizer` above.
print(layer.losses)

打印结果:

[Array(0.00217911, dtype=float32)]

在编写自定义训练循环时,应考虑到这些损失。

它们也可以与fit()方法无缝配合使用(如果有的话,会自动将它们求和并添加到主要损失中):

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

可以选择在您的层上启用序列化功能

如果需要将自定义层作为功能模型的一部分进行序列化,可以选择实现 get_config() 方法

class Linear(keras.layers.Layer):def __init__(self, units=32):super().__init__()self.units = unitsdef 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 ops.matmul(inputs, self.w) + self.bdef 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}

请注意,基本的Layer类的__init__()方法接受一些关键字参数,特别是name和dtype。

在__init__()中将这些参数传递给父类,并将它们包含在层的配置中是一个好的做法。

class Linear(keras.layers.Layer):def __init__(self, units=32, **kwargs):super().__init__(**kwargs)self.units = unitsdef 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 ops.matmul(inputs, self.w) + self.bdef get_config(self):config = super().get_config()config.update({"units": self.units})return configlayer = Linear(64)
config = layer.get_config()
print(config)
new_layer = Linear.from_config(config)
{'name': 'linear_7', 'trainable': True, 'dtype': 'float32', 'units': 64}

如果在从配置反序列化层时需要更大的灵活性,也可以覆盖 from_config() 类方法。

这是 from_config() 的基本实现:

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

顺序模型主题咱们就到这里。

这篇关于政安晨:【Keras机器学习实践要点】(五)—— 通过子类化创建新层和模型的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/851326

相关文章

Spring Security简介、使用与最佳实践

《SpringSecurity简介、使用与最佳实践》SpringSecurity是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架,本文给大家介绍SpringSec... 目录一、如何理解 Spring Security?—— 核心思想二、如何在 Java 项目中使用?——

防止Linux rm命令误操作的多场景防护方案与实践

《防止Linuxrm命令误操作的多场景防护方案与实践》在Linux系统中,rm命令是删除文件和目录的高效工具,但一旦误操作,如执行rm-rf/或rm-rf/*,极易导致系统数据灾难,本文针对不同场景... 目录引言理解 rm 命令及误操作风险rm 命令基础常见误操作案例防护方案使用 rm编程 别名及安全删除

C++统计函数执行时间的最佳实践

《C++统计函数执行时间的最佳实践》在软件开发过程中,性能分析是优化程序的重要环节,了解函数的执行时间分布对于识别性能瓶颈至关重要,本文将分享一个C++函数执行时间统计工具,希望对大家有所帮助... 目录前言工具特性核心设计1. 数据结构设计2. 单例模式管理器3. RAII自动计时使用方法基本用法高级用法

PHP应用中处理限流和API节流的最佳实践

《PHP应用中处理限流和API节流的最佳实践》限流和API节流对于确保Web应用程序的可靠性、安全性和可扩展性至关重要,本文将详细介绍PHP应用中处理限流和API节流的最佳实践,下面就来和小编一起学习... 目录限流的重要性在 php 中实施限流的最佳实践使用集中式存储进行状态管理(如 Redis)采用滑动

ShardingProxy读写分离之原理、配置与实践过程

《ShardingProxy读写分离之原理、配置与实践过程》ShardingProxy是ApacheShardingSphere的数据库中间件,通过三层架构实现读写分离,解决高并发场景下数据库性能瓶... 目录一、ShardingProxy技术定位与读写分离核心价值1.1 技术定位1.2 读写分离核心价值二

深入浅出Spring中的@Autowired自动注入的工作原理及实践应用

《深入浅出Spring中的@Autowired自动注入的工作原理及实践应用》在Spring框架的学习旅程中,@Autowired无疑是一个高频出现却又让初学者头疼的注解,它看似简单,却蕴含着Sprin... 目录深入浅出Spring中的@Autowired:自动注入的奥秘什么是依赖注入?@Autowired

MySQL分库分表的实践示例

《MySQL分库分表的实践示例》MySQL分库分表适用于数据量大或并发压力高的场景,核心技术包括水平/垂直分片和分库,需应对分布式事务、跨库查询等挑战,通过中间件和解决方案实现,最佳实践为合理策略、备... 目录一、分库分表的触发条件1.1 数据量阈值1.2 并发压力二、分库分表的核心技术模块2.1 水平分

Spring创建Bean的八种主要方式详解

《Spring创建Bean的八种主要方式详解》Spring(尤其是SpringBoot)提供了多种方式来让容器创建和管理Bean,@Component、@Configuration+@Bean、@En... 目录引言一、Spring 创建 Bean 的 8 种主要方式1. @Component 及其衍生注解

SpringBoot通过main方法启动web项目实践

《SpringBoot通过main方法启动web项目实践》SpringBoot通过SpringApplication.run()启动Web项目,自动推断应用类型,加载初始化器与监听器,配置Spring... 目录1. 启动入口:SpringApplication.run()2. SpringApplicat

Unity新手入门学习殿堂级知识详细讲解(图文)

《Unity新手入门学习殿堂级知识详细讲解(图文)》Unity是一款跨平台游戏引擎,支持2D/3D及VR/AR开发,核心功能模块包括图形、音频、物理等,通过可视化编辑器与脚本扩展实现开发,项目结构含A... 目录入门概述什么是 UnityUnity引擎基础认知编辑器核心操作Unity 编辑器项目模式分类工程