语义分割模型的优化

语义分割模型的优化

当发现验证集指标和训练集指标相差较大时,主要可以检查这些原因:

  1. 数据集类别芜杂、数据量不够,先检查数据集和数据迭代器的质量。
  2. 如果训练集较快拟合,则模型过于庞大,降低了鲁棒性,可以降低batchsize或减少层数、卷积核数量。
  3. 若训练集指标也不正常,将学习率调整到[1e-2, 1e-6],各测试一遍。若loss曲线仍有问题,调整损失函数和激活函数。再不行,换个网络和显卡试试吧。

常用优化技巧

  1. 数据增强
    使用 albumentations,随机旋转、镜像、模糊、色彩映射、噪音、曝光、翻转、色差平移。
    albumentations

  2. 模型的投票
    融合多个模型进行集成,采用多个模型对各个像素点进行投票,对于每张图片在不同scale(不同放大比例、旋转参数)下的结果融合。

  3. TTA (Test-Time Augmentation) ,即测试时的数据增

  4. 强。
    TTA
    TTA
    mask_xor = (mask^bg)&mask

  5. 联合损失函数BCE+Dice+Focal+lovasz_softmax
    将不同损失函数以一定权重混合,获得较好的鲁棒性。
    联合损失函数
    LovaszSoftmax

  6. CRF后处理
    SegNet做语义分割时通常在末端加入CRF模块做后处理,旨在进一步精修边缘的分割结果。
    CRF
    pydensecrf
    CRF-semantic-segmentation

关于学习率的优化

  1. Warm up 预热
    采用小学习率预训练。当训练时出现训练指标上升、验证集指标不动的奇怪现象时,此方法极其有效。预训练后再加大LR。
  2. 按val_miou减小
    使用keras.callbacks.ReduceLROnPlateau,根据val_miou的变化来动态调整lr。
  3. 余弦退火
    CosineAnnealing

损失函数和激活函数、通用评估指标

损失函数

损失函数选用keras.losses.binary_crossentropy

激活函数

单通道输出(二分类,[batch_size, 512, 512 ,1]),选用Sigmoid。
多通道输出(多分类,[batch_size, 512, 512 , N]),选用Softmax,其中第一层是背景层。

通用评估指标 IOU 代码

from tensorflow.keras import backend as K
def Iou_score(y_true, y_pred):
    '''总体的IOU'''
    smooth = 1e-5
    threhold = 0.5
    # score calculation
    y_pred = K.greater(y_pred, threhold)
    y_pred = K.cast(y_pred, K.floatx())
    intersection = K.sum(y_true * y_pred, axis=[0,1,2])
    '''
    这里y_pred为四维,[16,512,512,2],
    axis=[0,1,2]时输出的intersection是每个类别的得分(准确的数量,[20, 30])。
    [16,512,512,1]时候也通用。
    '''
    union = K.sum(y_true + y_pred, axis=[0,1,2]) - intersection
    return (intersection + smooth) / (union + smooth)

实现数据增强的语义分割数据迭代器代码

from tensorflow.keras.utils import Sequence
import random, os, gc, cv2
import numpy as np
seed = 295
random.seed(seed)
import os
import cv2 as cv
import albumentations as A
 # pip install albumentations -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com

class SequenceData(Sequence):

    def __init__(self, images_dir_X, images_dir_Y, img_size=(256,256),imgOnly=False,
                 batch_size=1, classes=None, imgListTxt=None, isOneHot=True, dataEnhancement=True):
        with open(imgListTxt) as f:
            # 为了对应样本和标签 读入的文件名不带后缀
            self.datas = list(f.readlines())
        self.images_dir_X = images_dir_X
        self.images_dir_Y = images_dir_Y
        self.batch_size = batch_size
        self.L = len(self.datas)
        self.img_size = img_size
        self.index = random.sample(range(self.L), self.L)
        self.classes=classes
        self.isOneHot=isOneHot
        self.imgOnly=imgOnly
        
        self.dataEnhancement=dataEnhancement
        prob = 0.4
        self.transform = A.Compose([
            # A.RandomCrop(width=256, height=256),
            A.HorizontalFlip(p=0.5),
            A.RandomBrightnessContrast(brightness_limit=(0, 0.2),
                                       contrast_limit=(0, 0.2), p=prob),
            A.Rotate(limit=30, interpolation=cv2.INTER_CUBIC, border_mode=4, p=prob),
            A.RandomGamma(gamma_limit=(80, 120), eps=1e-07, p=prob),
            A.MotionBlur(blur_limit=5, p=prob),
            A.IAASharpen(p=prob),
            A.IAAPerspective(p=prob),
            A.GaussNoise(var_limit=(10.0, 50.0), p=prob),
            
            A.HueSaturationValue(hue_shift_limit=10, sat_shift_limit=110,
                                          val_shift_limit=10, p=prob),
            A.RGBShift(r_shift_limit=5, g_shift_limit=5, b_shift_limit=5, p=prob),
            A.OpticalDistortion(distort_limit=0.05, shift_limit=0.05, interpolation=2, p=prob),
            A.GridDistortion(num_steps=5, distort_limit=0.3, interpolation=2, p=prob),
        ])
       
        
    # 返回长度,通过len(<你的实例>)调用
    def __len__(self):
        return int(np.ceil(len(self.datas) / self.batch_size))
    
    # 通过索引获取a[0],a[1]这种
    def __getitem__(self, idx):
        
        batch_indexs = self.index[idx:(idx+self.batch_size)]
        batch_datas = [self.datas[k] for k in batch_indexs]
        
        images = self.load_image_from_directory(
            images_dir=self.images_dir_X, img_size=self.img_size,
            suffix='.jpg', imgList=batch_datas)
        if self.imgOnly:
            return images
        
        labels = self.load_image_from_directory(
            images_dir=self.images_dir_Y, img_size=self.img_size,
            isLabel=True, isOneHot=self.isOneHot, classes=self.classes, suffix='.png',
            imgList=batch_datas)
        
        if self.dataEnhancement:
            images,labels = self.dataEnhancing(images,labels)
            
        
        return images,labels

    def load_image_from_directory(self, images_dir, img_size, isLabel=False,
                                  classes=None,isOneHot=True, suffix='.png',
                                  dtype=np.float32, imgList=None):
        """
        从数据集目录中加载图像数组
        :param images_dir: 数据集目录,原图或标签,该文件夹下直接是图像,三通道,24位深度
        :param img_size: 网络要求的图像大小
        :param isLabel: 是否为标签, 测试集的标签由于只是显示作用,不参与训练,因此保持默认False即可
        :param classes: 类,与isLabel相匹配,isLabel=True时,必须赋值
        :param suffix: 图像后缀
        :param dtype: 图像数据类型
        :param imgList: 图像文件名列表
        :return: 返回图像数组,[图像个数,高,宽,通道数]
        """
        images_path = []
        for fname in imgList:
            # if fname.endswith(suffix) and not fname.startswith('.'):
            # 数组填充,将图像绝对路径添加至数组images_path末尾
            images_path.append(os.path.join(images_dir, fname[:-1]+suffix))
        images_path = sorted(images_path)  # 按顺序整理
        # print(len(images_path))
        images = []  # 创建空数组
        for i, path in enumerate(images_path):
            img = cv.imdecode(np.fromfile(path, dtype=np.uint8),
                              cv.IMREAD_COLOR)  # 可为中文路径读取图像
            # img = cv.imread(path)  # 读取图像, 无中文路径
            if img.shape[:2] is not img_size:
                # img = cv.resize(img, dsize=img_size,
                #                 interpolation=cv.INTER_CUBIC)  # 不满足条件重置图像尺寸
                img = self.resize_img_keep_ratio(img=img, target_size=img_size)
            img = img[:, :, ::-1]  # 交换图像通道
            if isLabel:
                if isOneHot:
                    # 创建空数组用于储存图像数组
                    newImg = np.zeros(img.shape[:2] + (len(classes),), dtype=dtype)
                    for j, value in enumerate(classes.values()):
                        newImg[np.bitwise_and(np.bitwise_and(
                            img[:, :, 0] == value[0], img[:, :, 1] == value[1]),
                            img[:, :, 2] == value[2]), j] = 1  # 把标签转为one-hot形式
                    img = newImg
                else:
                    img = img[:,:,0] / 255.0
                    img[img>=0.5] = 1.0
                    img[img<0.5] = 0.0
                    img = np.expand_dims(img, axis=-1)  # 扩充维度
            images.append(img)
            # images.append(self.resize_img_keep_ratio(img=img, target_size=img_size))
        images = np.array(images, dtype=dtype)  # 改变数组类型
        if not isLabel:
            images = images/255.0
        # print(images.shape)
        return images
    def resize_img_keep_ratio(self, img=None,img_name=None,target_size=(256,256)):
            '''
            1.resize图片,先计算最长边的resize的比例,然后按照该比例resize。
            2.计算四个边需要padding的像素宽度,然后padding
            '''
            if img is None:
                img = cv2.imread(img_name)
            old_size= img.shape[0:2]
            ratio = min(float(target_size[i])/(old_size[i]) for i in range(len(old_size)))
            new_size = tuple([int(i*ratio) for i in old_size])
            img = cv2.resize(img,(new_size[1], new_size[0]),interpolation=cv2.INTER_CUBIC)  #注意插值算法
            pad_w = target_size[1] - new_size[1]
            pad_h = target_size[0] - new_size[0]
            top,bottom = pad_h//2, pad_h-(pad_h//2)
            left,right = pad_w//2, pad_w -(pad_w//2)
            img_new = cv2.copyMakeBorder(img,top,bottom,left,right,cv2.BORDER_CONSTANT,None,(0,0,0))
            # return cv2.cvtColor(img_new, cv2.COLOR_BGR2RGB)
            return img_new
    def dataEnhancing(self,images,labels,dtype=np.float32):
        '''数据增强'''
        transformed_images,transformed_masks=[],[]
        # for image,mask in zip(images,labels):
        # print(images.shape[0])
        for i in range(images.shape[0]):
            image=np.asarray(images[i,...]*255.0).astype(np.uint8)
            mask=labels[i,...]
            if(mask.shape[-1]==1):
                mask=mask.reshape(mask.shape[:-1])
            transformed = self.transform(image=image, mask=mask)
            outImg = np.asarray(transformed["image"]).astype(dtype)/255.0
            transformed_images.append(transformed["image"])
            transformed_masks.append(transformed["mask"])
        transformed_images=np.asarray(transformed_images,dtype=dtype)
        transformed_masks=np.asarray(transformed_masks,dtype=dtype)
        # print(transformed_images.shape,transformed_masks.shape)
        return transformed_images,transformed_masks
   


# from tensorflow.keras.preprocessing.image import array_to_img
# def showImg(frame):
#     array_to_img(frame).show()
# classes = dict(
#         [('background', [0, 0, 0]), ('object', [255, 255, 255])]) 
# trainSDG = SequenceData(r".\ourData\combine\images\1",
#                 r"ourData\combine\labels\1",
#             img_size=(512,512), classes=classes, batch_size=1,
#             imgListTxt=r".\ourData\combine\sets\train.txt", dataEnhancement=False)
# xx=trainSDG.__getitem__(9)
# x0=np.array(xx[0])[0,...]*255.0
# x1=np.array(xx[1])[0,...]
# showImg(np.expand_dims(x1[:,:,0],axis=-1))
# showImg(np.expand_dims(x1[:,:,1],axis=-1))
# showImg(x0)

# from tensorflow.keras.preprocessing.image import array_to_img
# def showImg(frame):
#     array_to_img(frame).show()
# classes = dict(
#         [('background', [0, 0, 0]), ('object', [255, 255, 255])]) 
# trainSDG = SequenceData(r".\ourData\combine\images\1",
#                 r"ourData\combine\labels\1", isOneHot=False,
#             img_size=(512,512), classes=classes, batch_size=2,
#             imgListTxt=r".\ourData\combine\sets\train.txt", dataEnhancement=False)
# xx=trainSDG.__getitem__(5)
# x0=np.array(xx[0])[0,...]*255.0
# x1=np.array(xx[1])[0,...]
# showImg(x1[:,:])
# showImg(x0)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值