【论文|复现]Vertebra-Focused Landmark Detection For Scoliosis Assessment

来源:2020 IEEE 17th International Symposium on Biomedical Imaging (ISBI) 

【论文】

翻译

Abstract

In this paper, we propose a novel vertebra-focused landmark detection method.

Our model first localizes the vertebra centers, based on which it then traces the four corner landmarks of the vertebra through the learned corner offset. In this way, our method is able to keep the order of the landmarks.

先定位椎体中心,然后在此基础上通过角偏移量去追踪四个角点。

Intro

AIS是什么、衡量它的金标准Cobb、Cobb是怎么测量-人工测量不足-自动方法是a surge of interest

传统无监督方法中的参数敏感和复杂处理过程-有监督方法、列举两个但不足且改进也不足(首先是参数多计算量大然后下采样对图片信息有损失)

⇒keypoint-based methods比回归好在哪里,本文就是点回归,本文是怎么做的。

Method

首先也说明为什么要先定位再根据偏移找4点(一次性找出来不能保证顺序而顺序对准确定位椎体很重要),并对如何保持点的顺序进行了非常有条理和清晰逻辑地阐述。

  1. Heatmap of Center Points

  2. Center Offset

    input中心位置与downsized feature map中位置相映射。

    从下采特征映射中追踪到中心点,再用center offset映射回原始输入图。

    The center offsets at the center points are trained with L1 loss.

  3. Corner Offset

    vectors向量

    use L1 loss to train the corner offsets at the center points

小结:首先用ResNet34和focal loss得出中心点热图;其次是得出input与下采特征图里center points的映射关系,用L1 loss训练;在定义到中心点的角偏移向量来得到4角点,也是用L1 loss.

Experiments

  1. Dataset:580=348(60%)训练+116(20%验证)+116(20%测试),初始2500x1000

  2. The backbone network ResNet34 [19] is pre-trained on ImageNet.

    数据增强、input resized、Adam优化、100epochs

  3. Metrics:SMAPE评估Cobb角准确性、Errordec评估landmarks检测准确性

Results

  1. 对比实验Fig3+Table1:
    1. regression输入有调整,定位不够准确;
    2. segmention定位准但在椎体不清晰/模糊/分辨率等情况mask不准导致定位错误,尤其腰椎;
    3. ours在case5形态特征不明显的地方椎体定位失败
    4. 结论是ours best但是只和两种方法进行对比,实验部分并不充分;但是定量可视化的效果还是不错的;[7]分割是四区、[10]回归是1区top但效果不好的原因是input修改了

     【7】 :2019.2、四区论文【10】:2019.12、1区top期刊论文

  2. The strategy of predicting center heatmaps enables our model to identify different vertebrae and allows it to detect landmarks robustly from the low contrast images and ambiguous boundaries.

【实验】

代码:Vertebra-Focused

实验过程
实验过程1(本实验)报错:
1、qt
loaded weights from weights_spinal/model_last.pth, epoch 16
processing 0/128 image ... sunhl-1th-01-Mar-2017-310 C AP.jpg
totol pts num is 17
qt.qpa.xcb: could not connect to display 
qt.qpa.plugin: Could not load the Qt platform plugin "xcb" in "/home/think/anaconda3/envs/zj170/lib/python3.9/site-packages/cv2/qt/plugins" even though it was found.
This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem.
# 此应用程序无法启动,因为无法初始化Qt平台插件。
Available platform plugins are: xcb.

解决:这和IDE不能直接回传图形界面相关,比如一份代码中添加cv.imshow()后报上述错误,可借助第三方ssh软件、

解决2

解决3

解决4

此问题为远程IDE不能直接回传图形界面相关,导致报错,网上有相关解决办法是下载支持图形回传的远程软件,如MobaXterm

上述都随机操作了一遍,最后在MobaXterm上运行命令后就出现了运行结果了。所以问题还是出在pycharm可视化那一部分。(没有本质解决,就先借助第三平台8)

2、TypeError: Expecting miMATRIX type here, got 3220159935

实验过程2(相关实验)

1、以为联网然鹅并没有,换了设备也是同样“网络不可达”;orz

 2、虽然在本地配置了远程,但是对缺少Module问题,还是要在服务器上配置环境

from scipy.io import loadmat       ModuleNotFoundError: No module named 'scipy'

pip install scipy

相关
夯实基础
torch.manual_seed():torch.manual_seed(0)中的0表示随机种子的一种标识。因为这里随机并不是真的随机,而是可以人为控制的伪随机,所以需要一个标识。这里的0你可以换成其它任意整数。
s.mkdir()与os.makedirs():os.mkdir(path),他的功能是一级一级的创建目录,前提是前面的目录已存在,如果不存在会报异常;os.makedirs(path),单从写法上就能猜出他的区别,他可以一次创建多级目录,哪怕中间目录不存在也能正常的(替你)创建 用法

Adam优化器及代码详解pytorch优化器AdamLossAll优化器就是需要根据网络反向传播的梯度信息来更新网络的参数,以起到降低loss函数计算值的作用。

Epoch, Batch, Iteration三个概念√:一个Epoch就是将所有训练样本训练一次的过程。|| 然而,当一个Epoch的样本(也就是所有的训练样本)数量可能太过庞大(对于计算机而言),就需要把它分成多个小块,也就是就是分成多个Batch 来进行训练。|| 训练一个Batch就是一次Iteration

CIFAR10 数据集有 50000 张训练图片,10000 张测试图片。现在选择 Batch Size = 256 对模型进行训练。

  • 每个 Epoch 要训练的图片数量:50000
  • 训练集具有的 Batch 个数:50000/256=195+1=196
  • 每个 Epoch 需要完成的 Batch 个数:196
  • 每个 Epoch 具有的 Iteration 个数:196
  • 每个 Epoch 中发生模型权重更新的次数:196
  • 训练 10 代后,模型权重更新的次数:196∗10=1960
  • 不同代的训练,其实用的是同一个训练集的数据。第 1 代和第 10 代虽然用的都是训练集的五万张图片,但是对模型的权重更新值却是完全不同的。因为不同代的模型处于代价函数空间上的不同位置,模型的训练代越靠后,越接近谷底,其代价越小。

optimizer.step() 和loss.backward()和scheduler.step()的关系与区别★:

1.优化器就是需要根据网络反向传播的梯度信息来更新网络的参数,以起到降低loss函数计算值的作用。从优化器的作用出发,要使得优化器能够起作用,需要主要两个东西:

(1)优化器需要知道当前的网络或者别的什么模型的参数空间,这也就是为什么在训练文件中,正式开始训练之前需要将网络的参数放到优化器里面

(2)需要知道反向传播的梯度信息

2.step这个函数使用的是参数空间(param_groups)中的grad,也就是当前参数空间对应的梯度,这也就解释了为什么optimzier使用之前需要zero清零一下,因为如果不清零,那么使用的这个grad就得同上一个mini-batch有关,这不是我们需要的结果。

        optimizer更新参数空间需要基于反向梯度,因此,当调用optimizer.step()的时候应当是loss.backward()的时候,也就是loss.backward()在前,然后跟一个step.

        optimizer.step()需要放在每一个batch训练中,而不是epoch训练中,这是因为现在的mini-batch训练模式是假定每一个训练集就只有mini-batch这样大,因此实际上可以将每一次mini-batch看做是一次训练,一次训练更新一次参数空间,因而optimizer.step()放在这里。

np.savetxt()

np.savetxt(os.path.join(save_path, 'train_loss.txt'), train_loss, fmt='%.6f')fname:保存文件路径
X:保存np对象
fmt:保存的数据格式
delimiter:分隔符默认为逗号,可以自己指定

 loss和Val_loss判定模型结果好坏:loss-训练集的损失值 Val_loss-测试集的损失值

情况一:train loss不断下降,test loss不断下降,说明网络仍然在学习中

解决办法:此时的网络模型是最好的,不需要其他措施

情况二:train loss不断下降,test loss趋于不变,说明网络出现过拟合

解决办法:采用数据增强、最大池化、正则化

情况三:train loss趋于不变,test loss不断下降,说明数据集100%有问题

解决办法:检查数据集(dataset)

情况四:train loss趋于不变,test loss趋于不变,说明学习遇到瓶颈

解决办法:减少学习率或者减少批量数目

情况五:train loss不断上升,test loss不断上升,说明网络设计有问题(最不好的情况)

解决办法:重置模型结构 重置数据集

unning_loss += loss.item()item()返回loss的值,叠加之后算出总loss,最后再除以mini-batches的数量,取loss平均值

loss为什么要加item():PyTorch 0.4.0版本去掉了Variable,将Variable和Tensor融合起来,可以视Variable为requires_grad=True的Tensor。其动态原理还是不变。

在获取数据的时候也变得更优雅:使用loss += loss.detach()来获取不需要梯度回传的部分。

或者使用loss.item()直接获得所对应的python数据类型。

PyTorch 有哪些坑

常用损失函数criterion及代码√

pytorch中to(device) 和cuda():这两种方法的功能都是指定 CPU 或 GPU 进行数据处理的 .to(device) 可以指定CPU 或者GPU;.cuda() 只能指定GPU

两个方法都可以达到同样的效果,在pytorch中,即使是有GPU的机器,它也不会自动使用GPU,而是需要在程序中显示指定。调用model.cuda(),可以将模型加载到GPU上去。这种方法不被提倡,而建议使用model.to(device)的方式,这样可以显示指定需要使用的计算资源,特别是有多个GPU的情况下。

with torch.no_grad()☆:

首先python中的with语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源,比如文件使用后自动关闭/线程中锁的自动获取和释放等。工作原理
(1)紧跟with后面的语句被求值后,返回对象的“–enter–()”方法被调用,这个方法的返回值将被赋值给as后面的变量;
(2)当with后面的代码块全部被执行完之后,将调用前面返回对象的“–exit–()”方法。

 关于 torch.no_grad()首先从requires_grad讲起:在pytorch中,tensor有一个requires_grad参数,如果设置为True,则反向传播时,该tensor就会自动求导。tensor的requires_grad的属性默认为False,若一个节点(叶子变量:自己创建的tensor)requires_grad被设置为True,那么所有依赖它的节点requires_grad都为True(即使其他相依赖的tensor的requires_grad = False);当requires_grad设置为False时,反向传播时就不会自动求导了,因此大大节约了显存或者说内存

with torch.no_grad的作用:在该模块下,所有计算得出的tensor的requires_grad都自动设置为False;即使一个tensor(命名为x)的requires_grad = True,在with torch.no_grad计算,由x得到的新tensor(命名为w-标量)requires_grad也为False,且grad_fn也为None,即不会对w求导。

 with torch.no_grad()有使用与不使用的结果对比:在使用pytorch时,并不是所有的操作都需要进行计算图的生成(计算过程的构建,以便梯度反向传播等操作)。而对于tensor的计算操作,默认是要进行计算图的构建的,在这种情况下,可以使用 with torch.no_grad():,强制之后的内容不进行计算图构建

 torch.enable_grad():启用梯度计算

criterion = loss.LossAll()

多种定义损失函数关于nn.Module 和 nn.functional 区别和联系自定义层的时候可以通过nn.Module 和 nn.functional二者来完成,但是推荐使用前者。因为nn.Module是一个包装好的类,具体定义了一个网络层,可以维护状态和存储参数信息;而nn.functional仅仅提供了一个计算,不会维护状态信息和存储参数。在Module类内部,层的功能其实又是通过nn.functional来实现的。对于一些不需要存储参数和状态信息的层,比如activation函数,比如(relu, sigmoid等),dropout,pooling等没有训练参数,可以使用functional模块,当然也可以使用nn.Module类来完成损失函数和层的共性:本质上来说,损失函数和自定义层有着很多类似的地方,他们都是通过对输入进行函数运算,得到一个输出,这不也就是层的功能吗?只不过层的函数运算比较不一样,可能是线性组合、卷积运算等,但终归也是函数运算,正是基于这样的共性,所以我们可以统一的使用nn.Module类来定义损失函数,而且定义的方式也和前面的层是大同小异的。

pytorch中,已经有很多的函数是作为类定义好了的

Focal loss:Binary Cross Entropy loss,这种训练目标要求模型 对自己的预测真的很有信心。而Focal Loss所做的是,它使模型可以更"放松"地预测事物,而无需80-100%确信此对象是“某物”。简而言之,它给模型提供了更多的自由,可以在进行预测时承担一些风险。这在处理高度不平衡的数据集时尤其重要,因为在某些情况下(例如癌症检测),即使预测结果为假阳性也可接受,确实需要模型承担风险并尽量进行预测。因此,Focal loss在样本不平衡的情况下特别有用。特别是在“对象检测”的情况下,大多数像素通常都是背景,图像中只有很少数的像素具有我们感兴趣的对象

在上图中,“蓝”线代表交叉熵损失。X轴即“预测为真实标签的概率”(为简单起见,将其称为pt)。举例来说,假设模型预测某物是自行车的概率为0.6,而它确实是自行车, 在这种情况下的pt为0.6。而如果同样的情况下对象不是自行车。则pt为0.4,因为此处的真实标签是0,而对象不是自行车的概率为0.4(1-0.6)Y轴是给定pt后Focal loss和CE的loss的值

从图像中可以看出,当模型预测为真实标签的概率为0.6左右时,交叉熵损失仍在0.5左右。因此,为了在训练过程中减少损失,我们的模型将必须以更高的概率来预测到真实标签。换句话说,交叉熵损失要求模型对自己的预测非常有信心。但这也同样会给模型表现带来负面影响。深度学习模型会变得过度自信, 因此模型的泛化能力会下降.

当使用γ> 1的Focal Loss可以减少“分类得好的样本”或者说“模型预测正确概率大”的样本的训练损失,而对于“难以分类的示例”,比如预测概率小于0.5的,则不会减小太多损失。因此,在数据类别不平衡的情况下,会让模型的注意力放在稀少的类别上,因为这些类别的样本见过的少,比较难分。

gather() 的函数功能:可以解释为根据 index 参数(即是索引)返回数组里面对应位置的值
这里的b.gather()写法和torch.gather(b)的写法都可以,重点是两个参数,dim和index

dim=0表示按来索引,也就是说index的值表示的是第几行
dim=1表示按来索引,也就是指index的值表示的是第几列

gather用法理解

torch.gather函数

【知识点补充】

The backbone network ResNet34 [19] is pre-trained on ImageNet.

1、pre-trained on ImageNet.

迁移学习神经网络需要用数据来训练,它从数据中获得信息,进而把它们转换成相应的权重。这些权重能够被提取出来,迁移到其他的神经网络中,我们“迁移”了这些学来的特征,就不需要从零开始训练一个神经网络了 。针对训练数据集小,防止过拟合使用。通常在计算机视觉imagenet 进行预训练。

预训练模型:简单来说,预训练模型(pre-trained model)是前人为了解决类似问题所创造出来的模型。你在解决问题的时候,不用从零开始训练一个新模型,可以从在类似问题中训练过的模型入手。比如说,如果你想做一辆自动驾驶汽车,可以花数年时间从零开始构建一个性能优良的图像识别算法,也可以从Google在ImageNet数据集上训练得到的inception model(一个预训练模型)起步,来识别图像。一个预训练模型可能对于你的应用中并不是100%的准确对口,但是它可以为你节省大量功夫怎样使用预训练模型:当在训练神经网络的时候我们希望网络能够在多次正向反向迭代的过程中,找到合适的权重。通过使用之前在大数据集上经过训练的预训练模型,我们可以直接使用相应的结构和权重,将它们应用到我们正在面对的问题上。这被称作是“迁移学习”,即将预训练的模型“迁移”到我们正在应对的特定问题中。ImageNet数据集已经被广泛用作训练集,因为它规模足够大(包括120万张图片),有助于训练普适模型。ImageNet的训练目标,是将所有的图片正确地划分到1000个分类条目下。这1000个分类基本上都来源于我们的日常生活,比如说猫猫狗狗的种类,各种家庭用品,日常通勤工具等。在迁移学习中,这些预训练的网络对于ImageNet 数据集外的图片也表现出了很好的泛化性能。既然预训练模型已经训练得很好,我们就不会在短时间内去修改过多的权重,在迁移学习中用到它的时候,往往只是进行微调(fine tune)。在修改模型的过程中,我们通过会采用比一般训练模型更低的学习速率。微调模型的方法:1特征提取:我们可以将预训练模型当做特征提取装置来使用。具体的做法是,将输出层去掉,然后将剩下的整个网络当做一个固定的特征提取机,从而应用到新的数据集中。2采用预训练模型的结构:我们还可以采用预训练模型的结构,但先将所有的权重随机化,然后依据自己的数据集进行训练。3训练特定层,冻结其他层4另一种使用预训练模型的方法是对它进行部分的训练。具体的做法是,将模型起始的一些层的权重保持不变,重新训练后面的层,得到新的权重。在这个过程中,我们可以多次进行尝试,从而能够依据结果找到frozen layers和retrain layers之间的最佳搭配。 如何使用与训练模型,是由数据集大小和新旧数据集(预训练的数据集和我们要解决的数据集)之间数据的相似度来决定的。

ImageNet预训练:一般在图像处理领域,喜欢用ImageNet来做网络的预训练,主要有两点,一方面ImageNet是图像领域里有超多事先标注好训练数据的数据集合,分量足是个很大的优势,量越大训练出的参数越靠谱;另外一方面因为ImageNet有1000类,类别多,算是通用的图像数据,跟领域没太大关系,所以通用性好,预训练完后哪哪都能用。

那么图像领域怎么做预训练呢,上图展示了这个过程,我们设计好网络结构以后,对于图像来说一般是CNN的多层叠加网络结构,可以先用某个训练集合比如训练集合A或者训练集合B对这个网络进行预先训练,在A任务上或者B任务上学会网络参数,然后存起来以备后用。假设我们面临第三个任务C,网络结构采取相同的网络结构,在比较浅的几层CNN结构,网络参数初始化的时候可以加载A任务或者B任务学习好的参数,其它CNN高层参数仍然随机初始化。之后我们用C任务的训练数据来训练网络,此时有两种做法,一种是浅层加载的参数在训练C任务过程中不动,这种方法被称为“Frozen”;另外一种是底层网络参数尽管被初始化了,在C任务训练过程中仍然随着训练的进程不断改变,这种一般叫“Fine-Tuning”,顾名思义,就是更好地把参数进行调整使得更适应当前的C任务。一般图像或者视频领域要做预训练一般都这么做。

这么做有几个好处,首先,如果手头任务C的训练集合数据量较少的话,现阶段的好用的CNN比如Resnet/Densenet/Inception等网络结构层数很深,几百万上千万参数量算起步价,上亿参数的也很常见,训练数据少很难很好地训练这么复杂的网络,但是如果其中大量参数通过大的训练集合比如ImageNet预先训练好直接拿来初始化大部分网络结构参数,然后再用C任务手头比较可怜的数据量上Fine-tuning过程去调整参数让它们更适合解决C任务,那事情就好办多了。这样原先训练不了的任务就能解决了,即使手头任务训练数据也不少,加个预训练过程也能极大加快任务训练的收敛速度,所以这种预训练方式是老少皆宜的解决方案,另外疗效又好,所以在做图像处理领域很快就流行开来。

目前我们已经知道,对于层级的CNN结构来说,不同层级的神经元学习到了不同类型的图像特征,由底向上特征形成层级结构,如上图所示,如果我们手头是个人脸识别任务,训练好网络后,把每层神经元学习到的特征可视化肉眼看一看每层学到了啥特征,你会看到最底层的神经元学到的是线段等特征,图示的第二个隐层学到的是人脸五官的轮廓,第三层学到的是人脸的轮廓,通过三步形成了特征的层级结构,越是底层的特征越是所有不论什么领域的图像都会具备的比如边角线弧线等底层基础特征,越往上抽取出的特征越与手头任务相关。正因为此,所以预训练好的网络参数,尤其是底层的网络参数抽取出特征跟具体任务越无关,越具备任务的通用性,所以这是为何一般用底层预训练好的参数初始化新任务网络参数的原因。而高层特征跟任务关联较大,实际可以不用使用,或者采用Fine-tuning用新数据集合清洗掉高层无关的特征抽取器。

要不要ImageNet预训练

为什么预训练模型好用:第一,领域数据太少。第二,学习难度大。就像人学习,如果拥有通用知识,比如学过高中语文,在此基础上去学领域知识会更容易,如果连基本造句都不会,就去学专业知识,怕是头都大了。而预训练模型用了大量的维基百科等通用数据来教会模型基础知识,我想这也是预训练模型会选择维基百科等作为语料的原因之一吧(数据容易获得当然是更重要的原因emm)

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值