pytorch 多进程数据加载 - 序列化数据/serialize_data

背景介绍

OpenMMLab项目中构建数据集的基础类BaseDataset类的时候,对多进程数据加载有一个优化,叫做 ‘‘’序列化’。

先看代码部分



class BaseDataset(Dataset):
    r"""BaseDataset for open source projects in OpenMMLab.

    The annotation format is shown as follows.

    .. code-block:: none

        {
            "metainfo":
            {
              "dataset_type": "test_dataset",
              "task_name": "test_task"
            },
            "data_list":
            [
              {
                "img_path": "test_img.jpg",
                "height": 604,
                "width": 640,
                "instances":
                [
                  {
                    "bbox": [0, 0, 10, 20],
                    "bbox_label": 1,
                    "mask": [[0,0],[0,10],[10,20],[20,0]],
                    "extra_anns": [1,2,3]
                  },
                  {
                    "bbox": [10, 10, 110, 120],
                    "bbox_label": 2,
                    "mask": [[10,10],[10,110],[110,120],[120,10]],
                    "extra_anns": [4,5,6]
                  }
                ]
              },
            ]
        }

    Args:
        ann_file (str, optional): Annotation file path. Defaults to ''.
        metainfo (Mapping or Config, optional): Meta information for
            dataset, such as class information. Defaults to None.
        data_root (str, optional): The root directory for ``data_prefix`` and
            ``ann_file``. Defaults to ''.
        data_prefix (dict): Prefix for training data. Defaults to
            dict(img_path='').
        filter_cfg (dict, optional): Config for filter data. Defaults to None.
        indices (int or Sequence[int], optional): Support using first few
            data in annotation file to facilitate training/testing on a smaller
        serialize_data (bool, optional): Whether to hold memory using
            serialized objects, when enabled, data loader workers can use
            shared RAM from master process instead of making a copy. Defaults
            to True.
        pipeline (list, optional): Processing pipeline. Defaults to [].
        test_mode (bool, optional): ``test_mode=True`` means in test phase.
            Defaults to False.
        lazy_init (bool, optional): Whether to load annotation during
            instantiation. In some cases, such as visualization, only the meta
            information of the dataset is needed, which is not necessary to
            load annotation file. ``Basedataset`` can skip load annotations to
            save time by set ``lazy_init=True``. Defaults to False.
        max_refetch (int, optional): If ``Basedataset.prepare_data`` get a
            None img. The maximum extra number of cycles to get a valid
            image. Defaults to 1000.

    Note:
        BaseDataset collects meta information from ``annotation file`` (the
        lowest priority), ``BaseDataset.METAINFO``(medium) and ``metainfo
        parameter`` (highest) passed to constructors. The lower priority meta
        information will be overwritten by higher one.

    Note:
        Dataset wrapper such as ``ConcatDataset``, ``RepeatDataset`` .etc.
        should not inherit from ``BaseDataset`` since ``get_subset`` and
        ``get_subset_`` could produce ambiguous meaning sub-dataset which
        conflicts with original dataset.

    Examples:
        >>> # Assume the annotation file is given above.
        >>> class CustomDataset(BaseDataset):
        >>>     METAINFO: dict = dict(task_name='custom_task',
        >>>                           dataset_type='custom_type')
        >>> metainfo=dict(task_name='custom_task_name')
        >>> custom_dataset = CustomDataset(
        >>>                      'path/to/ann_file',
        >>>                      metainfo=metainfo)
        >>> # meta information of annotation file will be overwritten by
        >>> # `CustomDataset.METAINFO`. The merged meta information will
        >>> # further be overwritten by argument `metainfo`.
        >>> custom_dataset.metainfo
        {'task_name': custom_task_name, dataset_type: custom_type}
    """

    METAINFO: dict = dict()
    _fully_initialized: bool = False

    def __init__(self,
                 ann_file: Optional[str] = '',
                 metainfo: Union[Mapping, Config, None] = None,
                 data_root: Optional[str] = '',
                 data_prefix: dict = dict(img_path=''),
                 filter_cfg: Optional[dict] = None,
                 indices: Optional[Union[int, Sequence[int]]] = None,
                 serialize_data: bool = True,
                 pipeline: List[Union[dict, Callable]] = [],
                 test_mode: bool = False,
                 lazy_init: bool = False,
                 max_refetch: int = 1000):
        self.ann_file = ann_file
        self._metainfo = self._load_metainfo(copy.deepcopy(metainfo))
        self.data_root = data_root
        self.data_prefix = copy.copy(data_prefix)
        self.filter_cfg = copy.deepcopy(filter_cfg)
        self._indices = indices
        self.serialize_data = serialize_data
        self.test_mode = test_mode
        self.max_refetch = max_refetch
        self.data_list: List[dict] = []
        self.data_bytes: np.ndarray

        # Join paths.
        self._join_prefix()

        # Build pipeline.
        self.pipeline = Compose(pipeline)
        # Full initialize the dataset.
        if not lazy_init:
            self.full_init()

    @force_full_init
    def get_data_info(self, idx: int) -> dict:
        """Get annotation by index and automatically call ``full_init`` if the
        dataset has not been fully initialized.

        序列化方式通过内存映射和反序列化,可能更适合处理大规模数据或减少内存占用,
        而非序列化方式则更简单直接,适用于数据规模较小或内存资源充足的情况。
        Args:
            idx (int): The index of data.

        Returns:
            dict: The idx-th annotation of the dataset.
            无论哪种方式,最后得到的 data_info 变量都包含了索引 idx 对应的数据。
            - 序列化数据加载时,通过地址计算、内存视图和反序列化,从字节数组中提取数据;
            - 非序列化数据加载时,直接从已存储的对象列表中复制所需数据。
            两种方式适应了不同的存储场景和性能需求。
        """
        if self.serialize_data:
            start_addr = 0 if idx == 0 else self.data_address[idx - 1].item()
            end_addr = self.data_address[idx].item()
            bytes = memoryview(
                self.data_bytes[start_addr:end_addr])  # type: ignore
            data_info = pickle.loads(bytes)  # type: ignore
        else:
            data_info = copy.deepcopy(self.data_list[idx])
        # Some codebase needs `sample_idx` of data information. Here we convert
        # the idx to a positive number and save it in data information.
        if idx >= 0:
            data_info['sample_idx'] = idx
        else:
            data_info['sample_idx'] = len(self) + idx

        return data_info

    def full_init(self):
        """Load annotation file and set ``BaseDataset._fully_initialized`` to
        True.

        If ``lazy_init=False``, ``full_init`` will be called during the
        instantiation and ``self._fully_initialized`` will be set to True. If
        ``obj._fully_initialized=False``, the class method decorated by
        ``force_full_init`` will call ``full_init`` automatically.

        Several steps to initialize annotation:

            - load_data_list: Load annotations from annotation file.
            - filter data information: Filter annotations according to
              filter_cfg.
            - slice_data: Slice dataset according to ``self._indices``
            - serialize_data: Serialize ``self.data_list`` if
              ``self.serialize_data`` is True.
        """
        
        # check是不是 self._fully_initialized 和 self.serialize_data 不能同时为 true
        if self._fully_initialized:
            return
        # load data information
        self.data_list = self.load_data_list()
        # filter illegal data, such as data that has no annotations.
        self.data_list = self.filter_data()
        # Get subset data according to indices.
        if self._indices is not None:
            self.data_list = self._get_unserialized_subset(self._indices)

        # serialize data_list
        if self.serialize_data:
            self.data_bytes, self.data_address = self._serialize_data()

        self._fully_initialized = True

BaseDataset类中定义了一些可能会影响内存使用的方法和属性,例如:

  • data_list:存储数据集所有样本的列表,每个样本都是一个字典,包含了图像路径、尺寸和实例信息等。
  • serialize_data:一个布尔值,指示是否在初始化时将data_list序列化以节省内存。当启用时,数据加载器的工作进程可以使用主进程的共享RAM,而不是进行复制。
  • _serialize_data和_get_serialized_subset:这些方法用于序列化和获取序列化数据的子集,这有助于在多进程数据加载时减少内存消耗。

在分布式训练中,如果每个GPU rank都加载完整的data_list,那么确实会导致内存的重复使用。为了解决这个问题,serialize_data属性被设置为True时,可以通过序列化数据来节省内存,这样每个工作进程就可以共享主进程的RAM,而不是各自复制一份数据。

serialize_data

在多进程数据加载的场景下,比如使用PyTorch的DataLoader时,每个工作进程(worker)通常需要加载数据集的一部分来并行处理。如果没有序列化处理,每个工作进程都会复制一份完整的data_list到自己的内存空间中,这会导致内存的大量重复使用,特别是在数据集很大的情况下。

通过serialize_data参数启用序列化后,数据集的样本信息会被转换成一个二进制格式的数组(data_bytes),并且每个样本信息的起始和结束位置会被记录在一个地址数组(data_address)中。这样,当数据加载器的工作进程需要获取数据时,它们可以直接从共享的data_bytes数组中按地址提取所需的样本信息,而无需复制整个数据列表。这意味着所有的工作进程都可以直接使用主进程中的共享内存,从而大大减少了内存的使用。

进一步理解 serialize data

用一个餐厅的比喻来理解serialize_data的概念。

你经营一家非常受欢迎的餐厅,这家餐厅的菜单上有100道菜。每天,你都需要为顾客提供这些菜,但是每道菜的需求量是不同的。为了高效地为顾客服务,你有两种选择:

  1. 不序列化(serialize_data=False
    这就像你在餐厅里为每个服务员准备一份完整的菜单,每份菜单上都有100道菜。每天早上,服务员们会从厨房领取他们需要的所有食材,准备一天的工作。这意味着每个服务员都需要携带大量的食材,而且厨房也需要准备足够的食材来满足所有服务员的需求。这在餐厅规模较小、顾客较少时是可行的,但如果餐厅很大,或者顾客非常多,这就会导致厨房的食材库存压力巨大,效率低下。

  2. 序列化(serialize_data=True
    现在,你决定改变策略。厨房不再为每个服务员准备一份完整的菜单,而是将每道菜的食材打包成单独的小包裹,并在每个包裹上贴上标签,说明这是哪道菜的食材。服务员们只需要根据顾客的订单来领取对应的食材包裹。这样,厨房只需要准备足够的食材来满足所有顾客的总需求,而不是每个服务员的需求。服务员们也不需要携带大量的食材,他们只需要根据需要领取相应的包裹即可。这种方式大大减少了食材的浪费和厨房的存储压力,提高了服务效率。

在数据集处理的上下文中,serialize_data的作用就像上述例子中的食材打包。如果没有序列化,每个工作进程(服务员)都需要一份完整的数据集副本(完整的菜单),这会导致大量的内存占用和数据重复。启用序列化后,数据集的每个样本都被打包成一个二进制格式的“包裹”(data_bytes),并附有一个地址标签(data_address),工作进程只需要根据需要加载和处理这些“包裹”,而不是整个数据集,这样可以显著减少内存的使用,提高数据处理的效率。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/556982.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

轻松点餐|餐饮小程序新玩法,美食触手可及

在企业经营领域,小程序正成为越来越多行业开展线上经营的重要工具。依托小程序等工具自主开发数字化经营平台,已经成为零售、餐饮等日常消费行业的趋势。餐饮行业向智能化快速迭代已势在必行,在此进程中,小程序成为了备受餐饮商家…

Mysql嵌套查询太简单了

1、子查询的分类 不相关查询: 子查询能独立执行 相关查询: 子查询不能独立运行 相关查询的执行顺序: 首先取外层查询中表的第一个元组,根据它与内层查询相关的属性值处理内层查询, 若WHERE子句返回值为真,则取此元组放入结果…

SpringBoot整合PDF动态填充数据并下载

目录 目录 一、准备环境 二、iTextPDF介绍 三、步骤 四、访问查看结果 五、源代码参考 一、准备环境 ①下载一个万兴pdf软件 ②准备一个pdf 文件 二、iTextPDF介绍 这是一个用于生成PDF文档的Java库, 文档创建与修改:iTextPDF能够从零开始创建…

2024红明谷杯——Misc 加密的流量

2024红明谷杯——Misc 加密的流量 写在前面: 这里是贝塔贝塔,照例来一段闲聊 打比赛但赛前一波三折,又是成功签到的一个比赛 说起来比赛全名叫红明谷卫星应用数据安全场景赛,但好像真的跟卫星的关系不大,没有bin方…

面试Spring框架

什么是Spring框架? Spring框架是一个开源的Java应用程序框架,提供了综合的基础设施支持,用于开发Java企业应用程序。它涵盖了从基本的核心容器到全面的企业服务,可以用于构建任何规模的应用程序。 Spring框架的核心特性是什么&am…

Go之map详解

map的结构 map实现的两个关键数据结构 hmap 定义了map的结构bmap 定义了hmap.buckets中每个bucket的结构 // A header for a Go map. type hmap struct {count int // 元素的个数flags uint8 // 状态标记,标记map当前状态,是否正在写入B …

<计算机网络自顶向下> 可靠数据传输的原理(未完成)

可靠数据传输(rdt:Reliable Data Transfer)的原理 rdt在应用层,传输层和数据链路层都很重要是网络TOP10问题之一信道的不可靠特点决定了可靠数据传输rdt的复杂性rdt_send: 被上层(如应用层)调用&#xff0…

41.缺失的第一个正数

1. 解题原理: (1)对于一个有序的、不缺失元素的正数数组nums,元素nums[i]应当位于nums[i]-1的位置处。 (2)nums数组的长度为N,缺失的第一个正数如果不位于[1,N],那么就肯定是N1 2. …

excel表格怎么设置密码?excel文件加密的两个方法

一、加密码的原理​ Excel加密码的原理主要基于加密算法和密钥管理。当用户为Excel文件或工作表设置密码时,Excel会采用一种加密算法对文件或工作表进行加密处理。这种加密算法通常是对称加密算法,如AES(高级加密标准)或DES(数据加密标准)。 二&#x…

海外住宅代理:推特账号为何容易被关小黑屋?

推特是全球最受欢迎的社交媒体之一,每天都有数以百万计的用户在这个平台上发布信息、分享观点和交流互动。然而,有些用户可能会发现他们的推特账号不幸陷入了所谓的“关小黑屋”状态,即账号被限制了可见度,导致发布的内容无法被其…

【数据分析面试】24.20个数据库问答题 (考察数据开发和实际应用能力)

作为数据从业者,日常工作除了对各类业务数据进行分析挖掘,也需要经常和数据库打交道、甚至也少不了要承担一些数据开发、数仓管理的工作。掌握数据库管理的基本概念和技术是至关重要的。无论是初学者还是从业者,理解数据库索引、范式、事务、…

四.音视频编辑-音频混合-概述

引言 当我们在前两篇博客中成功地构建了一个媒体组合,并且略过了音频部分时,我们意识到了我们需要对这个项目进行更详细的探讨。在本篇博客中,我们将会展示如何创建一个包含视频轨道、配音音频轨道以及背景音频轨道的完整媒体组合。更进一步…

游泳耳机哪个牌子好?体验与口碑兼顾的4大游泳耳机汇总!

最近的天气越来越炎热了,许多人选择游泳作为一种既能锻炼身体又能享受清凉的活动。而随着科技的发展,越来越多的运动爱好者希望在游泳时也能享受到音乐的乐趣。因此,游泳耳机应运而生,成为市场上的热门产品。然而,面对…

项目中的解耦小能手-观察者模式

目录 1.使用场景 2.什么是观察模式 3.观察者模式结构图 4.代码实现案例 4.1 subject代码实现 4.2 Observer类代码实现 5. 回顾总结 1.使用场景 当一个对象的改变需要同事改变其他对象的时候,如:订单中心-下单成功需要通知库存、物流和积分去做相应…

交流回馈老化测试负载优点和应用

交流回馈老化测试负载是用于模拟真实环境下设备运行状态的测试工具,通过对设备进行长时间的连续工作,以检测其性能的稳定性和可靠性。这种测试负载具有许多优点,并且在实际应用中有着广泛的用途。 在实际应用中,设备往往需要在各种…

Flask实战

from flask import Flask appFlask(__name__)点击Flask同时点击键盘ctrl即可查看Flask的默认初始化函数 def __init__(self,import_name: str,static_url_path: str | None None,static_folder: str | os.PathLike[str] | None "static",static_host: str | None …

产品心理学:为什么管钱的都是女生?

大家发现了吗?大部分公司女财务居多,而在家庭中,多数也是女生管钱。 为什么管钱的都是女生?答案文尾揭晓。 问题的答案,要从一个心理学名词“过度自信偏差”说起 用人话说,就是“迷之自信” 过度自信的例…

【剪映专业版】11音频的全流程剪辑操作

视频课程:B站有知公开课【剪映电脑版教程】 1.音乐素材 可能包含人声,音乐素材普遍比较长,几十秒到几分钟。要点击倒三角才会出现分类。 点击下载箭头下载素材;点击加号将素材增加到轨道;时间指示器在哪个地方&#…

Python | Leetcode Python题解之第35题搜索插入位置

题目&#xff1a; 题解&#xff1a; class Solution:def searchInsert(self, nums: List[int], target: int) -> int:left, right 0, len(nums) #采用左闭右开区间[left,right)while left < right: # 右开所以不能有,区间不存在mid left (right - left)//2 # 防止溢出…

UE5增强输入系统 Enhanced Input

关键字&#xff1a; Enhanced Input 、 输入、映射、事件、鼠标、键盘、键鼠、动作、Trigger、触发器、 疑问&#xff1a; 新输入系统怎么做一个基础的案例&#xff1f;Trigger修改器中每个项都是什么功能&#xff1f;InputAction和InputMappingContext中都有修改器&#xff…
最新文章