YOLOv3 代码详解(3) —— 数据处理 dataset.py的多进程改造

news/2024/7/10 3:17:52 标签: 目标检测, 深度学习, tensorflow

前言:

yolo系列的论文阅读
论文阅读 || 深度学习目标检测 重磅出击YOLOv3
论文阅读 || 深度学习目标检测yolov2
论文阅读 || 深度学习目标检测yolov1

该篇讲解的工程连接是:
tensorflow的yolov3:https://github.com/YunYang1994/tensorflow-yolov3

自己对该工程的解析博客:
YOLOv3 || 1. 使用自己的数据集训练yolov3
YOLOv3 || 2. dataset.py解析
YOLOv3 || 3. dataset.py的多进程改造
YOLOv3 || 4. yolov3.py 网络结构的搭建和loss的定义
YOLOv3 || 5. train.py
YOLOv3 || 6. anchorboxes的获取 kmeans.py
YOLOv3 || 7. yolov3的pb文件的测试

数据读取部分,需要经过较多处理,CPU的计算经常会占用大量时间。导致GPU经常处于等待的状态,GPU利用率较低,整个训练时间大大延长。 所以我们希望快速读取数据,来提高GPU的利用率。
该篇是对数据读取部分做多进程的改造,利用python的多进程,来完成数据的快速读取。

其实 tensorflow有相关函数来完成多进程,但个人感觉使用起来并不简便

from multiprocessing import Queue, Process

1 原工程的数据读取的回顾

上一篇链接中也提到

class Dataset(object):
  """implement Dataset here"""
  def __init__(self, dataset_type):
      ...
  def __iter__(self):
      return self
  def __next__(self):
      ...
  def __len__(self):
     return self.num_batchs


其中主要内容为def __next__(self),从多进程改造的角度来看,该函数分为3部分

  • 创建存放【输入图片】和【label 】的数组
  • 设置读取数量的判定条件 || if / for / while相关内容
  • 数据增强处理和label的制作,并return


具体定义为

    def __next__(self):

       with tf.device('/cpu:0'):

           """part 1:创建存放【输入图片】和【label 】的数组"""
           self.train_input_size = random.choice(self.train_input_sizes)
           self.train_output_sizes = self.train_input_size // self.strides
           # 创建存放【输入图片】的数组
           batch_image = np.zeros((self.batch_size, self.train_input_size, self.train_input_size, 3))
           # 创建存放【label】的数组
           batch_label_sbbox = np.zeros((self.batch_size, self.train_output_sizes[0], self.train_output_sizes[0], self.anchor_per_scale, 5 + self.num_classes))
           batch_label_mbbox = np.zeros((self.batch_size, self.train_output_sizes[1], self.train_output_sizes[1], self.anchor_per_scale, 5 + self.num_classes))
           batch_label_lbbox = np.zeros((self.batch_size, self.train_output_sizes[2], self.train_output_sizes[2], self.anchor_per_scale, 5 + self.num_classes))
            # 创建存放 在3个尺度下,负责预测的bboxes
           batch_sbboxes = np.zeros((self.batch_size, self.max_bbox_per_scale, 4))
           batch_mbboxes = np.zeros((self.batch_size, self.max_bbox_per_scale, 4))
           batch_lbboxes = np.zeros((self.batch_size, self.max_bbox_per_scale, 4))

           """part 2:设置读取数量的判定条件 || if / for / while相关内容"""
           num = 0       # 批内的计数器
           if self.batch_count < self.num_batchs:     # 当【已读取的批数】小于【一轮总批数】
               while num < self.batch_size:           # 当【批内读取个数】小于【batch】
                   index = self.batch_count * self.batch_size + num  # 【index】为一轮内已经读取的数据个数
                   if index >= self.num_samples: index -= self.num_samples  # 如果【index】大于【数据总量】,将index置0
                   
                   """part 3: 数据增强处理和label的制作,并return """
                   # 读取训练集或验证集的数据信息(输入图片路径、bboxes)
                   annotation = self.annotations[index]
                   # 数据增强:随机翻转、随机裁剪、随机平移、将图片缩放和填充到target_shape(相同规则处理bboxes)
                   image, bboxes = self.parse_annotation(annotation)
                   # 结合anchorbox等信息,将bboxes的信息,转化为神经网络训练的label
                   label_sbbox, label_mbbox, label_lbbox, sbboxes, mbboxes, lbboxes = self.preprocess_true_boxes(bboxes)

                   batch_image[num, :, :, :] = image
                   batch_label_sbbox[num, :, :, :, :] = label_sbbox
                   batch_label_mbbox[num, :, :, :, :] = label_mbbox
                   batch_label_lbbox[num, :, :, :, :] = label_lbbox
                   batch_sbboxes[num, :, :] = sbboxes
                   batch_mbboxes[num, :, :] = mbboxes
                   batch_lbboxes[num, :, :] = lbboxes
                   num += 1
               self.batch_count += 1
               return batch_image, batch_label_sbbox, batch_label_mbbox, batch_label_lbbox, \
                      batch_sbboxes, batch_mbboxes, batch_lbboxes
           else:
               self.batch_count = 0
               np.random.shuffle(self.annotations)
               raise StopIteration

2 工程改造

为了尽量小的变动源代码,这里重新创建个脚本load_data.py,来实现多进程。

思路: 使用队列和多进程对于一个dataset的对象,属性增加两个队列。
多进程获取数据信息(图片路径和bboxes)的来源与Q_name,多进程处理后的数据放入Q_data。

  • Q_data: 存放的是神经网络训练时的label。创建多个进程进行数据读取,然后都放入Q_data对列中,送入神经网络的placehoder然后进行训练。
  • Q_name: 存放文本读取的数据信息(图片路径和bboxes)。

疑问: 为什么要创建了读取数据名字等信息的队列呢?
因为按照多进程读取数据时,每个进程读取自己的数据名字的列表,相同的数据可能在一定范围内多次出现。这样就相当于数据集拷贝了n(多进程数量)倍,然后进行乱序读取(虽然这种方式对训练的结果影响不是很大)。所以创建个名字的对列,多进程获取数据信息的来源与Q_name,多进程处理后的数据放入Q_data。


2.1 dataset.py的改动

2.1.1 def __init__():部分的改动

该部分主要是添加了类Dataset的属性Q_data、Q_name。以便其他脚本对类Dataset的这两个属性操作的有效性。

class Dataset(object):
   def __init__(self, dataset_type):
        self.annot_path  = cfg.TRAIN.ANNOT_PATH if dataset_type == 'train' else cfg.TEST.ANNOT_PATH
        self.input_sizes = cfg.TRAIN.INPUT_SIZE if dataset_type == 'train' else cfg.TEST.INPUT_SIZE
        self.batch_size  = cfg.TRAIN.BATCH_SIZE if dataset_type == 'train' else  cfg.TEST.BATCH_SIZE
        self.data_aug    = cfg.TRAIN.DATA_AUG   if dataset_type == 'train' else cfg.TEST.DATA_AUG
        self.train_input_sizes = cfg.TRAIN.INPUT_SIZE
        self.strides = np.array(cfg.YOLO.STRIDES)
        self.classes = utils.read_class_names(cfg.YOLO.CLASSES)
        self.num_classes = len(self.classes)
        self.anchors = np.array(utils.get_anchors(cfg.YOLO.ANCHORS))
        self.anchor_per_scale = cfg.YOLO.ANCHOR_PER_SCALE
        self.max_bbox_per_scale = 150
           
        self.annotations = self.load_annotations(dataset_type)
        self.num_samples = len(self.annotations)    # 样本的数量
        self.num_batchs = int(np.ceil(self.num_samples / self.batch_size))  # 一轮读取批
        self.batch_count = 0
           
         """添加的部分"""
        self.num_trianepoch = cfg.TRAIN.FISRT_STAGE_EPOCHS + cfg.TRAIN.SECOND_STAGE_EPOCHS
        self.Q_name = []      
        self.Q_data = []


2.1.2 def __next__():部分的改动

前面提到过:def __next__(self),从多进程改造的角度来看,该函数分为3部分

  • 创建存放【输入图片】和【label 】的数组
  • 设置读取数量的判定条件 || if / for / while相关内容
  • 数据增强处理和label的制作,并return


def __next__(self)中,保留第一、三部分的操作。

   def Prepare(self):
     with tf.device('/cpu:0'):
       self.train_input_size = random.choice(self.train_input_sizes)
       self.train_output_sizes = self.train_input_size // self.strides
       # 创建存放【输入图片】的数组
       batch_image = np.zeros((self.batch_size, self.train_input_size, self.train_input_size, 3))
       # 创建存放【label】的数组
       batch_label_sbbox = np.zeros((self.batch_size, self.train_output_sizes[0], self.train_output_sizes[0], self.anchor_per_scale, 5 + self.num_classes))
       batch_label_mbbox = np.zeros((self.batch_size, self.train_output_sizes[1], self.train_output_sizes[1], self.anchor_per_scale, 5 + self.num_classes))
       batch_label_lbbox = np.zeros((self.batch_size, self.train_output_sizes[2], self.train_output_sizes[2], self.anchor_per_scale, 5 + self.num_classes))
        # 创建存放 在3个尺度下,负责预测的bboxes
       batch_sbboxes = np.zeros((self.batch_size, self.max_bbox_per_scale, 4))
       batch_mbboxes = np.zeros((self.batch_size, self.max_bbox_per_scale, 4))
       batch_lbboxes = np.zeros((self.batch_size, self.max_bbox_per_scale, 4))


  def readdata(self, annotation, num):
    # 读取训练集或验证集的数据信息(输入图片路径、bboxes)
    # annotation = self.annotations[index]

    # 数据增强:随机翻转、随机裁剪、随机平移、将图片缩放和填充到target_shape(相同规则处理bboxes)
    # 此时bboxes为【左上角右下角】形式
    image, bboxes = self.parse_annotation(annotation)
    # 结合anchorbox等信息,将bboxes的信息,转化为神经网络训练的label
    label_sbbox, label_mbbox, label_lbbox, sbboxes, mbboxes, lbboxes = self.preprocess_true_boxes(bboxes)

    self.batch_image[num, :, :, :] = image
    self.batch_label_sbbox[num, :, :, :, :] = label_sbbox
    self.batch_label_mbbox[num, :, :, :, :] = label_mbbox
    self.batch_label_lbbox[num, :, :, :, :] = label_lbbox
    self.batch_sbboxes[num, :, :] = sbboxes
    self.batch_mbboxes[num, :, :] = mbboxes
    self.batch_lbboxes[num, :, :] = lbboxes

2.2 load_data.py的编写

2.2.1 class QueueGene()

实现的功能:将数据存到队列中。包含原本工程中数据读取中的def __next__(self)的第二部分,设置读取数量的判定条件 || if / for / while相关内容。

from multiprocessing import Queue, Process
from core.dataset import *
import random


class QueueGene():
   def __init__(self):
       print("创建数据队列初始化成功")
     
   def getname(self, datagene):
       print("向队列Q_name中,存放文本读取的数据信息(图片路径和bboxes)")
       
       for i in range(datagene.num_trianepoch):
           random.shuffle(datagene.annotations)  #每一轮的数据信息进行打乱
           for j in range(datagene.num_samples):
               datagene.Q_name.put(datagene.annotations[j])  # 数据逐个放入Q_name中
               
   def Data(self, datagene, thread):
       print("向队列Q_data中,存放神经网络训练时的label")
      
       datagene.Prepare()

       name = []
       while 1:
           if datagene.batch_count < datagene.num_batchs:   # 当【读取了几批】小于【一轮总批数】
               num = 0                             # 统计批内读取个数
               while num < datagene.batch_size:        #【批内读取数据个数】小于【一个batch数值】
                   namefile = datagene.Q_name.get()    ##  获取数据名字等信息
                   datagene.readdata(namefile, num)   ## 读取并处理数据 
                   name.append(namefile)
                   num += 1
               datagene.batch_count += 1 # 统计一轮的训练,读取了几个批次
               datagene.Q_data.put([name, thread,     ## 存放name、thread是为了自己测试数据读取是否正确所用
                           datagene.batch_image, datagene.batch_label_sbbox,
                           datagene.batch_label_mbbox, datagene.batch_label_lbbox, datagene.batch_sbboxes,
                           datagene.batch_mbboxes, datagene.batch_lbboxes])
               name = []
           else:
               datagene.batch_count = 0

2.2.2 def load_data_start()

前面实现了class QueueGene(),接下来就要为这个类创建个实例,来运行相关功能。


def load_data_start(Q_datanum1=1000, Q_datanum2=1000, P1=4, P2=2):

  traindatagene = Dataset("train")
  testdatagene = Dataset("test")
  traindatagene.Q_name = Queue(traindatagene.num_samples)  # 读取训练数据的名字的队列
  testdatagene.Q_name = Queue(testdatagene.num_samples)  # 读取验证数据的名字的队列
  traindatagene.Q_data = Queue(Q_datanum1)  # 读取训练数据的队列
  testdatagene.Q_data = Queue(Q_datanum2)  # 读取验证数据的队列

  queuegene = QueueGene()
  Pname1 = Process(target=queuegene.getname, args=(traindatagene,))  # 创建进程,读取训练数据的名字等信息
  Pname1.daemon = True  # 避免形成僵尸进程
  Pname1.start())
  Pname2 = Process(target=queuegene.getname, args=(testdatagene,))  # 创建进程,读取验证数据的名字等信息
  Pname2.daemon = True  # 避免形成僵尸进程
  Pname2.start())
  Pdata1 = []
  for thread in range(P1):
      P = Process(target=queuegene.Data, args=(traindatagene, thread))  # 创建多进程,读取训练数据
      P.daemon = True  # 避免形成僵尸进程
      P.start())
      Pdata1.append(P)
      
  Pdata2 = []
  for thread in range(P2):
      P = Process(target=queuegene.Data, args=(testdatagene, thread))  # 创建多进程,读取验证数据
      P.daemon = True  # 避免形成僵尸进程
      P.start())
      Pdata2.append(P)

  return traindatagene.Q_data, testdatagene.Q_data, Pname1, Pname2,Pdata1,Pdata2
  
"""调用函数,用于测试函数获取数据 实现的是否有误"""
if __name__ == '__main__':
  Q_traindata, Q_validdata, Pname1, Pname2,Pdata1,Pdata2 = load_data_start(100,0, 1,0)
  for i in range(10):
      A = Q_traindata.get()
  # 这里工程主功能实现
  # ... ...
  # 一定要进行close和join,在主程序运行结束以后关闭数据读取的子程序,并且join要在close后面
  Pname1.close()
  Pname1.join()      
  Pname2.close()
  Pname2.join() 
  Pdata1.close()
  Pdata1.join() 
  Pdata2.close()
  Pdata2.join()         

另外:这部分内容这里只进行功能的实现,简洁并未留意去优化。

有任何问题,欢迎讨论
另外,在代码编写逐步优化过程中,数据的获取、多进程读取,在一起实现,阅读性更强,逻辑更清晰。参考:OpenPose -tensorflow代码解析(2)—— 数据增强和处理 dataset.py,以及调用方式在OpenPose -tensorflow代码解析(4)—— 训练脚本 train.py中完成。


http://www.niftyadmin.cn/n/960588.html

相关文章

import seaborn ImportError: DLL load failed: 找不到指定的模块。

我在anaconda中 import seaborn时&#xff0c;出现了“ImportError: DLL load failed: 找不到指定的模块。” 于是我先 conda uninstall scipy 然后 conda install scipy 再重新import seaborn Ok&#xff0c;问题解决&#xff01;&#xff01;&#xff01;

DataFrame object has no attribute ‘as_matrix‘

as_matrix是在老版本的pandas中&#xff0c;新版本已经删除了该方法&#xff0c;并建议使用.values方法 比如&#xff0c;我的代码中是这样的&#xff1a; temp data.as_matrix(columnscols) 修改后&#xff1a; temp data.values 下面是官网链接 https://pandas.pydat…

YOLOv3 代码详解(4) —— 网络结构的搭建、loss的定义

前言&#xff1a; yolo系列的论文阅读 论文阅读 || 深度学习之目标检测 重磅出击YOLOv3 论文阅读 || 深度学习之目标检测yolov2 论文阅读 || 深度学习之目标检测yolov1   该篇讲解的工程连接是&#xff1a; tensorflow的yolov3&#xff1a;https://github.com/YunYang1994/ten…

持续集成与自动化部署 - gitlab部署 (四)

1 gitlab部署 部署gitlab参考链接 GitLab是一个利用 Ruby on Rails 开发的开源应用程序&#xff0c;实现一个自托管的Git项目仓库&#xff0c;可通过Web界面进行访问公开的或者私人项目。 GitLab拥有与Github类似的功能&#xff0c;能够浏览源代码&#xff0c;管理缺陷和注释。…

ImportError: cannot import name ‘CustomObjectScope‘

这是引入包的问题 修改前&#xff1a; from keras.utils import CustomObjectScope 修改后&#xff1a; from keras.utils.generic_utils import CustomObjectScope

P1726 上白泽慧音

P1726 上白泽慧音 缩点的模板。 因为数据范围很小&#xff0c;所以对于输出方案的判断就可以很水。 #include<cstdio> #include<iostream> #include<algorithm> #include<vector> using namespace std; vector<int>line[5010]; int dfn[5010],lo…

你对CSS权重真的足够了解吗?

前言 css权重很多人都听过&#xff0c;也了解一些&#xff0c;但是很多人对具体的规则或者说再深如一些关于css权重的问题&#xff0c;可能会不那么清楚。日常开发中&#xff0c;或多或少都会遇到css规则不生效的问题&#xff0c;为了让我们能够减少调试css规则的时间&#xff…

tensorflow || 滑动平均的理解--tf.train.ExponentialMovingAverage

1 滑动平均的理解 滑动平均(exponential moving average)&#xff0c;或者叫做指数加权平均(exponentially weighted moving average)&#xff0c;可以用来估计变量的局部均值&#xff0c;使得变量的更新与一段时间内的历史取值有关。   已知变量 vvv 在 ttt 时刻记为 vtv_tvt…