UNET架构就像前半编码器和后半解码器。自动编码器有不同的变体,如稀疏、变分等。它们都压缩和解压缩数据,但 UNET 也同样用于压缩和解压缩。就我而言,我认为在简单的自动编码器中我们不使用 Transpose2D 卷积,但在 UNET 中我们使用这种上采样。在简单的自动编码器中,我们不使用 Transpose2D Conv 。上采样是如何发生的?如果我们在自动编码器中使用 Transpose2D,它与 UNET 有何不同?
来源,作者:Przemyslaw-Dolata
我认为 U-Net 和纯编码器-解码器网络之间存在重要区别。
在编码器-解码器网络中,恰好存在一个潜在空间(L),具有从输入(X)到该空间(E:X->L)的非线性映射,以及从该潜在空间到输出的相应映射空间(D:L->Y)。编码器和解码器之间有明显的区别:编码器将每个样本的表示更改为潜在空间中的某些“代码”,而解码器能够仅在给定此类代码的情况下构造输出。这意味着您可以拆开这样的网络并单独使用编码器和解码器,例如 Schlegl (2019) 所做的那样。
在 U-Nets 中,情况并非如此。在那里,输出映射也直接取决于输入空间 - 不是 L->Y,而是 [X+L]->Y(“跳过”连接)。这意味着不存在真正的“编码器”和“解码器”部分,即将样本映射到某个明确定义的潜在空间,然后计算其输出。你不能将 U-Net 分成多个部分并单独使用它们,因为为了计算输出,还需要输入 - 以及它的所有中间表示(因为 U-Net 中有多个潜在空间: X->L1->L2->...->Ln).
在 autoencoders 中,编码部分线性压缩输入,这会造成并非所有特征都可以传输的瓶颈。另一方面,U-Net在上采样侧进行反卷积,并克服了由于来自架构编码器侧的连接而导致的特征丢失的瓶颈问题(扩展路径与收缩路径对称)。通过这样做,U-Net 的上采样部分包含大量特征通道,这允许网络将上下文信息传播到更高分辨率的层。
瓶颈不是问题,也没有克服的余地……U-net 用于特定任务,例如分割,其中自动编码器用于其他一些任务,例如重建、生成、去噪等。
自动编码器负责逐渐减少然后增加尺寸并从图像中提取特征。 U-Net 专为精确图像分割而设计,其架构通过编码器层和解码器层之间的跳过连接来促进空间细节的保存。
在此源代码中(来自 Ribeiro 等人的源代码),您可以看到 AE 和 U-Net 之间的显着区别:
自动编码器class AutoEncoder(nn.Module):
def __init__(self, in_channels, out_channels, kernel_size=3, filters=[16, 32, 64],
weight_norm=True, batch_norm=True, activation=nn.ReLU, final_activation=None):
super().__init__()
assert len(filters) > 0
encoder = []
decoder = []
for i in range(len(filters)):
if i == 0:
encoder_layer = create_layer(in_channels, filters[i], kernel_size, weight_norm, batch_norm, activation, nn.Conv2d)
decoder_layer = create_layer(filters[i], out_channels, kernel_size, weight_norm, False, final_activation, nn.ConvTranspose2d)
else:
encoder_layer = create_layer(filters[i-1], filters[i], kernel_size, weight_norm, batch_norm, activation, nn.Conv2d)
decoder_layer = create_layer(filters[i], filters[i-1], kernel_size, weight_norm, batch_norm, activation, nn.ConvTranspose2d)
encoder = encoder + [encoder_layer]
decoder = [decoder_layer] + decoder
self.encoder = nn.Sequential(*encoder)
self.decoder = nn.Sequential(*decoder)
def forward(self, x):
return self.decoder(self.encoder(x))
注意前向仅将编码器的输出作为解码器的输入。优网
class UNet(nn.Module):
def __init__(self, in_channels, out_channels, kernel_size=3, filters=[16, 32, 64], layers=2,
weight_norm=True, batch_norm=True, activation=nn.ReLU, final_activation=None):
super().__init__()
assert len(filters) > 0
self.final_activation = final_activation
self.encoder = create_encoder(in_channels, filters, kernel_size, weight_norm, batch_norm, activation, layers)
self.decoder = create_decoder(out_channels, filters, kernel_size, weight_norm, batch_norm, activation, layers)
def encode(self, x):
tensors = []
indices = []
sizes = []
for encoder in self.encoder:
x = encoder(x)
sizes.append(x.size())
tensors.append(x)
x, ind = F.max_pool2d(x, 2, 2, return_indices=True)
indices.append(ind)
return x, tensors, indices, sizes
def decode(self, x, tensors, indices, sizes):
for decoder in self.decoder:
tensor = tensors.pop()
size = sizes.pop()
ind = indices.pop()
x = F.max_unpool2d(x, ind, 2, 2, output_size=size)
x = torch.cat([tensor, x], dim=1)
x = decoder(x)
return x
def forward(self, x):
x, tensors, indices, sizes = self.encode(x)
x = self.decode(x, tensors, indices, sizes)
if self.final_activation is not None:
x = self.final_activation(x)
return x
这里的区别在于代码行
x = torch.cat([tensor, x], dim=1)
x = decoder(x)
从编码器相应层获取的张量的输入值被评估为与 x 的变换(解码器)输出堆叠。