热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

fcn模型是如何resize224乘以224_使用VisualDL可视化模型:PSPNet

背景及工具介绍如果你是一个新手,在使用飞桨成熟的套件完成任务的同时,会不会好奇使用的网络长什么样呢?网络在套件中又是如何实现的呢ÿ

背景及工具介绍

如果你是一个新手,在使用飞桨成熟的套件完成任务的同时,会不会好奇使用的网络长什么样呢?网络在套件中又是如何实现的呢?

本项目首先会介绍 PSPNet,然后利用 VisualDL-Graph 可视化模型网络结构功能,看一看 PSPNet 到底长什么样,代码又是如何实现的,帮助大家更好的理解 PSPNet,同时使用了 VisualDL-Service 来共享可视化结果;

在PaddleSeg中已经实现了很多分割网络,其中就包含我们今天的主角:PSPNet,我们今天就通过 VisualDL-Graph 来看一看 PSPNet 是如何实现的;

VisualDL 是飞桨可视化分析工具,以丰富的图表呈现训练参数变化趋势、模型结构、数据样本、高维数据分布等。可帮助用户更清晰直观地理解深度学习模型训练过程及模型结构,进而实现高效的模型优化。支持标量、图结构、数据样本可视化、直方图、PR曲线及高维数据降维呈现等诸多功能,同时VisualDL提供可视化结果保存服务。具体细节大家可以自行去 VisualDL Github 主页查看;

这个可视化工具是非常好用的,也是训练中必不可少的,关于 VisualDL 其他功能如何在项目中使用,可以参考我的其他文章;

最后也希望大家能够去 Github 上点一点star,让官方能把这个工具做的越来越好!fb171d55de53116a0287c46f41a68de1.png


安装 PaddleSeg

我将官方的 PaddleSeg-v0.7.0 下载好了,已经挂载在项目中,这里直接解压安装,并切换至静态图默认工作目录 PaddleSeg/

如果项目中没有的话,搜索 公开数据集 PaddleSeg-v0.7.0 就可以找到了

!unzip /home/aistudio/data/data60663/PaddleSeg-release-v0.7.0.zip -d work/!mv work/PaddleSeg-release-v0.7.0/ work/PaddleSeg%cd work/PaddleSeg/


下载预训练模型并导出

PaddleSeg 提供了丰富的预训练模型,我们想要查看 PSPNet 的网络结构,首先需要下载一个 PSPNet 的预训练模型,我这里选择了:

pspnet50_bn_cityscapes

通过下面的代码就可以一键下载了,下载好的预训练模型也在该目录下

!python pretrained_model/download_model.py pspnet50_bn_cityscapes

下载好的模型权重参数为分散的文件,我们需要将其导出为推理模型,利用 pdseg/export_model.py 就可以完成了;但是该脚本需要指定一个配置文件,我们利用内置的配置文件 configs/pspnet_optic.yaml,首先需要下载数据集;

然后指定参数修改配置文件,执行下面的代码就可以完成了:

#下载数据集!python dataset/download_optic.py#更改配置文件参数,导出推理模型!python pdseg/export_model.py --cfg configs/pspnet_optic.yaml DATASET.NUM_CLASSES 19 TEST.TEST_MODEL  "./pretrained_model/pspnet50_bn_cityscapes/"


PSPNet 介绍

百度之前开过一门图像分割的课程,图像分割七日打卡营,课程中介绍了一些主流的分割网络,推荐大家去看一看;

先贴一张论文中截图,这张图很清晰的展示了 PSPNet 在 FCN 的基础上解决了什么问题:

9becd015513d0690d3a1d512286c69cb.png

我们看图像第一行,FCN 会把船识别为车,因为这张图中的船与车的外观很像,但是PSPNet 并没有误识别,因为其金字塔模块利用了上下文信息,周围有水的情况下,这应该是一艘船;

也就是感受野的问题,PSPNet 通过不同 scale 的金字塔进行处理,也就是图中红黄蓝绿四个部分,最后再将不同尺度的结果进行 concat;

55309d9baad73438b552e8845e6121e0.png

在这之前,需要利用 ResNet 提取图像特征;关于 ResNet 大家可以参考一下其他资料,我们下面只看一下实现的代码,原理就不细说了;

PaddleSeg 静态图实现的网络在 PaddleSeg/pdseg/models/modeling 目录下,其中有 fast_scnn, pspnet, deeplab, unet等;

我们查看 pspnet.py 的内容:

从第107开始是模型的定义,其中有四个部分,首先是使用 ResNet 作为 backbone, 然后就是一个 PSP 模块, 紧跟着有一个 dropout 层,最后是一个 get_logit_interp 得到原尺寸的输出;

def pspnet(input, num_classes): # Backbone: ResNet res = resnet(input) # PSP模块 psp = psp_module(res, 512) dropout = fluid.layers.dropout(psp, dropout_prob=0.1, name="dropout") # 根据类别数决定最后一层卷积输出, 并插值回原始尺寸 logit = get_logit_interp(dropout, num_classes, input.shape[2:]) return logit


利用 VisualDL-Graph 查看模型网络结构

接下来我们结合模型网络结构图,分别查看一下这四个部分的内容,

我们点击左侧标签

可视化->选择模型文件->选择 work/PaddleSeg/freeze_model/__ model __ ->启动VisualDL服务 -> 打开VisualDL

,在打开的网页中就可以看到我们的网络结构了

如果你在本地有模型文件,把文件直接拖入页面就可以进行加载了,十分方便

Backbone: ResNet

首先是第一个模块,也即网络的backbone: ResNet

在 pspnet.py 中我们可以看到结构的定义,也就是下面的代码,首先从配置文件中获取了 scale 和 layers,然后从 resnet_backbone 中获取了模型;

def resnet(input): # PSPNET backbone: resnet, 默认resnet50 # end_points: resnet终止层数 # dilation_dict: resnet block数及对应的膨胀卷积尺度 scale = cfg.MODEL.PSPNET.DEPTH_MULTIPLIER layers = cfg.MODEL.PSPNET.LAYERS end_points = layers - 1 dilation_dict = {2: 2, 3: 4} model = resnet_backbone(layers, scale, stem='pspnet') data, _ = model.net( input, end_points=end_points, dilation_dict=dilation_dict) return data

PaddleSeg的backbone文件都在

PaddleSeg/pdseg/models/backbone

目录下,我们找到 resnet.py, 第49行开始net函数开始就是backbone的实现;

开始是一些参数的设定,直到第88行开始,首先是3个 conv_bn_layer 操作,

conv = self.conv_bn_layer( input=input, num_filters=int(64 * self.scale), filter_size=3, stride=2, act='relu', name="conv1_1")

conv_by_layer 的操作从209行开始,里面有两个操作,224行的 conv = fluid.layers.conv2d 以及 241行的 fluid.layers.batch_norm,结合第88行调用的部分,我们可以得到操作为:

conv2d + batch_norm + relu

, 我们看网络结构图一开始的地方,应该能看到3个这样的结构:4c2b72edf57f675b1403eff7cd5b3b90.png

但是,这里多了个 elementwise_add 操作,这是因为在 conv2d 中指定了参数 bias_attr;

接着看resnet.py 的代码, 3个conv_bn_layer操作之后,到 119行有一个 conv = fluid.layers.pool2d,看图中第三个conv_bn_layer 之后确实有一个 pool2d

ef1315eaa19e802c2a724e64dc1e7988.png

Backbone: ResNet

接下来你可以先在网络结构页面滚动滑轮,进行缩放,你会发现之后的部分比较有规律,结构都比较相似,结合 resnet.py 第133行开始,我们发现进入了一个循环,

for block in range(len(depth)): for i in range(depth[block]):

因为我们的layers选择的是50,结合第80行代码,我们可以得到depth = [3, 4, 6, 3] ,所以可以得到这里的循环为 3 + 4 + 6 + 3 次,我们看一下循环体:

其中主要的就是145行的 conv = self.bottleneck_block,它的定义在258行开始:

def bottleneck_block(self, input, num_filters, stride, name, dilation=1): if self.stem == 'pspnet' and self.layers == 101: strides = [1, stride] else: strides = [stride, 1] conv0 = self.conv_bn_layer( input=input, num_filters=num_filters, filter_size=1, dilation=1, stride=strides[0], act='relu', name=name + "_branch2a") if dilation > 1: conv0 = self.zero_padding(conv0, dilation) conv1 = self.conv_bn_layer( input=conv0, num_filters=num_filters, filter_size=3, dilation=dilation, stride=strides[1], act='relu', name=name + "_branch2b") conv2 = self.conv_bn_layer( input=conv1, num_filters=num_filters * 4, dilation=1, filter_size=1, act=None, name=name + "_branch2c") short = self.shortcut( input, num_filters * 4, stride, is_first=False, name=name + "_branch1") return fluid.layers.elementwise_add( x=short, y=conv2, act='relu', name=name + ".add.output.5")

可以看到,首先是三个 conv_bn_layer,这个结构上面已经讲过了,接着是一个 shortcut, 其定义从251行开始:

def shortcut(self, input, ch_out, stride, is_first, name): ch_in = input.shape[1] if ch_in != ch_out or stride != 1 or is_first == True: return self.conv_bn_layer(input, ch_out, 1, stride, name=name) else: return input

可以看到这个函数要么直接返回 input,要么返回一个 conv_bn_layer 操作,最后是一个 fluid.layers.elementwise_add 将此函数的返回结果与第三个 conv_bn_layer (conv2)相加,注意 conv2 中的 act=None 也即没有进行 relu 操作, shortcut 中也一样没有relu

因为这里有两种返回结果的可能,所以你可以想象图中就会出现两种不同结构的 bottleneck_block, 先总结一下:

bottleneck_block = conv_bn_layer with relu * 2 + conv_bn_layer without relu + conv_bn_layer without relu or input + elementwise_add

我们接着刚才的 pool2d 往下看图,

587051e1779f78a469aefedef69d56ca.png

在图中你应该可以看到上面讲过的 conv_bn_layer, 根据上面的分析这就是一种 bottleneck_block,其中 shortcut 的返回是一个 conv_bn_layer without relu,我们称其为 bottleneck_block_0;

再往下看图:

e661276b285b297823455d8c28aeaf73.png

这就是另一种 bottleneck_block,其中 shortcut 直接返回 input, elementwise_add 将 input 直接与 conv2 的结果相加, 我们称其为 bottleneck_block_1;

再往下看图,出现了一个重复的结构,这是意料之中的,按照我们的分析确实会有重复的结构出现 3 + 4 + 6 + 3 次,以上我们已经过完了三个

bottleneck_block: bottleneck_block_0 + bottleneck_block_1 * 2

可以想到再往下会出现类似的四个 bottleneck_block,我们看图,确实出现了

bottleneck_block_0 + bottleneck_block_1 * 3

408c4d6f64b7e2533c9d239378ecf92a.png

这里由于分辨率的原因,我的截图不够清晰,大家可以去自己的页面对照一下,同时可以想到的是之后还会出现

bottleneck_block_0 + bottleneck_block_1 * 5 以及 bottleneck_block_0 + bottleneck_block_1 * 2;

至此,循环的部分就结束了,我们回到 resnet.py 第166行,还剩下 fluid.layers.pool2d 以及 fluid.layers.fc,但是我们在网络结构中并没有发现这两个操作,这是resnet进行分类的层,在分割中不需要用到;

PSP模块

backbone的部分终于看完了,接下来就是 psp 模块,在 pspnet.py 中49行开始:

def psp_module(input, out_features): # Pyramid Scene Parsing 金字塔池化模块 # 输入:backbone输出的特征 # 输出:对输入进行不同尺度pooling, 卷积操作后插值回原始尺寸,并concat # 最后进行一个卷积及BN操作 cat_layers = [] sizes = (1, 2, 3, 6) for size in sizes: psp_name = "psp" + str(size) with scope(psp_name): pool = fluid.layers.adaptive_pool2d( input, pool_size=[size, size], pool_type='avg', name=psp_name + '_adapool') data = conv( pool, out_features, filter_size=1, bias_attr=True, name=psp_name + '_conv') data_bn = bn(data, act='relu') interp = fluid.layers.resize_bilinear( data_bn, out_shape=input.shape[2:], name=psp_name + '_interp') cat_layers.append(interp) cat_layers = [input] + cat_layers[::-1] cat = fluid.layers.concat(cat_layers, axis=1, name='psp_cat') psp_end_name = "psp_end" with scope(psp_end_name): data = conv( cat, out_features, filter_size=3, padding=1, bias_attr=True, name=psp_end_name) out = bn(data, act='relu') return out

我们可以看到其中也有一个循环,

for size in sizes 其中 sizes = (1, 2, 3, 6)

,也即循环四次,每次取出1,2,3,6 作为参数;这也就是上面提到的四种 scale 的金字塔结构;

循环中的操作为:

fluid.layers.adaptive_pool2d + conv + bn + fluid.layers.resize_bilinear

也即我们应该在图中能看到 4 个类似的结构,我们在图中接着backbone结束的部分向下看,

37c833bb7895718ad554f52890d28c18.png

很清楚的能看到这样一个结构,再向下看代码,循环结束有一个 concat 操作,上图中也可以看到;

最后是一个 conv + bn,我们看图:

74dd7a49e0b7125afcd6909684ab0022.png

这样 PSP 模块的部分就结束了;

剩余模块

再往下还有两个部分,

dropout + get_logit_interp

,这次我们先看图,然后再去验证代码是不是一样的:

1e64c4b43c4b7207c0c9c246b829fcf8.png

接着bn结束的地方往下看图,我们看到一个dropout,dropout 后面应该就是get_logit_interp了,我们看到操作应该为:

conv + fluid.layers.resize_bilinear

之后的部分 transpose 等应该就是后处理的部分了,我们去代码中验证一下,get_logit_interp的定义在28行:

def get_logit_interp(input, num_classes, out_shape, name="logit"): # 根据类别数决定最后一层卷积输出, 并插值回原始尺寸 param_attr = fluid.ParamAttr( name=name + 'weights', regularizer=fluid.regularizer.L2DecayRegularizer( regularization_coeff=0.0), initializer=fluid.initializer.TruncatedNormal(loc=0.0, scale=0.01)) with scope(name): logit = conv( input, num_classes, filter_size=1, param_attr=param_attr, bias_attr=True, name=name + '_conv') logit_interp = fluid.layers.resize_bilinear( logit, out_shape=out_shape, name=name + '_interp') return logit_interp

# 后处理的代码在 work/PaddleSeg/pdseg/models/model_builder.py 中 第233行  logit = softmax(logit) # 其中softmax 的定义在 96行def softmax(logit): logit = fluid.layers.transpose(logit, [0, 2, 3, 1]) logit = fluid.layers.softmax(logit) logit = fluid.layers.transpose(logit, [0, 3, 1, 2]) return logit

与上图一致,

transpose + softmax + transpose

最后的 scale 是导出推理模型的操作;


利用VisualDL-Service共享可视化结果

此功能是 VisualDL 2.0.4 新添加的功能,你需要安装 VisualDL 2.0.4 或者以上的版本,只需要一行代码 visualdl service upload 即可以将自己的log文件上传到远端,非常推荐这个功能,我们上传文件之后,就不再需要在本地保存这些文件,直接访问生成的链接就可以了,十分方便!如果你没有安装 VisualDL 2.0.4 ,你需要使用命令pip install visualdl==2.0.4安装;执行下面的代码之后,访问生成的链接, 我也将本项目过程中的某些 log 文件通过此功能上传到了云端, 有需要的话可以进行查看对比;注意:当前版本上传时间间隔有 5min 的限制,上传的模型大小有100M的限制

!pip install visualdl==2.0.4

我也将模型的可视化结果通过 VisualDL-Service 分享了出来,大家直接复制下面的链接打开网页就可以查看了;

https://paddlepaddle.org.cn/paddle/visualdl/service/app?id=d8f9460527ce377a06fb26f0309237ce

# 共享可视化结果!visualdl service upload --model freeze_model/__model__

这样整个 PSPNet 的大致代码我们就看完了,你可以结合模型网络结构图再整体回顾一下,有没有觉得结合 VisualDL-Graph 可视化,代码看起来非常好懂呢?每一部分的代码实现的是网络的哪一部分是不是也一目了然呢?同时通过 VisualDL-Service 生成一个链接就实现了可视化结果共享,是不是很方便呢?如果你有其他感兴趣的网络或者搞不懂的网络,结合 VisualDL-Graph 看一看网络长什么样吧,我相信你一定会很快理解的!其实 VisualDL 的强大之处远不止于此,其他功能的使用可以参考的我的其他文章哦,赶快用起来 VisualDL 吧!

小提示:去AIStudio查看此项目更舒爽~


结束语

怎么样?VisualDL是不是很不错呢?快去Github上点点Star吧!

什么?你觉得不太行?点完Star, 去issue里吐槽一下吧,会彳亍起来的!

想深入了解一下其他功能?来我的 地块分割 PaddleSeg 篇看看吧!

觉得写得不错的话,互相点个关注吧,如果你觉得写的有问题,也欢迎在评论区指正!




推荐阅读
  • PHP中元素的计量单位是什么? ... [详细]
  • 结语 | 《探索二进制世界:软件安全与逆向分析》读书笔记:深入理解二进制代码的逆向工程方法
    结语 | 《探索二进制世界:软件安全与逆向分析》读书笔记:深入理解二进制代码的逆向工程方法 ... [详细]
  • 深入解析Gradle中的Project核心组件
    在Gradle构建系统中,`Project` 是一个核心组件,扮演着至关重要的角色。通过使用 `./gradlew projects` 命令,可以清晰地列出当前项目结构中包含的所有子项目,这有助于开发者更好地理解和管理复杂的多模块项目。此外,`Project` 对象还提供了丰富的配置选项和生命周期管理功能,使得构建过程更加灵活高效。 ... [详细]
  • JVM参数设置与命令行工具详解
    JVM参数配置与命令行工具的深入解析旨在优化系统性能,通过合理设置JVM参数,确保在高吞吐量的前提下,有效减少垃圾回收(GC)的频率,进而降低系统停顿时间,提升服务的稳定性和响应速度。此外,本文还将详细介绍常用的JVM命令行工具,帮助开发者更好地监控和调优JVM运行状态。 ... [详细]
  • 技术日志:深入探讨Spark Streaming与Spark SQL的融合应用
    技术日志:深入探讨Spark Streaming与Spark SQL的融合应用 ... [详细]
  • IIS配置大全:从基础到高级的全面指南
    IIS配置详解:从基础到高级的全面指南IIS前端配置与web.config文件紧密相关,相互影响。本文详细介绍了如何设置允许通过的HTTP请求方法,包括HEAD、POST、GET、TRACE和OPTIONS。提供了两种主要的配置方法,并探讨了它们在实际应用中的优缺点。此外,还深入讲解了其他高级配置选项,帮助读者全面提升IIS服务器的性能和安全性。 ... [详细]
  • 表面缺陷检测数据集综述及GitHub开源项目推荐
    本文综述了表面缺陷检测领域的数据集,并推荐了多个GitHub上的开源项目。通过对现有文献和数据集的系统整理,为研究人员提供了全面的资源参考,有助于推动该领域的发展和技术进步。 ... [详细]
  • 如何在Android应用中设计和实现专业的启动欢迎界面(Splash Screen)
    在Android应用开发中,设计与实现一个专业的启动欢迎界面(Splash Screen)至关重要。尽管Android设计指南对使用Splash Screen的态度存在争议,但一个精心设计的启动界面不仅能提升用户体验,还能增强品牌识别度。本文将探讨如何在遵循最佳实践的同时,通过技术手段实现既美观又高效的启动欢迎界面,包括加载动画、过渡效果以及性能优化等方面。 ... [详细]
  • Android开发常见问题汇总(含Gradle解决方案)第二篇
    本文继续深入探讨Android开发中常见的问题及其解决方案,特别聚焦于Gradle相关的挑战。通过详细分析和实例演示,帮助开发者高效解决构建过程中的各种难题,提升开发效率和项目稳定性。 ... [详细]
  • Node.js 教程第五讲:深入解析 EventEmitter(事件监听与发射机制)
    本文将深入探讨 Node.js 中的 EventEmitter 模块,详细介绍其在事件监听与发射机制中的应用。内容涵盖事件驱动的基本概念、如何在 Node.js 中注册和触发自定义事件,以及 EventEmitter 的核心 API 和使用方法。通过本教程,读者将能够全面理解并熟练运用 EventEmitter 进行高效的事件处理。 ... [详细]
  • 深入解析 UIImageView 与 UIImage 的关键细节与应用技巧
    本文深入探讨了 UIImageView 和 UIImage 的核心特性及应用技巧。首先,详细介绍了如何在 UIImageView 中实现动画效果,包括创建和配置 UIImageView 实例的具体步骤。此外,还探讨了 UIImage 的加载方式及其对性能的影响,提供了优化图像显示和内存管理的有效方法。通过实例代码和实际应用场景,帮助开发者更好地理解和掌握这两个重要类的使用技巧。 ... [详细]
  • 织梦系统多条件联动筛选功能详细教程及删除操作指南
    多条件联动筛选功能广泛应用于图片展示、装修设计、机械设备和在线商城等场景,通常筛选条件应聚焦于用户最关心的要素,而非涵盖所有可能的选项。在DedeCMS中,多条件筛选的PHP开发并未内置删除已选条件的功能,但通过理解PHP筛选与JS筛选的不同机制,实现这一功能相对简单且易于操作。 ... [详细]
  • Android 图像色彩处理技术详解
    本文详细探讨了 Android 平台上的图像色彩处理技术,重点介绍了如何通过模仿美图秀秀的交互方式,利用 SeekBar 实现对图片颜色的精细调整。文章展示了具体的布局设计和代码实现,帮助开发者更好地理解和应用图像处理技术。 ... [详细]
  • HBase在金融大数据迁移中的应用与挑战
    随着最后一台设备的下线,标志着超过10PB的HBase数据迁移项目顺利完成。目前,新的集群已在新机房稳定运行超过两个月,监控数据显示,新集群的查询响应时间显著降低,系统稳定性大幅提升。此外,数据消费的波动也变得更加平滑,整体性能得到了显著优化。 ... [详细]
  • 本文深入探讨了Spring Cloud Eureka在企业级应用中的高级使用场景及优化策略。首先,介绍了Eureka的安全配置,确保服务注册与发现过程的安全性。接着,分析了Eureka的健康检查机制,提高系统的稳定性和可靠性。随后,详细讨论了Eureka的各项参数调优技巧,以提升性能和响应速度。最后,阐述了如何实现Eureka的高可用性部署,保障服务的连续性和可用性。通过这些内容,开发者可以更好地理解和运用Eureka,提升微服务架构的整体效能。 ... [详细]
author-avatar
田小多
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有