Unet介绍
Unet的提出的初衷是为了解决医学图像分割的问题,在2015年2015年的ISBI cell tracking比赛中获得了多个第一,现在大多数医疗影像语义分割任务都使用baseline,Unet可以说是最经典的图像分割的网络之一。
Unet结构介绍
总得来说Unet结构是,对进行原图片进行卷积+池化实现的下采样,下采样主要是通过池化实现,进行四个下采样后,再进下上采样(可以通过反向卷积实现,也可以通过双线性插值实现)。每次上采样后,与左半部分卷积下采样部分相同通道数的张量进行拼接(注意拼接的时候需要对一张图进行裁剪或者填充使两张图高宽相同,拼接后图的通道数翻倍),然后再进行上采样再拼接,进行几次这样的操作后,用1*1的卷积核卷积输出使得输出的通道等于分割类别的通道数。
(其中的细节就是其中除了输出卷积只卷积一次,都是进行双层卷积操作,与resnet的类似的地方就是,residual基本模块都是由两次的卷积构成,不是进行三次也不是一次,估计是经过实验这种设计网络的习惯比较好。)
对U-NET的理解
我们知道卷积是用多通道的卷积核,如3*3的卷积,如果要使得张量从[b,3,h,w]->[b,c,h,w],卷积核就是c个3*3卷积核,每个卷积核与原来三个通道图分别进行卷积后再进行累加。卷积后的c个通道,每个通道多有其对应的特征。
最大池化
一张图经过网络输出后得到32个通道,我们希望32个通道的每个通道的比较亮(像素值比较高的元素,是这个分类的这个通道对应类别物体),所以这就是每次卷积后进行最大值池化的原因,能让网络训练过程种只关注每个通道最大值(当然结果最好就是这个通道是车辆类别,那就只让车辆对应的像素为255,其它像素为0)。
U型网络结构设计理解
这样结构设计,似乎可以让网络的神经元连接变得多得更有多样性,似乎条条大路通罗马。另外个人认为unet与resnet思想有异曲同工之妙,比如当如图绿色箭头输出可以是全零时,也就是这条线断掉,它就是个进行四层卷积的网络,网络也就是当训练集比较少的时候,它不会因为数据集太少导致网络无法拟合数据集,当然数据集多的时候,为了拟合的更好,更深层次的参数也会得到更新,一层。从U-net设计初衷来看,就是为了让网络适用于简单的数据,与这样的理解也符合,本人试过比较少的数据集比如100张,图像二值分类问题效果也比较好,能达到95%以上的accuracy。
Unet代码(unet_model.py)
class DoubleConv(nn.Module): #双层卷积模块
def __init__(self,ch_in,ch_out):
super(DoubleConv,self).__init__()
self.double_conv= nn.Sequential(
nn.Conv2d(ch_in,ch_out,kernel_size=3,padding=1),
nn.BatchNorm2d(ch_out),
nn.ReLU(inplace=True) , #inplace=True 表示原张量内存进行操作
nn.Conv2d(ch_out,ch_out,kernel_size=3,padding=1),
nn.BatchNorm2d(ch_out),
nn.ReLU(inplace=True)
)
def forward(self,x):
return self.double_conv(x)
class Down(nn.Module): #双层卷积+最大值池化的下采样模块
def __init__(self,ch_in,ch_out):
super(Down,self).__init__() #继承父类
self.maxpool_conv =nn.Sequential(
nn.MaxPool2d(kernel_size=2),
DoubleConv(ch_in,ch_out)
)
def forward(self,x):
return self.maxpool_conv(x)
"""
转置卷积:定义*表示卷积
Y=C*X :表示卷积核C卷积X得到 Y
X=C'*Y :C‘就是转置卷积核 就是用C’进行反向卷积操作,以实现图像分辨率的恢复
"""
class Up(nn.Module): #转置卷积(逆卷积)上采样和双层卷积操作
def __init__(self,ch_in,ch_out):
super(Up,self).__init__()
self.up = nn.ConvTranspose2d(ch_in,ch_in//2,kernel_size=2,stride=2) #对x1进行运算通道数是x2的一半
self.conv =DoubleConv(ch_in,ch_out)
def forward(self,x1,x2):
x1= self.up(x1) #x1 [batch,ch,h,w]
diffY=torch.tensor([x2.size()[2]-x1.size()[2]]) #计算x1与x2的高度h差
diffX=torch.tensor(x2.size()[3]-x1.size()[3]) #计算x1与x2的宽度w差
# x2 =x2[:,:,diffY//2:x1.size()[2]+diffY//2,diffX//2:x1.size()[3]+diffX//2] #裁剪x2操作
x1 = F.pad(x1, [diffX // 2, diffX - diffX // 2, #填充x1操作
diffY // 2, diffY - diffY // 2])
x= torch.cat([x2,x1],dim=1) #将两个图片在通道维度上进行拼接 拼接后通道数翻倍
return self.conv(x) #最后进行双层卷积运算并返回
class OutConv(nn.Module): #最后输出使用1*1的卷积核进行卷积
def __init__(self, in_channels, out_channels):
super(OutConv, self).__init__()
self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=1)
def forward(self, x):
return self.conv(x)
class UNet(nn.Module):
def __init__(self,ch_in,n_classes):
super(UNet,self).__init__()
self.inc =DoubleConv(ch_in,64)
self.down1= Down(64,128)
self.down2= Down(128,256)
self.down3= Down(256,512)
self.down4= Down(512,1024)
self.up1=Up(1024,512)
self.up2=Up(512,256)
self.up3=Up(256,128)
self.up4=Up(128,64)
self.outc =OutConv(64,n_classes)
def forward(self,x):
x1=self.inc(x)
x2=self.down1(x1)
x3=self.down2(x2)
x4=self.down3(x3)
x5=self.down4(x4)
x =self.up1(x5,x4)
x = self.up2(x,x3)
x = self.up3(x,x2)
x = self.up4(x, x1)
x = self.outc(x)
return x
if __name__ == '__main__': #网络测试
temp = torch.randn([2,3,512,512])
net=UNet(3,1)
out= net(temp)
print("out shape",out.shape)
总结
下一章将介绍Unet基于Camvid数据集的实战效果