Transformers documentation
Phân loại video
Phân loại video
Phân loại video là nhiệm vụ gán một nhãn hoặc lớp cho toàn bộ video. Mỗi video được kỳ vọng chỉ có một lớp duy nhất. Mô hình phân loại video nhận video làm đầu vào và trả về dự đoán về lớp mà video đó thuộc về. Các mô hình này có thể được sử dụng để phân loại nội dung chính của video. Một ứng dụng thực tế của phân loại video là nhận diện hành động / hoạt động, rất hữu ích cho các ứng dụng thể dục. Nó cũng hỗ trợ người khiếm thị, đặc biệt là khi họ di chuyển.
Hướng dẫn này sẽ cho bạn thấy cách:
- Tinh chỉnh VideoMAE trên một tập con của bộ dữ liệu UCF101.
- Sử dụng mô hình đã tinh chỉnh của bạn để suy luận.
Để xem tất cả các kiến trúc và checkpoint tương thích với tác vụ này, chúng tôi khuyến nghị bạn kiểm tra trang tác vụ.
Trước khi bắt đầu, hãy đảm bảo bạn đã cài đặt tất cả các thư viện cần thiết:
pip install -q pytorchvideo transformers evaluate
Bạn sẽ sử dụng PyTorchVideo (gọi tắt là pytorchvideo) để xử lý và chuẩn bị các video.
Chúng tôi khuyến khích bạn đăng nhập vào tài khoản Hugging Face của mình để có thể tải lên và chia sẻ mô hình của bạn với cộng đồng. Khi được yêu cầu, hãy nhập token của bạn để đăng nhập:
>>> from huggingface_hub import notebook_login
>>> notebook_login()Tải bộ dữ liệu UCF101
Bắt đầu bằng cách tải một tập con của UCF-101 dataset. Điều này sẽ giúp bạn thử nghiệm và đảm bảo mọi thứ hoạt động trước khi dành thêm thời gian huấn luyện trên toàn bộ tập dữ liệu.
>>> 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")Sau khi tập con đã được tải xuống, bạn cần giải nén tệp lưu trữ nén:
>>> import tarfile
>>> with tarfile.open(file_path) as t:
... t.extractall(".")Nhìn chung, tập dữ liệu được tổ chức như sau:
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
...
...Sau đó, bạn có thể đếm tổng số video.
>>> 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]Các đường dẫn video (sorted) trông như sau:
...
'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'
...Bạn sẽ nhận thấy có các đoạn video thuộc cùng một nhóm / cảnh, trong đó nhóm được ký hiệu bằng g trong đường dẫn tệp video. Ví dụ như v_ApplyEyeMakeup_g07_c04.avi và v_ApplyEyeMakeup_g07_c06.avi.
Đối với các tập dữ liệu kiểm định và đánh giá, bạn không muốn có các đoạn video từ cùng một nhóm / cảnh để ngăn chặn chảy dữ liệu. Tập con mà bạn đang sử dụng trong hướng dẫn này đã tính đến thông tin này.
Tiếp theo, bạn sẽ xác định tập hợp các nhãn có trong bộ dữ liệu. Ngoài ra, hãy tạo hai từ điển sẽ hữu ích khi khởi tạo mô hình:
label2id: ánh xạ tên lớp sang các số nguyên.id2label: ánh xạ các số nguyên sang tên lớp.
>>> 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'].Có 10 lớp duy nhất. Đối với mỗi lớp, tập huấn luyện có 30 video.
Tải một mô hình để tinh chỉnh
Khởi tạo một mô hình phân loại video từ một checkpoint được huấn luyện trước cùng bộ xử lý hình ảnh liên quan. Encoder của mô hình đi kèm các tham số đã được huấn luyện trước, trong khi đầu phân loại được khởi tạo ngẫu nhiên. Bộ xử lý hình ảnh sẽ hữu ích khi viết pipeline tiền xử lý cho tập dữ liệu của chúng ta.
>>> 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
... )Trong khi mô hình đang được tải, bạn có thể nhận thấy cảnh báo sau:
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.Cảnh báo cho biết chúng ta đang loại bỏ một số trọng số (ví dụ: trọng số và bias của lớp classifier) và khởi tạo ngẫu nhiên một số trọng số khác (trọng số và bias của lớp classifier mới). Điều này là dự kiến trong trường hợp này, vì chúng ta đang thêm một đầu mới mà không có trọng số được huấn luyện trước, do đó thư viện cảnh báo rằng chúng ta nên tinh chỉnh mô hình này trước khi sử dụng để suy luận, và đó chính xác là điều chúng ta sắp làm.
Lưu ý rằng checkpoint này cho hiệu suất tốt hơn trên tác vụ này vì checkpoint được thu được bằng cách fine-tuning trên một tác vụ downstream tương tự có sự trùng lặp đáng kể về domain. Bạn có thể xem checkpoint này được thu được bằng cách fine-tuning MCG-NJU/videomae-base-finetuned-kinetics.
Chuẩn bị các bộ dữ liệu cho huấn luyện
Để tiền xử lý các video, bạn sẽ sử dụng thư viện PyTorchVideo. Bắt đầu bằng cách nhập các phụ thuộc cần thiết.
>>> import pytorchvideo.data
>>> from pytorchvideo.transforms import (
... ApplyTransformToKey,
... Normalize,
... RandomShortSideScale,
... RemoveKey,
... ShortSideScale,
... UniformTemporalSubsample,
... )
>>> from torchvision.transforms import (
... Compose,
... Lambda,
... RandomCrop,
... RandomHorizontalFlip,
... Resize,
... )Đối với các phép biến đổi trên tập dữ liệu huấn luyện, hãy sử dụng kết hợp của lấy mẫu thời gian đều, chuẩn hóa pixel, cắt ngẫu nhiên và lật ngang ngẫu nhiên. Đối với các phép biến đổi trên tập dữ liệu xác thực và đánh giá, giữ nguyên chuỗi biến đổi như trên ngoại trừ cắt ngẫu nhiên và lật ngang. Để tìm hiểu thêm về chi tiết của các phép biến đổi này, hãy xem tài liệu chính thức của PyTorchVideo.
Sử dụng image_processor liên quan đến mô hình được huấn luyện trước để thu thập các thông tin sau:
- Trung bình và độ lệch chuẩn của hình ảnh dùng để chuẩn hóa các pixel của khung hình video.
- Độ phân giải không gian mà các khung hình video sẽ được thay đổi kích thước.
Bắt đầu bằng cách định nghĩa một số hằng số.
>>> 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 / fpsBây giờ, hãy xác định các phép biến đổi cụ thể cho tập dữ liệu và các tập dữ liệu tương ứng. Bắt đầu với tập huấn luyện:
>>> 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,
... )Cùng một trình tự quy trình làm việc có thể được áp dụng cho các tập kiểm chứng và đánh giá:
>>> 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,
... )Ghi chú: Các pipeline dataset ở trên được lấy từ ví dụ chính thức của PyTorchVideo. Chúng tôi sử dụng hàm pytorchvideo.data.Ucf101() vì nó được thiết kế riêng cho dataset UCF-101. Bên trong, hàm này trả về một đối tượng pytorchvideo.data.labeled_video_dataset.LabeledVideoDataset. Lớp LabeledVideoDataset là lớp cơ sở cho mọi thứ liên quan đến video trong dataset của PyTorchVideo. Do đó, nếu bạn muốn sử dụng một dataset tùy chỉnh không được hỗ trợ sẵn bởi PyTorchVideo, bạn có thể mở rộng lớp LabeledVideoDataset cho phù hợp. Tham khảo tài liệu API data để tìm hiểu thêm. Ngoài ra, nếu dataset của bạn có cấu trúc tương tự (như đã nêu ở trên), thì việc sử dụng pytorchvideo.data.Ucf101() sẽ hoạt động hoàn toàn bình thường.
Bạn có thể truy cập đối số num_videos để biết số lượng video trong bộ dữ liệu.
>>> print(train_dataset.num_videos, val_dataset.num_videos, test_dataset.num_videos)
# (300, 30, 75)Trực quan hóa video đã tiền xử lý để gỡ lỗi tốt hơn
>>> 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)
Huấn luyện mô hình
Sử dụng Trainer từ 🤗 Transformers để huấn luyện mô hình. Để khởi tạo một Trainer, bạn cần định nghĩa cấu hình huấn luyện và một chỉ số đánh giá. Quan trọng nhất là TrainingArguments, một lớp chứa tất cả các thuộc tính để cấu hình quá trình huấn luyện. Nó yêu cầu tên thư mục đầu ra, sẽ được dùng để lưu các checkpoint của mô hình. Nó cũng giúp đồng bộ tất cả thông tin trong kho lưu trữ mô hình trên 🤗 Hub.
Hầu hết các đối số huấn luyện đều tự giải thích, nhưng một đối số khá quan trọng ở đây là remove_unused_columns=False. Đối số này sẽ loại bỏ bất kỳ đặc trưng nào không được sử dụng bởi hàm call của mô hình. Mặc định nó là True vì thông thường việc loại bỏ các cột đặc trưng không dùng đến là lý tưởng, giúp việc giải nén các đầu vào thành hàm call của mô hình dễ dàng hơn. Tuy nhiên, trong trường hợp này, bạn cần các đặc trưng không được sử dụng (cụ thể là ‘video’) để tạo ra pixel_values (đây là một khóa bắt buộc mà mô hình của chúng ta kỳ vọng trong các đầu vào của nó).
>>> 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,
... )Dataset do pytorchvideo.data.Ucf101() trả về không triển khai phương thức __len__. Do đó, chúng ta phải định nghĩa max_steps khi khởi tạo TrainingArguments.
Tiếp theo, bạn cần định nghĩa một hàm để tính các chỉ số từ các dự đoán, hàm này sẽ sử dụng metric mà bạn sẽ tải ngay bây giờ. Việc tiền xử lý duy nhất bạn phải thực hiện là lấy argmax của các logits dự đoán:
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)Lưu ý về đánh giá:
Trong bài báo VideoMAE, các tác giả sử dụng chiến lược đánh giá sau. Họ đánh giá mô hình trên một số đoạn clip từ các video kiểm thử, áp dụng các kiểu cắt khác nhau lên những đoạn clip đó và báo cáo điểm tổng hợp. Tuy nhiên, vì mục đích đơn giản và ngắn gọn, chúng ta sẽ không xem xét điều này trong hướng dẫn này.
Ngoài ra, hãy định nghĩa collate_fn, sẽ được dùng để nhóm các ví dụ thành từng lô. Mỗi lô gồm 2 khóa, cụ thể là pixel_values và 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}Sau đó, bạn chỉ cần truyền tất cả những thứ này cùng với các tập dữ liệu vào 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,
... )Bạn có thể tự hỏi tại sao lại truyền image_processor như một tokenizer trong khi dữ liệu đã được tiền xử lý. Điều này chỉ nhằm đảm bảo tệp cấu hình bộ xử lý ảnh (được lưu dưới dạng JSON) cũng sẽ được tải lên kho trên Hub.
Bây giờ, tinh chỉnh mô hình của chúng ta bằng cách gọi phương thức train:
>>> train_results = trainer.train()Khi quá trình huấn luyện hoàn tất, hãy chia sẻ mô hình của bạn lên Hub bằng phương thức push_to_hub() để mọi người có thể sử dụng mô hình của bạn:
>>> trainer.push_to_hub()Suy luận
Tuyệt, giờ đây bạn đã tinh chỉnh một mô hình, bạn có thể sử dụng nó để suy luận!
Tải video để suy luận:
>>> sample_test_video = next(iter(test_dataset))
Cách đơn giản nhất để thử nghiệm mô hình đã tinh chỉnh của bạn cho suy luận là sử dụng nó trong một VideoClassificationPipeline. Khởi tạo một pipeline cho phân loại video với mô hình của bạn, và truyền video của bạn vào đó:
>>> 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'}]Bạn cũng có thể tự động tái tạo kết quả của pipeline nếu muốn.
>>> 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 logitsBây giờ, truyền đầu vào của bạn vào mô hình và trả về logits:
>>> logits = run_inference(trained_model, sample_test_video["video"])Giải mã logits, ta có:
>>> predicted_class_idx = logits.argmax(-1).item()
>>> print("Predicted class:", model.config.id2label[predicted_class_idx])
# Predicted class: BasketballDunk