nnUnet

github: https://github.com/MIC-DKFZ/nnUNet

论文地址: https://arxiv.org/abs/1809.10486

相较于常规的自然图像,以UNet为代表的编解码网络在医学图像分割中应用更为广泛。常见的各类医学成像方式,包括计算机断层扫描(Computed Tomography, CT)、核磁共振成像(Magnetic Resonance Imaging, MRI)、超声成像(Ultrasound Imaging)、X光成像(X-ray Imaging)和光学相干断层扫描(Optical Coherence Tomography, OCT)等。对于临床而言,影像学的检查是一项非常重要的诊断方式。在各类模态的影像检查中,精准地对各种器官和病灶进行分割是影像分析的关键步骤,目前深度学习图像分割在各类影像检测和分割中大放异彩。比如基于胸部CT的肺结节检测、基于颅内MR影像的脑胶质瘤分割、基于心脏CT的左心室分割、基于甲状腺超声的结节检测和基于X光的胸片肺部器官分割等。

虽然基于UNet的系列编解码分割网络在各类医学图像分割上取得了长足的进展,并且部分基于相关模型的应用设计已经广泛用于临床分析中。但医学影像本身的复杂性和差异性也极度影响着分割模型的泛化性和通用性,主要体现在以下几个方面:

(1)各类模态的医学影像之间差异大,如研究队列的大小、图像尺寸和维度、分辨率和体素(voxel)强度等。

(2)分割的语义标签的极度不平衡。相较于影像中的正常组织,病变区域一般都只占极少部分,这就造成了正常组织的体素标签与病灶组织的体素标签之间极度的类不平常。

(3)不同影像数据之间的专家标注差异大,并且一些图像的标注结果会存在模棱两可的情况。

(4)一些数据集在图像几何和形状等属性上差异明显,切片不对齐和各向异性的问题也非常严重。

提出一种鲁棒的基于2D UNet和3D UNet的自适应框架nnUMet。作者在各种任务上拿这个框架和目前的STOA方法进行了比较,且该方法不需要手动调参。最终nnUNet得到了最高的平均dice。

当前的医学图像分割被CNN的方法主导,但是在不同的任务上需要不同的结构和不同的调参策略才达到了各自任务的最佳,这些在某个任务上拿到第一的方法,在其他任务上却不行。

The Medical Segmentation Decathlon计划通过这种方式解决这个问题:希望参赛者设计一种算法,在10种数据集上进行测试,都能够达到很好的效果,而算法不能够针对某种数据集进行人为的调整,只能自动的去适应。

比赛分为两个阶段:(1)开发阶段参与者拿到7个数据集用于优化算法;(2)冻结代码后公开剩余的3个数据集,用于评估。

作者认为过多的人为调整网络结构,会导致对于特定数据集的过拟合。非网络结构方面的影响可能对于分割任务影响更大。

作者提出一种nnUNet(no-new-Net)框架,基于原始的UNet(很小的修改),不去采用哪些新的结构,如相残差连接、dense连接、注意力机制等花里胡哨的东西。相反的,把重心放在:预处理(resampling和normalization)、训练(loss,optimizer设置、数据增广)、推理(patch-based策略、test-time-augmentations集成和模型集成等)、后处理(如增强单连通域等)。

  • 网络结构

基础版UNet:2D UNet,3D UNet,UNet级联(第一级对下采样低分辨率图像进行粗分割,第二级结合第一级的结果进行微调,两级都用3DUNet)

微小修改:

(1)ReLU换 leaky ReLU(neg.slope 1e-2);

(2)Batch Norm换Instance Norm

图1 UNet Cascade. Stage1:下采样数据上进行粗分割;Stage2:分割结果和原图concat送入第二个网络进行refine

网络拓扑自适应:输入图像尺寸会有不同,而硬件的资源是有限的,因此需要在网络容量和Batch-size上做到权衡。

默认参数设置:

2D UNet:crop-size<=256×256(中值尺寸小于256时,采用中值尺寸); batch-size<=42; base-channel=30; pooling to size>=8; pooling_num<6
3D UNet: crop-size<=128x128x128(中值尺寸小于128时,采用中值尺寸); batch-size>=2; base_channel=30; pooling to size>=8; poolingnum<6

  • 预处理

整体数据Crop:只在非零区域内crop,减少计算消耗

Resample:数据集中存在不同spacing的数据,默认自动归一化到数据集所有数据spacing的中值spacing。原始数据使用三阶spline插值;Mask使用最邻近插值。

UNet Cascade采用特殊的Resample策略:中值尺寸大于显存限制下可处理尺寸的4倍时(batch-size=2),采用级联策略,对数据进行下采样(采样2的倍数,直到满足前面的要求);如果数据分辨率三个轴方向不相等,先降采样高分辨率轴使得三轴相等,再三轴同时降采样直到满足上述要求。

Normalization

CT:通过统计整个数据集中mask内像素的HU值范围,clip出[0.05,99.5]百分比范围的HU值范围,然后使用z-score方法进行归一化;

MR:对每个患者数据单独执行z-score归一化。

如果crop导致数据集的平均尺寸减小到1/4甚至更小,则只在mask内执行标准化,mask设置为0.

  • 训练过程

从头训练,使用五折交叉验证,loss函数:结合dice loss和交叉熵loss:

对于在全训练集上训练的3D-UNet(UNet Cascade的第一阶段和非级联的3D UNet,不包括UNet Cascade的第二阶段),对每一个样本单独计算dice loss,然后在batch上去平均。对其他的网络(2D UNet和UNet Cascade的第二阶段),将一个batch内的所有样本当做一个整体的样本计算整个batch上的dice(防止当crop后出现局部区域内不存在某一类时单独计算该类loss导致分母为零的情况,这也要保证batch-size不能太小)。

dice loss形式如下:

其中u为概率输出(softmax output),v为硬编码(one hot encoding)的ground truth。K为多分类类别数。

其他训练参数

Adam优化器,学习率3e-4;250个batch/epoch;
学习率调整策略:计算训练集和验证集的指数移动平均loss,如果训练集的指数移动平均loss在30个epoch内减少不够5e-3,则学习率衰减5倍;
训练停止条件:当验证集指数移动平均loss在60个epoch内减少不够5e-3,或者学习率小于1e-6,则停止训练。

数据增广:随机旋转、随机缩放、随机弹性变换、伽马校正、镜像。

注意:

1.如果3D UNet的输入patch的尺寸的最大边长是最短边长的两倍以上,那么应用三维数据扩充可能是次优的。这种情况下可以使用2D的数据增广。
2.UNet Cascade的stage 2接收前一阶段的输出作为输入的一部分,为了防止强co-adaptation,我们可以应用随机形态学操作(erode、dilate、open、close),随机的去除掉一些分割结果的连通域。

patch采样:为了增加网络的稳定性,patch采样的时候会保证一个batch的样本中有超过1/3的像素是前景类的像素。这个很关键,否则你的前景dice会收敛的很慢。

  • 推理(Inference)

所有的推理都是基于patch的。

patch的边界上精度会有损,因此在对patch重叠处的像素进行fuse时,边界的像素权重低,中心的像素权重高;patch重叠的stride为size/2;使用test-data-augmentation(增广方式:绕各个轴的镜像增广);使用了5个训练的模型集成进行推理(5个模型是通过5折交叉验证产生的5个模型)

  • 后处理

主要就是使用连通域分析

Swin-Unet:Unet形状的纯Transformer的医学图像分割

首个基于纯Transformer的U-Net形的医学图像分割网络,其中利用Swin Transformer构建编码器、bottleneck和解码器,表现SOTA!性能优于TransUnet、Att-UNet等

单位:慕尼黑工业大学, 复旦大学, 华为(田奇等人)
代码:https://github.com/HuCaoFighting/Swin-Unet
论文下载链接:https://arxiv.org/abs/2105.0553

在过去的几年中,卷积神经网络(CNN)在医学图像分析中取得了里程碑式的进展。尤其是,基于U形架构和跳跃连接的深度神经网络已广泛应用于各种医学图像任务中。但是,尽管CNN取得了出色的性能,但是由于卷积操作的局限性,它无法很好地学习全局和远程语义信息交互。

在本文中,我们提出了Swin-Unet,它是用于医学图像分割的类似Unet的纯Transformer。标记化的图像块通过跳跃连接被馈送到基于Transformer的U形En-Decoder架构中,以进行局部全局语义特征学习。

具体来说,我们使用带有偏移窗口的分层Swin Transformer作为编码器来提取上下文特征。

实验结果

在对输入和输出进行4倍的直接下采样和上采样的情况下,对多器官和心脏分割任务进行的实验表明,基于纯Transformer的U形编码器/解码器网络优于那些全卷积或者Transformer和卷积的组合。

个人感觉,这个transformer架构下的参数量应该会大很多吧(近百M)?目前作者好像并未在论文中给出对比。另外就是这个 感觉没啥创新点..感觉没啥有点水。

UNet网络

论文: https://arxiv.org/abs/1505.04597

FCN虽然做出了开创性的工作,FCN-8s相较于此前的SOTA分割表现,已经取得了巨大的优势。但从分割效果上看还很粗糙,对图像的细节处理还很不成熟,也没有考虑到像素与像素之间的上下文(context)关系,所以FCN更像是一项抛砖引玉式的工作,随着U形的编解码结构成为通用的语义分割网络设计范式,各种网络如雨后春笋般涌现。UNet是U形网络结构最经典和最主要的代表网络,因其网络结构是一个U形而得名,这类编解码的结构也因而被称之为U形结构。提出UNet的论文为U-Net: Convolutional Networks for Biomedical Image Segmentation,与FCN提出时间相差了两个月,其结构设计在FCN基础上做了进一步的改进,设计初衷主要是用于医学图像的分割。截至到本书写稿,UNet在谷歌学术上的引用次数已达44772次,堪称深度学习语义分割领域的里程碑式的工作。

1、与FCN区别

U-Net和FCN非常的相似,U-Net比FCN稍晚提出来,但都发表在2015年,和FCN相比,U-Net的第一个特点是完全对称,也就是左边和右边是很类似的,而FCN的decoder相对简单,只用了一个deconvolution的操作,之后并没有跟上卷积结构。第二个区别就是skip connection,FCN用的是加操作(summation),U-Net用的是叠操作(concatenation)。这些都是细节,重点是它们的结构用了一个比较经典的思路,也就是编码和解码(encoder-decoder),早在2006年就被Hinton大神提出来发表在了nature上.

当时这个结构提出的主要作用并不是分割,而是压缩图像和去噪声。输入是一幅图,经过下采样的编码,得到一串比原先图像更小的特征,相当于压缩,然后再经过一个解码,理想状况就是能还原到原来的图像。这样的话我们存一幅图的时候就只需要存一个特征和一个解码器即可。这个想法我个人认为是很漂亮了。同理,这个思路也可以用在原图像去噪,做法就是在训练的阶段在原图人为的加上噪声,然后放到这个编码解码器中,目标是可以还原得到原图。

后来把这个思路被用在了图像分割的问题上,也就是现在我们看到的U-Net结构,在它被提出的三年中,有很多很多的论文去讲如何改进U-Net或者FCN,不过这个分割网络的本质的拓扑结构是没有改动的。举例来说,去年ICCV上凯明大神提出的Mask RCNN. 相当于一个检测,分类,分割的集大成者,我们仔细去看它的分割部分,其实使用的也就是这个简单的FCN结构。说明了这种“U形”的编码解码结构确实非常的简洁,并且最关键的一点是好用。

2、为什么有效

相比于FCN和Deeplab等,UNet共进行了4次上采样,并在同一个stage使用了skip connection,而不是直接在高级语义特征上进行监督和loss反传,这样就保证了最后恢复出来的特征图融合了更多的low-level的feature,也使得不同scale的feature得到了的融合,从而可以进行多尺度预测和DeepSupervision。4次上采样也使得分割图恢复边缘等信息更加精细。

其次我们聊聊【医疗影像】,医疗影像有什么样的特点呢(尤其是相对于自然影像而言)?

1.图像语义较为简单、结构较为固定。我们做脑的,就用脑CT和脑MRI,做胸片的只用胸片CT,做眼底的只用眼底OCT,都是一个固定的器官的成像,而不是全身的。由于器官本身结构固定和语义信息没有特别丰富,所以高级语义信息和低级特征都显得很重要(UNet的skip connection和U型结构就派上了用场)。

2.数据量少。医学影像的数据获取相对难一些,很多比赛只提供不到100例数据。所以我们设计的模型不宜多大,参数过多,很容易导致过拟合。

原始UNet的参数量在28M左右(上采样带转置卷积的UNet参数量在31M左右),而如果把channel数成倍缩小,模型可以更小。缩小两倍后,UNet参数量在7.75M。缩小四倍,可以把模型参数量缩小至2M以内,非常轻量。个人尝试过使用Deeplab v3+和DRN等自然图像语义分割的SOTA网络在自己的项目上,发现效果和UNet差不多,但是参数量会大很多。

为什么适用于医学图像?

(1)因为医学图像边界模糊、梯度复杂,需要较多的高分辨率信息。高分辨率用于精准分割。

(2)人体内部结构相对固定,分割目标在人体图像中的分布很具有规律,语义简单明确,低分辨率信息能够提供这一信息,用于目标物体的识别。

UNet结合了低分辨率信息(提供物体类别识别依据)和高分辨率信息(提供精准分割定位依据),完美适用于医学图像分割。

网络结构

在医学图像领域,具体到更加细分的医学图像识别任务时,大量的带有高质量标注的图像数据十分难得,在此之前的通常做法是采用滑动窗口卷积(类似于图像分块)的方式来进行图像局部预测,这么做的好处是可以做图像像素做到一定程度定位,其次就是滑窗分块能够使得训练样本量增多。但缺点也很明显,一个是滑窗操作非常耗时,推理的时候效率低下,其次就是不能兼顾定位精度和像素上下文信息的利用率。UNet在FCN的基础上,完整地给出了U形的编解码结构,如下图所示

输入是一幅图,输出是目标的分割结果。继续简化就是,一幅图,编码,或者说降采样,然后解码,也就是升采样,然后输出一个分割结果。根据结果和真实分割的差异,反向传播来训练这个分割网络。我们可以说,U-Net里面最精彩的部分就是这三部分:

  • 下采样
  • 上采样
  • skip connection

UNet结构包括编码器下采样、解码器上采样和同层跳跃连接三个组成部分。编码器由4组卷积、ReLU激活和最大池化构成,每一组均有两次3*3的卷积,每个卷积层后面都有一次ReLU激活函数,然后再进行一次步长为2的2*2最大池化进行下采样,如第一组操作输入图像大小为572*572,两轮3*3的卷积之后的特征图大小为568*568,再经过22最大池化后的输出尺寸为284*284。解码器由4组2*2转置卷积、3*3卷积构成和一个ReLU激活函数构成,在最后的输出层又补充了一个1*1卷积。最后是同层跳跃连接,这也是UNet的特色操作之一,指的是将下采样时每一层的输出裁剪后连接到同层的上采样层做融合。每一次下采样都会有一个跳跃连接与对应的上采样进行融合,这种不同尺度的特征融合对上采样恢复像素大有帮助,具体来说就是高层(浅层)下采样倍数小,特征图具备更加细致的图特征,低层(深层)下采样倍数大,信息经过大量浓缩,空间损失大,但有助于目标区域(分类)判断,当高层和低层的特征进行融合时,分割效果往往会非常好。从某种程度上讲,这种跳跃连接也可以视为一种深度监督。

我们将UNet结构按照编码器、解码器和同层跳跃连接进行简化,如下图所示。编码器下采样用于特征提取和语义信息浓缩,解码器上采样用于图像像素恢复,跳跃连接则用于信息补充。自此,基于U形结构的编解码设计成为深度学习语义分割中的奠基性的网络结构,经过近几年的发展,语义分割虽然取得了长足的进步,但UNet和编解码结构一直是新的模型设计的参照对象。

代码实现:

# 导入PyTorch相关模块
import torch
import torch.nn as nn
import torch.nn.functional as F

### 编码块
class UNetEnc(nn.Module):
    def __init__(self, in_channels, out_channels, dropout=False):
        super().__init__()
    # 每一个编码块中的结构
        layers = [
            nn.Conv2d(in_channels, out_channels, 3, dilation=2),
            nn.ReLU(inplace=True),
            nn.Conv2d(out_channels, out_channels, 3, dilation=2),
            nn.ReLU(inplace=True),
        ]
        if dropout:
            layers += [nn.Dropout(.5)]
        layers += [nn.MaxPool2d(2, stride=2, ceil_mode=True)]
        self.down = nn.Sequential(*layers)
  # 编码块前向计算流程
    def forward(self, x):
        return self.down(x)

### 解码块    
class UNetDec(nn.Module):
    def __init__(self, in_channels, features, out_channels):
        super().__init__()
    # 每一个解码块中的结构
        self.up = nn.Sequential(
            nn.Conv2d(in_channels, features, 3),
            nn.ReLU(inplace=True),
            nn.Conv2d(features, features, 3),
            nn.ReLU(inplace=True),
            nn.ConvTranspose2d(features, out_channels, 2, stride=2),
            nn.ReLU(inplace=True),
        )
  # 解码块前向计算流程
    def forward(self, x):
        return self.up(x)

### 基于编解码的U-Net
class UNet(nn.Module):
    def __init__(self, num_classes):
        super().__init__()
    # 四个编码块
        self.enc1 = UNetEnc(3, 64)
        self.enc2 = UNetEnc(64, 128)
        self.enc3 = UNetEnc(128, 256)
        self.enc4 = UNetEnc(256, 512, dropout=True)
    # 中间部分(U形底部)
        self.center = nn.Sequential(
            nn.Conv2d(512, 1024, 3),
            nn.ReLU(inplace=True),
            nn.Conv2d(1024, 1024, 3),
            nn.ReLU(inplace=True),
            nn.Dropout(),
            nn.ConvTranspose2d(1024, 512, 2, stride=2),
            nn.ReLU(inplace=True),
        )
    # 四个解码块
        self.dec4 = UNetDec(1024, 512, 256)
        self.dec3 = UNetDec(512, 256, 128)
        self.dec2 = UNetDec(256, 128, 64)
        self.dec1 = nn.Sequential(
            nn.Conv2d(128, 64, 3),
            nn.ReLU(inplace=True),
            nn.Conv2d(64, 64, 3),
            nn.ReLU(inplace=True),
        )
        self.final = nn.Conv2d(64, num_classes, 1)

    # 前向传播过程
    def forward(self, x):
        enc1 = self.enc1(x)
        enc2 = self.enc2(enc1)
        enc3 = self.enc3(enc2)
        enc4 = self.enc4(enc3)
        center = self.center(enc4)
        # 包含了同层分辨率级联的解码块
        dec4 = self.dec4(torch.cat([
            center, F.upsample_bilinear(enc4, center.size()[2:])], 1))
        dec3 = self.dec3(torch.cat([
            dec4, F.upsample_bilinear(enc3, dec4.size()[2:])], 1))
        dec2 = self.dec2(torch.cat([
            dec3, F.upsample_bilinear(enc2, dec3.size()[2:])], 1))
        dec1 = self.dec1(torch.cat([
            dec2, F.upsample_bilinear(enc1, dec2.size()[2:])], 1))
        return F.upsample_bilinear(self.final(dec1), x.size()[2:])

Unet论文合集(待更新)–医学图像

自2015年以来,UNET在医学图像细分中取得了重大突破,开放了深度学习时代。后来的研究人员在UNET的基础上做出了很多改进,以提高语义细分的性能。

摘自:https://github.com/ShawnBIT/UNet-family

如何查找代码:在 https://paperswithcode.com/ 查找论文即可

UNet-family

2015

  • U-Net: Convolutional Networks for Biomedical Image Segmentation (MICCAI) [paper] [my-pytorch][keras]

2016

  • V-Net: Fully Convolutional Neural Networks for Volumetric Medical Image Segmentation [paper] [caffe][pytorch]
  • 3D U-Net: Learning Dense Volumetric Segmentation from Sparse Annotation [paper][pytorch]

2017

  • H-DenseUNet: Hybrid Densely Connected UNet for Liver and Tumor Segmentation from CT Volumes (IEEE Transactions on Medical Imaging)[paper][keras]
  • GP-Unet: Lesion Detection from Weak Labels with a 3D Regression Network (MICCAI) [paper]

2018

  • UNet++: A Nested U-Net Architecture for Medical Image Segmentation (MICCAI) [paper][my-pytorch][keras]
  • MDU-Net: Multi-scale Densely Connected U-Net for biomedical image segmentation [paper]
  • DUNet: A deformable network for retinal vessel segmentation [paper]
  • RA-UNet: A hybrid deep attention-aware network to extract liver and tumor in CT scans [paper]
  • Dense Multi-path U-Net for Ischemic Stroke Lesion Segmentation in Multiple Image Modalities [paper]
  • Stacked Dense U-Nets with Dual Transformers for Robust Face Alignment [paper]
  • Prostate Segmentation using 2D Bridged U-net [paper]
  • nnU-Net: Self-adapting Framework for U-Net-Based Medical Image Segmentation [paper][pytorch]
  • SUNet: a deep learning architecture for acute stroke lesion segmentation and outcome prediction in multimodal MRI [paper]
  • IVD-Net: Intervertebral disc localization and segmentation in MRI with a multi-modal UNet [paper]
  • LADDERNET: Multi-Path Networks Based on U-Net for Medical Image Segmentation [paper][pytorch]
  • Glioma Segmentation with Cascaded Unet [paper]
  • Attention U-Net: Learning Where to Look for the Pancreas [paper]
  • Recurrent Residual Convolutional Neural Network based on U-Net (R2U-Net) for Medical Image Segmentation [paper]
  • Concurrent Spatial and Channel ‘Squeeze & Excitation’ in Fully Convolutional Networks [paper]
  • A Probabilistic U-Net for Segmentation of Ambiguous Images (NIPS) [paper] [tensorflow]
  • AnatomyNet: Deep Learning for Fast and Fully Automated Whole-volume Segmentation of Head and Neck Anatomy [paper]
  • 3D RoI-aware U-Net for Accurate and Efficient Colorectal Cancer Segmentation [paper][pytorch]
  • Detection and Delineation of Acute Cerebral Infarct on DWI Using Weakly Supervised Machine Learning (Y-Net) (MICCAI) [paper](Page 82)
  • Fully Dense UNet for 2D Sparse Photoacoustic Tomography Artifact Removal [paper]

2019

  • MultiResUNet : Rethinking the U-Net Architecture for Multimodal Biomedical Image Segmentation [paper][keras]
  • U-NetPlus: A Modified Encoder-Decoder U-Net Architecture for Semantic and Instance Segmentation of Surgical Instrument [paper]
  • Probability Map Guided Bi-directional Recurrent UNet for Pancreas Segmentation [paper]
  • CE-Net: Context Encoder Network for 2D Medical Image Segmentation [paper][pytorch]
  • Graph U-Net [paper]
  • A Novel Focal Tversky Loss Function with Improved Attention U-Net for Lesion Segmentation (ISBI) [paper]
  • ST-UNet: A Spatio-Temporal U-Network for Graph-structured Time Series Modeling [paper]
  • Connection Sensitive Attention U-NET for Accurate Retinal Vessel Segmentation [paper]
  • CIA-Net: Robust Nuclei Instance Segmentation with Contour-aware Information Aggregation [paper]
  • W-Net: Reinforced U-Net for Density Map Estimation [paper]
  • Automated Segmentation of Pulmonary Lobes using Coordination-guided Deep Neural Networks (ISBI oral) [paper]
  • U2-Net: A Bayesian U-Net Model with Epistemic Uncertainty Feedback for Photoreceptor Layer Segmentation in Pathological OCT Scans [paper]
  • ScleraSegNet: an Improved U-Net Model with Attention for Accurate Sclera Segmentation (ICB Honorable Mention Paper Award) [paper]
  • AHCNet: An Application of Attention Mechanism and Hybrid Connection for Liver Tumor Segmentation in CT Volumes [paper]
  • A Hierarchical Probabilistic U-Net for Modeling Multi-Scale Ambiguities [paper]
  • Recurrent U-Net for Resource-Constrained Segmentation [paper]
  • MFP-Unet: A Novel Deep Learning Based Approach for Left Ventricle Segmentation in Echocardiography [paper]
  • A Partially Reversible U-Net for Memory-Efficient Volumetric Image Segmentation (MICCAI 2019) [paper][pytorch]
  • ResUNet-a: a deep learning framework for semantic segmentation of remotely sensed data [paper]
  • A multi-task U-net for segmentation with lazy labels [paper]
  • RAUNet: Residual Attention U-Net for Semantic Segmentation of Cataract Surgical Instruments [paper]
  • 3D U2-Net: A 3D Universal U-Net for Multi-Domain Medical Image Segmentation (MICCAI 2019) [paper] [pytorch]
  • SegNAS3D: Network Architecture Search with Derivative-Free Global Optimization for 3D Image Segmentation (MICCAI 2019) [paper]
  • 3D Dilated Multi-Fiber Network for Real-time Brain Tumor Segmentation in MRI [paper][pytorch] (MICCAI 2019)
  • The Domain Shift Problem of Medical Image Segmentation and Vendor-Adaptation by Unet-GAN [paper]
  • Recurrent U-Net for Resource-Constrained Segmentation [paper] (ICCV 2019)
  • Siamese U-Net with Healthy Template for Accurate Segmentation of Intracranial Hemorrhage (MICCAI 2019)

2020

  • U^2-Net: Going Deeper with Nested U-Structure for Salient Object Detection (Pattern Recognition 2020) [paper][pytorch]
  • UNET 3+: A Full-Scale Connected UNet for Medical Image Segmentation (ICASSP 2020) [paper][pytorch]

2021

  • TransUNet: Transformers Make Strong Encoders for Medical Image Segmentation [paper][pytorch]
  • Swin-Unet: Unet-like Pure Transformer for Medical Image Segmentation [paper][pytorch]
  • UCTransNet: Rethinking the Skip Connections in U-Net from a Channel-wise Perspective with Transformer [paper][pytorch]

FCN全卷积网络–图像分割的开山之作

论文地址: https://arxiv.org/abs/1411.4038

随着CNN在图像识别中取得巨大成功,一些经典的图像分类网络(AlexNet、VGG、GoogLeNet、ResNet)也逐渐被应用于更加细分的视觉任务中。很多研究者也在探索如何将分类网络进行改造后用于语义分割的密集预测问题(dense predictions)。在更高效的语义分割网络提出之前,学术界用于密集预测任务的模型主要有以下几个特点:

(1)小模型。早期的网络结构受限于数据量和高性能的计算资源,在设计上一般不会使用过大的模型。

(2)分块训练。分块训练(patchwise training)在当时是图像训练的普遍做法,但该方法对于全卷积网络的训练会显得相对低效,但分块训练的优点在于能够规避类别不均衡问题,并且能够缓解密集分块的空间相关性问题。

(3)输入移位与输出交错。该方法可以视为一种输入与输出的变换方法,在OverFeat等结构中被广泛使用。

(4)后处理。对于神经网络输出质量不高的问题,对输出加后处理也是常见做法,常用的后处理方法包括超像素投影(superpixel projection)、随机场正则化(random field regularization)和图像滤波处理等。

可以看到,早期用于目标检测、关键点预测和语义分割等密集预测问题整体来看有两个缺陷,一是无法实现端到端(end-to-end)的流程,模型整体效率不佳;第二个则是不能做到真正的密集预测的特征:像素到像素(pixels-to-pixels)的预测。

全卷积网络(Fully Convolutional Networks, FCN)的提出,正好可以解决早期网络结构普遍存在的上述两个缺陷。FCN在2015年的一篇论文Fully Convolutional Networks for Semantic Segmentation中提出,其主要思路在于用卷积层代替此前分类网络中的全连接层,将全连接层的语义标签输出改为卷积层的语义热图(heatmap)输出,再结合上采样技术实现像素到像素的密集预测。如下图所示,上图为常见分类网络的流程,在五层卷积网络之后有三层全连接网络,最后输出一个包含类别语义信息的输出概率;下图为FCN网络流程,在上图分类网络的基础上,将最后三层全连接层改为卷积层,输出也相应的变为分类预测的热图,这样就为了最后的像素级的密集预测提供了基础。

所以,FCN实现密集预测的关键在于修改全连接层为卷积层,那么具体是如何修改的呢?先来详细分析一下的卷积层和全连接层的特征。卷积层与全连接层最大的区别在于卷积层每次计算时只与输入图像中一个具体的局部做运算,但二者都是做点积计算,其函数形式是类似的。假设给定在指定网络层任意坐标点(i,j)的数据向量Xij,而下一层对应坐标点的数据向量为Yij,有:

其中为卷积核大小或者权重向量长度,s为步长(stride),而f_ks则表示当前层到下一层的映射函数,f_ks既可以表示为卷积层又可以表示为全连接层,所以二者之间的转换是有理论基础的。

FCN分别在AlexNet、VGG和GoogLeNet上进行了全连接层转卷积层的修改,通过实验发现以VGG16作为主干网络效果最好,完整的FCN结构如下图所示,第一行最左边为原始输入图像,图像尺寸为32×32,conv为卷积层,pool为池化层,可以注意到conv6-7是最后的卷积层,此时得到的密集预测热图尺寸为输入图像的1/32,为了实现像素到像素的预测,还需要对热图进行上采样,FCN采用双线性插值(bilinear interpolation)进行上采样,所以这里需要将热图上采样32倍来恢复到原始图像的尺寸,因而第一行的网络结构也叫FCN-32s。直接进行32倍上采样得到的输出无疑是较为粗糙的,为了提高像素预测质量,FCN又分别有FCN-16s和FCN-8s的改进版本。图中第二行即为FCN-16s,主要区别在于先将conv7(1×1)的输出热图进行2倍上采样,然后将其与pool4(2×2)进行融合,最后对融合后的结果进行16倍上采样得到最终预测结果,同理FCN-8s将pool3(4×4)、2倍上采样后的pool4(4×4)以及4倍上采样的conv7(4×4)进行融合,最后再进行8倍的上采样得到语义分割图像。

所以,从FCN-32s到FCN-8s,其实一种粗分割到精细分割的演变过程,FCN通过融合浅层图像特征和深层卷积热图的方式来得到当时的SOTA(State of the art)水平的语义分割模型。下图是FCN-32s、FCN-16s和FCN-8s在同一张图像上的分割效果,与分割的标准图像(Ground truth)相比,可以看到三个模型的分割精度是在不断优化的。

下方代码给出FCN-8s的一个PyTorch简略实现方式,便于读者加深对FCN的理解。代码中对于卷积下采样使用了VGG16的预训练权重,分别构建了四个特征提取模块、一个卷积块和三个独立的卷积层。在前向传播流程中,将conv7、pool3和pool4进行融合,最后再做8倍的双线性插值上采样。

# 导入PyTorch相关模块
import torch
import torch.nn as nn
import torch.nn.functional as F
from torchvision import models

### 定义FCN-8s模型类
class FCN8(nn.Module):
    def __init__(self, num_classes):
        super().__init__()
        # 提取VGG16预训练权重作为特征
        feats =list(models.vgg16(pretrained=True).features.children())
        # 取前9层为第一特征模块
        self.feat1 = nn.Sequential(*feats[0:9])
        # 取第10-15层为第二特征模块
        self.feat2 = nn.Sequential(*feats[10:16])
        # 取第16-22层为第三特征模块
        self.feat3 = nn.Sequential(*feats[17:23])
        # 取后6层为第四特征模块
        self.feat4 = nn.Sequential(*feats[24:30])
        # 卷积层权重不参与训练更新
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                m.requires_grad = False
        # 定义卷积块
        self.conv_blocks = nn.Sequential(
            nn.Conv2d(512, 4096, 7),
            nn.ReLU(inplace=True),
            nn.Dropout(),
            nn.Conv2d(4096, 4096, 1),
            nn.ReLU(inplace=True),
            nn.Dropout(),
        )
        # 改最后三层的全连接层为卷积层
        self.conv1 = nn.Conv2d(256, num_classes, 1)
        self.conv2 = nn.Conv2d(512, num_classes, 1)
        self.conv3 = nn.Conv2d(4096, num_classes, 1)

    ### 定义前向计算流程
    def forward(self, x):
        feat1 = self.feat1(x)
        feat2 = self.feat2(feat1)
        feat3 = self.feat3(feat2)
        feat4 = self.feat4(feat3)
        conv_blocks = self.conv_blocks(feat4)

        conv1 = self.conv1(feat2)
        conv2 = self.conv2(feat3)
        conv3 = self.conv3(conv_blocks)      
        outputs = F.upsample_bilinear(conv_blocks, conv2.size()[2:])
        # 第一次融合
        outputs += conv2
        outputs = F.upsample_bilinear(outputs, conv1.size()[2:])
        # 第二次融合
        outputs += conv1
        return F.upsample_bilinear(outputs, x.size()[2:]) 

FCN是深度学习语义分割网络的开山之作,在结构设计上率先将全卷积网络用于深度学习语义分割任务,在经典分类网络的基础上实现了像素到像素和端到端的分割。FCN整体上已具备编解码架构的U形网络雏形,为后续的网络设计开创了坚实的基础。

SUNet: Swin Transformer with UNet for Image Denoising

ISCAS 2022的一篇文章,作为首个Swin Transformer在图像去噪领域的应用,效果来说感觉还有很大提高空间。但不的不说,自从Swin Transformer(2021)提出后,在整个cv领域独领风骚。作为一个通用的架构,可以将其应用在各个cv领域,从paperwithcode里就可以见其影响力:(截止到22.8.28)

1、目标检测:

2、图像超分辨率

3、实例分割:

4、3D医学图像分割:

今天,就来看看Swin Transformer 对于图像去噪任务的处理效果:

个人觉得 Swin Transformer 对于去噪来说还有很大的扩展空间,这篇论文的模型效果不是很好,可以值得去尝试尝试,看看有没有更好的方法提高模型效果。

论文的主要贡献:

1、结合Unet网络+ Swin Transformer

2、提出了一个双上采样模块 dual up-sample block

3、首个将Swin +unet用于图像去噪领域

4、在 两个通用数据集中测试的结果还不错

网络结构:

网络分为三个部分:1)Shallow feature extraction; 2) UNet feature extraction; and
3) Reconstruction module

1、Shallow feature extraction

使用3*3卷积,提取特征,输出通道96

2、 UNet feature extraction

带有 Swin Transformer Block 的UNET体系结构,其中包含8个 Swin Transformer 层,以取代卷积。
Swin Transformer Block(STB)和Swin Transformer层(STL):

STB:包含8个STL

这块建议去看 Swin Transformer 论文,讲的比较清楚。注意此时的输入输出大小完全一致,因此需要下采样。

下采样: Patch merging

Patch merging:通过查看Patch merging的源码,可以看到,其实就是一个下采样的过程,它可以看成一种加权池化的过程。实现维度下采样、特征加倍的效果。

class PatchMerging(nn.Module):
    def __init__(self, input_resolution, dim, norm_layer=nn.LayerNorm):
        super().__init__()
        self.input_resolution = input_resolution
        self.dim = dim
        self.reduction = nn.Linear(4 * dim, 2 * dim, bias=False)
        self.norm = norm_layer(4 * dim)

    def forward(self, x):
        """
        x: B, H*W, C
        """
        H, W = self.input_resolution
        B, L, C = x.shape
        assert L == H * W, "input feature has wrong size"
        assert H % 2 == 0 and W % 2 == 0, f"x size ({H}*{W}) are not even."

        x = x.view(B, H, W, C)

        x0 = x[:, 0::2, 0::2, :]  # B H/2 W/2 C
        x1 = x[:, 1::2, 0::2, :]  # B H/2 W/2 C
        x2 = x[:, 0::2, 1::2, :]  # B H/2 W/2 C
        x3 = x[:, 1::2, 1::2, :]  # B H/2 W/2 C
        x = torch.cat([x0, x1, x2, x3], -1)  # B H/2 W/2 4*C
        x = x.view(B, -1, 4 * C)  # B H/2*W/2 4*C

        x = self.norm(x)
        x = self.reduction(x)

        return x

上采样:Dual up-sample

作者提出了 上采样,

该模块包括两种现有的上样本方法(即双线性和PixelShuffle),以防止棋盘伪影(Deconvolution and Checkerboard Artifacts中提出的)https://distill.pub/2016/deconv-checkerboard/ 产生原因:主要是出现在反卷积中。

上采样模块

通过两种上采样后,cat维度拼接后,通过一个卷积层将维度减半C/2

实验:

如上图所示。

中文文本清洗与特征提取

摘自知乎:

bookname嵌入式AI算法研究

中文文本清洗

中文文本清洗:

– 去除指定无用的符号

– 让文本只保留汉字

– 文本中的表情符号去除

– 繁体中文与简体中文转换

中文文本清洗类

import re
from opencc import OpenCC
from bs4 import BeautifulSoup
import jieba
from glob import glob

import torch
from tqdm.auto import tqdm

import sys
!ls ../package/
sys.path.insert(0, "../package/")
from ltp import LTP
nlp = LTP(path="base")

class TextCleaner:
    '''
        批量清洗数据
    '''
    def __init__(self,
                 remove_space=True, # 去除空格
                 remove_suspension=True, # 转换省略号
                 only_zh=False, # 只保留汉子
                 remove_sentiment_character=True, # 去除表情符号
                 to_simple=True, # 转化为简体中文
                 remove_html_label=True,
                 remove_stop_words=False,
                 stop_words_dir="./停用词/",
                 with_space=False,
                 batch_size=256):
        self._remove_space = remove_space
        self._remove_suspension = remove_suspension
        self._remove_sentiment_character = remove_sentiment_character

        self._only_zh = only_zh
        self._to_simple = to_simple

        self._remove_html_label = remove_html_label
        self._remove_stop_words = remove_stop_words
        self._stop_words_dir = stop_words_dir

        self._with_space = with_space
        self._batch_size = batch_size

    def clean_single_text(self, text):
        if self._remove_space:
            text = self.remove_space(text)
        if self._remove_suspension:
            text = self.remove_suspension(text)
        if self._remove_sentiment_character:
            text = self.remove_sentiment_character(text)
        if self._to_simple:
            text = self.to_simple(text)
        if self._only_zh:
            text = self.get_zh_only(text)
        if self._remove_html_label:
            text = self.remove_html(text)
        return text

    def clean_text(self, text_list):
        text_list = [self.clean_single_text(text) for text in tqdm(text_list)]
        tokenized_words_list = self.tokenizer_batch_text(text_list)
        if self._remove_stop_words:
            text_list = [self.remove_stop_words(words_list, self._stop_words_dir, self._with_space) for words_list in tokenized_words_list]
        return text_list

    def remove_space(self, text):     #定义函数
        return text.replace(' ','')   # 去掉文本中的空格

    def remove_suspension(self, text):
        return text.replace('...', '。')

    def get_zh_only(self, text):
        def is_chinese(uchar):
            if uchar >= u'\u4e00' and uchar <= u'\u9fa5':  # 判断一个uchar是否是汉字 中文字符的编码范围 \u4e00 - \u9fff,只要在这个范围就可以
                return True
            else:
                return False

        content = ''
        for i in text:
            if is_chinese(i):
                content = content+i
        return content

    def remove_sentiment_character(self, sentence):    
        pattern = re.compile("[^\u4e00-\u9fa5^,^.^!^,^。^?^?^!^a-z^A-Z^0-9]")  #只保留中英文、数字和符号,去掉其他东西
        #若只保留中英文和数字,则替换为[^\u4e00-\u9fa5^a-z^A-Z^0-9]
        line = re.sub(pattern,'',sentence)  #把文本中匹配到的字符替换成空字符
        new_sentence=''.join(line.split())    #去除空白
        return new_sentence

    def to_simple(self, sentence):
        new_sentence = OpenCC('t2s').convert(sentence)   # 繁体转为简体
        return new_sentence

    def to_tradition(self, sentence):
        new_sentence = OpenCC('s2t').convert(sentence)   # 简体转为繁体
        return new_sentence

    def remove_html(self, text):
        return BeautifulSoup(text, 'html.parser').get_text() #去掉html标签

    def tokenizer_batch_text(self, text_list):
        tokenized_text = []
        len_text = len(text_list)
        with torch.no_grad():
            steps = self._batch_size
            for start_idx in tqdm(range(0, len_text, steps)):
                if start_idx + steps > len_text:
                    tokenized_text += nlp.seg(text_list[start_idx:])[0]
                else:
                    tokenized_text += nlp.seg(text_list[start_idx:start_idx+steps])[0]
        return tokenized_text

    def remove_stop_words(self, words_list, stop_words_dir, with_space=False):
        """
        中文数据清洗  stopwords_chineses.txt存放在博客园文件中
        :param text:
        :return:
        """
        stop_word_filepath_list = glob(stop_words_dir + "/*.txt")
        for stop_word_filepath in stop_word_filepath_list:
            with open(stop_word_filepath) as fp:
                stopwords = {}.fromkeys([line.rstrip() for line in fp]) #加载停用词(中文)
        eng_stopwords = set(stopwords) #去掉重复的词
        words = [w for w in words_list if w not in eng_stopwords] #去除文本中的停用词
        if with_space:
            return ' '.join(words)
        else:
            return ''.join(words)
ltp


file /root/.cache/torch/ltp/8909177e47aa4daf900c569b86053ac68838d09da28c7bbeb42b8efcb08f56aa-edb9303f86310d4bcfd1ac0fa20a744c9a7e13ee515fe3cf88ad31921ed616b2-extracted/config.json not found
file /root/.cache/torch/ltp/8909177e47aa4daf900c569b86053ac68838d09da28c7bbeb42b8efcb08f56aa-edb9303f86310d4bcfd1ac0fa20a744c9a7e13ee515fe3cf88ad31921ed616b2-extracted/config.json not found
cleaner = TextCleaner(remove_stop_words=True, with_space=True)
contents = ['   大家好, 欢迎一起来学习文本的空格   去除   !', '   大家好,文本的空格   去除   !']
results = cleaner.clean_text(contents)
print(results)
0%|          | 0/2 [00:00<?, ?it/s]



  0%|          | 0/1 [00:00<?, ?it/s]


['好 , 学习 文本 空格 去除 !', '好 , 文本 空格 去除 !']

去除空格

# 去除空格
contents = '   大家好, 欢迎一起来学习文本的空格   去除   !'
print('处理前文本:'+contents)
def process(our_data):     #定义函数
    content = our_data.replace(' ','')   # 去掉文本中的空格
    print('处理后文本:'+content)
process(contents)
处理前文本:   大家好, 欢迎一起来学习文本的空格   去除   !
处理后文本:大家好,欢迎一起来学习文本的空格去除!

去除空格的同时把省略号转换为句号

# 去除空格的同时把省略号转换为句号
contents = '   大家好, 这里还有  很多的知识...一起拉学习吧 !'
print('处理前文本:'+contents)
def process(data):     #定义函数
    content1 = data.replace(' ','')    # 去掉文本中的空格
    content2 = content1.replace('...','。')    # 去掉文本中的空格
    print('处理后文本:'+ content2)
process(contents)
处理前文本:   大家好, 这里还有  很多的知识...一起拉学习吧 !
处理后文本:大家好,这里还有很多的知识。一起拉学习吧!

让文本只保留汉字

def is_chinese(uchar):
    if uchar >= u'\u4e00' and uchar <= u'\u9fa5':  # 判断一个uchar是否是汉字
        return True
    else:
        return False

def allcontents(contents):
    content = ''
    for i in contents:
        if is_chinese(i):
            content = content+i
    print('\n处理后的句子为:\n'+content)

centents = '1,2,3...我们开始吧, 加油!'
print('原句子为:\n'+centents)
allcontents(centents)
原句子为:
1,2,3...我们开始吧, 加油!

处理后的句子为:
我们开始吧加油

文本中的表情符号去除

import re
sentence='现在听着音乐,duo rui mi,很开心*_*'
print('原句子为:\n'+sentence)

def clear_character(sentence):    
    pattern = re.compile("[^\u4e00-\u9fa5^,^.^!^a-z^A-Z^0-9]")  #只保留中英文、数字和符号,去掉其他东西
    #若只保留中英文和数字,则替换为[^\u4e00-\u9fa5^a-z^A-Z^0-9]
    line=re.sub(pattern,'',sentence)  #把文本中匹配到的字符替换成空字符
    new_sentence=''.join(line.split())    #去除空白
    print('\n处理后的句子为:\n'+new_sentence) 

clear_character(sentence)
原句子为:
现在听着音乐,duo rui mi,很开心*_*

处理后的句子为:
现在听着音乐,duoruimi,很开心

繁体中文与简体中文转换

from opencc import OpenCC

sentence = '你现在读的这里是简体,这里是繁体,能看懂吗?'
print('原句子为:\n'+sentence)

def Simplified(sentence):
    new_sentence = OpenCC('t2s').convert(sentence)   # 繁体转为简体
    print('\n处理后的句子为:\n'+new_sentence)

def Traditional(sentence):
    new_sentence = OpenCC('s2t').convert(sentence)   # 简体转为繁体
    print('\n处理后的句子为:\n'+new_sentence) 

Simplified(sentence)
Traditional(sentence)
原句子为:
你现在读的这里是简体,这里是繁体,能看懂吗?

处理后的句子为:
你现在读的这里是简体,这里是繁体,能看懂吗?

处理后的句子为:
你现在读的这里是简体,这里是繁体,能看懂吗?

OpenCC的参数设置:

- hk2s: Traditional Chinese (Hong Kong standard) to Simplified Chinese
- s2hk: Simplified Chinese to Traditional Chinese (Hong Kong standard)
- s2t: Simplified Chinese to Traditional Chinese
- s2tw: Simplified Chinese to Traditional Chinese (Taiwan standard)
- s2twp: Simplified Chinese to Traditional Chinese (Taiwan standard, with phrases)
- t2hk: Traditional Chinese to Traditional Chinese (Hong Kong standard)
- t2s: Traditional Chinese to Simplified Chinese
- t2tw: Traditional Chinese to Traditional Chinese (Taiwan standard)
- tw2s: Traditional Chinese (Taiwan standard) to Simplified Chinese
- tw2sp: Traditional Chinese (Taiwan standard) to Simplified Chinese (with phrases)

去除html标签和停用词

from bs4 import BeautifulSoup
import jieba
from glob import glob

def clean_chineses_text(text, with_space=False):
    """
    中文数据清洗  stopwords_chineses.txt存放在博客园文件中
    :param text:
    :return:
    """
    text = BeautifulSoup(text, 'html.parser').get_text() #去掉html标签
    text = jieba.lcut(text)
    stop_word_filepath_list = glob("./停用词/*.txt")
#     print(stop_word_filepath_list)
    for stop_word_filepath in stop_word_filepath_list:
        with open(stop_word_filepath) as fp:
            stopwords = {}.fromkeys([line.rstrip() for line in fp]) #加载停用词(中文)
    eng_stopwords = set(stopwords) #去掉重复的词
    words = [w for w in text if w not in eng_stopwords] #去除文本中的停用词
    if with_space:
        return ' '.join(words)
    else:
        return ''.join(words)
clean_chineses_text("你现在读的这里是简体,这里是繁体,能看懂吗?", with_space=True)
Building prefix dict from the default dictionary ...
Loading model from cache /tmp/jieba.cache
Loading model cost 0.703 seconds.
Prefix dict has been built successfully.





'读 简体 , 这里 繁体 , 能看懂 吗 ?'
ENGLISH_STOP_WORDS = frozenset([
    "about", "above", "across", "after", "afterwards", "again", "against",
    "all", "almost", "alone", "along", "already", "also", "although", "always",
    "am", "among", "amongst", "amoungst", "amount", "an", "and", "another",
    "any", "anyhow", "anyone", "anything", "anyway", "anywhere", "are",
    "around", "as", "at", "back", "be", "became", "because", "become",
    "becomes", "becoming", "been", "before", "beforehand", "behind", "being",
    "below", "beside", "besides", "between", "beyond", "bill", "both",
    "bottom", "but", "by", "call", "can", "cannot", "cant", "co", "con",
    "could", "couldnt", "cry", "de", "describe", "detail", "do", "done",
    "down", "due", "during", "each", "eg", "eight", "either", "eleven", "else",
    "elsewhere", "empty", "enough", "etc", "even", "ever", "every", "everyone",
    "everything", "everywhere", "except", "few", "fifteen", "fifty", "fill",
    "find", "fire", "first", "five", "for", "former", "formerly", "forty",
    "found", "four", "from", "front", "full", "further", "get", "give", "go",
    "had", "has", "hasnt", "have", "he", "hence", "her", "here", "hereafter",
    "hereby", "herein", "hereupon", "hers", "herself", "him", "himself", "his",
    "how", "however", "hundred", "ie", "if", "in", "inc", "indeed",
    "interest", "into", "is", "it", "its", "itself", "keep", "last", "latter",
    "latterly", "least", "less", "ltd", "made", "many", "may", "me",
    "meanwhile", "might", "mill", "mine", "more", "moreover", "most", "mostly",
    "move", "much", "must", "my", "myself", "name", "namely", "neither",
    "never", "nevertheless", "next", "nine", "no", "nobody", "none", "noone",
    "nor", "not", "nothing", "now", "nowhere", "of", "off", "often", "on",
    "once", "one", "only", "onto", "or", "other", "others", "otherwise", "our",
    "ours", "ourselves", "out", "over", "own", "part", "per", "perhaps",
    "please", "put", "rather", "re", "same", "see", "seem", "seemed",
    "seeming", "seems", "serious", "several", "she", "should", "show", "side",
    "since", "sincere", "six", "sixty", "so", "some", "somehow", "someone",
    "something", "sometime", "sometimes", "somewhere", "still", "such",
    "system", "take", "ten", "than", "that", "the", "their", "them",
    "themselves", "then", "thence", "there", "thereafter", "thereby",
    "therefore", "therein", "thereupon", "these", "they", "thick", "thin",
    "third", "this", "those", "though", "three", "through", "throughout",
    "thru", "thus", "to", "together", "too", "top", "toward", "towards",
    "twelve", "twenty", "two", "un", "under", "until", "up", "upon", "us",
    "very", "via", "was", "we", "well", "were", "what", "whatever", "when",
    "whence", "whenever", "where", "whereafter", "whereas", "whereby",
    "wherein", "whereupon", "wherever", "whether", "which", "while", "whither",
    "who", "whoever", "whole", "whom", "whose", "why", "will", "with",
    "within", "without", "would", "yet", "you", "your", "yours", "yourself",
    "yourselves", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l",
    "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"])

特征抽取

  • BOW
  • TF-IDF
  • LDA

文本特征提取类

import numpy as np
import pandas as pd
from tqdm.auto import tqdm
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer, HashingVectorizer

import sys
!ls ../package/
sys.path.insert(0, "../package/")
from ltp import LTP
nlp = LTP(path="base")

from gensim.models import Word2Vec

class TextFeatures:
    def __init__(self, ngram_range=(1, 2)):
        self.cvt = CountVectorizer(tokenizer=self.tokenizer, ngram_range=ngram_range)
        self.tvt = TfidfVectorizer(tokenizer=self.tokenizer, ngram_range=ngram_range)
        self.hvt = HashingVectorizer(tokenizer=self.tokenizer, ngram_range=ngram_range)
        self.cleaner = TextCleaner(remove_html_label=True, remove_stop_words=True, with_space=True)

    def clean_text(self, text_list):
        return self.cleaner.clean_text(text_list)

    def tokenizer(self, text):
        return text.split(" ")

    def get_bow(self, text_list):
        return self.cvt.fit_transform(text_list)

    def get_tfidf(self, text_list):
        return self.tvt.fit_transform(text_list)

    def get_hashing(self, text_list):
        return self.hvt.fit_transform(text_list)
ltp


file /root/.cache/torch/ltp/8909177e47aa4daf900c569b86053ac68838d09da28c7bbeb42b8efcb08f56aa-edb9303f86310d4bcfd1ac0fa20a744c9a7e13ee515fe3cf88ad31921ed616b2-extracted/config.json not found
file /root/.cache/torch/ltp/8909177e47aa4daf900c569b86053ac68838d09da28c7bbeb42b8efcb08f56aa-edb9303f86310d4bcfd1ac0fa20a744c9a7e13ee515fe3cf88ad31921ed616b2-extracted/config.json not found
train_df = pd.read_csv("../0.数据/1.情感分析/NLPCC14-SC/train.tsv", sep="\t", error_bad_lines=False)
train_df.head()
labeltext_a
set(train_df["label"]), train_df.shape
({0, 1}, (10000, 2))
cleaner = TextCleaner(remove_html_label=True, remove_stop_words=True, with_space=True)
contents = ['   大家好, 欢迎一起来学习文本的空格   去除   !']
results = cleaner.clean_text(contents)
print(results)
0%|          | 0/1 [00:00<?, ?it/s]



  0%|          | 0/1 [00:00<?, ?it/s]


['好 , 学习 文本 空格 去除 !']
tqdm.pandas(desc="clean data")
train_df["cleaned_text"] = cleaner.clean_text(train_df["text_a"].values)
0%|          | 0/10000 [00:00<?, ?it/s]



  0%|          | 0/40 [00:00<?, ?it/s]
train_df.to_csv("cleaned_train.csv", index=None)
# import torch
# from tqdm.auto import tqdm

# tokenized_text = []
# text_list = list(train_df["cleaned_text"].values)
# with torch.no_grad():
#     steps = 256
#     for start_idx in tqdm(range(0, train_df.shape[0], steps)):
# #         print(start_idx)
#         if start_idx + steps > train_df.shape[0]:
#             tokenized_text += nlp.seg(text_list[start_idx:])[0]
#         else:
#             tokenized_text += nlp.seg(text_list[start_idx:start_idx+steps])[0]
# from joblib import dump, load
# 关掉显存占用
# from numba import cuda

# cuda.select_device(0)
# cuda.close()

BOW

!ls ../1.基础/停用词/
中文停用词库.txt  哈工大停用词表.txt  四川大学停用词表.txt  百度停用词表.txt
from glob import glob
# 停用词列表
stop_words = []
txt_list = glob("../1.基础/停用词/*.txt")
for txt_path in txt_list:
    with open(txt_path, "r") as fp:
        lines = fp.readlines()
    stop_words += [line.strip() for line in lines]
len(stop_words)
3893
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer, HashingVectorizer
from sklearn.linear_model import Ridge, Lasso, LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, roc_auc_score
def tokenizer(text):
    return text.split(" ")
# corpus = [" ".join(text_list) for text_list in tokenized_text]
# corpus[:2]
corpus = train_df["cleaned_text"].values
cvt = CountVectorizer(stop_words=stop_words, tokenizer=tokenizer, ngram_range=(1, 2))
x_cvt = cvt.fit_transform(corpus)
len(cvt.vocabulary_)
137525
y = train_df["label"].values
X_train, X_val, y_train, y_val = train_test_split(x_cvt, y, test_size=0.1)

clf = Ridge(alpha=500.)
clf.fit(X_train, y_train)

print("train score: ")
y_pred = clf.predict(X_train)
print(roc_auc_score(y_train, y_pred), accuracy_score(y_train, y_pred>0.5))
print()
print("valid score: ")
y_pred = clf.predict(X_val)
print(roc_auc_score(y_val, y_pred), accuracy_score(y_val, y_pred>0.5))
train score: 
0.8657380740314067 0.798

valid score: 
0.8009079767378523 0.733

TFIDF

from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer, HashingVectorizer
tvt = TfidfVectorizer(stop_words=stop_words, tokenizer=tokenizer, ngram_range=(1, 2))
x_tvt = tvt.fit_transform(corpus)
len(tvt.vocabulary_)
137525
y = train_df["label"].values
X_train, X_val, y_train, y_val = train_test_split(x_tvt, y, test_size=0.1)

clf = Ridge(alpha=10.)
clf.fit(X_train, y_train)

print("train score: ")
y_pred = clf.predict(X_train)
print(roc_auc_score(y_train, y_pred), accuracy_score(y_train, y_pred>0.5))
print()
print("valid score: ")
y_pred = clf.predict(X_val)
print(roc_auc_score(y_val, y_pred), accuracy_score(y_val, y_pred>0.5))
train score: 
0.9349220324539836 0.8745555555555555

valid score: 
0.7963706773775423 0.728

HashingVectorizer

from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer, HashingVectorizer
hvt = HashingVectorizer(stop_words=stop_words, tokenizer=tokenizer, ngram_range=(1, 2))
x_hvt = hvt.fit_transform(corpus)
y = train_df["label"].values
X_train, X_val, y_train, y_val = train_test_split(x_hvt, y, test_size=0.1)

clf = Ridge(alpha=1.)
clf.fit(X_train, y_train)

print("train score: ")
y_pred = clf.predict(X_train)
print(roc_auc_score(y_train, y_pred), accuracy_score(y_train, y_pred>0.5))
print()
print("valid score: ")
y_pred = clf.predict(X_val)
print(roc_auc_score(y_val, y_pred), accuracy_score(y_val, y_pred>0.5))
train score: 
0.99204728016389 0.969

valid score: 
0.8349841394447204 0.749

LDA

train_df = pd.read_csv("./cleaned_train.csv")
train_df.head()
labeltext_acleaned_text
from glob import glob
# 停用词列表
stop_words = []
txt_list = glob("../1.基础/停用词/*.txt")
for txt_path in txt_list:
    with open(txt_path, "r") as fp:
        lines = fp.readlines()
    stop_words += [line.strip() for line in lines]
len(stop_words)
3893
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer, HashingVectorizer
from sklearn.decomposition import LatentDirichletAllocation
from sklearn.linear_model import Ridge, Lasso, LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, roc_auc_score
def tokenizer(text):
    return text.split(" ")

corpus = train_df["cleaned_text"].values
corpus = [string if string is not np.nan else "" for string in corpus]
cvt = CountVectorizer(tokenizer=tokenizer, ngram_range=(1, 2))
x_cvt = cvt.fit_transform(corpus)
lda = LatentDirichletAllocation(n_components=32, doc_topic_prior=None, topic_word_prior=None, learning_method='batch', 
                                learning_decay=0.7, learning_offset=50.0, max_iter=10, batch_size=128, evaluate_every=-1, 
                                total_samples=1000000.0, perp_tol=0.1, mean_change_tol=0.001, max_doc_update_iter=100, 
                                n_jobs=None, verbose=0, random_state=402)
docres = lda.fit_transform(x_cvt)
docres.shape
(10000, 32)
y = train_df["label"].values
X_train, X_val, y_train, y_val = train_test_split(docres, y, test_size=0.1)

clf = Ridge(alpha=500.)
clf.fit(X_train, y_train)

print("train score: ")
y_pred = clf.predict(X_train)
print(roc_auc_score(y_train, y_pred), accuracy_score(y_train, y_pred>0.5))
print()
print("valid score: ")
y_pred = clf.predict(X_val)
print(roc_auc_score(y_val, y_pred), accuracy_score(y_val, y_pred>0.5))
train score: 
0.5984059229289742 0.5741111111111111

valid score: 
0.5797141495568878 0.57

gensim

corpus = [string.split(" ") for string in corpus]
from gensim import corpora
dictionary = corpora.Dictionary(corpus)
dictionary.save('qzone.dict')
dictionary.filter_extremes(no_below=20, no_above=0.5)
dictionary.compactify()
corpus = [dictionary.doc2bow(s) for s in corpus]
corpora.MmCorpus.serialize('corpus_bow.mm', corpus)  # 存储语料库
from gensim.models import LdaModel

num_topics = 100
chunksize = 2000
passes = 20
iterations = 400
eval_every = None 

temp = dictionary[0]
id2word = dictionary.id2token

model = LdaModel(
    corpus=corpus,
    id2word=id2word,
    chunksize=chunksize,
    alpha='auto',
    eta='auto',
    iterations=iterations,
    num_topics=num_topics,
    passes=passes,
    eval_every=eval_every
)

model.save('qzone.model')
top_topics = model.top_topics(corpus)
avg_topic_coherence = sum([t[1] for t in top_topics]) / num_topics
print('Average topic coherence: %.4f.' % avg_topic_coherence)
Average topic coherence: -5.7200.
len(top_topics), len(corpus)
(100, 10000)

LTP特征提取

import sys
!ls ../package/

sys.path.insert(0, "../package/")

from ltp import LTP
nlp = LTP(path="base")
ltp


file /root/.cache/torch/ltp/8909177e47aa4daf900c569b86053ac68838d09da28c7bbeb42b8efcb08f56aa-edb9303f86310d4bcfd1ac0fa20a744c9a7e13ee515fe3cf88ad31921ed616b2-extracted/config.json not found
file /root/.cache/torch/ltp/8909177e47aa4daf900c569b86053ac68838d09da28c7bbeb42b8efcb08f56aa-edb9303f86310d4bcfd1ac0fa20a744c9a7e13ee515fe3cf88ad31921ed616b2-extracted/config.json not found
seg, hidden = nlp.seg(["他叫汤姆去拿外衣。"])
pos = nlp.pos(hidden)
ner = nlp.ner(hidden)
srl = nlp.srl(hidden)
dep = nlp.dep(hidden)
sdp = nlp.sdp(hidden)

对于LTP提取的特征,可以参考LTP的文档

  • 静态词向量
  • 动态词向量

推荐系统的基本概念

王树森大佬又开了一门公开课:推荐系统,抱着学习的心态来学习下王老师的课。并做个笔记。

视频地址

github课件:https://github.com/wangshusen/Recomme…

基本概念:

曝光:类似系统给你的推荐的内容

点击:用户点击推荐的内容

阅读:用户点击后在页面停留一段时间

转化流程:

用户行为:点击、点赞、收藏、转发

消费指标:用于反应消费侧对推荐系统的满意程度(非最重要)

消费指标:点击率 (click rate)、交互率 (engagement rate)

北极星指标(最核心指标):用户规模、消费、发布 (关键指标)

DAU:日活跃用户数,用户本日登入小红书,就算一个DAU(且不重复计数)

MAU: 用户本月登入小红书,就算一个MAU(且不重复计数)

实验流程:离线实验、AB测试、推全

离线实验只能反映部分指标,还需要线上实验。

推荐系统链路

链路包括召回、粗排、精排、重排。

– 召回(retrieval):快速从海量数据中取回几千个用户可能感兴趣的物品。

– 粗排:用小规模的模型的神经网络给召回的物品打分,然后做截断,选出分数最高的几百个物品。

– 精排:用大规模神经网络给粗排选中的几百个物品打分,可以做截断,也可以不做截断。 – 重排:对精排结果做多样性抽样,得到几十个物品,然后用规则调整物品的排序。

当用户刷新页面时候,系统就会调用几十条召回通道,每个通道取回几百篇笔记内容,然后使用 用小规模的模型的神经网络给召回的物品打分,然后做截断,选出分数最高的几百个物品。 在下一部精排: 用大规模神经网络给粗排选中的几百个物品打分,可以做截断,也可以不做截断。最后:对精排结果做多样性抽样,得到几十个物品,然后用规则调整物品的排序。

重排

做多样性抽样(⽐如MMR、DPP),从⼏百篇中选出⼏⼗篇。
• ⽤规则打散相似笔记。
• 插⼊广告、运营推广内容,根据⽣态要求调整排序。

总结:

推荐系统的小流量A/B测试 (线上实验)

推荐系统算法工程师的日常工作就是改进模型和策略,目标是提升推荐系统的业务指标。所有对模型和策略的改进,都需要经过线上 AB 测试,用实验数据来验证模型和策略是否有效。

小流量:比如只对10%的用户开放该算法,观测用户的反馈,这样避免大范围的影响。

使用随机分桶测试不同的实验参数效果:

分层实验:解决流量不足的问题(测试的用户不足)

同层互斥,不同层正交:

实验推全和反转实验

小红书推荐系统 —公开课

推荐系统课件:https://github.com/wangshusen/RecommenderSystem

工业界的推荐系统

这门课程结合小红书的业务场景和内部实践,讲解主流的工业界推荐系统技术。

  1. 概要
  2. 召回
  3. 排序
  4. 交叉结构
  5. 用户行为序列建模
    • 用户行为序列特征
    • DIN 模型
    • SIM 模型
  6. 重排
    • 多样性 MMR
    • 多样性 DPP
    • 多样性 MGS
  7. 物品冷启动

Real-ESRGAN 超分辨网络

论文:Real-ESRGAN: TrainingReal-World Blind Super-Resolution with Pure Synthetic Data

代码:https://github.com/xinntao/Real-ESRGAN

Real-ESRGAN 的目标是开发出实用的图像/视频修复算法。
在 ESRGAN 的基础上使用纯合成的数据来进行训练,以使其能被应用于实际的图片修复的场景(顾名思义:Real-ESRGAN)。

  1. 目标:解决真实场景下的图像模糊问题。
  2. 数据集的构建:模糊核、噪声、尺寸缩小、压缩四种操作的随机顺序。
  3. 超分网络backbone:ESRGAN的生成网络+U-Net discriminator判别器。
  4. 损失函数:L1 loss,perceptual loss,生成对抗损失。
  5. 主要对比方法是:RealSR、ESRGAN、BSRGAN、DAN、CDC。

创新点

  1. 提出了新的构建数据集的方法,用高阶处理,增强降阶图像的复杂度。
  2. 构造数据集时引入sinc filter,解决了图像中的振铃和过冲现象。
  3. 替换原始ESRGAN中的VGG-discriminator,使用U-Net discriminator,以增强图像的对细节上的对抗学习。
  4. 引入spectral normalization以稳定由于复杂数据集和U-Net discriminator带来的训练不稳定情况。

数据集构建

在讨论数据集的构建前,作者详细讨论了造成图像模糊的原因,例如:年代久远的手机、传感器噪声、相机模糊、图像编辑、图像在网络中的传输、JPEG压缩以及其它噪声。原文如下:

For example, when we take a photo with our cellphones, the photos may have several degradations, such as camera blur, sensor noise, sharpening artifacts, and JPEG compression. We then do some editing and upload to a social media APP, which introduces further compression and unpredictable noises.

所以作者针对以上问题,提出了High-order降级模型。先面我们先介绍first-order降级模型,然后就很好理解High-order降级模型了。

First-order

First-order降级模型其实就是常规的降级模型,如上式所示,按顺序执行上述操作。

x代表降级后的图像,D代表降级函数,y代表原始图像;
k代表模糊核,r代表缩小比例,n代表加入的噪声,JPEG代表进行压缩。

每一种降级方法又有多种降级方案可以选择,如下图所示:

对于模糊核k,本方法使用各项同性(isotropic)和各向异性(anisotropic)的高斯模糊核。关于sinc filter会在下文中提到。

对于缩小操作r,常用的方法又双三次插值、双线性插值、区域插值—由于最近邻插值需要考虑对齐问题,所以不予以考虑。在执行缩小操作时,本方法从提到的3种插值方式中随机选择一种。

对于加入噪声操作n,本方法同时加入高斯噪声和服从泊松分布的噪声。同时,根据待超分图像的通道数,加入噪声的操作可以分为对彩色图像添加噪声和对灰度图像添加噪声。

JPEG压缩,本方法通过从[0, 100]范围中选择压缩质量,对图像进行JPEG压缩,其中0表示压缩后的质量最差,100表示压缩后的质量最好。JPEG压缩方法点此处

  • High-order

First-order由于使用相对单调的降级方法,其实很难模仿真实世界中的图像低分辨模糊情况。因此,作者提出的High-order其实是为了使用更复杂的降级方法,更好的模拟真实世界中的低分辨模糊情况,从而达到更好的学习效果。一阶降级模型构建的数据集训练结果如下:

高阶降级模型公式如下:

上式,其实就是对First-order进行多次重复操作,也就是每一个D都是执行一次完整的First-order降级。作者通过实验得出,当执行2次First-order时生成的数据集训练效果最好。所以,High-order的pipeline如下:

  • sinc filter

为了解决超分图像的振铃和过冲现象(振铃过冲在图像处理中很常见,此处不过多介绍),作者提出了在构建降级模型中增加sinc filter的操作。先来看一下振铃和过冲伪影的效果:

上图表示实际中的振铃和过冲伪影现象,下图表示通过对sinc filter设置不同的因子人工模仿的振铃和过冲伪影现象。过于如何构造sinc filter,详细细节建议看原文。

sinc filter在两个位置进行设置,一是在每一阶的模糊核k的处理中,也就是在各项同性和各项异性的高斯模糊之后,设置sinc filter;二是在最后一阶的JPEG压缩时,设置sinc filter,其中最后一阶的JPEG和sinc filter执行先后顺序是随机的。

网络结构

  • 生成网络

生成网络是ESRGAN的生成网络,基本没变,只是在功能上增加了对x2和x1倍的图像清晰度提升。对于x4倍的超分辨,网络完全按照ESRGAN的生成器执行;而对于X2和X1倍的超分辨,网络先进行pixel-unshuffle(pixel-shuffl的反操作,pixel-shuffle可理解为通过压缩图像通道而对图像尺寸进行放大),以降低图像分辨率为前提,对图像通道数进行扩充,然后将处理后的图像输入网络进行超分辨重建。举个例子:对于一幅图像,若只想进行x2倍放大变清晰,需先通过pixel-unshuffle进行2倍缩小,然后通过网络进行4倍放大。

对抗网络

由于使用的复杂的构建数据集的方式,所以需要使用更先进的判别器对生成图像进行判别。之前的ESRGAN的判别器更多的集中在图像的整体角度判别真伪,而使用U-Net 判别器可以在像素角度,对单个生成的像素进行真假判断,这能够在保证生成图像整体真实的情况下,注重生成图像细节。

  • 光谱标准正则化

通过加入这一操作,可以缓和由于复杂数据集合复杂网络带来的训练不稳定问题。

训练

分为两步:

  1. 先通过L1 loss,训练以PSRN为导向的网络,获得的模型称为Real-ESRNet。
  2. 以Real-ESRNet的网络参数进行网络初始化,损失函数设置为 L1 loss、perceptual loss、 GAN loss,训练最终的网络Real-ESRGAN。