Skip to content

aitf-sr1/convnext-final-code-sera

Repository files navigation

ConvNeXt V2-Femto : SERA (Student Emotion Recognition and Analytics)

Model ini merupakan model konvolusi berukuran kecil yang digunakan untuk mengenali empat kondisi emosi yang berkaitan dengan engagement belajar dari satu gambar wajah (face crop). Keempat kondisi diprediksi secara independen (multi-label), sehingga satu gambar dapat memiliki lebih dari satu label secara bersamaan. Sebagai contoh, seorang siswa dapat menunjukkan tanda-tanda boredom dan confusion pada saat yang sama.

Keadaan Boredom · Engagement · Confusion · Frustration
Formulasi Multi-label (satu sigmoid untuk setiap label)
Backbone convnextv2_femto.fcmae_ft_in1k (timm)
Parameter ±4,9 juta (4,8 juta backbone + 0,1 juta head klasifikasi)
Input Crop wajah RGB berukuran 224×224 dengan normalisasi ImageNet
Macro-F1 validasi 0,7908
Macro-F1 test 0,8289

Catatan parameter. Di tabel benchmark timm, convnextv2_femto tercatat 5,23 juta parameter karena angka tersebut sudah termasuk head klasifikasi ImageNet (1000 kelas). Pada model ini head bawaan tersebut (~0,39 juta) dibuang dan diganti head 4-label (~0,10 juta), sehingga totalnya menjadi ~4,9 juta parameter.

Repositori ini berisi seluruh konfigurasi dan kode yang digunakan untuk menghasilkan model yang dirilis. Bobot model terlatih dalam format ONNX (convnextv2_femto_v3_crop.onnx) tidak disertakan di repositori ini, melainkan tersedia di HuggingFace: aitf-ub-2026/ub-sr-01-model-fer-convnextv2-datasetv3. Menjalankan train.py menggunakan konfigurasi bawaan akan menghasilkan model yang setara dengan checkpoint tersebut. Perbedaan hasil yang mungkin muncul umumnya hanya disebabkan oleh sifat non-deterministik selama training pada GPU.

Pendekatan

Arsitektur model menggunakan backbone ConvNeXt V2-Femto yang telah melalui proses pre-training menggunakan metode Fully Convolutional Masked Autoencoder (FCMAE) dan kemudian di-fine-tune pada ImageNet-1K. Di atas backbone tersebut ditambahkan head klasifikasi dua lapis untuk memprediksi empat label secara independen.

Training dilakukan dalam dua tahap. Pada tahap awal, parameter backbone di-freeze sehingga hanya head klasifikasi yang diperbarui. Pendekatan ini membantu head mempelajari representasi awal tanpa mengubah fitur hasil pre-training. Setelah beberapa epoch, seluruh parameter model dibuka dan dilakukan fine-tuning menggunakan learning rate yang dinaikkan secara bertahap (linear warmup), kemudian diturunkan mengikuti cosine learning rate schedule.

Distribusi data yang tidak seimbang menjadi tantangan utama karena label engagement jauh lebih sering muncul dibandingkan frustration. Untuk mengatasi hal tersebut, training menggunakan Focal Loss yang dipadukan dengan pembobotan kelas berdasarkan rasio jumlah sampel negatif dan positif (pos_weight). Dengan cara ini, model tetap memberikan perhatian pada kelas-kelas yang jumlahnya lebih sedikit.

Setelah setiap epoch selesai, nilai threshold untuk masing-masing label tidak langsung menggunakan nilai baku 0,5. Threshold ditentukan melalui pencarian nilai yang memberikan skor F1 terbaik pada sebagian data validasi, kemudian dievaluasi pada bagian validasi lainnya agar tidak terjadi penyesuaian terhadap data yang sama (data leakage). Model dengan nilai Macro-F1 validasi terbaik kemudian disimpan sebagai checkpoint akhir.

Mengapa crop wajah penting?

Seluruh data training menggunakan hasil crop wajah yang diperoleh dari detektor wajah yang sama dengan yang digunakan saat inferensi pada website MVP. Konsistensi proses ini penting karena perbedaan cara melakukan crop dapat mengubah distribusi data yang diterima model. Pada percobaan awal, penggunaan metode crop yang berbeda menyebabkan wajah yang berada dekat dengan kamera lebih sering diprediksi sebagai confusion. Dengan menggunakan proses deteksi dan crop yang konsisten, perbedaan distribusi tersebut dapat diminimalkan sehingga hasil prediksi menjadi lebih stabil.

Hasil Evaluasi

Model dievaluasi pada set test sebanyak 1.831 gambar menggunakan threshold per label hasil pencarian pada data validasi. Tabel berikut merangkum kinerja keseluruhan.

Metrik Nilai
Macro-F1 0,8289
Subset accuracy 0,7002
Rata-rata akurasi per label 0,9089
Rata-rata AUC 0,9174

Rincian kinerja untuk masing-masing label adalah sebagai berikut.

Label F1 AUC Akurasi
Boredom 0,9136 0,9975 0,9924
Engagement 0,9836 0,8719 0,9689
Confusion 0,7232 0,8645 0,7952
Frustration 0,6952 0,9356 0,8793

Subset accuracy mengukur proporsi gambar yang keempat labelnya diprediksi benar secara bersamaan, sehingga merupakan metrik yang paling ketat. Skor pada label Confusion dan Frustration relatif lebih rendah karena jumlah sampel kedua kelas tersebut jauh lebih sedikit dibandingkan Engagement dan Boredom.

Konfigurasi Training

Nilai berikut merupakan konfigurasi bawaan pada config.py yang digunakan untuk menghasilkan model yang dirilis.

Hyperparameter Nilai
Fungsi loss Focal Loss (α = 0,5; γ = 1,5)
Label smoothing 0,1
Pembobotan kelas pos_weight = jumlah negatif / positif per kelas
Optimizer AdamW
Learning rate backbone 7.412564594596496e-05
Learning rate head 10× learning rate backbone
Weight decay 1e-3
Batch size 128
Gradient accumulation 1
Gradient clipping 1,0 (L2 norm)
Jumlah epoch 25
Pembekuan backbone 3 epoch pertama (hanya head yang dilatih)
Warmup learning rate 3 epoch (linear), lalu cosine decay
Dropout (head) 0,5
Seed 42

Struktur Repositori

config.py        Konfigurasi dan hyperparameter
model.py         Backbone ConvNeXt V2-Femto dan head klasifikasi
dataset.py       Dataset berbasis CSV, augmentasi, dataloader, dan pembobotan kelas
losses.py        Implementasi BCE, Focal Loss, dan Asymmetric Focal Loss
metrics.py       Perhitungan F1, AUC, akurasi, serta pencarian threshold
train.py         Proses training (freeze → unfreeze → warmup → cosine)
export_onnx.py   Ekspor checkpoint ke format ONNX beserta metadata
.env.example     Contoh konfigurasi path dataset
requirements.txt Daftar dependensi Python

Format Data

Training menggunakan tiga berkas CSV dan satu direktori yang berisi hasil crop wajah.

<FER_LABEL_DIR>/train.csv
<FER_LABEL_DIR>/val.csv
<FER_LABEL_DIR>/test.csv

Setiap berkas CSV memuat kolom frame_path, yang akan dicocokkan terhadap direktori <FER_IMAGE_ROOT>, serta empat kolom biner yang menunjukkan keberadaan masing-masing label.

frame_path,Boredom,Engagement,Confusion,Frustration
subject01/000123.jpg,0,1,0,0

Dataset wajah siswa yang digunakan dalam penelitian ini bersifat privat sehingga tidak disertakan dalam repositori.

Memulai

Pasang seluruh dependensi terlebih dahulu.

python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt

Selanjutnya salin berkas konfigurasi contoh dan sesuaikan lokasi dataset pada komputer Anda.

cp .env.example .env
# ubah FER_LABEL_DIR, FER_IMAGE_ROOT, dan FER_OUTPUT_DIR

Berkas .env tidak akan ikut tersimpan di Git sehingga konfigurasi lokal tetap aman.

Training

Untuk mereproduksi model yang dirilis, jalankan:

python train.py

Hyperparameter tertentu juga dapat diubah melalui baris perintah, misalnya:

python train.py --epochs 25 --batch-size 128 --loss focal

Hasil setiap proses training akan disimpan pada direktori <FER_OUTPUT_DIR>/<timestamp>/, termasuk checkpoint terbaik (checkpoints/best.pth) dan riwayat metrik training (metrics.csv).

Ekspor ke ONNX

python export_onnx.py runs/<timestamp>/checkpoints/best.pth model.onnx

Model ONNX yang dihasilkan menyimpan informasi tambahan berupa daftar label, arsitektur backbone, serta threshold terbaik untuk setiap label sebagai metadata. Informasi ini dapat langsung digunakan oleh aplikasi inferensi tanpa memerlukan konfigurasi tambahan.

Inferensi dengan ONNX Runtime

Unduh terlebih dahulu bobot model dari HuggingFace, lalu jalankan:

import json
import cv2
import numpy as np
import onnxruntime as ort

session = ort.InferenceSession("convnextv2_femto_v3_crop.onnx")
metadata = session.get_modelmeta().custom_metadata_map
labels = json.loads(metadata["labels"])
thresholds = json.loads(metadata["thresholds"])

mean = np.array([0.485, 0.456, 0.406], np.float32)
std = np.array([0.229, 0.224, 0.225], np.float32)

face = cv2.cvtColor(cv2.imread("face.jpg"), cv2.COLOR_BGR2RGB)
face = cv2.resize(face, (224, 224)).astype(np.float32) / 255.0
tensor = ((face - mean) / std).transpose(2, 0, 1)[None]

logits = session.run(["logits"], {"image": tensor})[0][0]
probabilitas = 1.0 / (1.0 + np.exp(-logits))

for label, p in zip(labels, probabilitas):
    print(f"{label:<12} p={p:.3f} aktif={p >= thresholds[label]}")

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages