憨批的语义分割8——Keras 搭建自己的PSPnet语义分割平台

憨批的语义分割8——Keras 搭建自己的PSPnet语义分割平台注意事项学习前言什么是PSPNet模型代码下载PSPNet实现思路一、预测部分1、主干网络介绍2、加强特征提取结构3、利用特征获得预测结果二、训练部分1、训练文件详解2、LOSS解析训练自己的PSPNet模型

注意事项

这是重新构建了的PSPnet语义分割网络,主要是文件框架上的构建,还有代码的实现,和之前的语义分割网络相比,更加完整也更清晰一些。建议还是学习这个版本的PSPnet。

学习前言

由于之前写的语义分割BLOG存在一些小问题,做的不够优美,效果也不是很好,甚至不是很接地气,于是乎我想重置一下。
憨批的语义分割8——Keras 搭建自己的PSPnet语义分割平台

什么是PSPNet模型

PSPNet模型最主要的特点是采用了PSP模块。

该模型提出的金字塔池化模块(Pyramid Pooling Module)能够聚合不同区域的上下文信息,从而提高获取全局信息的能力。实验表明这样的先验表示(即指代PSP这个结构)是有效的,在多个数据集上展现了优良的效果。

PSP结构的功能是将获取到的特征层划分成不同大小的网格,每个网格内部各自进行平均池化。实现聚合不同区域的上下文信息,从而提高获取全局信息的能力。

在PSPNet中,PSP结构典型情况下,会将输入进来的特征层划分成6×6,3×3,2×2,1×1的网格,对应了图片中的绿色、蓝色、橙色、红色的的输出:
憨批的语义分割8——Keras 搭建自己的PSPnet语义分割平台
其中:
红色:将输入进来的特征层整个进行平均池化。
橙色:将输入进来的特征层划分为2×2个子区域,然后对每个子区域进行平均池化。
蓝色:将输入进来的特征层划分为3×3个子区域,然后对每个子区域进行平均池化。
绿色:将输入进来的特征层划分为6×6个子区域,然后对每个子区域进行平均池化。

代码下载

Github源码下载地址为:
https://github.com/bubbliiiing/pspnet-keras

PSPNet实现思路
一、预测部分
1、主干网络介绍

憨批的语义分割8——Keras 搭建自己的PSPnet语义分割平台
PSPNet在论文中采用的是Resnet系列作为主干特征提取网络,本博客会给大家提供两个主干网络,分别是resnet50和mobilenetv2。

但是由于算力限制(我没有什么卡),为了方便博客的进行,本文以mobilenetv2为例,给大家进行解析。

关于mobilenetv2的介绍大家可以看我的另外一篇博客https://blog.csdn.net/weixin_44791964/article/details/102851214。

MobileNet模型是Google针对手机等嵌入式设备提出的一种轻量级的深层神经网络,其使用的核心思想便是depthwise separable convolution。

MobileNetV2是MobileNet的升级版,它具有两个特征点:

1、Inverted residuals,在ResNet50里我们认识到一个结构,bottleneck design结构,在3×3网络结构前利用1×1卷积降维,在3×3网络结构后,利用1×1卷积升维,相比直接使用3×3网络卷积效果更好,参数更少,先进行压缩,再进行扩张。而在MobileNetV2网络部分,其采用Inverted residuals结构,在3×3网络结构前利用1×1卷积升维,在3×3网络结构后,利用1×1卷积降维,先进行扩张,再进行压缩。
憨批的语义分割8——Keras 搭建自己的PSPnet语义分割平台

2、Linear bottlenecks,为了避免Relu对特征的破坏,在在3×3网络结构前利用1×1卷积升维,在3×3网络结构后,再利用1×1卷积降维后,不再进行Relu6层,直接进行残差网络的加法。
憨批的语义分割8——Keras 搭建自己的PSPnet语义分割平台
整体网络结构如下,该图是针对输入为(224,224,3)的图片而言的:(其中bottleneck进行的操作就是上述的创新操作)
憨批的语义分割8——Keras 搭建自己的PSPnet语义分割平台
需要注意的是,在PSPNet当中,一般不会5次下采样,可选的有3次下采样和4次下采样,本文使用的4次下采样。这里所提到的下采样指的是不会进行五次长和宽的压缩,通常选用三次或者四次长和宽的压缩。

from keras.models import Model
from keras import layers
from keras.layers import Input
from keras.layers import Lambda
from keras.layers import Activation
from keras.layers import Concatenate
from keras.layers import Add
from keras.layers import Dropout
from keras.layers import BatchNormalization
from keras.layers import Conv2D
from keras.layers import DepthwiseConv2D
from keras.layers import ZeroPadding2D
from keras.layers import GlobalAveragePooling2D
from keras.activations import relu

def _make_divisible(v, divisor, min_value=None):
if min_value is None:
min_value = divisor
new_v = max(min_value, int(v + divisor / 2) // divisor * divisor)
if new_v < 0.9 * v:
new_v += divisor
return new_v

def relu6(x):
return relu(x, max_value=6)

def _inverted_res_block(inputs, expansion, stride, alpha, filters, block_id, skip_connection, rate=1):
in_channels = inputs.shape[-1].value # inputs._keras_shape[-1]
pointwise_conv_filters = int(filters * alpha)
pointwise_filters = _make_divisible(pointwise_conv_filters, 8)
x = inputs
prefix = 'expanded_conv_{}_'.format(block_id)
if block_id:
# Expand

x = Conv2D(expansion * in_channels, kernel_size=1, padding='same',
use_bias=False, activation=None,
name=prefix + 'expand')(x)
x = BatchNormalization(epsilon=1e-3, momentum=0.999,
name=prefix + 'expand_BN')(x)
x = Activation(relu6, name=prefix + 'expand_relu')(x)
else:
prefix = 'expanded_conv_'
# Depthwise
x = DepthwiseConv2D(kernel_size=3, strides=stride, activation=None,
use_bias=False, padding='same', dilation_rate=(rate, rate),
name=prefix + 'depthwise')(x)
x = BatchNormalization(epsilon=1e-3, momentum=0.999,
name=prefix + 'depthwise_BN')(x)

x = Activation(relu6, name=prefix + 'depthwise_relu')(x)

# Project
x = Conv2D(pointwise_filters,
kernel_size=1, padding='same', use_bias=False, activation=None,
name=prefix + 'project')(x)
x = BatchNormalization(epsilon=1e-3, momentum=0.999,
name=prefix + 'project_BN')(x)

if skip_connection:
return Add(name=prefix + 'add')([inputs, x])

# if in_channels == pointwise_filters and stride == 1:
# return Add(name='res_connect_' + str(block_id))([inputs, x])

return x

def get_mobilenet_encoder(inputs_size, downsample_factor=8):
if downsample_factor == 16:
block4_dilation = 1
block5_dilation = 2
block4_stride = 2
elif downsample_factor == 8:
block4_dilation = 2
block5_dilation = 4
block4_stride = 1
else:
raise ValueError('Unsupported factor - `{}`, Use 8 or 16.'.format(downsample_factor))

inputs = Input(shape=inputs_size)

alpha=1.0
first_block_filters = _make_divisible(32 * alpha, 8)
# 416,416 -> 208,208
x = Conv2D(first_block_filters,
kernel_size=3,
strides=(2, 2), padding='same',
use_bias=False, name='Conv')(inputs)
x = BatchNormalization(
epsilon=1e-3, momentum=0.999, name='Conv_BN')(x)
x = Activation(relu6, name='Conv_Relu6')(x)

x = _inverted_res_block(x, filters=16, alpha=alpha, stride=1,
expansion=1, block_id=0, skip_connection=False)

# 208,208 -> 104,104
x = _inverted_res_block(x, filters=24, alpha=alpha, stride=2,
expansion=6, block_id=1, skip_connection=False)
x = _inverted_res_block(x, filters=24, alpha=alpha, stride=1,
expansion=6, block_id=2, skip_connection=True)

# 104,104 -> 52,52
x = _inverted_res_block(x, filters=32, alpha=alpha, stride=2,
expansion=6, block_id=3, skip_connection=False)
x = _inverted_res_block(x, filters=32, alpha=alpha, stride=1,
expansion=6, block_id=4, skip_connection=True)
x = _inverted_res_block(x, filters=32, alpha=alpha, stride=1,
expansion=6, block_id=5, skip_connection=True)
#---------------------------------------------------------------#
x = _inverted_res_block(x, filters=64, alpha=alpha, stride=block4_stride,
expansion=6, block_id=6, skip_connection=False)
x = _inverted_res_block(x, filters=64, alpha=alpha, stride=1, rate=block4_dilation,
expansion=6, block_id=7, skip_connection=True)
x = _inverted_res_block(x, filters=64, alpha=alpha, stride=1, rate=block4_dilation,
expansion=6, block_id=8, skip_connection=True)
x = _inverted_res_block(x, filters=64, alpha=alpha, stride=1, rate=block4_dilation,
expansion=6, block_id=9, skip_connection=True)

x = _inverted_res_block(x, filters=96, alpha=alpha, stride=1, rate=block4_dilation,
expansion=6, block_id=10, skip_connection=False)
x = _inverted_res_block(x, filters=96, alpha=alpha, stride=1, rate=block4_dilation,
expansion=6, block_id=11, skip_connection=True)
x = _inverted_res_block(x, filters=96, alpha=alpha, stride=1, rate=block4_dilation,
expansion=6, block_id=12, skip_connection=True)
f4 = x

#---------------------------------------------------------------#
x = _inverted_res_block(x, filters=160, alpha=alpha, stride=1, rate=block4_dilation, # 1!
expansion=6, block_id=13, skip_connection=False)
x = _inverted_res_block(x, filters=160, alpha=alpha, stride=1, rate=block5_dilation,
expansion=6, block_id=14, skip_connection=True)
x = _inverted_res_block(x, filters=160, alpha=alpha, stride=1, rate=block5_dilation,
expansion=6, block_id=15, skip_connection=True)

x = _inverted_res_block(x, filters=320, alpha=alpha, stride=1, rate=block5_dilation,
expansion=6, block_id=16, skip_connection=False)
f5 = x
return inputs, f4, f5

2、加强特征提取结构

憨批的语义分割8——Keras 搭建自己的PSPnet语义分割平台
PSPNet所使用的加强特征提取结构是PSP模块。

PSP结构的做法是将获取到的特征层划分成不同大小的区域,每个区域内部各自进行平均池化。实现聚合不同区域的上下文信息,从而提高获取全局信息的能力。

在PSPNet中,PSP结构典型情况下,会将输入进来的特征层划分成6x6,3x3,2x2,1x1的区域,然后每个区域内部各自进行平均池化。

假设PSP结构输入进来的特征层为30x30x320,此时这个特征层的高和宽均为30,如果我们要将这个特征层划分成6x6的区域,只需要使得平均池化的步长stride=30/6=5和kernel_size=30/6=5就行了,此时的平均池化相当于将特征层划分成6x6的区域,每个区域内部各自进行平均池化。

当PSP结构输入进来的特征层为30x30x320时,PSP结构的具体构成如下。
憨批的语义分割8——Keras 搭建自己的PSPnet语义分割平台

def resize_image(inp, s, data_format):
return Lambda(lambda x: tf.image.resize_images(x, (K.int_shape(x)[1]*s[0], K.int_shape(x)[2]*s[1])))(inp)

def pool_block(feats, pool_factor):
h = K.int_shape(feats)[1]
w = K.int_shape(feats)[2]
# strides = [30,30],[15,15],[10,10],[5,5]
pool_size = strides = [int(np.round(float(h)/pool_factor)),int(np.round(float(w)/pool_factor))]
# 进行不同程度的平均
x = AveragePooling2D(pool_size , data_format=IMAGE_ORDERING , strides=strides, padding='same')(feats)
# 进行卷积
x = Conv2D(512, (1 ,1), data_format=IMAGE_ORDERING, padding='same', use_bias=False)(x)
x = BatchNormalization()(x)
x = Activation('relu' )(x)
x = Lambda(lambda x: tf.image.resize_images(x, (K.int_shape(feats)[1], K.int_shape(feats)[2]), align_corners=True))(x)
return x

3、利用特征获得预测结果

利用1、2步,我们可以获取输入进来的图片的特征,此时,我们需要利用特征获得预测结果。

利用特征获得预测结果的过程可以分为3步:
1、利用一个3x3卷积对特征进行整合。
2、利用一个1x1卷积进行通道调整,调整成Num_Classes。
3、利用resize进行上采样使得最终输出层,宽高和输入图片一样。
憨批的语义分割8——Keras 搭建自己的PSPnet语义分割平台

from keras.models import *
from keras.layers import *
from nets.mobilenetv2 import get_mobilenet_encoder
from nets.resnet50 import get_resnet50_encoder
import tensorflow as tf

IMAGE_ORDERING = 'channels_last'
MERGE_AXIS = -1

def resize_image(inp, s, data_format):
return Lambda(lambda x: tf.image.resize_images(x, (K.int_shape(x)[1]*s[0], K.int_shape(x)[2]*s[1])))(inp)

def pool_block(feats, pool_factor, out_channel):
h = K.int_shape(feats)[1]
w = K.int_shape(feats)[2]
# strides = [30,30],[15,15],[10,10],[5,5]
pool_size = strides = [int(np.round(float(h)/pool_factor)),int(np.round(float(w)/pool_factor))]
# 进行不同程度的平均
x = AveragePooling2D(pool_size , data_format=IMAGE_ORDERING , strides=strides, padding='same')(feats)
# 进行卷积
x = Conv2D(out_channel//4, (1 ,1), data_format=IMAGE_ORDERING, padding='same', use_bias=False)(x)
x = BatchNormalization()(x)
x = Activation('relu' )(x)
x = Lambda(lambda x: tf.image.resize_images(x, (K.int_shape(feats)[1], K.int_shape(feats)[2]), align_corners=True))(x)
return x

def pspnet(n_classes, inputs_size, downsample_factor=8, backbone='mobilenet', aux_branch=True):
if backbone == "mobilenet":
img_input, f4, o = get_mobilenet_encoder(inputs_size, downsample_factor=downsample_factor)
out_channel = 320
elif backbone == "resnet50":
img_input, f4, o = get_resnet50_encoder(inputs_size, downsample_factor=downsample_factor)
out_channel = 2048
else:
raise ValueError('Unsupported backbone - `{}`, Use mobilenet, resnet50.'.format(backbone))

# 进行不同程度的池化
pool_factors = [1,2,3,6]
pool_outs = [o]

for p in pool_factors:
pooled = pool_block(o, p, out_channel)
pool_outs.append(pooled)

# 连接
# 60x60x
o = Concatenate(axis=MERGE_AXIS)(pool_outs)

# 卷积
# 60x60x512
o = Conv2D(out_channel//4, (3,3), data_format=IMAGE_ORDERING, padding='same', use_bias=False)(o)
o = BatchNormalization()(o)
o = Activation('relu')(o)
o = Dropout(0.1)(o)

# 60x60x21
o = Conv2D(n_classes,(1,1),data_format=IMAGE_ORDERING, padding='same')(o)
# [473,473,nclasses]
o = Lambda(lambda x: tf.image.resize_images(x, (inputs_size[1], inputs_size[0]), align_corners=True))(o)
o = Activation("softmax", name="main")(o)

if aux_branch:
f4 = Conv2D(out_channel//8, (3,3), data_format=IMAGE_ORDERING, padding='same', use_bias=False)(f4)
f4 = BatchNormalization()(f4)
f4 = Activation('relu')(f4)
f4 = Dropout(0.1)(f4)

# 60x60x21
f4 = Conv2D(n_classes,(1,1),data_format=IMAGE_ORDERING, padding='same')(f4)
# [473,473,nclasses]
f4 = Lambda(lambda x: tf.image.resize_images(x, (inputs_size[1], inputs_size[0]), align_corners=True))(f4)
f4 = Activation("softmax", name="aux")(f4)
model = Model(img_input,[f4,o])
return model
else:
model = Model(img_input,[o])
return model

二、训练部分
1、训练文件详解

我们使用的训练文件采用VOC的格式。
语义分割模型训练的文件分为两部分。
第一部分是原图,像这样:
憨批的语义分割8——Keras 搭建自己的PSPnet语义分割平台
第二部分标签,像这样:
憨批的语义分割8——Keras 搭建自己的PSPnet语义分割平台
原图就是普通的RGB图像,标签就是灰度图或者8位彩色图。

原图的shape为[height, width, 3],标签的shape就是[height, width],对于标签而言,每个像素点的内容是一个数字,比如0、1、2、3、4、5……,代表这个像素点所属的类别。

语义分割的工作就是对原始的图片的每一个像素点进行分类,所以通过预测结果中每个像素点属于每个类别的概率与标签对比,可以对网络进行训练。

2、LOSS解析

本文所使用的LOSS由两部分组成:
1、Cross Entropy Loss。
2、Dice Loss。

Cross Entropy Loss就是普通的交叉熵损失,当语义分割平台利用Softmax对像素点进行分类的时候,进行使用。

Dice loss将语义分割的评价指标作为Loss,Dice系数是一种集合相似度度量函数,通常用于计算两个样本的相似度,取值范围在[0,1]。

计算公式如下:
憨批的语义分割8——Keras 搭建自己的PSPnet语义分割平台
就是预测结果和真实结果的交乘上2,除上预测结果加上真实结果。其值在0-1之间。越大表示预测结果和真实结果重合度越大。所以Dice系数是越大越好。

如果作为LOSS的话是越小越好,所以使得Dice loss = 1 - Dice,就可以将Loss作为语义分割的损失了。
实现代码如下:

def dice_loss_with_CE(beta=1, smooth = 1e-5, alpha = 0.25, gamma=2.0, threhold=0.5):
def _dice_loss_with_CE(y_true, y_pred):
y_pred = K.clip(y_pred, K.epsilon(), 1.0 - K.epsilon())

CE_loss = - y_true[...,:-1] * K.log(y_pred)
CE_loss = K.mean(K.sum(CE_loss, axis = -1))

tp = K.sum(y_true[...,:-1] * y_pred, axis=[0,1,2])
fp = K.sum(y_pred , axis=[0,1,2]) - tp
fn = K.sum(y_true[...,:-1], axis=[0,1,2]) - tp

score = ((1 + beta ** 2) * tp + smooth) / ((1 + beta ** 2) * tp + beta ** 2 * fn + fp + smooth)
score = tf.reduce_mean(score)
dice_loss = 1 - score
# dice_loss = tf.Print(dice_loss, [dice_loss, focal_loss])
return CE_loss + dice_loss
return _dice_loss_with_CE

训练自己的PSPNet模型

整个PSPNet的文件构架为:
憨批的语义分割8——Keras 搭建自己的PSPnet语义分割平台
在训练模型之前,我们需要首先准备好数据集。
大家可以下载我上传的voc数据集,也可以根据voc数据集格式进行数据集制作。
憨批的语义分割8——Keras 搭建自己的PSPnet语义分割平台
如果大家下载的是我上传的voc数据集,那么就不需要运行VOCdevkit文件夹下面的voc2pspnet.py。
如果是自己制作的数据集,那么需要运行VOCdevkit文件夹下面的voc2pspnet.py,从而生成train.txt和val.txt。
憨批的语义分割8——Keras 搭建自己的PSPnet语义分割平台
生成完成后。
在train.py文件夹下面,选择自己要使用的主干模型和下采样因子。
本文提供的主干模型有mobilenet和resnet50。
下采样因子可以在8和16中选择。
需要注意的是,预训练模型需要和主干模型相对应。
憨批的语义分割8——Keras 搭建自己的PSPnet语义分割平台
之后就可以开始训练了。
憨批的语义分割8——Keras 搭建自己的PSPnet语义分割平台

原创:https://www.panoramacn.com
源码网提供WordPress源码,帝国CMS源码discuz源码,微信小程序,小说源码,杰奇源码,thinkphp源码,ecshop模板源码,微擎模板源码,dede源码,织梦源码等。

专业搭建小说网站,小说程序,杰奇系列,微信小说系列,app系列小说

憨批的语义分割8——Keras 搭建自己的PSPnet语义分割平台

免责声明,若由于商用引起版权纠纷,一切责任均由使用者承担。

您必须遵守我们的协议,如果您下载了该资源行为将被视为对《免责声明》全部内容的认可-> 联系客服 投诉资源
www.panoramacn.com资源全部来自互联网收集,仅供用于学习和交流,请勿用于商业用途。如有侵权、不妥之处,请联系站长并出示版权证明以便删除。 敬请谅解! 侵权删帖/违法举报/投稿等事物联系邮箱:2640602276@qq.com
未经允许不得转载:书荒源码源码网每日更新网站源码模板! » 憨批的语义分割8——Keras 搭建自己的PSPnet语义分割平台
关注我们小说电影免费看
关注我们,获取更多的全网素材资源,有趣有料!
120000+人已关注
分享到:
赞(0) 打赏

评论抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

您的打赏就是我分享的动力!

支付宝扫一扫打赏

微信扫一扫打赏