如何强制Keras为训练与测试模型创建不同自定义层实例
如何让Keras训练与测试模型使用参数不同的自定义层(避免层共享)
我理解你的问题:你写了一个无训练权重的自定义层Foo,给输入加高斯噪声,训练时用sigma-trg,测试时想复用训练好的其他层,但给Foo层换sigma-test,结果发现两个模型共享了同一个Foo层,参数没变,测试效果和训练时一样。这是因为Keras会复用同一个层实例,不管你在调用时传什么参数——核心问题是你没有为测试模型创建全新的Foo层实例。
先看你提供的相关代码:
跨模型共享层代码
if enable_trg is True: model_trg = Model(inputs=source_input, outputs= preds) # some code specifying the layers and connections # some code to compile and train the model model_trg.save_weights(model_name + '.h5') # save weights if enable_test is True: model_trg.load_weights(model_name + '.h5') model_test= Model(inputs=source_input, outputs = preds)
训练模型中调用层
y = foo(batch_size=trg_batch_size,noise_stdev=stdev_trg)(x)
测试模型中调用层
y = foo(batch_size=test_batch_size,noise_stdev=stdev_test)(x)
自定义层代码
class Foo(Layer): def __init__(self, batch_size, noise_stdev, **kwargs): super(Foo, self).__init__(**kwargs) self.batch_size = batch_size self.stdev = noise_stdev self.supports_masking = True def build(self,inputShape): super(Foo,self).build(inputShape) def call(self, inputs, training=None): y = inputs[0] noise = K.random_normal(shape=K.shape(y),mean=0.,stddev=self.stdev) return y + noise def get_config(self): config = { 'batch_size': self.batch_size, 'noise_stdev': self.stdev } base_config = super(Foo, self).get_config() return dict(list(base_config.items()) + list(config.items())) def compute_output_shape(self, input_shape): return input_shape
解决方案:为测试模型创建独立的层实例
Keras的层是对象实例,只要同一个实例被多个模型引用,就会共享其所有参数(包括你在__init__里定义的stdev)。你之前的代码里,训练和测试用的是同一个foo实例,所以即使调用时传了不同的参数,实际还是同一个层,stdev不会改变。解决方法很简单:
1. 把模型构建逻辑封装成函数,每次调用创建全新层
把模型的构建过程写成一个可复用的函数,这样训练和测试时分别调用,就能为Foo层创建独立的实例:
from tensorflow.keras.layers import Input from tensorflow.keras.models import Model def build_custom_model(input_shape, batch_size, noise_stdev): # 1. 重新定义输入 source_input = Input(shape=input_shape) # 2. 定义你的网络结构(和之前训练时的结构完全一致) # --- 这里替换成你原来的层连接逻辑 --- x = ... # 前面的层处理,比如Dense、Conv2D等 # --- 到这里结束 --- # 3. 创建全新的Foo层实例,用当前传入的noise_stdev foo_layer = Foo(batch_size=batch_size, noise_stdev=noise_stdev) y = foo_layer(x) # 4. 后续层处理(和训练时一致) # --- 替换成你原来的后续层逻辑 --- preds = ... # 最终输出层 # --- 到这里结束 --- # 5. 创建并返回模型 model = Model(inputs=source_input, outputs=preds) return model
2. 训练模型时正常构建、保存权重
if enable_trg is True: # 构建训练模型,用sigma-trg model_trg = build_custom_model( input_shape=your_input_shape, # 替换成你的输入形状 batch_size=trg_batch_size, noise_stdev=stdev_trg ) # 编译、训练模型(和你原来的代码一致) model_trg.compile(optimizer='adam', loss='your_loss') model_trg.fit(your_train_data, your_train_labels, epochs=10) # 保存权重(只保存可训练层的权重,Foo层没有可训练参数,不会被保存) model_trg.save_weights(model_name + '.h5')
3. 测试模型时重新构建、加载权重
if enable_test is True: # 构建测试模型,用sigma-test(全新的Foo层实例) model_test = build_custom_model( input_shape=your_input_shape, batch_size=test_batch_size, noise_stdev=stdev_test ) # 加载训练好的权重:用by_name=True确保只加载同名的可训练层权重 # 因为Foo层没有可训练参数,所以不会被影响 model_test.load_weights(model_name + '.h5', by_name=True) # 现在model_test的Foo层用的是stdev_test,其他层和model_trg权重完全一致 # 可以正常测试泛化能力了 test_results = model_test.evaluate(your_test_data, your_test_labels)
关键说明
- 为什么原来的代码不行?因为你构建
model_test时,复用了model_trg的preds张量,这个张量是基于训练时的foo实例构建的,所以model_test会直接引用那个旧的Foo层,不管你后来怎么调用foo(...),都是同一个实例,stdev还是训练时的值。 - 你的
Foo层没有可训练权重,所以加载权重时不会有冲突,by_name=True只是更安全的做法,确保只加载需要的层权重。 - 你的
get_config方法已经正确实现了,这很重要——如果以后需要保存整个模型(而不是只保存权重),Keras能正确恢复Foo层的参数。
内容的提问来源于stack exchange,提问作者Vikram Chandrasekhar
相关产品推荐
相关产品推荐

