Datasets documentation

الاستخدام مع PyTorch

Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

الاستخدام مع PyTorch

هذا المستند مقدمة سريعة لاستخدام datasets مع PyTorch، مع تركيز خاص على كيفية استخراج كائنات torch.Tensor من مجموعات البيانات لدينا، وكيفية استخدام DataLoader من PyTorch وDataset من Hugging Face بأفضل أداء.

تنسيق مجموعة البيانات

بشكل افتراضي، تُرجع مجموعات البيانات كائنات بايثون العادية: أعداد صحيحة، وأعداد عشرية، ونصوص، وقوائم، وما إلى ذلك.

للحصول على متجهات PyTorch بدلاً من ذلك، يمكنك تعيين صيغة مجموعة البيانات إلى pytorch باستخدام Dataset.with_format():

>>> from datasets import Dataset
>>> data = [[1, 2],[3, 4]]
>>> ds = Dataset.from_dict({"data": data})
>>> ds = ds.with_format("torch")
>>> ds[0]
{'data': tensor([1, 2])}
>>> ds[:2]
{'data': tensor([[1, 2],
         [3, 4]])}

يُعدّ كائن Dataset غلافًا لجدول Arrow، مما يتيح قراءات سريعة بدون نسخ من المصفوفات في مجموعة البيانات إلى متجهات PyTorch.

لتحميل البيانات كمُتجهات على وحدة معالجة رسومية، حدد معامل device:

>>> import torch
>>> device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
>>> ds = ds.with_format("torch", device=device)
>>> ds[0]
{'data': tensor([1, 2], device='cuda:0')}

مصفوفات N الأبعاد

إذا كانت مجموعة بياناتك تتكون من مصفوفات ذات أبعاد N، فستلاحظ أنه افتراضياً تُعتبر المصفوفات ذات الشكل الثابت على أنها نفس الموتر:

>>> from datasets import Dataset
>>> data = [[[1, 2],[3, 4]],[[5, 6],[7, 8]]]  # fixed shape
>>> ds = Dataset.from_dict({"data": data})
>>> ds = ds.with_format("torch")
>>> ds[0]
{'data': tensor([[1, 2],
         [3, 4]])}
>>> from datasets import Dataset
>>> data = [[[1, 2],[3]],[[4, 5, 6],[7, 8]]]  # varying shape
>>> ds = Dataset.from_dict({"data": data})
>>> ds = ds.with_format("torch")
>>> ds[0]
{'data': [tensor([1, 2]), tensor([3])]}

ومع ذلك، تتطلب هذه المنطق غالبًا مقارنات بطيئة للأشكال ونسخًا للبيانات. لتجنب ذلك، يجب عليك استخدام نوع الميزة Array بشكل صريح وتحديد شكل متجهاتك:

>>> from datasets import Dataset, Features, Array2D
>>> data = [[[1, 2],[3, 4]],[[5, 6],[7, 8]]]
>>> features = Features({"data": Array2D(shape=(2, 2), dtype='int32')})
>>> ds = Dataset.from_dict({"data": data}, features=features)
>>> ds = ds.with_format("torch")
>>> ds[0]
{'data': tensor([[1, 2],
         [3, 4]])}
>>> ds[:2]
{'data': tensor([[[1, 2],
          [3, 4]],
 
         [[5, 6],
          [7, 8]]])}

أنواع ميزات أخرى

تُحوَّل بيانات ClassLabel بشكل صحيح إلى متجهات:

>>> from datasets import Dataset, Features, ClassLabel
>>> labels = [0, 0, 1]
>>> features = Features({"label": ClassLabel(names=["negative", "positive"])})
>>> ds = Dataset.from_dict({"label": labels}, features=features) 
>>> ds = ds.with_format("torch")  
>>> ds[:3]
{'label': tensor([0, 0, 1])}

تبقى كائنات النص والثنائية دون تغيير، لأن PyTorch يدعم الأرقام فقط.

تُدعم أنواع الميزات Image وAudio أيضًا.

لاستخدام نوع الميزة Image، ستحتاج إلى تثبيت الإضافة vision عبر الأمر pip install datasets[vision].

>>> from datasets import Dataset, Features, Audio, Image
>>> images = ["path/to/image.png"] * 10
>>> features = Features({"image": Image()})
>>> ds = Dataset.from_dict({"image": images}, features=features) 
>>> ds = ds.with_format("torch")
>>> ds[0]["image"].shape
torch.Size([512, 512, 4])
>>> ds[0]
{'image': tensor([[[255, 215, 106, 255],
         [255, 215, 106, 255],
         ...,
         [255, 255, 255, 255],
         [255, 255, 255, 255]]], dtype=torch.uint8)}
>>> ds[:2]["image"].shape
torch.Size([2, 512, 512, 4])
>>> ds[:2]
{'image': tensor([[[[255, 215, 106, 255],
          [255, 215, 106, 255],
          ...,
          [255, 255, 255, 255],
          [255, 255, 255, 255]]]], dtype=torch.uint8)}

لاستخدام نوع الميزة Audio، ستحتاج إلى تثبيت الإضافة audio عبر الأمر pip install datasets[audio].

>>> from datasets import Dataset, Features, Audio, Image
>>> audio = ["path/to/audio.wav"] * 10
>>> features = Features({"audio": Audio()})
>>> ds = Dataset.from_dict({"audio": audio}, features=features) 
>>> ds = ds.with_format("torch")  
>>> ds[0]["audio"]["array"]
tensor([ 6.1035e-05,  1.5259e-05,  1.6785e-04,  ..., -1.5259e-05,
        -1.5259e-05,  1.5259e-05])
>>> ds[0]["audio"]["sampling_rate"]
tensor(44100)

تحميل البيانات

على غرار كائنات torch.utils.data.Dataset، يمكن تمرير Dataset مباشرةً إلى DataLoader في PyTorch:

>>> import numpy as np
>>> from datasets import Dataset 
>>> from torch.utils.data import DataLoader
>>> data = np.random.rand(16)
>>> label = np.random.randint(0, 2, size=16)
>>> ds = Dataset.from_dict({"data": data, "label": label}).with_format("torch")
>>> dataloader = DataLoader(ds, batch_size=4)
>>> for batch in dataloader:
...     print(batch)                                                                                            
{'data': tensor([0.0047, 0.4979, 0.6726, 0.8105]), 'label': tensor([0, 1, 0, 1])}
{'data': tensor([0.4832, 0.2723, 0.4259, 0.2224]), 'label': tensor([0, 0, 0, 0])}
{'data': tensor([0.5837, 0.3444, 0.4658, 0.6417]), 'label': tensor([0, 1, 0, 0])}
{'data': tensor([0.7022, 0.1225, 0.7228, 0.8259]), 'label': tensor([1, 1, 1, 1])}

تحسين تحميل البيانات

تتوفر عدة طرق يمكنك من خلالها زيادة سرعة تحميل بياناتك، مما يوفر وقتك، لا سيما عند العمل مع مجموعات بيانات كبيرة. يوفر PyTorch تحميل بيانات متوازيًا، حيث يستخرج دفعات من الفهارس بدلاً من استخلاصها بشكل فردي، كما يوفر البث (streaming) للتكرار على مجموعة البيانات دون تنزيلها على القرص.

استخدام عدة عمّال

يمكنك موازنة تحميل البيانات باستخدام معامل num_workers الخاص بـ DataLoader في PyTorch للحصول على معدل معالجة أعلى.

في الخلفية، يبدأ DataLoader عددًا من العمليات يساوي num_workers. تعيد كل عملية تحميل مجموعة البيانات الممررة إلى DataLoader وتُستخدم للاستعلام عن الأمثلة. إعادة تحميل مجموعة البيانات داخل عامل لا تُشغل ذاكرة الوصول العشوائي لديك، لأنها ببساطة تعيد تعيين خريطة الذاكرة لمجموعة البيانات من القرص الخاص بك.

>>> import numpy as np
>>> from datasets import Dataset, load_from_disk
>>> from torch.utils.data import DataLoader
>>> data = np.random.rand(10_000)
>>> Dataset.from_dict({"data": data}).save_to_disk("my_dataset")
>>> ds = load_from_disk("my_dataset").with_format("torch")
>>> dataloader = DataLoader(ds, batch_size=32, num_workers=4)

بث البيانات

قم ببث مجموعة بيانات عن طريق تحميلها كـ IterableDataset. يتيح لك ذلك التكرار التدريجي على مجموعة بيانات عن بُعد دون تنزيلها على القرص، أو على ملفات بيانات محلية. تعرف على المزيد حول نوع مجموعة البيانات الأنسب لحالتك الاستخدامية في دليل الاختيار بين مجموعة بيانات عادية أو مجموعة بيانات قابلة للتكرار.

تورث مجموعة البيانات القابلة للتكرار من datasets من torch.utils.data.IterableDataset مما يتيح لك تمريرها إلى torch.utils.data.DataLoader:

>>> import numpy as np
>>> from datasets import Dataset, load_dataset
>>> from torch.utils.data import DataLoader
>>> data = np.random.rand(10_000)
>>> Dataset.from_dict({"data": data}).push_to_hub("<username>/my_dataset")  # Upload to the Hugging Face Hub
>>> my_iterable_dataset = load_dataset("<username>/my_dataset", streaming=True, split="train")
>>> dataloader = DataLoader(my_iterable_dataset, batch_size=32)

إذا كان مجموعة البيانات مقسمة إلى عدة أجزاء (أي إذا كانت تتكون من ملفات بيانات متعددة)، فيمكنك البث بالتوازي باستخدام num_workers:

>>> my_iterable_dataset = load_dataset("deepmind/code_contests", streaming=True, split="train")
>>> my_iterable_dataset.num_shards
39
>>> dataloader = DataLoader(my_iterable_dataset, batch_size=32, num_workers=4)

في هذه الحالة، يُمنح كل عامل مجموعة فرعية من قائمة الشرائح التي سيتم بثها منها.

نقطة التفتيش والاستئناف

If you need a DataLoader that you can checkpoint and resume in the middle of training, you can use the StatefulDataLoader from torchdata:

>>> from torchdata.stateful_dataloader import StatefulDataLoader
>>> my_iterable_dataset = load_dataset("deepmind/code_contests", streaming=True, split="train")
>>> dataloader = StatefulDataLoader(my_iterable_dataset, batch_size=32, num_workers=4)
>>> # save in the middle of training
>>> state_dict = dataloader.state_dict()
>>> # and resume later
>>> dataloader.load_state_dict(state_dict)

يُصبح ذلك ممكناً بفضل IterableDataset.state_dict() و IterableDataset.load_state_dict().

موزّع

To split your dataset across your training nodes, you can use datasets.distributed.split_dataset_by_node():

import os
from datasets.distributed import split_dataset_by_node

ds = split_dataset_by_node(ds, rank=int(os.environ["RANK"]), world_size=int(os.environ["WORLD_SIZE"]))

يعمل هذا مع مجموعات البيانات ذات نمط الخريطة (map-style) ومجموعات البيانات القابلة للتكرار (iterable). يتم تقسيم مجموعة البيانات للعقدة ذات الترتيب rank في مجموعة عقد حجمها world_size.

للمجموعات البيانات ذات نمط الخريطة:

يُسنَد إلى كل عقدة جزء من البيانات، على سبيل المثال يُمنح الترتيب 0 الجزء الأول من مجموعة البيانات.

للمجموعات البيانات القابلة للتكرار:

يمكن أن تكون strategy للتقسيم "auto" أو "shards" أو "examples":

  • "shards": يتم توزيع الشرائح بالتساوي عبر العقد
  • "examples": يحتفظ كل عقدة بمثال واحد من world_size، متجاوزةً الأمثلة الأخرى
  • "auto" (الافتراضي): يستخدم "shards" إذا كان عدد الشرائح في مجموعة البيانات عاملًا قاسمًا لـ world_size (أي إذا كان dataset.num_shards % world_size == 0)، وهو الخيار الأكثر تحسينًا. وإلا يستخدم "examples".

يمكن أيضًا دمج هذا مع torch.utils.data.DataLoader إذا كنت ترغب في استخدام كل عقدة عدة عمّال لتحميل البيانات.

إذا قمت بتبديل ترتيب مجموعة البيانات القابلة للتكرار في إعداد موزّع، فتأكد من تعيين seed ثابت في IterableDataset.shuffle() بحيث تُستخدم نفس القائمة المُبدَّلة من الشرائح على كل عقدة لمعرفة الشرائح التي يجب على العقدة تجاوزها.

التوازي الهجين المجزأ للبيانات (HSDP)

يصف torch.distributed.device_mesh.DeviceMesh بنية التدريب الخاصة بك على شكل شبكة من الرتب (ranks)، حيث يمثل كل بُعد استراتيجية موازاة واحدة. الأبعاد المتوازية على مستوى البيانات هي الوحيدة ذات الأهمية في تحميل البيانات، ويجعل التقسيم الهجين (hybrid sharding) من السهل إغفالها:

  • "fsdp" (عادةً داخل العقدة): مجموعة FSDP حيث يحتفظ كل رتبة بشريحة فقط من حالات النموذج (المعاملات والتدرجات وحالات المُحسِّن) وتجمع مؤقتًا المعاملات الكاملة للوحدة قبل الحساب عليها مباشرة. يبقى التدريب متوازيًا على مستوى البيانات: حيث تقوم كل رتبة بتنفيذ مسارين كاملين للأمام والخلف على دفعتها الخاصة، لذا تحتاج هذه البُعد إلى بيانات مختلفة لكل رتبة، تمامًا مثل DDP.
  • "dp_replicate" (عادةً بين العقد): النسخ المكررة من ذلك النموذج المجزأ، والتي تُبقي متزامنة عبر عملية all-reduce إضافية بين النسخ. كما تعالج النسخ بيانات مختلفة.
  • "tp" (داخل العقدة): رتب التوازي على مستوى الموترات التي تقسم أوزان كل طبقة لحساب دفعة واحدة معًا. وهي ليست توازية على مستوى البيانات، ويجب أن تقرأ نفس الأمثلة.

درجة التوازي على مستوى البيانات هي حاصل ضرب أبعاد التجزئة (FSDP) والتكرار، وهي أصغر من WORLD_SIZE بمجرد أن يحتوي الشبكة على بُعد مثل "tp". مرّر هذا الحاصل إلى datasets.distributed.split_dataset_by_node() مع الترتيب المسطّح عبر هذين البعدين:

from torch.distributed.device_mesh import init_device_mesh
from datasets.distributed import split_dataset_by_node

# Hybrid sharding spreads data parallelism over two mesh dimensions, but both
# need different data per rank, so `dp_size` is their product.
mesh = init_device_mesh("cuda", (2, 2, 2), mesh_dim_names=("dp_replicate", "fsdp", "tp"))
dp_replicate_mesh, fsdp_mesh = mesh["dp_replicate"], mesh["fsdp"]

dp_size = dp_replicate_mesh.size() * fsdp_mesh.size()
dp_rank = dp_replicate_mesh.get_local_rank() * shard.size() + fsdp_mesh.get_local_rank()

ds = split_dataset_by_node(ds, rank=dp_rank, world_size=dp_size)

هنا dp_size يساوي 2 * 2 = 4: حيث يقرأ كل رتبة موازية للبيانات ربع مجموعة البيانات، وتقرأ الرتبتان في كل زوج من أزواج "tp" نفس الأمثلة.

Update on GitHub