如何让Keras正常追踪训练不依赖输入的待合并层子图
问题说明
请参考如下示例:
import tensorflow as tf units=11 entrada=tf.keras.Input(name="entrada", shape=(units,)) unidad= tf.Variable([[1.0]]) # + 0.0* entrada[:,:1] denseSoftmax=tf.keras.layers.Dense(units,name="denseSoftmax",activation="softmax") softMaxOutput=denseSoftmax(unidad) finalproduct=tf.keras.layers.Multiply()([entrada,softMaxOutput]) modelo=tf.keras.Model(entrada,finalproduct) modelo.summary()
运行该示例得到的模型无可训练参数,原因是denseSoftmax层未接入模型输入的计算链路。若取消代码中+ 0.0 * entrada[:,:1]的注释,伪造一条指向输入的零值连接,就能生成符合预期的计算图,模型结构输出如下:
Layer (type) Output Shape Param # Connected to ================================================================================================== entrada (InputLayer) [(None, 11)] 0 [] tf.__operators__.getitem (Slic (None, 1) 0 ['entrada[0][0]'] ingOpLambda) tf.math.multiply (TFOpLambda) (None, 1) 0 ['tf.__operators__.getitem[0][0]' tf.__operators__.add (TFOpLamb (None, 1) 0 ['tf.math.multiply[0][0]'] denseSoftmax (Dense) (None, 11) 22 ['tf.__operators__.add[0][0]'] multiply (Multiply) (None, 11) 0 ['entrada[0][0]', 'denseSoftmax[0][0]']
但这种伪造零值输入连接的方案存在明显缺陷,和在输入层中新增常量分支的做法一样不够合理。
规范解决方案
不需要伪造零值连接,Keras本身提供了标准实现方式:将独立于输入的可训练参数封装为tf.keras.layers.Layer子类即可,框架会自动追踪这类不依赖输入实际值、但参与输出计算的层的参数,不需要额外手动伪造输入链路。
修正后的可运行代码如下:
import tensorflow as tf units = 11 # 自定义独立可训练常量层,不依赖输入的实际数值 class TrainableConstant(tf.keras.layers.Layer): def __init__(self, initial_value, **kwargs): super().__init__(**kwargs) self.initial_value = initial_value def build(self, input_shape=None): # 注册可训练权重,不需要关联输入的具体特征维度 self.kernel = self.add_weight( name="kernel", shape=tf.shape(self.initial_value), initializer=tf.keras.initializers.Constant(self.initial_value), trainable=True ) self.built = True def call(self, inputs): # 仅用输入获取batch维度做对齐,不使用输入的实际数值参与计算 batch_size = tf.shape(inputs)[0] return tf.tile(self.kernel, [batch_size, 1]) entrada = tf.keras.Input(name="entrada", shape=(units,)) # 初始化独立可训练常量,初始值为[[1.0]] unidad = TrainableConstant([[1.0]], name="trainable_const")(entrada) denseSoftmax = tf.keras.layers.Dense(units, name="denseSoftmax", activation="softmax") softMaxOutput = denseSoftmax(unidad) finalproduct = tf.keras.layers.Multiply()([entrada, softMaxOutput]) modelo = tf.keras.Model(entrada, finalproduct) modelo.summary()
运行后输出的模型结构可以看到denseSoftmax的22个参数被正常识别为可训练参数,不需要伪造零值连接:
Model: "model" __________________________________________________________________________________________________ Layer (type) Output Shape Param # Connected to ================================================================================================== entrada (InputLayer) [(None, 11)] 0 [] trainable_const (TrainableCons (None, 1) 1 ['entrada[0][0]'] tant) denseSoftmax (Dense) (None, 11) 22 ['trainable_const[0][0]'] multiply (Multiply) (None, 11) 0 ['entrada[0][0]', 'denseSoftmax[0][0]'] ================================================================================================== Total params: 23 Trainable params: 23 Non-trainable params: 0 __________________________________________________________________________________________________
这种实现是Keras自定义可训练权重的标准写法:
- 所有可训练参数都在层的
build方法中通过add_weight注册,框架会自动识别并加入训练参数列表,只要该层的输出最终流向模型输出,就会被正常追踪 - 不需要给独立子图强行伪造和输入的计算连接,层的
call方法仅用输入获取batch size做维度对齐,不会引入无意义的零值运算 - 自定义层可以灵活设置初始值、是否可训练、权重正则化等属性,比伪造连接的方案可控性强得多
如果不想自定义层,也可以直接使用内置的tf.keras.layers.Embedding实现固定长度的可训练常量,本质逻辑和自定义层一致:将输入设为全0的固定索引,映射为可训练的向量,同样不需要伪造有实际计算意义的输入连接。
内容的提问来源于stack exchange,提问作者arivero
相关产品推荐
相关产品推荐

