轻量级语义分割网络 BiseNetv2学习:手把手教你搭建BiseNetv2(tensorflow2),并在Cityscapes上完成语义分割任务

2023-11-07

轻量级语义分割网络 BiseNetv2学习:手把手教你搭建BiseNetv2(tensorflow2),并在Cityscapes上完成语义分割任务

前言

博主在之前从头搭建BiseNet的博客(BiseNet学习:利用tensorflow2搭建BiseNet并训练完成语义分割任务_theworld666的博客-CSDN博客)中提到过认识BiseNetv2的契机,是在一次调研语义分割任务中,BiseNetv2作为非常优秀的轻量级语义分割的网络被我选中用于测试,那时的我由于啥都不懂最终什么都没跑出来,在最近学习深度学习后,膨胀了,我个人觉得能自己搭建神经网络,就不必被他人限制系统运行的环境,从而环境由我个人决定,于是在阅读作者论文,和提供的代码,我个人使用tensorflow2版本从头到尾复现了作者所说的网络,并在CityScapes数据集上完成了训练任务。(作者使用tensorflow1版本,我用的是tensorflow2,这两个版本的差别简直是天差地别,导致我看代码的时候总是需要搜索引擎,制作本次博客不易,感觉有帮助的麻烦点个赞)。
。。。
这是六个月后的博主,那个代码发邮箱太累了,大家上git自取吧: https://github.com/liujiawen-jpg/AIleraning

1.网络搭建

BiseNet又称双边分割网络,一般架构是,一边是一个较为粗糙的特征提取网络作为一边,而另一边可以由经典的特征提取网络(EfficientNet,resNet,xception)组成,

在这里插入图片描述

而我们今天的主角BiseNetv2,也类似该架构,在上面(也就是DetailBranch)是一个较为简单的特征提取网络,我们先从他开始构造,他在作者提供的架构图中是这样的(k是卷积核大小,c是输出单元数,s是步长,r是该层重复次数)

1.1DetailBranch的构建

在这里插入图片描述

也就是一个卷积神经网络,不断卷积维度不断增强,随着卷积层步长的调节,图片不断减小(这里要选择填充,不然你的图片大小会由于不填充而产生不符合预期的尺寸错误,从而导致错误,这里的卷积层自然不是单纯的卷积层,而是Conv+BN+Relu的经典组合,BN层的加入可以加速训练),那么说了这么多,我就直接贴代码了

#先构造卷积块,以便我接下来多次运用卷积层的时候可以复用
class ConvBlock(layers.Layer):
    def __init__(self,units,kenral_size,strides,use_activation=True):
        super(ConvBlock,self).__init__()
        self.conv2d=layers.Conv2D(units,kernel_size=kenral_size,strides=strides,padding='same')
        self.bn=layers.BatchNormalization()
        self.ua=use_activation
    def call(self,input):
        x=self.conv2d(input)
        x=self.bn(x)
        if self.ua==True:
            x=tf.nn.relu(x)
        return x
 
#这个细节分支非常简单,在每次卷积减少图片大小之后,后面在跟上不改变图像大小的卷积层用于提取特征
class DetailBranch(layers.Layer):
    def __init__(self):
        super(DetailBranch,self).__init__()
        self.s1Conv1=ConvBlock(units=64,kenral_size=3,strides=2)
        self.s1Conv2=ConvBlock(units=64,kenral_size=3,strides=1)
        self.s2Conv1=ConvBlock(units=64,kenral_size=3,strides=2)
        self.s2Conv2=ConvBlock(units=64,kenral_size=3,strides=1)
        self.s2Conv3=ConvBlock(units=64,kenral_size=3,strides=1)
        self.s3Conv1=ConvBlock(units=128,kenral_size=3,strides=2)
        self.s3Conv2=ConvBlock(units=128,kenral_size=3,strides=1)
        self.s3Conv3=ConvBlock(units=128,kenral_size=3,strides=1)
    def call(self,input):
        x=self.s1Conv1(input)
        x=self.s1Conv2(x)
        x=self.s2Conv1(x)
        x=self.s2Conv2(x)
        x=self.s2Conv3(x)
        x=self.s3Conv1(x)
        x=self.s3Conv2(x)
        x=self.s3Conv3(x)
        return x

这样一个非常简单的,DetailBranch我们就自定义好了,他用于提取图片特征最终将图片大小缩小在原来的1/8,我们可以检验一下

in1=keras.Input(shape=(256,256,3))
db=DetailBranch()
db(in1)

在这里插入图片描述

可以看到在经过网络输出后,数据大小缩小为原来1/8,维度为128层

1.2 SemanticBranch的构建

在这里插入图片描述

在这里语义分支作者有说明其实可以替换为其他的各种经典网络(resNet这些),但是作者在这里阐述了三种特殊的网络构造也可以用于代替经典网络,作为语义分支的主体,并且最终效果会更好,那么于是我个人也选择了作者提供的网络老作为主体,可以看到他们的名称简写分别为Stem,GE,CE,我们一个个介绍。

1.2.1 StemBlock的创建

Stem也就是StemBlock,论文中说:“它采用两种不同的下采样方式来缩小特征表示。然后将两个分支的输出特征串联起来作为输出。该结构具有高效的计算成本和有效的特征表达能力”。他的结构如下

在这里插入图片描述

也就是说stemBlock可以兼顾计算成本和特征提取,我们会将他作为分割分支的第一个特征提取块,图片输入后,经过卷积后,分为两个分支后,最后将经过两层卷积块后的输出与经过卷积核大小为3的最大池化层concate在一起,再卷积作为我们的输出,在这里作者给的这张图非常详细(甚至把每一步的输出都说明了,十分详细),那么我们也就可以直接照着这张图构造StemBlock了

class StemBlock(layers.Layer):
    def __init__(self,channels=16):
        super(StemBlock,self).__init__()
        self.conv1=ConvBlock(units=channels,kenral_size=3,strides=2)
        self.conv2=ConvBlock(units=channels//2,kenral_size=1,strides=1)
        self.conv3=ConvBlock(units=channels,kenral_size=3,strides=2)
        self.conv4=ConvBlock(units=channels,kenral_size=3,strides=1)
        self.maxpool=layers.MaxPool2D(pool_size=3,strides=2,padding='same')#这里注意一下padding一定要设置为‘same'不然输出数据大小会较小
    def call(self,input):
        x=self.conv1(input)
        x1=self.maxpool(x)#分支经过最大池化层后输出
        x2=self.conv2(x)
        x2=self.conv3(x2)#分支经过两层卷积后输出
        x3=tf.concat([x2,x1],axis=-1)
        #这里将两个分支的输出在最后一个维度融合在一起
        x3=self.conv4(x3)#最后一层卷积作为我们的输出
        return x3

那么到了这步作为分割分支的第一个块就自定义完毕了,那么我们仍然可以测试一下(最好是一定要测试一下,确定每个快没有错误之后,减少最终他们组建起来的错误)

in1=keras.Input(shape=(256,256,3))
s1=StemBlock()
s1(in1)

在这里插入图片描述

根据上图我们可以知道这里的理想输出是缩小为原图的1/4,维度数为16,与我这里构造的输出达到了完全一样的结果也就是我这里的结构创造的是正确的。

1.2.2 Gather-and-Expansion Layer的创建

GE,全称Gather-and-Expansion Layer,我个人翻译为特征聚集扩展层,在这里作者提供了他的多种结构

在这里插入图片描述

​ 其中a为是MobileNetv2中提出的移动反向瓶颈卷积,当步长为2的时候,虚线所示内容不存在。b,c都为建议的特征聚集扩展结构我这里采用较为复杂的c结构(多出的层效果应该会更好一些),所以我在这里选择了C结构进行搭建。

​ 那么可以看到这里的结构有一种特殊的卷积DWconv,全称DepthwiseConv2D,这种特殊的卷积层的卷积核个数不是我们人为决定的,是由输入数据的维度数决定的,他的处理数据方式如下图(https://blog.csdn.net/weixin_43937316/article/details/99545506),但是层提供了一个扩展因子参数depth_multiplier(默认情况下输出为1),数据H* w* c输入步长为1,padding方式为’same’的网络中输出为H* w* (c * depth_multiplier)

在这里插入图片描述

那么可以看到在提供的C结构中只由conv,DWConv这两中卷积为主体,所以对于该结构,我也就直接给出我的代码了

#因为在论文给出的结构中卷积层与BN层总是连在一起吗,所以我们为了代码复用性自定义层,控制卷积核大小,步长,和膨胀系数e
class DWConv(layers.Layer):
    def __init__(self,kernel_size,strides,e=1):
        super(DWConv,self).__init__()
        self.dwconv=layers.DepthwiseConv2D(kernel_size=kernel_size,strides=strides,depth_multiplier=e,padding='same')
        #为了防止图片大小尺寸错误,这里设置填充方式为'same
        self.bn=layers.BatchNormalization()
    def call(self,input):
        x=self.dwconv(input)
        return self.bn(x)
 
class GatherExpansion(layers.Layer):
    def __init__(self,units,expansion_ration,layers_name='',strides=2):
        super(GatherExpansion,self).__init__()
        self.conv1=ConvBlock(units=units,kenral_size=3,strides=1)
        self.conv2=ConvBlock(units=units,kenral_size=1,strides=1,use_activation=False)
        self.conv3=ConvBlock(units=units,kenral_size=1,strides=1,use_activation=False)
        self.dwconv1=DWConv(kernel_size=3,
                            strides=strides,
                            e=expansion_ration)
        self.dwconv2=DWConv(kernel_size=3,
                            strides=1,
                            e=1)
        self.dwconv3=DWConv(kernel_size=3,
                            strides=strides,
                            e=1)
        self.relu=layers.ReLU(name=layers_name)                    
    def call(self,input):
        x=self.conv1(input)
        x1=self.dwconv1(x)
        #这里是结构中唯一一次扩展通道将输出特征向高维输出
        x1=self.dwconv2(x1)
        x1=self.conv2(x1)
        x2=self.dwconv3(input)
        x2=self.conv3(x2)
        x3=tf.add(x1,x2)#两层分支最后加和      
        return self.relu(x3)#加和之后relu激活输出
1.2.3 ContextEmbeldingBlock的创建

CE,全称ContextEmbeldingBlock,原文中是这么说的“语义分支需要较大的接受域来捕获高级语义。因此,我们设计了一个具有全局平均池的上下文嵌入块来嵌入全局上下文信息”(因为博主太菜了,所以就直接引用原文QAQ),他的结构如下

在这里插入图片描述

是一个非常简单的结构输入数据经过全局池化(保留维度的全局池化),再经过不改变数据大小的卷积层处理后输出为(1 * 1 * C),与我们的原输入相加(H * W* C),这里其实在相加的时候运用了类似ndarray的广播机制,在相加的时候1* 1* C的数据自动扩展成H *W *C,然后与原数据相加,所以最终输出为H *W *C,代码如下:

class ContextEmbelding(layers.Layer):
    def __init__(self,units):
#这里需要注意我们的数据输入输出的维度数是要一样的,所以我的
        super(ContextEmbelding,self).__init__()
        self.conv1=ConvBlock(units,kenral_size=1,strides=1)
        self.conv2=ConvBlock(units,kenral_size=3,strides=1)
    def call(self,input):
        x=tf.reduce_mean(input,axis=[1,2],keepdims=True)
        #保持维度不变的求平均值
        x=layers.BatchNormalization()(x)
        x=self.conv1(x)
        x1=tf.add(input,x)#相加
        x1=self.conv2(x1)
        return x1

至此组成分割分支的所有组件,我们就全部自定义完了,但是再组装的时候还有个问题是我们需要注意的,在结构图中可以看到,分割分支中间总会输出一些数据,经过segHead,然后去计算loss,这里作者称之为分割头。“为了进一步提高分割精度,我们提出了一种增强训练策略。顾名思义,它类似于火箭助推器:它可以在训练阶段增强特征表示,在推理阶段可以放弃。因此,它在推理阶段增加的计算复杂度很少。”(这里的推理阶段指的是用于验证的时候?)

在这里插入图片描述

也就是在分割分支中,我们插入分割头,使得在模型还未完成的时候便计算loss,然后去应用于模型,所以我在这里先定义分割头的结构,在这里作者给出了它的结构

在这里插入图片描述

可以看到经过两层卷积后最后上采样输出结果去计算loss,那么在这里Ct参数也就是第一个卷积核的单元数,可以控制计算的复杂性,这里我个人因为CityScapes数据集的种类有34种,我倾向于先卷积为64层,然后第二层卷积为34层,最后上采样,为了减少计算量,最后我们直接使用双线性插值上采样(相对于反卷积这样的计算量会减少)。代码如下:

class SegHead(layers.Layer):
    def __init__(self,units,numclasses,size):
        #上采样的倍数由不同数据而改变
        super(SegHead,self).__init__()
        self.conv1=ConvBlock(units=units,kenral_size=3,strides=1)
        self.conv2=ConvBlock(units=numclasses,kenral_size=1,strides=1,use_activation=False)
        self.up=layers.UpSampling2D(size,interpolation='bilinear')
    def call(self,input):
        x1=self.conv1(input)
        x1=self.conv2(x1)
        x1=self.up(x1)
        return x1

1.3 双向引导汇聚层层的创建

在数据分别从细节分支和分割分支后输出我们还要将他聚合作为我们的最后输出,作者给出的结构如下,

在这里插入图片描述

这里细节分支的输出大小为原图的1/8,分割分支的输出为1/32,也就是分割分支的输出大小为细节分支的1/4,可以看到这里结构都非常的明显,分割分支经过处理sigmoid激活输出,然后与经过处理的细节分支相乘,这里要注意一点,这里在最后加和的时候不知道是作者没注意还是什么,最后sum的时候一个大小时另一个大小的1/4直接求和会出错,所以我个人是将右边的输出经过一次上采样之后与左边输出相同大小,在加和所以定义该模块代码如下:

class FeatureFusion(layers.Layer):
    def __init__(self,units=128,numclasses=34):
        super(FeatureFusion,self).__init__()
        self.dwconv1=DWConv(kernel_size=3,strides=1)
        self.conv1=ConvBlock(units=units,kenral_size=3,strides=2,use_activation=False)
        self.conv2=layers.Conv2D(units,kernel_size=1,strides=1,padding='same')
        self.avgpool=layers.AveragePooling2D(pool_size=3,strides=2,padding='same')
        self.conv3=ConvBlock(units=units,kenral_size=3,strides=1,use_activation=False)
        self.dwconv2=DWConv(kernel_size=3,strides=1)
        self.up1=layers.UpSampling2D(size=4,interpolation='bilinear')
        self.up2=layers.UpSampling2D(size=4,interpolation='bilinear')
        self.conv4=ConvBlock(units=units,kenral_size=1,strides=1,use_activation=False)
        self.conv5=ConvBlock(units=numclasses,kenral_size=3,strides=1,use_activation=False)
    def call(self,DB_input,SB_input):
        x1=self.dwconv1(DB_input)
        x1=self.conv2(x1)
        x2=self.conv1(DB_input)
        x2=self.avgpool(x2)
        x3=self.conv3(SB_input)
        x3=self.up1(x3)
        x3=tf.nn.sigmoid(x3)
        x4=self.dwconv2(SB_input)
        x4=self.conv4(x4)
        x4=tf.nn.sigmoid(x4)
        x=tf.multiply(x1,x3)
        y=tf.multiply(x2,x4)
        y=self.up2(y)
        out=tf.add(x,y)
        out=self.conv5(out)
        return out

1.4 整体模型的创建

在定义完组成整个模型的所有组件之后,我们接下来就开始将整个模型拼装在一起

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xE0lpgMN-1613790565150)(C:\Users\admin\Desktop\suanfa\博客BiseNetv2\2.png)]

这里我们直接按照如图给出的分支来构造整个网络,其中k是卷积核大小,c是输出维度,e为拥有DWConv网络的膨胀系数,s为步长,r为重复次数,这里由于我们要获得中间的多个输出,我这里打算使用函数式API来写,那我们就依葫芦画瓢,照着表格写网络

input1=layers.Input(shape=(1024,2048,3))
#CityScapes的数据大小为(1024,2048,3)
s1=StemBlock()
x1=s1(input1)#首先经过StemBlock
ge1=GatherExpansion(units=32,expansion_ration=6,layers_name='ge1',strides=2)
x2=ge1(x1)
ge2=GatherExpansion(units=32,expansion_ration=6,layers_name='ge2',strides=1)
x2=ge2(x2)#这里的x2是第二个需要经过分割头的数据
ge3=GatherExpansion(units=64,expansion_ration=6,layers_name='ge3',strides=2)
ge4=GatherExpansion(units=64,expansion_ration=6,layers_name='ge4',strides=1)
x3=ge3(x2)
x3=ge4(x3)
ge5=GatherExpansion(units=128,expansion_ration=6,layers_name='ge5',strides=2)
ge6=GatherExpansion(units=128,expansion_ration=6,layers_name='ge6',strides=1)
ge7=GatherExpansion(units=128,expansion_ration=6,layers_name='ge7',strides=1)
ge8=GatherExpansion(units=128,expansion_ration=6,layers_name='ge8',strides=1)#这里按照论文所说聚集扩展层重复三次

x4=ge5(x3)
x4=ge6(x4)
x4=ge7(x4)
x4=ge8(x4)
x4

在这里插入图片描述

这里可以清楚的看到,最终我们得到了需要用于输入分割头计算损失的四个数据,那么接下来我们就可以将所有零件拼装起来,作为我们的整体模型

class BiSeNetV2(keras.Model):
    def __init__(self,numclasses=34):
        super(BiSeNetV2,self).__init__()
        self._DetailBranch=DetailBranch()
        self.model1=model1=keras.models.Model(inputs=input1,outputs=x4)#利用函数式API创建模型,在上面的四个数据我们只需要x4作为我的最终输入
        self.FeatureFusion=FeatureFusion()
        self.contex=ContextEmbelding(128)
        self.up1=SegHead(units=64,numclasses=34,size=8)
    def call(self,input):
        DB_input=self._DetailBranch(input)#细节分支的输入
        x4=self.model1(input)
        SB_input=self.contex(x4)#最后经过上下文嵌入快
        result=self.FeatureFusion(DB_input,SB_input)
        result=self.up1(result)#z最后输出经过分割头作为我们的输出
        return result

至此,我们的整个模型就创造完成了(由于整个论文有给出非常详细的结构图,所以我在对于较为简单的结构注释上就没有过多注释)

2.数据预处理

本次使用的数据集是语义分割中经常使用的数据集,在我之前搭建BiseNet的博客(BiseNet学习:利用tensorflow2搭建BiseNet并训练完成语义分割任务_theworld666的博客-CSDN博客)中我介绍过,但是忘了写数据预处理部分,所以这里我就介绍一下我个人对于CityScapes的数据处理的部分所采用的操作(也算是我个人对于自己的备忘录)

首先我们明确目标,要读入数据的是一张原图和标注好的分割图,那么如果要读取图片就需要先获得图片的地址,所以我们先获得所有图片的地址

import glob
all_image_path=glob.glob('../input/cityscapes/Cityspaces/images/train/*/*.png')
#该数据集训练集的所有数据在train文件夹下的所有图片
all_label_path=glob.glob('../input/cityscapes/Cityspaces/gtFine/train/*/*_gtFine_labelIds.png')
#训练接对应的标签数据就是后缀为_gtFine_labelIds的图片

读取完所有图片了之后这里出现了一个问题,那就是我们如何保持图片与标签保持不变呢,这里其实很简单,我们经过排序之后,因为图片地址后缀一样所以对应的图片标签,就一定会在一个位置里所以我们编写代码,排序,然后为了增强数据在使用相同的种子,进行随机乱序,最终查看得到的结果,数据与标签是否一致

import numpy as np
all_label_path.sort()
all_image_path.sort()
index=np.random.permutation(len(all_image_path))
all_image_path=np.array(all_image_path)[index]
all_label_path=np.array(all_label_path)[index]
all_label_path[510:515],all_image_path[510:515]

在这里插入图片描述

可以看到我们随机得到的截取结果,数据和标签的名称都是一模一样的所以,我们就完成了整个数据的搭建,这里我们可以查看一下数据与标签

import matplotlib.pyplot as plt
def read_png_image(path):
    img=tf.io.read_file(path)
    img=tf.image.decode_png(img,channels=3)
    return img
def  read_png_label(path):
    img=tf.io.read_file(path)
    img=tf.io.decode_png(img,channels=1)
    return img   #定义读取图片和标签,标签为灰度图
img_1=read_png_image(all_image_path[0])
label_1=read_png_label(all_label_path[0])
plt.subplot(1,2,1)
plt.imshow(img_1.numpy())
plt.subplot(1,2,2)
plt.imshow(label_1.numpy())

在这里插入图片描述

可以看到我们成功的读取了图片和标签,那么接下来我们就可以开始创建输入模型的管道

@tf.function
def normal_data(img,label):
    img=tf.cast(img,tf.float32)
    img=img/255.0
    img=img*2-1#将整张图片的值规范在[-1,1]之间
    label=tf.cast(label,tf.float32)
    return img,label
def path_to_data_train(img_path,label_path):
    img=read_png_image(img_path)
    label=read_png_label(label_path)
    img=tf.image.resize(img,(1024,2048))
    label=tf.image.resize(label,(1024,2048))
    if tf.random.uniform(())>0.5:#随机左右翻转图像,增强数据
        img=tf.image.flip_left_right(img)
        label=tf.image.flip_left_right(label)
    return normal_data(img,label)
def path_to_data_test(img_path,label_path):#这里是测试数据的读取
    img=read_png_image(img_path)
    label=read_png_label(label_path)
    img=tf.image.resize(img,(1024,2048))
    label=tf.image.resize(label,(1024,2048))
    return normal_data(img,label)
BATCH_SIZE=2#这个批次大小是在是设备扛不住最终设定为2
BUFFER_SIZE=100
train_count=2975
val_count=500
step_per_epoch=train_count//BATCH_SIZE
val_step=val_count//BATCH_SIZE
auto=tf.data.experimental.AUTOTUNE#这个参数是加强CPU读取图片能力的

确定好所有参数后,我们开始划分训练集与测试集,并对训练集进行处理,设置各项参数

dataset_train=tf.data.Dataset.from_tensor_slices((all_image_path,all_label_path))
dataset_val=tf.data.Dataset.from_tensor_slices((img_val,label_val))
dataset_train=dataset_train.map(path_to_data_train,num_parallel_calls=auto)#使用map函数可以使用对应函数对于数据集中的所有数据按照所示函数进行转换
dataset_val=dataset_val.map(path_to_data_test,num_parallel_calls=auto)#验证集的读取方法与测试集一样,所以我就没有再说明
dataset_val,dataset_train

3.模型训练与结果评估

搭建完了模型,处理完了数据,那么万事具备,我们接下来自定义训练步骤就行了(由于使用了自定义模型,使用model.fit可能会报错,所以我们个人自定义模型)就可以了,那么我们先定义各个自定义训练组件如下

#语义分割有一个重要评估标准IOU,这里我们也需要自定义
class MeanIOU(keras.metrics.MeanIoU):
    def __call__(self,y_true,y_pred):
        y_pred=tf.argmax(y_pred,axis=-1)
        return super().__call__(y_true,y_pred)
optimizer=keras.optimizers.SGD(learning_rate=0.01,momentum=0.9)
#按照论文所说,这里我们使用SGD优化器
loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True)#指定多分类损失,并且由于最后没有激活所以指定from_logits为True
train_loss=keras.metrics.Mean(name='train_loss')
train_acc=keras.metrics.SparseCategoricalAccuracy(name='train_accuracy')
train_iou=MeanIOU(34,name='train_iou')
test_loss=keras.metrics.Mean(name='test_loss')
test_acc=keras.metrics.SparseCategoricalAccuracy(name='test_accuracy')
test_iou=MeanIOU(34,name='test_iou')
#指定用于验证的损失,准确率,iou

还记得我们之前说过的分割头结构,提前计算损失并应用于模型这里我们定义他如下

def rocket (inputs,units,size,numclasses,x):
    model=keras.models.Model(inputs=input1,outputs=x)
    with tf.GradientTape() as t:
        pred=model(inputs)
        pred=SegHead(units,numclasses,size)(pred)
        loss1=loss(labels,pred)#计算损失
    gradies=t.gradient(loss1,net.trainable_variables)
    #求解梯度
    optimizer.apply_gradients(zip(gradies,net.trainable_variables))#应用梯度

然后在训练步骤中,我们这样采用


def train_step(images,labels):
    rocket(images,16,4,numclasses=34,x=x1)
    rocket(images,32,8,numclasses=34,x=x2)
    rocket(images,64,16,numclasses=34,x=x3)
    rocket(images,128,32,numclasses=34,x=x4)#增加四层分割头结构
    with tf.GradientTape() as t:
        pred=net(images)
        loss_step=loss(labels,pred)
    gradies=t.gradient(loss_step,net.trainable_variables)
    #求解梯度
    optimizer.apply_gradients(zip(gradies,net.trainable_variables))#将梯度应用于优化器从而让模型的可训练参数改变
    train_loss(loss_step)
    train_acc(labels,pred)
    train_iou(labels,pred)

那么开始我们的训练


def test_step(images,labels):
    pred=net(images)
    loss_step=loss(labels,pred)
    test_loss(loss_step)
    test_acc(labels,pred)
    test_iou(labels,pred)
Epoch=20#先训练20次
for epoch in range(Epoch):
    train_loss.reset_states()#重置每个参数的状态
    train_acc.reset_states()
    train_iou.reset_states()
    test_acc.reset_states()
    test_loss.reset_states()
    train_iou.reset_states()
    for images,labels in dataset_train:
        train_step(images,labels)
        print('-',end='')#查看训练没批次是否完成
    print('>')
    for img_test,label_test in dataset_val:
        test_step(img_test,label_test)
    template = 'Epoch {:.3f}, Loss: {:.3f}, Accuracy: {:.3f}, \
                IOU: {:.3f}, Test Loss: {:.3f}, \
                Test Accuracy: {:.3f}, Test IOU: {:.3f}'
    print (template.format(epoch+1,
                           train_loss.result(),
                           train_acc.result()*100,
                           train_iou.result(),
                           test_loss.result(),
                           test_acc.result()*100,
                           test_iou.result() 
                           ))#输出我们的评价结果

我们可以来看一下训练结果,在经过多次训练后,达到效果如下:

Epoch 17.000, Loss: 0.608, Accuracy: 82.627, IOU: 0.251, Test Loss: 0.632, Test Accuracy: 81.939, Test IOU: 0.195

Epoch 18.000, Loss: 0.594, Accuracy: 82.996, IOU: 0.256, Test Loss: 0.631, Test Accuracy: 82.225, Test IOU: 0.197

可以看到在第十七次到第十八次的时候我们的模型仍有非常明显的上升趋势(但是机器扛不住了,我是在KAGGLE上训练的,已经达到KAGGLE能允许离线的最长时间了),我个人认为如果使用预训练神经网络应该能在几次训练中迅速达到非常高的正确率(因为使用了提前训练好的权重),但是这里由于采用了我们自己搭建的架构,权重是随机初始化的,所以我的正确率在十几次训练的时候达到82.996(十几次在训练中也算少了吧,只是由于本人没设备就只能训练这几次),但是自己搭建的话上限应该会比预训练神经网络为架构的模型高。有兴趣的朋友可以自己搭建模型训练一下(我看看什么时候优化下模型和训练步骤再把博客修改一下

︿( ̄︶ ̄)︿)。

结语

在本篇博客中,博主按照论文和源码完成了BiseNetV2的所有网络组件的搭建,并搭建了网络,完成了对CityScapes数据集的预处理,并最终训练评估了整体模型。由于博主个人水平有限,所以出现错误在所难免,如果有任何建议或者疑问欢迎在评论区交流。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

轻量级语义分割网络 BiseNetv2学习:手把手教你搭建BiseNetv2(tensorflow2),并在Cityscapes上完成语义分割任务 的相关文章

随机推荐

  • 计算机保护插件无法安装,电脑无法安装ActiveX控件怎么办

    ActiveX控件是网站常用的一款网页辅助工具 有时候我们可能需要安装它 但是却发现浏览器阻止了它安装 那么你知道电脑无法安装ActiveX控件怎么办吗 下面是学习啦小编整理的一些关于电脑无法安装ActiveX控件的相关资料 供你参考 电脑
  • flask中路由函数定义中遇到的问题

    flask的路由功能很强大 可以很清晰明了的定义出需要的路由函数 但是由于python语言的弱类型设计引来了一些不易发现的问题 app route task get methods GET def get tasks get the tas
  • python字符串转日期_使用Python将字符串转换为格式化的日期时间字符串

    我正在尝试将字符串 20091229050936 转换为 2009年12月29日 UTC gt gt gt import time gt gt gt s time strptime 20091229050936 Y m d H M S gt
  • python3 [爬虫入门实战]爬虫之scrapy爬取游天下南京短租房存mongodb

    总结 总的来说不是很难 只是提取的字段有些多 总共获取了一个120多个南京房租信息 1 爬取的item coding utf 8 Define here the models for your scraped items See docum
  • 《重构 - 改善既有代码的设计》总结

    1 重构 第一个示例 重构前 先检查自己是否有一套可靠的测试集 这些测试必须有自我验证能力 TDD 重构技术就是以微小的步伐修改程序 如果犯下错误 很容易便可发现它 傻瓜都能写出计算机可以理解的代码 唯有能写出人类容易理解的代码的 才是优秀
  • 元宇宙不是Web3

    个人对元宇宙的定义为 大规模 可互操作的网络 能够实时渲湘3D虚拟世界 借助大量连使性数据 如身份 历史 权利 对象 通信和支付等 可以让无限数量的用户体脸实时同步和持续有效的在场感 现在 你应该能理解我为何会给出这样的定义了 许多人可能会
  • 使用Junit进行单元测试超详细,这你还学不会?

    单元测试 从字面上来看就是对某一个功能单元进行测试 测试其功能是否正常 也就是说在给定的输入参数情况下 测试其结果的正确性 当这几天又重新温顾这一章节 我马上想起了前几天较劲脑静通过其测试用例的场景 一 介绍 1 Java中的最小功能单元是
  • react实现Modal弹窗

    一 Dialog js文件 import React useMemo useEffect useState from react import ReactDOM from react dom 需要把元素渲染到组件之外 用 createPor
  • git通过http的方式下载和提交代码

    之前一直用git的SSH方式下载代码 唯一的缺点可能就是需要建立SSH秘钥 需要额外生成一个密钥 然后在下载和提交代码的时候都需要输入秘钥 才能操作 时间长了可能就忘了密码 今天在做项目提交的时候就出现了这种情况 密码怎么试都不对 于是弃用
  • 理解不同加密币的要点(一)—— 共识机制

    一 加密货币分类 一 价值层面 与实体资产绑定的代币 为了因应市场需求而生 与实体资产做挂钩的代币 也就是我们说的稳定币 例如与美元做挂钩的USDT TUSD PAX与USDC等 仰赖网络共识的代币 代币价值仰赖网路上市场共识的代币 基本上
  • Java使用流去除集合中某个字段为空的对象

    文章目录 0 写在前面 1 情景复刻 2 解决方案 3 写在最后 0 写在前面 最近写了一些业务逻辑 调试的时候总会报空指针异常 Java中空指针异常是危险恐怖分子 最好不要碰见他 所以有些时候 处理集合中的数据时 特定情况下需要略过一些数
  • python在两行中分别输入一个字符串s和整数n,定义一个函数将字符串s循环向右移动n位

    解题思路 将字符串转为列表处理 123456 1 2 3 4 5 6 将列表重复两遍 1 2 3 4 5 6 1 2 3 4 5 6 删去前面和后面多余的数字即可 4 5 6 1 2 3 4 5 6 4 5 6 1 2 3 问题描述 在两行
  • MySQL修改和删除索引(DROP INDEX)

    在 MySQL 中修改索引可以通过删除原索引 再根据需要创建一个同名的索引 从而实现修改索引的操作 基本语法 当不再需要索引时 可以使用 DROP INDEX 语句或 ALTER TABLE 语句来对索引进行删除 1 使用 DROP IND
  • 小智AI chatgpt的功能是什么

    ChatGPT是一种基于GPT 3 5架构的大型语言模型 由OpenAI开发 它是一种聊天机器人 能够回答各种问题 提供有用的信息和娱乐 ChatGPT的工作原理非常简单 当用户输入问题或话题时 ChatGPT会自动根据语境和关键字生成响应
  • Idea SpringBoot多模块项目打包血泪史

    本文主要介绍基于Idea的SpringBoot多模块打包中遇到的各种问题以及解决方法 基本概况介绍 小弟之前是用的myeclipse进行的开发 但是在建立多模块的时候却错误的使用了web archtype 然后强转的SpringBoot 在
  • CentOS 6和Centos 7 虚拟机 关闭防火墙

    Centos 7 虚拟机 关闭防火墙 https blog csdn net preserveXing article details 127076756 CentOS 6 防火墙的关闭 关闭其服务即可 查看CentOS防火墙信息 etc
  • CDN的加速原理

    CDN的加速原理是什么 CDN Content Delivery Network 内容分发网络 是构建在现有互联网基础之上的一层智能虚拟网络 通过在网络各处部署节点服务器 实现将源站内容分发至所有CDN节点 使用户可以就近获得所需的内容 C
  • Centos8(7)安装tomcat9以及常见用法

    1 安装jdk tomcat9可以工作在jdk8以上的版本 所以可以安装jdk8或是jdk11 dnf install java 11 openjdk centos8 yum install java 11 openjdk centos7
  • 关于之前封装Sku组件异步获取数据的问题思考

    使用watchEffect来监听父组件传来的goods 为什么我会用watchEffect 起初我的想法是 我需要生成数据字典并且初始化数据这两个函数当中都会用到很多的goods中的数据 如果使用watch监听要设置岂不是很麻烦 但是其实后
  • 轻量级语义分割网络 BiseNetv2学习:手把手教你搭建BiseNetv2(tensorflow2),并在Cityscapes上完成语义分割任务

    轻量级语义分割网络 BiseNetv2学习 手把手教你搭建BiseNetv2 tensorflow2 并在Cityscapes上完成语义分割任务 文章目录 轻量级语义分割网络 BiseNetv2学习 手把手教你搭建BiseNetv2 ten