U-Net: Convolutional Networks for Biomedical Image Segmentation
U-Net架构包括一个捕获上下文信息的收缩路径和一个支持精确本地化的对称扩展路径。U-Net证明了这样一个网络使用非常小的图像可以进行端到端的训练,并在生物分割挑战赛中取得了比以前最好的方法。
收缩路径由2个3 * 3的卷积组成,每个卷积后面跟的是RELU激活函数和一个进行下采样的2 * 2较大池化运算。
扩张阶段包括一个特征通道的上采样。后面跟得上是2 * 2的转置卷积,它能够将特征通道数目减半,同时加大特征图。
最后一层是1 * 1卷积,用这种卷积来组成的特征向量映射到需要的类别数量上。
- a.U-Net建立在FCN的网络架构上,作者修改并扩大了这个网络框架,使其能够使用很少的训练图像就能得到很精确的分割结果。
- b.添加上采样阶段,并且添加了很多的特征通道,允许更多的原图像纹理的信息在高分辨率的layers中进行传播
- c.U-Net没有FC层,且全程使用valid来进行卷积,这样的话可以保证分割的结果都是基于没有缺失的上下文特征得到的,因此输入输出的图像尺寸不太一样(但在keras上代码做的都是same convolution),对于图像很大的输入,可以使用overlap-strategy来进行无缝的图像输出.
- d.为了预测输入图像的边缘部分,通过镜像输入图像来外推丢失的上下文,实则输入大图像也是可以的,但是这个侧率基于GPU内存不够的情况下所提出的。
- e.细胞分割的另外一个难点在于将相同类别且互相接触的细胞分开,因此提出weighted loss,也就是赋予相互接触的两个细胞之间的background标签更高的权重。
创新点关键点
- 1.U-Net简单地将编码器的特征图拼接至每个阶段解码器对的上采样特征图,从而形成一个梯形结构。
- 2.通过跳跃拼接连接的架构,在每个阶段都允许解码器学习在编码器中丢失的相关特征。
- 3.上采样采用转置卷积
论文架构
- Introduction
- Network Architecture
- Training
- 预先计算权重图来获得每一个像素在损失函数中的权值,补充了训练数据每类像素的不同频率。
- 可借鉴这里的softmax公式以及交叉熵公式
- Data Augmentation 数据增强操作 Dropout层,隐氏加强了数据增强
- 改进loss 提出一种带权重的损失
- Experiments
- Conclusion
公式部分
代码架构
encoder部分:
def vgg_encoder(n_classes,input_height,input_width):
assert input_height % 32 == 0
assert input_width % 32 == 0
img_input = Input(shape=(input_height, input_width, 3))
# Block1
x = Conv2D(64,(3,3),activation="relu",padding="same",name="block1_conv1")(img_input)
x = Conv2D(64,(3,3),activation="relu",padding="same",name="block1_conv2")(x)
x = MaxPooling2D((2,2),strides=(2,2),name="block1_pool")(x)
f1 = x
# Block2
x = Conv2D(128,(3,3),activation="relu",padding="same",name="block2_conv1")(x)
x = Conv2D(128,(3,3),activation="relu",padding="same",name="block2_conv2")(x)
x = MaxPooling2D((2,2),strides=(2,2),name="block2_pool")(x)
f2 = x
# Block3
x = Conv2D(256,(3,3),activation="relu",padding="same",name="block3_conv1")(x)
x = Conv2D(256,(3,3),activation="relu",padding="same",name="block3_conv2")(x)
x = Conv2D(256,(3,3),activation="relu",padding="same",name="block3_conv3")(x)
x = MaxPooling2D((2, 2), strides=(2, 2), name="block13_pool")(x)
f3 = x
# Block4
x = Conv2D(512,(3,3),activation="relu",padding="same",name="block4_conv1")(x)
x = Conv2D(512,(3,3),activation="relu",padding="same",name="block4_conv2")(x)
x = Conv2D(512,(3,3),activation="relu",padding="same",name="block4_conv3")(x)
x = MaxPooling2D((2,2),strides=(2,2),name="block4_pool")(x)
f4 = x
# Block5
x = Conv2D(512,(3,3),activation="relu",padding="same",name="block5_conv1")(x)
x = Conv2D(512,(3,3),activation="relu",padding="same",name="block5_conv2")(x)
x = Conv2D(512,(3,3),activation="relu",padding="same",name="block5_conv3")(x)
x = MaxPooling2D((2,2),strides=(2,2),name="block5_pool")(x)
f5 = x
return img_input, [f1, f2, f3, f4, f5]
decoder部分
def UNet_decoder(n_classes,levels):
[f1, f2, f3, f4, f5] = levels
# 解码层1
y = UpSampling2D((2,2))(f5)
y = concatenate([f4,y],axis=-1)
y = Conv2D(512,(3,3),padding="same")(y)
y = BatchNormalization()(y)
# 解码层2
y = UpSampling2D((2,2))(y)
y = concatenate([f3,y],axis=-1)
y = Conv2D(256,(3,3),padding="same")(y)
y = BatchNormalization()(y)
# 解码层3
y = UpSampling2D((2,2))(y)
y = concatenate([f2,y],axis=-1)
y = Conv2D(128,(3,3),padding="same")(y)
y = BatchNormalization()(y)
# 解码层4
y = UpSampling2D((2,2))(y)
y = concatenate([f1,y],axis=-1)
y = Conv2D(64,(3,3),padding="same")(y)
y = BatchNormalization()(y)
# 解码层5
y = UpSampling2D((2,2))(y)
y = Conv2D(64,(3,3),padding="same")(y)
y = BatchNormalization()(y)
# 最后的softmax层
y = Conv2D(n_classes,(1,1),padding="same")(y)
y = BatchNormalization()(y)
y = Activation("relu")(y)
y = Reshape((-1,n_classes))(y)
y = Activation("softmax")(y)
return y
UNet部分
def UNet(n_classes,input_height,input_width):
assert input_height % 32 == 0
assert input_width % 32 == 0
img_input,levels =vgg_encoder(n_classes,input_height,input_width)
output = UNet_decoder(n_classes, levels)
Vgg_UNet = Model(img_input, output)
return Vgg_UNet