面试腾讯,开心起飞。。。

文摘   科技   2024-11-25 17:15   北京  

我们在初学kaggle比赛时,可以找一些金牌or优秀解决方案参考学习。我已经整理好了,大家可以扫码领取(下滑更多干货内容)

金牌优秀解决方案
扫码领

Hello,我是kk~

上周有同学面试了腾讯,没想到的是,居然问到他最熟悉的部分,因为前前后后把自己的笔记刚刚复习过。

就是关于 U-Net 的一些理论基础,加上论文部分也是刚刚看完。

那么,今天咱们也和大家聊聊关于 U-Net 的一些理论基础~

基本内容

U-Net 是一种常用于图像语义分割的深度学习架构,由斯坦福大学的研究者 Olaf Ronneberger、Philipp Fischer 和 Thomas Brox 在2015年提出。它的设计灵感来自于医学图像分割领域,特别是用于生物医学图像中细胞识别和分割的任务。

U-Net 架构具有编码器-解码器结构,通过这种结构可以捕获不同尺度的特征,并在解码器部分将这些特征重新结合起来,从而实现像素级别的语义分割。

论文出处:

Ronneberger, O., Fischer, P., & Brox, T. (2015). U-Net: Convolutional Networks for Biomedical Image Segmentation. International Conference on Medical Image Computing and Computer-Assisted Intervention (MICCAI), 234-241.

论文地址:https://arxiv.org/abs/1505.04597https://github.com/milesial/Pytorch-UNet)

重要意义:

  1. 高效的图像分割架构:U-Net 架构在图像分割任务中取得了出色的效果,尤其在生物医学图像处理领域,如医学图像分割和细胞识别等方面表现突出。
  2. 模块化结构:其编码器-解码器结构模块化且易于理解,使得研究者可以很容易地修改和适应到其他领域的图像分割任务中。
  3. 开放性:U-Net 的论文和代码均是公开的,促进了该领域的进展和更多研究者的参与。

摘要

  • 深度网络训练需要大量标注样本。
  • U-Net通过数据增强有效利用有限的标注样本。
  • 网络包含收缩路径捕获上下文和扩展路径实现精确定位。
  • 在ISBI挑战赛中,U-Net在电子显微镜堆叠的神经结构分割任务上超越了先前的最佳方法。
  • 网络速度快,可以在GPU上快速处理512x512图像。

网络架构

U-Net的网络架构是该论文的核心贡献之一,它专门设计用于处理图像分割任务。以下是对U-Net网络架构的详细描述:

收缩路径(Contracting Path)

  • 目的:捕获图像的上下文信息,理解图像中的整体结构。
  • 结构:由多个连续的层组成,每层包括两个3x3的卷积操作(不填充),后接ReLU激活函数和2x2的最大池化操作(步长为2)。
  • 特征通道:在每个池化步骤中,特征通道的数量翻倍,以便捕获更抽象的信息。

扩展路径(Expanding Path)

  • 目的:实现精确的定位,将收缩路径中捕获的上下文信息映射回原始图像的分辨率。
  • 结构:由一系列上采样操作和卷积层组成。每个步骤包括:
    • 特征图的上采样(使用转置卷积)。
    • 2x2的卷积操作,减少特征通道的数量。
    • 与收缩路径中相应层的特征图进行拼接(concatenation),这些特征图通过裁剪以匹配上采样后的特征图尺寸。
    • 两个3x3的卷积操作,后接ReLU激活函数。
  • 最终层:最后一个卷积层使用1x1的卷积核,将特征向量映射到目标类别数。

特征图的表示

  • 蓝色框:表示多通道的特征图,框上的数字表示通道数,框左下角的x-y尺寸表示特征图的空间维度。
  • 白色框:表示复制的特征图,这些特征图在扩展路径中与上采样的特征图进行拼接。

对称性

  • 设计:扩展路径在结构上与收缩路径大致对称,形成U形的网络架构。

无全连接层

  • 特点:U-Net不包含全连接层,避免了将网络输出固定到特定尺寸的需要。

有效卷积

  • 策略:网络只使用每个卷积操作的有效部分,即只包含输入图像中完整上下文信息的像素。

无缝分割

  • 重叠瓦片策略:为了无缝分割任意大小的图像,使用重叠瓦片策略。在预测边界区域的像素时,通过镜像输入图像来外推缺失的上下文信息。

U-Net的这种设计允许网络在有限的训练数据下进行有效的学习,并且能够快速准确地处理图像分割任务。其对称的扩展路径和收缩路径的设计,以及对特征图的有效利用,使得U-Net在生物医学图像分割领域得到了广泛的应用。

训练

U-Net的训练过程是确保网络能够有效学习并泛化到新数据的关键步骤。

训练数据

  • 输入:训练过程中使用输入图像及其对应的分割图(即标注)。
  • 数据准备:由于生物医学图像分割任务中通常缺乏大量标注样本,因此训练数据的准备工作尤为重要。

优化方法

  • 随机梯度下降(SGD):使用基于Caffe框架实现的SGD来优化网络权重。
  • 动量:采用高动量值(0.99)以利用之前训练样本的信息,这有助于加速训练过程并提高收敛速度。

损失函数

  • 像素级损失:使用像素级的交叉熵损失函数来衡量网络预测和真实标注之间的差异。
  • 权重图:引入权重图()来给予训练中某些像素更多的重要性,特别是在处理相邻细胞的分割边界时。

权重初始化

  • 重要性:在深层网络中,权重的良好初始化对于防止部分网络层过度激活或不激活至关重要。
  • 策略:使用标准差为的高斯分布进行初始化,其中表示前一层中一个神经元的输入节点数。

数据增强

  • 目的:当训练样本有限时,数据增强有助于提高网络的泛化能力和鲁棒性。
  • 方法:主要采用随机弹性变形,通过在训练样本上应用随机位移向量来生成平滑变形,这些变形是从具有10像素标准差的高斯分布中采样的。

训练细节

  • 输入块:选择较大的输入块而不是大批量大小,以最大化GPU内存的使用。
  • 批处理:由于输入块较大,因此将批量大小减少到单个图像。

性能评估

  • 评估指标:使用不同的误差度量,如“warping error”、“Rand error”和“pixel error”,来评估网络在ISBI挑战赛中的表现。

实验结果

  • 表现:U-Net在多个数据集上的分割任务中取得了优异的表现,特别是在电子显微镜图像的神经结构分割任务和ISBI细胞跟踪挑战赛中的细胞分割任务上。

U-Net的训练过程强调了数据增强的重要性,以及如何通过细致的设计和优化策略来克服标注样本数量有限的挑战。这些训练策略和技术细节对于理解和复现U-Net的成功至关重要。

损失函数

损失函数是训练过程中评估模型性能的关键指标,它衡量了模型预测结果与真实标签之间的差异。在U-Net中,损失函数的设计特别关注于提高模型在边界区域的分割精度,并平衡类别间的频率差异。以下是对U-Net损失函数部分的详细细化:

交叉熵损失函数

U-Net使用交叉熵损失函数来衡量每个像素的预测概率分布与真实标签之间的差异。交叉熵损失函数的一般形式为:

其中:

  •  是损失函数的值。
  •  表示像素位置。
  •  是图像中的所有像素位置的集合。
  •  是类别的数量。
  •  是真实标签中第类别在位置的指示函数(即,如果像素属于类别,则,否则为0)。
  •  是模型预测位置属于类别的概率。

像素级损失

在U-Net中,损失函数被简化为像素级损失,即对每个像素独立计算损失,然后对所有像素求和:

其中:

  •  是权重函数,用于给予不同像素不同的重要性。
  •  是模型预测像素属于类别的概率。

权重图

为了提高模型在边界区域的分割精度,引入了权重图,它给予分割边界附近的像素更高的权重。权重图的计算方法如下:

其中:

  •  是用于平衡类别频率的权重。
  •  是像素到最近细胞边界的距离。
  •  是像素到第二近细胞边界的距离。
  •  和  是超参数,用于调整权重的分布。

通过这种设计,U-Net的损失函数不仅能够促进模型在整个图像上的准确分割,还能够特别关注于细胞或结构的边界区域,这对于生物医学图像分割任务尤为重要。权重图的引入使得模型能够更好地学习到细胞或结构的精确轮廓,即使在训练样本有限的情况下也能实现高精度的分割。

实验

  • U-Net在三个不同的分割任务上展示了应用,包括电子显微镜记录的神经结构分割和光镜图像中的细胞分割。
  • 在ISBI细胞跟踪挑战赛2015中,U-Net在两个最具挑战性的2D透射光数据集上取得了显著的胜利。

代码案例

下面是一个简单的PyTorch实现U-Net的代码示例~

import torch
import torch.nn as nn
import torch.nn.functional as F

class UNet(nn.Module):
    def __init__(self, in_channels, out_channels):
        super(UNet, self).__init__()
        
        # Contracting Path
        self.conv1 = self.conv_block(in_channels, 64)
        self.conv2 = self.conv_block(64128)
        self.conv3 = self.conv_block(128256)
        self.conv4 = self.conv_block(256512)
        self.conv5 = self.conv_block(5121024)
        
        # Expansive Path
        self.upconv4 = nn.ConvTranspose2d(1024512, kernel_size=2, stride=2)
        self.conv6 = self.conv_block(1024512)
        self.upconv3 = nn.ConvTranspose2d(512256, kernel_size=2, stride=2)
        self.conv7 = self.conv_block(512256)
        self.upconv2 = nn.ConvTranspose2d(256128, kernel_size=2, stride=2)
        self.conv8 = self.conv_block(256128)
        self.upconv1 = nn.ConvTranspose2d(12864, kernel_size=2, stride=2)
        self.conv9 = self.conv_block(12864)
        
        # Output layer
        self.conv10 = nn.Conv2d(64, out_channels, kernel_size=1)
        
    def conv_block(self, in_channels, out_channels):
        return nn.Sequential(
            nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1),
            nn.ReLU(inplace=True)
        )
        
    def forward(self, x):
        # Contracting Path
        conv1 = self.conv1(x)
        conv2 = self.conv2(F.max_pool2d(conv1, kernel_size=2, stride=2))
        conv3 = self.conv3(F.max_pool2d(conv2, kernel_size=2, stride=2))
        conv4 = self.conv4(F.max_pool2d(conv3, kernel_size=2, stride=2))
        conv5 = self.conv5(F.max_pool2d(conv4, kernel_size=2, stride=2))
        
        # Expansive Path
        upconv4 = self.upconv4(conv5)
        upconv4 = self.crop_and_concat(conv4, upconv4)
        conv6 = self.conv6(upconv4)
        
        upconv3 = self.upconv3(conv6)
        upconv3 = self.crop_and_concat(conv3, upconv3)
        conv7 = self.conv7(upconv3)
        
        upconv2 = self.upconv2(conv7)
        upconv2 = self.crop_and_concat(conv2, upconv2)
        conv8 = self.conv8(upconv2)
        
        upconv1 = self.upconv1(conv8)
        upconv1 = self.crop_and_concat(conv1, upconv1)
        conv9 = self.conv9(upconv1)
        
        # Output layer
        output = self.conv10(conv9)
        
        return output
    
    def crop_and_concat(self, x1, x2):
        crop = (x1.size()[2] - x2.size()[2]) // 2
        x1 = F.pad(x1, (-crop, -crop, -crop, -crop))
        return torch.cat([x1, x2], dim=1)

在这个PyTorch的U-Net实现中:

  • UNet类定义了U-Net模型的结构,包括收缩路径(Contracting Path)和扩张路径(Expansive Path)。
  • conv_block方法定义了U-Net中使用的卷积块,它由一对卷积层和ReLU激活函数组成。
  • forward方法定义了数据在模型中的前向传播过程,其中收缩路径和扩张路径的操作交替进行,中间使用了跳跃连接(skip connections)来保留更多低级特征。
  • crop_and_concat方法用于裁剪和连接两个特征图大小不同的张量。

最后

  • U-Net在多种生物医学图像分割应用中表现出色。
  • 通过弹性变形的数据增强,U-Net只需要很少的标注图像,并且训练时间合理。

这篇论文的核心贡献是提出了U-Net架构,它通过数据增强和特定的网络设计,使得在标注样本有限的情况下也能进行有效的图像分割。论文还提供了网络的实现和预训练模型,供研究者进一步使用和探索。

最后

好了,今天的内容先这样,继续想看解决什么问题,评论区留言~

都到这里了,记得点赞哦~


我们在初学kaggle比赛时,可以找一些金牌or优秀解决方案参考学习。我已经整理好了,大家可以扫码领取(下滑更多干货内容)

金牌优秀解决方案
扫码领

kk机器学习算法
机器学习基础、计算机视觉…
 最新文章