【问题标题】:Why Reshape and Permute for segmentation with unet?为什么用 unet 重塑和置换进行分割?
【发布时间】:2019-12-01 00:24:15
【问题描述】:

我正在使用 unet 进行图像语义分割工作。我对像素分类的最后一层感到困惑。 Unet代码是这样的:

...
reshape = Reshape((n_classes,self.img_rows * self.img_cols))(conv9)
permute = Permute((2,1))(reshape)
activation = Activation('softmax')(permute)
model = Model(input = inputs, output = activation) 
return model
...

我可以在不使用 Permute 的情况下直接重塑吗?

reshape = Reshape((self.img_rows * self.img_cols, n_classes))(conv9)

更新:

使用直接reshape方式时发现训练结果不对:

reshape = Reshape((self.img_rows * self.img_cols, n_classes))(conv9) // the loss is not convergent

我的groundtruth是这样生成的:

X = []
Y = []
im = cv2.imread(impath)
X.append(im)
seg_labels = np.zeros((height, width, n_classes))
for spath in segpaths:
    mask = cv2.imread(spath, 0)
    seg_labels[:, :, c] += mask
Y.append(seg_labels.reshape(width*height, n_classes))

为什么直接reshape不起作用?

【问题讨论】:

    标签: tensorflow keras image-segmentation tf.keras semantic-segmentation


    【解决方案1】:

    你明显误解了每个操作的含义和最终目标:

    • 最终目标:对每个像素进行分类,即沿语义类轴的 softmax
    • 如何在原代码中实现这个目标?我们逐行看代码:
    reshape = Reshape((n_classes,self.img_rows * self.img_cols))(conv9) # L1
    permute = Permute((2,1))(reshape) # L2
    activation = Activation('softmax')(permute) # L3
    
    • L1 的输出亮度 = n_class-by-n_pixs, (n_pixs=img_rows x img_cols)
    • L2 的输出亮度 = n_pixs-by-n_class
    • L3 的输出亮度 = n_pixs-by-n_class
    • 请注意,默认的 softmax 激活应用于最后一个轴,即 n_class 代表的轴,即语义类轴。

    因此,这个原始代码实现了语义分割的最终目标。


    让我们重新审视一下你要更改的代码,即

    reshape = Reshape((self.img_rows * self.img_cols, n_classes))(conv9) # L4
    
    • L4 的输出亮度 = n_pixs-by-n_class

    我的猜测是你认为 L4 的输出 dim 与 L2 的匹配,因此 L4 是相当于执行 L1 和 L2 的捷径。

    但是,匹配形状并不一定意味着匹配轴的物理意义。为什么?一个简单的例子将解释。

    假设您有 2 个语义类和 3 个像素。要查看差异,假设所有三个像素都属于同一类。

    换句话说,地面实况张量看起来像这样

    # cls#1 cls#2
    [   [0, 1], # pixel #1
        [0, 1], # pixel #2
        [0, 1], # pixel #3
    ]
    

    假设您有一个完美的网络并为每个像素生成准确的响应,但您的解决方案将创建如下所示的张量

    # cls#1 cls#2
    [   [0, 0], # pixel #1
        [0, 1], # pixel #2
        [1, 1], # pixel #3
    ]
    

    其形状与地面实况相同,但与轴的物理含义不匹配。

    这进一步使softmax操作变得毫无意义,因为它应该适用于类维度,但这个维度在物理上并不存在。结果,应用softmax后导致如下错误输出,

    # cls#1 cls#2
    [   [0.5, 0.5], # pixel #1
        [0, 1], # pixel #2
        [0.5, 0.5], # pixel #3
    ]
    

    即使在理想假设下也会完全搞砸训练。


    因此,写下张量每个轴的物理意义是一个好习惯。当你做任何张量整形操作时,问问自己一个轴的物理意义是否以你预期的方式改变。

    例如,如果您有一个形状为 batch_dim x img_rows x img_cols x feat_dim 的张量 T,您可以做很多事情,但并非所有事情都有意义(由于轴的物理意义有问题)

    1. (错误)将其重塑为whatever x feat_dim,因为whatever 维度在测试batch_size 可能不同的情况下毫无意义。
    2. (错误)将其重塑为batch_dim x feat_dim x img_rows x img_cols,因为第 2 维不是特征维,也不是第 3 维和第 4 维。
    3. (正确)置换轴(3,1,2),这将引导您形状张量batch_dim x feat_dim x img_rows x img_cols,同时保持每个轴的物理意义。
    4. (正确)将其重塑为batch_dim x whatever x feat_dim。这也是有效的,因为whatever=img_rows x img_cols相当于像素位置维度,batch_dimfeat_dim的含义不变。

    【讨论】:

      【解决方案2】:

      您的代码仍然可以运行,因为形状相同,但结果(反向传播)会不同,因为张量的值会不同。例如:

      arr = np.array([[[1,1,1],[1,1,1]],[[2,2,2],[2,2,2]],[[3,3,3],[3,3,3]],[[4,4,4],[4,4,4]]])
      arr.shape
      >>>(4, 2, 3)
      
      #do reshape, then premute
      reshape_1 = arr.reshape((4, 2*3))
      np.swapaxes(reshape_1, 1, 0)
      >>>array([[1, 2, 3, 4],
                [1, 2, 3, 4],
                [1, 2, 3, 4],
                [1, 2, 3, 4],
                [1, 2, 3, 4],
                [1, 2, 3, 4]])
      
      #do reshape directly
      reshape_2 = arr.reshape(2*3, 4)
      reshape_2
      >>>array([[1, 1, 1, 1],
                [1, 1, 2, 2],
                [2, 2, 2, 2],
                [3, 3, 3, 3],
                [3, 3, 4, 4],
                [4, 4, 4, 4]])
      

      【讨论】:

        【解决方案3】:

        Reshape 和 Permute 用于在每个像素位置获取 softmax。添加到@meowongac 的答案中,Reshape 保留了元素的顺序。在这种情况下,由于必须交换通道尺寸,因此 Reshape 后跟 Permute 是合适的。

        考虑 (2,2) 图像在每个位置有 3 个值的情况,

        arr = np.array([[[1,1],[1,1]],[[2,2],[2,2]],[[3,3],[3,3]]]) 
        >>> arr.shape
        (3, 2, 2)
        >>> arr
        array([[[1, 1],
                [1, 1]],
        
               [[2, 2],
                [2, 2]],
        
               [[3, 3],
                [3, 3]]])
        
        >>> arr[:,0,0]
        array([1, 2, 3])
        

        每个位置的通道值为 [1,2,3]。目标是将通道轴(长度为 3)交换到末端。

        >>> arr.reshape((2,2,3))[0,0] 
        array([1, 1, 1])   # incorrect
        
        >>> arr.transpose((1,2,0))[0,0] # similar to what permute does.
        array([1, 2, 3])  # correct 
        

        更多示例见此链接:https://discuss.pytorch.org/t/how-to-change-shape-of-a-matrix-without-dispositioning-the-elements/30708

        【讨论】:

          猜你喜欢
          • 2019-01-31
          • 2021-08-17
          • 2020-11-29
          • 2020-05-03
          • 2018-07-25
          • 2020-04-08
          • 1970-01-01
          • 1970-01-01
          • 2018-05-18
          相关资源
          最近更新 更多