# Klasifikasi video

Klasifikasi video adalah tugas untuk memberikan label atau kelas pada seluruh video. Setiap video diharapkan memiliki hanya satu kelas. Model klasifikasi video menerima video sebagai input dan mengembalikan prediksi mengenai kelas mana yang dimiliki oleh video tersebut. Model-model ini dapat digunakan untuk mengkategorikan topik atau isi dari sebuah video. Salah satu aplikasi dunia nyata dari klasifikasi video adalah pengenalan aksi/aktivitas, yang berguna untuk aplikasi kebugaran. Hal ini juga sangat membantu bagi individu dengan gangguan penglihatan, terutama saat mereka sedang dalam perjalanan.

Panduan ini akan menunjukkan cara Anda untuk:

1. Lakukan fine-tuning pada [VideoMAE](../model_doc/videomae) menggunakan subset dari dataset [UCF101](https://www.crcv.ucf.edu/data/UCF101.php).
2. Gunakan model yang telah Anda fine-tune untuk inferensi.

Untuk melihat semua arsitektur dan checkpoint yang kompatibel dengan tugas ini, kami menyarankan untuk memeriksa [halaman tugas](https://huggingface.co/tasks/video-classification).

Sebelum memulai, pastikan Anda telah menginstal semua pustaka yang diperlukan:

```bash
pip install -q pytorchvideo transformers evaluate
```

Anda akan menggunakan [PyTorchVideo](https://pytorchvideo.org/) (disebut `pytorchvideo`) untuk memproses dan menyiapkan video.

Kami menyarankan Anda untuk masuk ke akun Hugging Face Anda agar dapat mengunggah dan membagikan model Anda dengan komunitas. Saat diminta, masukkan token Anda untuk masuk:

```py
>>> from huggingface_hub import notebook_login

>>> notebook_login()
```

## Muat dataset UCF101

Mulai dengan memuat subset dari [UCF-101 dataset](https://www.crcv.ucf.edu/data/UCF101.php). Hal ini akan memberi Anda kesempatan untuk bereksperimen dan memastikan semuanya berfungsi dengan baik sebelum menghabiskan lebih banyak waktu untuk melatih pada dataset lengkap.

```py
>>> 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")
```

Setelah subset diunduh, Anda perlu mengekstrak arsip terkompresi:

```py
>>> import tarfile

>>> with tarfile.open(file_path) as t:
...      t.extractall(".")
```

Secara garis besar, dataset ini diorganisasikan sebagai berikut:

```bash
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
            ...
        ...
```

Anda kemudian dapat menghitung total jumlah video.

```py
>>> import pathlib
>>> dataset_root_path = "UCF101_subset"
>>> dataset_root_path = pathlib.Path(dataset_root_path)
```

```py
>>> 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}")
```

```py
>>> 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]
```

The Jalur video (`sorted`) tampak seperti ini:

```bash
...
'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'
...
```

Anda akan melihat bahwa ada klip video yang termasuk dalam grup / adegan yang sama, di mana grup ditunjukkan oleh `g` pada path file video. Misalnya, `v_ApplyEyeMakeup_g07_c04.avi` dan `v_ApplyEyeMakeup_g07_c06.avi`.

Untuk split validasi dan evaluasi, Anda tidak ingin memiliki klip video dari grup / adegan yang sama untuk mencegah [kebocoran data](https://www.kaggle.com/code/alexisbcook/data-leakage). Subset yang Anda gunakan dalam tutorial ini memperhitungkan informasi ini.

Selanjutnya, Anda akan menurunkan himpunan label yang ada dalam dataset. Selain itu, buatlah dua kamus yang akan membantu saat menginisialisasi model:

* `label2id`: memetakan nama kelas ke bilangan bulat.
* `id2label`: memetakan bilangan bulat ke nama kelas.

```py
>>> 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'].
```

Terdapat 10 kelas unik. Untuk setiap kelas, terdapat 30 video dalam set pelatihan.

## Muat model untuk fine-tuning

Instansiasi model klasifikasi video dari checkpoint pretrained dan image processor yang terkait. Encoder model ini dilengkapi dengan parameter pretrained, sedangkan kepala klasifikasinya diinisialisasi secara acak. Image processor akan sangat berguna saat menulis pipeline pra-pemrosesan untuk dataset kami.

```py
>>> 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
... )
```

Saat model dimuat, Anda mungkin akan melihat peringatan berikut:

```bash
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.
```

Peringatan ini memberi tahu kita bahwa beberapa bobot dibuang (misalnya bobot dan bias dari lapisan `classifier`) dan beberapa lainnya diinisialisasi secara acak (bobot dan bias dari lapisan `classifier` baru). Hal ini wajar dalam kasus ini, karena kita menambahkan head baru yang tidak memiliki bobot terlatih sebelumnya, sehingga pustaka memberi tahu kita bahwa model ini harus ditala halus sebelum digunakan untuk inferensi, yang memang akan kita lakukan.

**Catatan** bahwa [checkpoint ini](https://huggingface.co/MCG-NJU/videomae-base-finetuned-kinetics) menghasilkan performa yang lebih baik pada tugas ini karena checkpoint tersebut diperoleh melalui fine-tuning pada tugas downstream serupa yang memiliki tumpang tindih domain yang signifikan. Anda dapat melihat [checkpoint ini](https://huggingface.co/sayakpaul/videomae-base-finetuned-kinetics-finetuned-ucf101-subset) yang diperoleh melalui fine-tuning `MCG-NJU/videomae-base-finetuned-kinetics`.

## Siapkan dataset untuk pelatihan

Untuk prapemrosesan video, Anda akan memanfaatkan [PyTorchVideo library](https://pytorchvideo.org/). Mulailah dengan mengimpor dependensi yang kita butuhkan.

```py
>>> import pytorchvideo.data

>>> from pytorchvideo.transforms import (
...     ApplyTransformToKey,
...     Normalize,
...     RandomShortSideScale,
...     RemoveKey,
...     ShortSideScale,
...     UniformTemporalSubsample,
... )

>>> from torchvision.transforms import (
...     Compose,
...     Lambda,
...     RandomCrop,
...     RandomHorizontalFlip,
...     Resize,
... )
```

Untuk transformasi dataset pelatihan, gunakan kombinasi subsampling temporal seragam, normalisasi piksel, pemotongan acak, dan pembalikan horizontal acak. Untuk transformasi dataset validasi dan evaluasi, pertahankan rantai transformasi yang sama kecuali untuk pemotongan acak dan pembalikan horizontal. Untuk mempelajari lebih lanjut tentang detail transformasi ini, lihat [dokumentasi resmi PyTorchVideo](https://pytorchvideo.org).

Gunakan `image_processor` yang terkait dengan model terlatih sebelumnya untuk memperoleh informasi berikut:

* Rata-rata dan deviasi standar gambar yang digunakan untuk menormalisasi piksel bingkai video.
* Resolusi spasial yang akan digunakan untuk mengubah ukuran bingkai video.

Mulai dengan mendefinisikan beberapa konstanta.

```py
>>> 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
```

Sekarang, definisikan transformasi yang spesifik untuk dataset dan datasetnya masing-masing. Dimulai dengan set pelatihan:

```py
>>> 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,
... )
```

Urutan alur kerja yang sama dapat diterapkan pada set validasi dan evaluasi:

```py
>>> 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,
... )
```

**Catatan**: Pipeline dataset di atas diambil dari [contoh resmi PyTorchVideo](https://pytorchvideo.org/docs/tutorial_classification#dataset). Kami menggunakan fungsi [`pytorchvideo.data.Ucf101()`](https://pytorchvideo.readthedocs.io/en/latest/api/data/data.html#pytorchvideo.data.Ucf101) karena dirancang khusus untuk dataset UCF-101. Di balik layar, fungsi ini mengembalikan objek [`pytorchvideo.data.labeled_video_dataset.LabeledVideoDataset`](https://pytorchvideo.readthedocs.io/en/latest/api/data/data.html#pytorchvideo.data.LabeledVideoDataset). Kelas `LabeledVideoDataset` adalah kelas dasar untuk semua hal video dalam dataset PyTorchVideo. Jadi, jika Anda ingin menggunakan dataset kustom yang tidak didukung secara bawaan oleh PyTorchVideo, Anda dapat memperluas kelas `LabeledVideoDataset` sesuai kebutuhan. Lihat [dokumentasi](https://pytorchvideo.readthedocs.io/en/latest/api/data/data.html) API `data` untuk mempelajari lebih lanjut. Selain itu, jika dataset Anda mengikuti struktur yang serupa (seperti yang ditunjukkan di atas), maka penggunaan `pytorchvideo.data.Ucf101()` seharusnya akan berjalan dengan baik.

Anda dapat mengakses argumen `num_videos` untuk mengetahui jumlah video dalam dataset.

```py
>>> print(train_dataset.num_videos, val_dataset.num_videos, test_dataset.num_videos)
# (300, 30, 75)
```

## Visualisasikan video yang telah diproses pra-pemrosesan untuk debugging yang lebih baik

```py
>>> 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)
```

    

## Latih model

Manfaatkan [Trainer](/docs/transformers/main/id/main_classes/trainer#transformers.Trainer) dari 🤗 Transformers untuk melatih model. Untuk membuat instans `Trainer`, Anda perlu mendefinisikan konfigurasi pelatihan dan metrik evaluasi. Yang paling penting adalah [TrainingArguments](/docs/transformers/main/id/main_classes/trainer#transformers.TrainingArguments), yaitu kelas yang berisi semua atribut untuk mengonfigurasi pelatihan. Kelas ini memerlukan nama folder output, yang akan digunakan untuk menyimpan checkpoint model. Kelas ini juga membantu menyinkronkan semua informasi di repositori model pada 🤗 Hub.

Sebagian besar argumen pelatihan sudah jelas maknanya, tetapi satu yang cukup penting di sini adalah `remove_unused_columns=False`. Argumen ini akan membuang fitur-fitur yang tidak digunakan oleh fungsi call model. Secara default nilainya `True` karena biasanya ideal untuk membuang kolom fitur yang tidak terpakai, sehingga memudahkan penguraian input ke dalam fungsi call model. Namun, dalam kasus ini, Anda membutuhkan fitur yang tidak terpakai (khususnya 'video') untuk membuat `pixel_values` (yang merupakan kunci wajib yang diharapkan model dalam inputnya).

```py
>>> 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 yang dikembalikan oleh `pytorchvideo.data.Ucf101()` tidak mengimplementasikan metode `__len__`. Oleh karena itu, kita harus mendefinisikan `max_steps` saat menginstansiasi `TrainingArguments`.

Selanjutnya, Anda perlu mendefinisikan fungsi untuk menghitung metrik dari prediksi, yang akan menggunakan `metric` yang akan Anda muat sekarang. Satu-satunya pra-pemrosesan yang harus Anda lakukan adalah mengambil argmax dari logits prediksi kita:

```py
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)
```

**Catatan tentang evaluasi**:

Dalam [makalah VideoMAE](https://huggingface.co/papers/2203.12602), para penulis menggunakan strategi evaluasi berikut. Mereka mengevaluasi model pada beberapa klip dari video uji dan menerapkan berbagai crop pada klip tersebut, lalu melaporkan skor agregat. Namun, demi kesederhanaan dan ringkasan, kami tidak mempertimbangkan hal itu dalam tutorial ini.

Selain itu, definisikan `collate_fn`, yang akan digunakan untuk mengelompokkan contoh-contoh menjadi batch. Setiap batch terdiri dari 2 kunci, yaitu `pixel_values` dan `labels`.

```py
>>> 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}
```

Lalu Anda cukup meneruskan semua ini bersama dataset ke `Trainer`:

```py
>>> trainer = Trainer(
...     model,
...     args,
...     train_dataset=train_dataset,
...     eval_dataset=val_dataset,
...     processing_class=image_processor,
...     compute_metrics=compute_metrics,
...     data_collator=collate_fn,
... )
```

Anda mungkin bertanya-tanya mengapa Anda tetap meneruskan `image_processor` sebagai tokenizer meskipun data sudah dipreprocess sebelumnya. Hal ini hanya untuk memastikan file konfigurasi image processor (disimpan sebagai JSON) juga diunggah ke repo di Hub.

Sekarang lakukan fine-tuning pada model kita dengan memanggil metode `train`:

```py
>>> train_results = trainer.train()
```

Setelah pelatihan selesai, bagikan model Anda ke Hub dengan metode [push_to_hub()](/docs/transformers/main/id/main_classes/trainer#transformers.Trainer.push_to_hub) agar semua orang dapat menggunakan model Anda:

```py
>>> trainer.push_to_hub()
```

## Inferensi

Bagus, sekarang setelah Anda melakukan fine-tuning pada model, Anda dapat menggunakannya untuk inferensi!

Muat video untuk inferensi:

```py
>>> sample_test_video = next(iter(test_dataset))
```

    

Cara termudah untuk mencoba model fine-tune Anda untuk inferensi adalah dengan menggunakannya dalam [VideoClassificationPipeline](/docs/transformers/main/id/main_classes/pipelines#transformers.VideoClassificationPipeline). Buat instans `pipeline` untuk klasifikasi video dengan model Anda, lalu lewati video Anda ke dalamnya:

```py
>>> 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'}]
```

Anda juga dapat mereplikasi hasil dari `pipeline` secara manual jika Anda mau.

```py
>>> 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
```

Sekarang, berikan input Anda ke model dan kembalikan `logits`:

```py
>>> logits = run_inference(trained_model, sample_test_video["video"])
```

Mendekode `logits`, kita mendapatkan:

```py
>>> predicted_class_idx = logits.argmax(-1).item()
>>> print("Predicted class:", model.config.id2label[predicted_class_idx])
# Predicted class: BasketballDunk
```

