Transformers documentation
تصنيف الفيديو
تصنيف الفيديو
تصنيف الفيديو هو مهمة إسناد تسمية أو فئة إلى فيديو كامل. يُفترض أن يكون لكل فيديو فئة واحدة فقط. تستقبل نماذج تصنيف الفيديو فيديوً كمدخل وتعيد تنبؤًا بالفئة التي ينتمي إليها الفيديو. يمكن استخدام هذه النماذج لتصنيف موضوع الفيديو. تطبيق واقعي لتصنيف الفيديو هو التعرف على الحركة/النشاط، وهو مفيد لتطبيقات اللياقة البدنية. كما أنه مفيد للأشخاص ذوي الإعاقة البصرية، خاصةً أثناء تنقلاتهم.
ستُظهر لك هذه الدليل كيفية:
للاطلاع على جميع البنى المعمارية ونقاط التفتيش المتوافقة مع هذه المهمة، ننصح بالاطلاع على صفحة المهمة.
قبل أن تبدأ، تأكد من تثبيت جميع المكتبات اللازمة:
pip install -q pytorchvideo transformers evaluate
ستستخدم PyTorchVideo (المعروف باسم pytorchvideo) لمعالجة الفيديو وتجهيزه.
ننصحك بتسجيل الدخول إلى حسابك على Hugging Face حتى تتمكن من رفع نموذجك ومشاركته مع المجتمع. عند الطلب، أدخل رمزك (token) لتسجيل الدخول:
>>> from huggingface_hub import notebook_login
>>> notebook_login()تحميل مجموعة بيانات UCF101
ابدأ بتحميل جزء من مجموعة بيانات UCF-101. سيمنحك هذا فرصة للتجربة والتأكد من عمل كل شيء بشكل صحيح قبل قضاء مزيد من الوقت في التدريب على المجموعة الكاملة.
>>> from huggingface_hub import hf_hub_download
>>> hf_dataset_identifier = "sayakpaul/ucf101-subset"
>>> filename = "UCF101_subset.tar.gz"
>>> file_path = hf_hub_download(repo_id=hf_dataset_identifier, filename=filename, repo_type="dataset")بعد تنزيل المجموعة الفرعية، تحتاج إلى استخراج الأرشيف المضغوط:
>>> import tarfile
>>> with tarfile.open(file_path) as t:
... t.extractall(".")على مستوى عالٍ، يتم تنظيم مجموعة البيانات على النحو التالي:
UCF101_subset/
train/
BandMarching/
video_1.mp4
video_2.mp4
...
Archery
video_1.mp4
video_2.mp4
...
...
val/
BandMarching/
video_1.mp4
video_2.mp4
...
Archery
video_1.mp4
video_2.mp4
...
...
test/
BandMarching/
video_1.mp4
video_2.mp4
...
Archery
video_1.mp4
video_2.mp4
...
...يمكنك بعد ذلك حساب العدد الإجمالي للفيديوهات.
>>> import pathlib
>>> dataset_root_path = "UCF101_subset"
>>> dataset_root_path = pathlib.Path(dataset_root_path)>>> video_count_train = len(list(dataset_root_path.glob("train/*/*.avi")))
>>> video_count_val = len(list(dataset_root_path.glob("val/*/*.avi")))
>>> video_count_test = len(list(dataset_root_path.glob("test/*/*.avi")))
>>> video_total = video_count_train + video_count_val + video_count_test
>>> print(f"Total videos: {video_total}")>>> all_video_file_paths = (
... list(dataset_root_path.glob("train/*/*.avi"))
... + list(dataset_root_path.glob("val/*/*.avi"))
... + list(dataset_root_path.glob("test/*/*.avi"))
... )
>>> all_video_file_paths[:5]تظهر مسارات الفيديو (sorted) على النحو التالي:
...
'UCF101_subset/train/ApplyEyeMakeup/v_ApplyEyeMakeup_g07_c04.avi',
'UCF101_subset/train/ApplyEyeMakeup/v_ApplyEyeMakeup_g07_c06.avi',
'UCF101_subset/train/ApplyEyeMakeup/v_ApplyEyeMakeup_g08_c01.avi',
'UCF101_subset/train/ApplyEyeMakeup/v_ApplyEyeMakeup_g09_c02.avi',
'UCF101_subset/train/ApplyEyeMakeup/v_ApplyEyeMakeup_g09_c06.avi'
...ستلاحظ أن هناك مقاطع فيديو تنتمي إلى نفس المجموعة / المشهد، حيث تُرمَّز المجموعة بـ g في مسارات ملفات الفيديو. على سبيل المثال، v_ApplyEyeMakeup_g07_c04.avi وv_ApplyEyeMakeup_g07_c06.avi.
في حالتي التحقق والتقييم، لا يُنصح بوجود مقاطع فيديو من نفس المجموعة / المشهد لمنع تسرب البيانات. الفرع الفرعي الذي تستخدمه في هذا الشرح يأخذ هذه المعلومات في الاعتبار.
في الخطوة التالية، ستستنتج مجموعة التسميات الموجودة في مجموعة البيانات. كما أنشئ قاموسين سيكونان مفيدين عند تهيئة النموذج:
label2id: يربط أسماء الفئات بالأعداد الصحيحة.id2label: يحوّل الأعداد الصحيحة إلى أسماء الفئات.
>>> class_labels = sorted({str(path).split("/")[2] for path in all_video_file_paths})
>>> label2id = {label: i for i, label in enumerate(class_labels)}
>>> id2label = {i: label for label, i in label2id.items()}
>>> print(f"Unique classes: {list(label2id.keys())}.")
# Unique classes: ['ApplyEyeMakeup', 'ApplyLipstick', 'Archery', 'BabyCrawling', 'BalanceBeam', 'BandMarching', 'BaseballPitch', 'Basketball', 'BasketballDunk', 'BenchPress'].توجد 10 فئات فريدة. ولكل فئة، توجد 30 فيديو في مجموعة التدريب.
تحميل نموذج للضبط الدقيق
أنشئ نموذج تصنيف فيديو من نقطة تحقق مسبقة التدريب ومعالج الصور المرتبط بها. يأتي مشفّر النموذج مع معاملات مُدرَّبة مسبقاً، بينما تُهيَّأ طبقة التصنيف عشوائياً. سيكون لمعالج الصور دورٌ مفيد عند كتابة خط المعالجة المسبقة لمجموعتنا البيانات.
>>> from transformers import VideoMAEImageProcessor, VideoMAEForVideoClassification
>>> model_ckpt = "MCG-NJU/videomae-base"
>>> image_processor = VideoMAEImageProcessor.from_pretrained(model_ckpt)
>>> model = VideoMAEForVideoClassification.from_pretrained(
... model_ckpt,
... label2id=label2id,
... id2label=id2label,
... ignore_mismatched_sizes=True, # provide this in case you're planning to fine-tune an already fine-tuned checkpoint
... )قد تلاحظ أثناء تحميل النموذج تحذيرًا مما يلي:
Some weights of the model checkpoint at MCG-NJU/videomae-base were not used when initializing VideoMAEForVideoClassification: [..., 'decoder.decoder_layers.1.attention.output.dense.bias', 'decoder.decoder_layers.2.attention.attention.key.weight']
- This IS expected if you are initializing VideoMAEForVideoClassification from the checkpoint of a model trained on another task or with another architecture (e.g. initializing a BertForSequenceClassification model from a BertForPreTraining model).
- This IS NOT expected if you are initializing VideoMAEForVideoClassification from the checkpoint of a model that you expect to be exactly identical (initializing a BertForSequenceClassification model from a BertForSequenceClassification model).
Some weights of VideoMAEForVideoClassification were not initialized from the model checkpoint at MCG-NJU/videomae-base and are newly initialized: ['classifier.bias', 'classifier.weight']
You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.يُخبرنا التحذير بأننا نتخلص من بعض الأوزان (مثل أوزان الطبقة classifier والانحراف) ونُهيّئ بعض الأوزان الأخرى عشوائياً (أوزان طبقة classifier جديدة والانحراف). وهذا متوقع في هذه الحالة، لأننا نضيف رأساً جديداً لا تتوفر له أوزان مُسبقة التدريب، لذا ينبهنا المكتبة إلى ضرورة ضبط هذا النموذج الدقيق قبل استخدامه للاستدلال، وهو ما سنقوم به بالضبط.
ملاحظة أن نقطة التفتيش هذه تؤدي إلى أداء أفضل في هذه المهمة، حيث تم الحصول على نقطة التفتيش من خلال الضبط الدقيق على مهمة تالية مماثلة تتمتع بتداخل كبير في المجال. يمكنك الاطلاع على نقطة التفتيش هذه التي تم الحصول عليها من خلال ضبط MCG-NJU/videomae-base-finetuned-kinetics بدقة.
تجهيز مجموعات البيانات للتدريب
للمعالجة المسبقة للفيديوهات، ستستفيد من مكتبة PyTorchVideo. ابدأ باستيراد التبعيات التي نحتاجها.
>>> import pytorchvideo.data
>>> from pytorchvideo.transforms import (
... ApplyTransformToKey,
... Normalize,
... RandomShortSideScale,
... RemoveKey,
... ShortSideScale,
... UniformTemporalSubsample,
... )
>>> from torchvision.transforms import (
... Compose,
... Lambda,
... RandomCrop,
... RandomHorizontalFlip,
... Resize,
... )لتحويلات مجموعة بيانات التدريب، استخدم مزيجًا من أخذ العينات الزمني المنتظم، وتطبيع البكسلات، والقص العشوائي، والقلب الأفقي العشوائي. أما لتحويلات مجموعات بيانات التحقق والتقييم، فحافظ على سلسلة التحويلات نفسها باستثناء القص العشوائي والقلب الأفقي. لمعرفة المزيد من التفاصيل حول هذه التحويلات، راجع التوثيق الرسمي لـ PyTorchVideo.
استخدم image_processor المرتبط بالنموذج المُدرَّب مسبقًا للحصول على المعلومات التالية:
- متوسط الصورة والانحراف المعياري الذي تُطبَّق به عملية تطبيع بكسلات إطار الفيديو.
- الدقة المكانية التي سيتم إعادة تحجيم إطارات الفيديو إليها.
ابدأ بتعريف بعض الثوابت.
>>> mean = image_processor.image_mean
>>> std = image_processor.image_std
>>> if "shortest_edge" in image_processor.size:
... height = width = image_processor.size["shortest_edge"]
>>> else:
... height = image_processor.size["height"]
... width = image_processor.size["width"]
>>> resize_to = (height, width)
>>> num_frames_to_sample = model.config.num_frames
>>> sample_rate = 4
>>> fps = 30
>>> clip_duration = num_frames_to_sample * sample_rate / fpsالآن، عرّف التحويلات الخاصة بالبيانات المجمّعة والبيانات المجمّعة نفسها على التوالي. ابدأ بمجموعة التدريب:
>>> train_transform = Compose(
... [
... ApplyTransformToKey(
... key="video",
... transform=Compose(
... [
... UniformTemporalSubsample(num_frames_to_sample),
... Lambda(lambda x: x / 255.0),
... Normalize(mean, std),
... RandomShortSideScale(min_size=256, max_size=320),
... RandomCrop(resize_to),
... RandomHorizontalFlip(p=0.5),
... ]
... ),
... ),
... ]
... )
>>> train_dataset = pytorchvideo.data.Ucf101(
... data_path=os.path.join(dataset_root_path, "train"),
... clip_sampler=pytorchvideo.data.make_clip_sampler("random", clip_duration),
... decode_audio=False,
... transform=train_transform,
... )يمكن تطبيق نفس تسلسل سير العمل على مجموعات التحقق والتقييم:
>>> val_transform = Compose(
... [
... ApplyTransformToKey(
... key="video",
... transform=Compose(
... [
... UniformTemporalSubsample(num_frames_to_sample),
... Lambda(lambda x: x / 255.0),
... Normalize(mean, std),
... Resize(resize_to),
... ]
... ),
... ),
... ]
... )
>>> val_dataset = pytorchvideo.data.Ucf101(
... data_path=os.path.join(dataset_root_path, "val"),
... clip_sampler=pytorchvideo.data.make_clip_sampler("uniform", clip_duration),
... decode_audio=False,
... transform=val_transform,
... )
>>> test_dataset = pytorchvideo.data.Ucf101(
... data_path=os.path.join(dataset_root_path, "test"),
... clip_sampler=pytorchvideo.data.make_clip_sampler("uniform", clip_duration),
... decode_audio=False,
... transform=val_transform,
... )ملاحظة: تم استيراد خطوط أنابيب مجموعات البيانات المذكورة أعلاه من مثال PyTorchVideo الرسمي. نستخدم الدالة pytorchvideo.data.Ucf101() لأنها مصممة خصيصًا لمجموعة بيانات UCF-101. في الخلفية، تُرجع كائنًا من نوع pytorchvideo.data.labeled_video_dataset.LabeledVideoDataset. تُعد فئة LabeledVideoDataset الفئة الأساسية لكل ما يتعلق بالفيديو في مجموعات بيانات PyTorchVideo. لذا، إذا كنت ترغب في استخدام مجموعة بيانات مخصصة لا يدعمها PyTorchVideo بشكل جاهز، يمكنك توسيع فئة LabeledVideoDataset وفقًا لذلك. راجع التوثيق الخاص بواجهة data للتعرف على المزيد. أيضًا، إذا كانت مجموعة بياناتك تتبع بنية مشابهة (كما هو موضح أعلاه)، فسيكون استخدام pytorchvideo.data.Ucf101() مناسبًا تمامًا.
يمكنك الوصول إلى الحجة num_videos لمعرفة عدد مقاطع الفيديو في مجموعة البيانات.
>>> print(train_dataset.num_videos, val_dataset.num_videos, test_dataset.num_videos)
# (300, 30, 75)تصوير الفيديو بعد المعالجة المسبقة لتحسين تصحيح الأخطاء
>>> import imageio
>>> import numpy as np
>>> from IPython.display import Image
>>> def unnormalize_img(img):
... """Un-normalizes the image pixels."""
... img = (img * std) + mean
... img = (img * 255).astype("uint8")
... return img.clip(0, 255)
>>> def create_gif(video_tensor, filename="sample.gif"):
... """Prepares a GIF from a video tensor.
...
... The video tensor is expected to have the following shape:
... (num_frames, num_channels, height, width).
... """
... frames = []
... for video_frame in video_tensor:
... frame_unnormalized = unnormalize_img(video_frame.permute(1, 2, 0).numpy())
... frames.append(frame_unnormalized)
... kargs = {"duration": 0.25}
... imageio.mimsave(filename, frames, "GIF", **kargs)
... return filename
>>> def display_gif(video_tensor, gif_name="sample.gif"):
... """Prepares and displays a GIF from a video tensor."""
... video_tensor = video_tensor.permute(1, 0, 2, 3)
... gif_filename = create_gif(video_tensor, gif_name)
... return Image(filename=gif_filename)
>>> sample_video = next(iter(train_dataset))
>>> video_tensor = sample_video["video"]
>>> display_gif(video_tensor)
تدريب النموذج
استفد من Trainer من 🤗 Transformers لتدريب النموذج. لإنشاء كائن Trainer، تحتاج إلى تحديد إعدادات التدريب ومقياس التقييم. الأهم هو TrainingArguments، وهي فئة تحتوي على جميع السمات اللازمة لإعداد التدريب. تتطلب اسم مجلد للإخراج، الذي سيُستخدم لحفظ نقاط التفتيش (checkpoints) للنموذج. كما تساعد على مزامنة جميع المعلومات في مستودع النموذج على 🤗 Hub.
معظم حُجج التدريب ذاتية الشرح، لكن أحد الحُجج المهمة هنا هو remove_unused_columns=False. يؤدي هذا الحُجج إلى حذف أي ميزات لا تُستخدم بواسطة دالة استدعاء النموذج. افتراضيًا، يكون هذا الحُجج True لأن حذف أعمدة الميزات غير المستخدمة يكون مثاليًا عادةً، مما يسهّل تفكيك المدخلات إلى دالة استدعاء النموذج. لكن، في هذه الحالة، تحتاج إلى الميزات غير المستخدمة (وخاصة 'video') من أجل إنشاء pixel_values (وهو مفتاح إلزامي يتوقعه نموذجنا في مدخلاته).
>>> from transformers import TrainingArguments, Trainer
>>> model_name = model_ckpt.split("/")[-1]
>>> new_model_name = f"{model_name}-finetuned-ucf101-subset"
>>> num_epochs = 4
>>> args = TrainingArguments(
... new_model_name,
... remove_unused_columns=False,
... eval_strategy="epoch",
... save_strategy="epoch",
... learning_rate=5e-5,
... per_device_train_batch_size=batch_size,
... per_device_eval_batch_size=batch_size,
... warmup_steps=0.1,
... logging_steps=10,
... load_best_model_at_end=True,
... metric_for_best_model="accuracy",
... push_to_hub=True,
... max_steps=(train_dataset.num_videos // batch_size) * num_epochs,
... )لا ينفّذ مجموعة البيانات التي يعيدها pytorchvideo.data.Ucf101() طريقة __len__. وعليه، يجب تحديد max_steps عند إنشاء TrainingArguments.
بعد ذلك، تحتاج إلى تعريف دالة لحساب المقاييس من التنبؤات، والتي ستستخدم metric التي ستحمّلها الآن. المعالجة المسبقة الوحيدة التي يجب عليك القيام بها هي أخذ argmax لقيم logits المتنبأ بها:
import evaluate
metric = evaluate.load("accuracy")
def compute_metrics(eval_pred):
predictions = np.argmax(eval_pred.predictions, axis=1)
return metric.compute(predictions=predictions, references=eval_pred.label_ids)ملاحظة حول التقييم:
في ورقة VideoMAE، يستخدم المؤلفون استراتيجية التقييم التالية. يقومون بتقييم النموذج على عدة مقاطع من مقاطع الفيديو الاختبارية، ويطبّقون قصّات مختلفة على تلك المقاطع، ثم يبلّغون عن الدرجة المجمّعة. ومع ذلك، من باب البساطة والإيجاز، لا نأخذ ذلك في الاعتبار في هذا الشرح التعليمي.
كما يجب تعريف collate_fn، الذي سيُستخدم لتجميع الأمثلة في دفعات. تتكون كل دفعة من مفتاحين، وهما pixel_values وlabels.
>>> def collate_fn(examples):
... # permute to (num_frames, num_channels, height, width)
... pixel_values = torch.stack(
... [example["video"].permute(1, 0, 2, 3) for example in examples]
... )
... labels = torch.tensor([example["label"] for example in examples])
... return {"pixel_values": pixel_values, "labels": labels}ثم تمرر كل هذا مع مجموعات البيانات إلى Trainer:
>>> trainer = Trainer(
... model,
... args,
... train_dataset=train_dataset,
... eval_dataset=val_dataset,
... processing_class=image_processor,
... compute_metrics=compute_metrics,
... data_collator=collate_fn,
... )قد تتساءل عن سبب تمرير image_processor كمُرمِّز (tokenizer) رغم أنك قمت بمعالجة البيانات مسبقًا. الهدف من ذلك هو التأكد من رفع ملف إعدادات مُعالج الصور (المُخزَّن بصيغة JSON) إلى المستودع على Hub أيضًا.
الآن، قم بضبط نموذجنا عن طريق استدعاء طريقة train:
>>> train_results = trainer.train()بمجرد اكتمال التدريب، شارك نموذجك على Hub باستخدام طريقة push_to_hub() ليتمكن الجميع من استخدام نموذجك:
>>> trainer.push_to_hub()الاستدلال
ممتاز، والآن بعد أن قمت بضبط نموذج، يمكنك استخدامه للاستدلال!
تحميل فيديو للاستدلال:
>>> sample_test_video = next(iter(test_dataset))
أبسط طريقة لتجربة نموذجك المُحسَّن للاستدلال هي استخدامه في VideoClassificationPipeline. قم بإنشاء كائن pipeline لتصنيف الفيديو باستخدام نموذجك، ثم مرّر الفيديو الخاص بك إليه:
>>> from transformers import pipeline
from accelerate import Accelerator
>>> video_cls = pipeline(model="my_awesome_video_cls_model")
>>> video_cls("https://huggingface.co/datasets/sayakpaul/ucf101-subset/resolve/main/v_BasketballDunk_g14_c06.avi")
[{'score': 0.9272987842559814, 'label': 'BasketballDunk'},
{'score': 0.017777055501937866, 'label': 'BabyCrawling'},
{'score': 0.01663011871278286, 'label': 'BalanceBeam'},
{'score': 0.009560945443809032, 'label': 'BandMarching'},
{'score': 0.0068979403004050255, 'label': 'BaseballPitch'}]يمكنك أيضًا إعادة إنتاج نتائج pipeline يدويًا إذا رغبت في ذلك.
>>> def run_inference(model, video):
... # (num_frames, num_channels, height, width)
... permuted_sample_test_video = video.permute(1, 0, 2, 3)
... inputs = {
... "pixel_values": perumuted_sample_test_video.unsqueeze(0),
... "labels": torch.tensor(
... [sample_test_video["label"]]
... ), # this can be skipped if you don't have labels available.
... }
... device = Accelerator().device
... inputs = {k: v.to(device) for k, v in inputs.items()}
... model = model.to(device)
... # forward pass
... with torch.no_grad():
... outputs = model(**inputs)
... logits = outputs.logits
... return logitsالآن، مرّر مدخلاتك إلى النموذج وأعد logits:
>>> logits = run_inference(trained_model, sample_test_video["video"])عند فك ترميز logits، نحصل على:
>>> predicted_class_idx = logits.argmax(-1).item()
>>> print("Predicted class:", model.config.id2label[predicted_class_idx])
# Predicted class: BasketballDunk