【发布时间】:2017-12-03 18:00:45
【问题描述】:
如何最好地向 keras (v2.0.5) 模型添加预处理层(例如,减去均值并除以标准差),以使模型完全自包含以进行部署(可能在 C++ 环境中)。我试过了:
def getmodel():
model = Sequential()
mean_tensor = K.placeholder(shape=(1,1,3), name="mean_tensor")
std_tensor = K.placeholder(shape=(1,1,3), name="std_tensor")
preproc_layer = Lambda(lambda x: (x - mean_tensor) / (std_tensor + K.epsilon()),
input_shape=im_shape)
model.add(preproc_layer)
# Build the remaining model, perhaps set weights,
...
return model
然后,在其他地方设置模型的均值/标准差。我找到了set_value 函数,所以尝试了以下方法:
m = getmodel()
mean, std = get_mean_std(..)
graph = K.get_session().graph
mean_tensor = graph.get_tensor_by_name("mean_tensor:0")
std_tensor = graph.get_tensor_by_name("std_tensor:0")
K.set_value(mean_tensor, mean)
K.set_value(std_tensor, std)
但是set_value 失败了
AttributeError: 'Tensor' object has no attribute 'assign'
所以set_value 不像(有限的)文档所建议的那样工作。这样做的正确方法是什么?获取 TF 会话,将所有训练代码包装在 with (session) 中并使用 feed_dict?我原以为会有一种原生的 keras 方式来设置张量值。
我没有使用占位符,而是尝试使用 K.variable 或 K.constant 设置模型构造的均值/标准:
mean_tensor = K.variable(mean, name="mean_tensor")
std_tensor = K.variable(std, name="std_tensor")
这避免了任何set_value 问题。尽管我注意到如果我尝试训练该模型(我知道这不是特别有效,因为您正在为每个图像重新进行标准化)它可以工作,但在第一个时期结束时,ModelCheckpoint 处理程序以非常深度堆栈跟踪:
...
File "/Users/dgorissen/Library/Python/2.7/lib/python/site-packages/keras/models.py", line 102, in save_model
'config': model.get_config()
File "/Users/dgorissen/Library/Python/2.7/lib/python/site-packages/keras/models.py", line 1193, in get_config
return copy.deepcopy(config)
File "/usr/local/Cellar/python/2.7.12_2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/copy.py", line 163, in deepcopy
y = copier(x, memo)
...
File "/usr/local/Cellar/python/2.7.12_2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/copy.py", line 190, in deepcopy
y = _reconstruct(x, rv, 1, memo)
File "/usr/local/Cellar/python/2.7.12_2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/copy.py", line 343, in _reconstruct
y.__dict__.update(state)
AttributeError: 'NoneType' object has no attribute 'update'
更新 1:
我也尝试了不同的方法。像往常一样训练一个模型,然后在前面添加一个进行预处理的模型:
# Regular model, trained as usual
model = ...
# Preprocessing model
preproc_model = Sequential()
mean_tensor = K.constant(mean, name="mean_tensor")
std_tensor = K.constant(std, name="std_tensor")
preproc_layer = Lambda(lambda x: (x - mean_tensor) / (std_tensor + K.epsilon()),
input_shape=im_shape, name="normalisation")
preproc_model.add(preproc_layer)
# Prepend the preprocessing model to the regular model
full_model = Model(inputs=[preproc_model.input],
outputs=[model(preproc_model.output)])
# Save the complete model to disk
full_model.save('full_model.hdf5')
这似乎一直有效,直到 save() 调用失败,并出现与上述相同的深度堆栈跟踪。
也许Lambda 层是问题所在,但从this issue 来看,它似乎应该正确序列化。
总的来说,我如何在不影响序列化(和导出到 pb)能力的情况下将规范化层附加到 keras 模型?
我相信你可以通过直接下拉到 TF 来让它工作(例如this thread,或使用tf.Transform),但我认为它可以直接在 keras 中使用。
更新 2:
所以我发现可以通过这样做来避免深度堆栈跟踪
def foo(x):
bar = K.variable(baz, name="baz")
return x - bar
因此在函数内部定义bar,而不是从外部范围捕获。
然后我发现我可以保存到磁盘但无法从磁盘加载。围绕这个有一套 github 问题。我使用#5396 中指定的解决方法将所有变量作为参数传递,然后允许我保存和加载。
以为我快到了,我继续使用上述更新 1 中的方法,将预处理模型堆叠在经过训练的模型前面。
这导致了Model is not compiled 错误。解决了这些问题,但最终我从未设法使以下工作:
- 构建和训练模型
- 将其保存到磁盘
- 加载它,添加预处理模型
- 将堆叠模型作为冻结的 pb 文件导出到磁盘
- 从磁盘加载冻结的 pb
- 将其应用于一些看不见的数据
我做到了没有错误的地步,但无法让归一化张量传播到冻结的 pb。在这上面花了太多时间后,我放弃了,转而使用不太优雅的方法:
- 从一开始就使用模型中的预处理操作构建模型,但设置为无操作(mean=0,std=1)
- 训练模型,构建一个相同的模型,但这次使用正确的平均值/标准值。
- 转移权重
- 将模型导出并冻结到 pb
所有这些现在都可以按预期工作了。训练开销很小,但对我来说可以忽略不计。
仍然无法弄清楚如何在 keras 中设置张量变量的值(不引发 assign 异常),但现在可以不用它。
会接受@Daniel 的回答,因为它让我朝着正确的方向前进。
相关问题:
【问题讨论】:
-
如果操作是固定的(没什么可学习的)并且在第一层之前,也许您应该在将数据提供给模型之前直接在输入数据中进行操作。这样可以节省您在训练时进行额外不必要计算的处理成本。
-
mean_tensor和std_tensor是否与 X 具有相同的“批量大小”?这可以显着改变答案... -
@Daniel 这是一个稍微简化的场景。我现在忽略了训练的开销,但你可以想象将这一层粘贴到一个即将投入生产的模型上,你会遇到同样的问题。如果我将张量定义为常量,则不会出现形状不匹配错误。否则我会得到描述的无属性错误。
-
我正在尝试提出一个常规的 Keras 解决方案,但我需要这些形状来了解发生了什么。由于
x - mean_tensor,它很可能不起作用。如果它们的形状不同或均值不是标量,则此方法无效。你能分享X、mean和std的形状吗? -
@Daniel 可以从变量声明中看到形状:(1,1,3)。例如,[[[123.4, 54.9, 123.55 ]]]。每个通道一个值。
标签: python tensorflow deep-learning keras keras-layer