为什么使用 unet 进行重塑和置换以进行分割?
Why Reshape and Permute for segmentation with unet?
我正在用 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不行?
您的代码仍然可以运行,因为形状相同,但结果(反向传播)将不同,因为张量的值将不同。例如:
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]])
完成重塑和置换以在每个像素位置获取 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
此处有更多示例 link:https://discuss.pytorch.org/t/how-to-change-shape-of-a-matrix-without-dispositioning-the-elements/30708
你显然误解了每个操作的含义和最终目标:
- 最终目标:class每个像素的化,即沿语义 class 轴的 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
代表的轴,即语义 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 个语义 classes 和 3 个像素。要查看差异,假设所有三个像素都属于同一个 class。
换句话说,真值张量看起来像这样
# 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
]
其形状与ground truth相同,但与坐标轴的物理意义不符。
这进一步让softmax操作变得毫无意义,因为它应该应用于class维度,但这个维度在物理上并不存在。因此,在应用 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
,你可以做很多事情,但并非所有事情都有意义(由于轴的物理意义有问题)
- (错误)将其重塑为
whatever x feat_dim
,因为 whatever
维度在 batch_size 可能不同的测试中毫无意义。
- (错误)将其重塑为
batch_dim x feat_dim x img_rows x img_cols
,因为第 2 维不是特征维,第 3 维和第 4 维也不是。
- (正确)置换轴 (3,1,2),这将引导您得到形状为
batch_dim x feat_dim x img_rows x img_cols
的张量,同时保持每个轴的物理意义。
- (正确)将其重塑为
batch_dim x whatever x feat_dim
。这也是有效的,因为whatever=img_rows x img_cols
相当于像素位置维度,batch_dim
和feat_dim
的含义都没有改变。
我正在用 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不行?
您的代码仍然可以运行,因为形状相同,但结果(反向传播)将不同,因为张量的值将不同。例如:
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]])
完成重塑和置换以在每个像素位置获取 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
此处有更多示例 link:https://discuss.pytorch.org/t/how-to-change-shape-of-a-matrix-without-dispositioning-the-elements/30708
你显然误解了每个操作的含义和最终目标:
- 最终目标:class每个像素的化,即沿语义 class 轴的 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
ximg_cols
) - L2 的输出暗淡 =
n_pixs
-by-n_class
- L3 的输出暗淡 =
n_pixs
-by-n_class
- 请注意,默认的 softmax 激活应用于最后一个轴,即
n_class
代表的轴,即语义 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 个语义 classes 和 3 个像素。要查看差异,假设所有三个像素都属于同一个 class。
换句话说,真值张量看起来像这样
# 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
]
其形状与ground truth相同,但与坐标轴的物理意义不符。
这进一步让softmax操作变得毫无意义,因为它应该应用于class维度,但这个维度在物理上并不存在。因此,在应用 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
,你可以做很多事情,但并非所有事情都有意义(由于轴的物理意义有问题)
- (错误)将其重塑为
whatever x feat_dim
,因为whatever
维度在 batch_size 可能不同的测试中毫无意义。 - (错误)将其重塑为
batch_dim x feat_dim x img_rows x img_cols
,因为第 2 维不是特征维,第 3 维和第 4 维也不是。 - (正确)置换轴 (3,1,2),这将引导您得到形状为
batch_dim x feat_dim x img_rows x img_cols
的张量,同时保持每个轴的物理意义。 - (正确)将其重塑为
batch_dim x whatever x feat_dim
。这也是有效的,因为whatever=img_rows x img_cols
相当于像素位置维度,batch_dim
和feat_dim
的含义都没有改变。