当前位置 博文首页 > Cam运行报错:ValueError: Unable to determine penultimate `Co

    Cam运行报错:ValueError: Unable to determine penultimate `Co

    作者:[db:作者] 时间:2021-08-28 10:04

    之前一直想可视化模型的注意力热力图,找到了gradcam算法,具体是调用keras-vis的库。但是一直调试一直报错很苦恼,前前后后搞了一个月才成功跑通,于是今天准备写个博客记录一下心路历程,也方便后来者参考,不必再浪费太多时间在debug上

    问题描述:
    在用预训练模型VGG16做base_model并finetune时,报如下错误:

    ValueError: Unable to determine penultimate Conv or Pooling layer for layer_idx: 2

    但是如果是自己定义的模型就可以正常输出。

    看起来应该是一个找不到对应layer的错误。于是很自然地我就查看了model的summary:

    Model: "sequential_1"
    _________________________________________________________________
    Layer (type)                 Output Shape              Param #   
    =================================================================
    vgg16 (Model)                (None, 7, 7, 512)         14714688  
    _________________________________________________________________
    flatten_1 (Flatten)          (None, 25088)             0         
    _________________________________________________________________
    visualized_layer (Dense)     (None, 1)                 25089     
    =================================================================
    Total params: 14,739,777
    Trainable params: 25,089
    Non-trainable params: 14,714,688
    _________________________________________________________________
    

    很显然,问题出在那个‘vgg16’上。我用的是操作更为简洁的Sequential搭建序列,模型设置如下:

    base_model = VGG16(weights='imagenet',include_top=False,input_shape=IMG_SIZE+(3,))
    base_model.trainable=False
    NUM_CLASSES = 1
    model = Sequential()
    model.add(base_model)
    model.add(layers.Flatten())
    model.add(layers.Dense(NUM_CLASSES,activation='sigmoid',name='visualized_layer'))
    

    之后DEBUG时发现我的代码在调用grad_cam时输入的layer_Idx是2(对应visualized_layer),然而在真实模型结构中很显然这层的索引绝对不止2,因此问题就出在Sequential对base_model的模型表述方式上。很显然这里Sequential把base_model看作一个不可拆分的整体,因此grad_cam也无法得到模型每一层输出的具体张量了(猜测)。
    之后尝试过手动指定layer_Idx(也就是百度vgg16的网络结构,数一数有几层,然后手动输入Dense层的索引),但都已失败告终。

    解决方式是:
    使用keras.models.Model()来函数式定义网络结构。
    这能够允许grad_cam算法获得打包好的预训练模型内部layers的信息。而这些信息是gradcam算法运行所需要的。
    这可能相比于简介的Sequential显得有些复杂,但说他能够更全面的定义整个网络结构,也更具有可操作性。修改后的代码如下:

    base_model = VGG16(weights='imagenet',include_top=False,input_shape=IMG_SIZE+(3,))
    mid_layer=layers.Flatten()
    mid_layer_tensor=mid_layer(base_model.output)
    output_layer=layers.Dense(2,activation='softmax',name='visualized_layer')
    output_layer_tensor=output_layer(mid_layer_tensor)
    model = Model(inputs=base_model.input,outputs=output_layer_tensor)
    for layer in model.layers:
        layer.trainable = False
    model.layers[-1].trainable = True
    model.layers[-2].trainable = True
    

    注意:使用Model()后要手动设置for循环来指定冻结层(也就是有预训练权重的层)。要不然会默认全部训练。

    cs