0

我正在用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不起作用?

4

3 回答 3

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]])
于 2019-07-23T03:10:47.030 回答
2

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

  • 最终目标:对每个像素进行分类,即沿着语义类轴的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 的输出 dim = n_class-by- n_pixs, ( n_pixs= img_rowsx 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 的输出暗淡与 L2 的匹配,因此 L4 是相当于执行 L1 和 L2 的捷径。

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

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

换句话说,ground truth 张量看起来像这样

# 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
]

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


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

例如,如果你有一个张量Tshape batch_dim x img_rows x img_cols x feat_dim,你可以做很多事情,但并不是所有事情都有意义(由于轴的物理意义有问题)

  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),这将引导您 shape 的张量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没有改变。
于 2019-07-29T00:48:01.550 回答
1

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-07-23T03:22:53.827 回答