dashakoryakovskaya commited on
Commit
db04509
·
verified ·
1 Parent(s): 7aa84c2

Upload 5 files

Browse files
Files changed (5) hide show
  1. app.py +67 -0
  2. config_copy.toml +118 -0
  3. inference.py +151 -0
  4. multimodal_model.pt +3 -0
  5. requirements.txt +86 -0
app.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import plotly.express as px
3
+ import pandas as pd
4
+ import logging
5
+ import torch
6
+ import numpy as np
7
+ import pandas as pd
8
+
9
+ from inference import predict
10
+
11
+ logging.basicConfig(level=logging.INFO)
12
+
13
+
14
+ def plotly_plot_video(video_path):
15
+ data = pd.DataFrame()
16
+ data['Emotion'] = ['😠 anger', '🤢 disgust', '😨 fear', '😄 joy/happiness', '😐 neutral', '😢 sadness', '😲 surprise/enthusiasm']
17
+ try:
18
+ data['Probability'] = predict(video_path)[0]
19
+ p = px.bar(data, x='Emotion', y='Probability', color="Probability")
20
+ return (
21
+ p,
22
+ f"## ✔️ Dominant Emotion: {data['Emotion'].values[np.argmax(np.array(data['Probability']))]}"
23
+ )
24
+
25
+ except Exception as e:
26
+ logging.error(f"Processing failed: {e}")
27
+ data['Probability'] = [0] * data.shape[0]
28
+ p = px.bar(data, x='Emotion', y='Probability', color="Probability")
29
+ return (
30
+ p,
31
+ "⚠️ Processing Error"
32
+ )
33
+
34
+ def create_demo_video():
35
+ with gr.Blocks(theme='Nymbo/rounded-gradient', css=".gradio-container {background-color: #F0F8FF}", title="Emotion Detection") as demo:
36
+ gr.Markdown("# Мультимодальная модель")
37
+
38
+ with gr.Row():
39
+ video_input = gr.Video(
40
+ sources=["upload", "webcam"],
41
+ type="filepath",
42
+ label="Record or Upload Video",
43
+ format="mp4",
44
+ interactive=True
45
+ )
46
+ with gr.Row():
47
+ top_emotion = gr.Markdown("## ✔️ Dominant Emotion: Waiting for input ...",
48
+ elem_classes="dominant-emotion")
49
+
50
+ with gr.Row():
51
+ text_plot = gr.Plot(label="Text Analysis")
52
+
53
+ video_input.change(fn=plotly_plot_video, inputs=video_input, outputs=[text_plot, top_emotion])
54
+ return demo
55
+
56
+ def create_demo():
57
+ audio = create_demo_video()
58
+ demo = gr.TabbedInterface(
59
+ [audio],
60
+ ["Video Prediction"],
61
+ )
62
+ return demo
63
+
64
+
65
+ if __name__ == "__main__":
66
+ demo = create_demo()
67
+ demo.launch()
config_copy.toml ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ─────────────────────────────
2
+ # Dataset Settings
3
+ # ─────────────────────────────
4
+
5
+ [datasets.cmu_mosei]
6
+ base_dir = "../data/CMU-MOSEI"
7
+ csv_path = "{base_dir}/{split}_full.csv"
8
+ video_dir = "{base_dir}/video/{split}/"
9
+
10
+ [datasets.fiv2]
11
+ base_dir = "../data/FirstImpressionsV2"
12
+ csv_path = "{base_dir}/{split}_full.csv"
13
+ video_dir = "{base_dir}/video/{split}/"
14
+
15
+
16
+ # ─────────────────────────────
17
+ # DataLoader Parameters
18
+ # ─────────────────────────────
19
+ [dataloader]
20
+ num_workers = 0
21
+ shuffle = true
22
+ prepare_only = false
23
+ average_features = true
24
+
25
+
26
+ # ─────────────────────────────
27
+ # General Training Parameters
28
+ # ─────────────────────────────
29
+ [train.general]
30
+ random_seed = 42 # fixed random seed (0 = random each run)
31
+ subset_size = 0 # subset limit (0 = use entire dataset)
32
+ batch_size = 32 # batch size
33
+ num_epochs = 100 # total number of training epochs
34
+ max_patience = 25 # max epochs without improvement (Early Stopping)
35
+ save_best_model = true # save the best model
36
+ save_prepared_data = true # save precomputed embeddings
37
+ save_feature_path = "../../../../s3_ml_data/dokoryakovskaya/train/train/features/" # path to save embeddings
38
+ search_type = "greedy" # "greedy", "exhaustive", or "none"
39
+ checkpoint_dir = "checkpoints"
40
+ device = "cuda" # "cuda" or "cpu"
41
+ selection_metric = "mean_combo" # metric for model selection: mean_combo, mean_emo, mF1, mUAR, ACC, etc.
42
+ single_task = true
43
+ opt_set = 'test'
44
+
45
+ # ─────────────────────────────
46
+ # Model Parameters
47
+ # ─────────────────────────────
48
+ [train.model]
49
+ id_ablation_type_by_modality = 0
50
+ id_ablation_type_by_component = 6
51
+ single_task_id = 0
52
+ model_stage = "fusion" # stage: personality, emotion, fusion
53
+ per_activation = "relu" # activation for personality branch
54
+ hidden_dim = 1024 # hidden layer size
55
+ num_transformer_heads = 4 # number of attention heads
56
+ positional_encoding = false # enable/disable positional encoding
57
+ dropout = 0.2 # dropout between layers
58
+ out_features = 256 # output feature size before classification
59
+ image_embedding_dim = 2560
60
+ tr_layer_number = 2
61
+
62
+ name_best_emo_model = "EmotionTransformer"
63
+ hidden_dim_emo = 512
64
+ out_features_emo = 1024
65
+ tr_layer_number_emo = 2
66
+ num_transformer_heads_emo = 16
67
+ positional_encoding_emo = false
68
+ path_to_saved_emotion_model = "results_emotiontransformer_2026-03-08_13-20-14/metrics_by_epoch/metrics_epochlog_EmotionTransformer_num_transformer_heads_16_20260308_145515_2026-03-08_14-55-15/best_model_dev.pt"
69
+
70
+ name_best_per_model = "PersonalityTransformer"
71
+ hidden_dim_per = 128
72
+ out_features_per = 1024
73
+ tr_layer_number_per = 2
74
+ num_transformer_heads_per = 4
75
+ positional_encoding_per = false
76
+ path_to_saved_personality_model = "results_personalitytransformer_2026-03-11_01-20-41/metrics_by_epoch/metrics_epochlog_PersonalityTransformer_out_features_1024_20260311_014658_2026-03-11_01-46-58/best_model_dev.pt"
77
+
78
+
79
+
80
+
81
+ # Loss weighting
82
+ weight_emotion = 0.2
83
+ weight_pers = 0.2
84
+ ssl_weight_emotion = 1.0
85
+ ssl_weight_personality = 1.0
86
+ ssl_confidence_threshold_emo = 0.5
87
+ ssl_confidence_threshold_pt = 0.5
88
+
89
+ # Loss configuration
90
+ pers_loss_type = "mae" # options: ccc, mae, mse, rmse_bell, rmse_logcosh, RMGL
91
+ emotion_loss_type = "BCE" # classification loss for emotion
92
+ flag_emo_weight = true # use class weighting for emotion imbalance
93
+
94
+ # GradNorm & SSL parameters
95
+ alpha_sup = 1.0
96
+ w_lr_sup = 0.005
97
+ alpha_ssl = 1.5
98
+ w_lr_ssl = 0.01
99
+ lambda_ssl = 0.4
100
+ w_floor = 1e-3
101
+
102
+
103
+ # ─────────────────────────────
104
+ # Optimizer Parameters
105
+ # ─────────────────────────────
106
+ [train.optimizer]
107
+ optimizer = "adam" # "adam", "adamw", "lion", "sgd", "rmsprop"
108
+ lr = 1e-4 # learning rate
109
+ weight_decay = 1e-5 # weight decay for regularization
110
+ momentum = 0.9 # used only for SGD
111
+
112
+
113
+ # ─────────────────────────────
114
+ # Scheduler Parameters
115
+ # ─────────────────────────────
116
+ [train.scheduler]
117
+ scheduler_type = "plateau" # "none", "plateau", "cosine", "onecycle", or HuggingFace variants
118
+ warmup_ratio = 0.1 # ratio of warmup iterations (0.1 = 10%)
inference.py ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding: utf-8
2
+ import os, logging
3
+ import random
4
+ from pathlib import Path
5
+ from typing import Dict, List
6
+ import numpy as np
7
+ import torch
8
+ from utils.config_loader import ConfigLoader
9
+ from torch.utils.data import ConcatDataset, DataLoader
10
+ from tqdm import tqdm
11
+ import whisper
12
+ from models.models import MultiModalFusionModelWithAblation
13
+ from modalities.vlm.feature_extractor import PretrainedVLMEmbeddingExtractor
14
+ from modalities.text.feature_extractor import PretrainedTextEmbeddingExtractor
15
+
16
+ def transform_matrix(matrix):
17
+ threshold1 = 1 - 1 / 7
18
+ threshold2 = 1 / 7
19
+ mask1 = matrix[:, 0] >= threshold1
20
+ result = np.zeros_like(matrix[:, 1:])
21
+ transformed = (matrix[:, 1:] >= threshold2).astype(int)
22
+ result[~mask1] = transformed[~mask1]
23
+ return result
24
+
25
+
26
+ def process_predictions(pred_emo):
27
+ pred_emo = torch.nn.functional.softmax(pred_emo, dim=1).cpu().detach().numpy()
28
+ pred_emo = transform_matrix(pred_emo).tolist()
29
+ return pred_emo
30
+
31
+ def aggregate(self, feats, average: bool = None):
32
+ """
33
+ Unified feature aggregation.
34
+
35
+ Args:
36
+ feats (Union[Tensor, dict, None]): Input features.
37
+ average (bool): If True — average over time (dim=1) when applicable.
38
+
39
+ Returns:
40
+ Aggregated features or None.
41
+
42
+ - If feats is a Tensor with shape [B, T, D] and average=True → average over T.
43
+ - If average=False → return as is.
44
+ - If feats is a dict → recurse over values.
45
+ - If feats is None → return None.
46
+ """
47
+
48
+ if average is None:
49
+ average = self.average_features
50
+
51
+ if feats is None:
52
+ return None
53
+
54
+ if isinstance(feats, torch.Tensor):
55
+ if average and feats.ndim == 3:
56
+ feats = feats.mean(dim=1) # → [B, D]
57
+ return feats.squeeze()
58
+
59
+ if isinstance(feats, dict):
60
+ return {
61
+ key: self.aggregate(val, average)
62
+ for key, val in feats.items()
63
+ }
64
+
65
+ raise TypeError(f"Unsupported feature type: {type(feats)}")
66
+
67
+ def transcribe_audio(audio_path):
68
+ whisper_model = whisper.load_model("base")
69
+ try:
70
+ result = whisper_model.transcribe(audio_path, fp16=False)
71
+ return result.get('text', '')
72
+ except Exception as e:
73
+ logging.error(f"Transcription failed: {e}")
74
+ return ""
75
+
76
+ def predict(video_path):
77
+ base_config = ConfigLoader("config_copy.toml")
78
+ vlm_feature_extractor = PretrainedVLMEmbeddingExtractor(device=base_config.device)
79
+ text_feature_extractor = PretrainedTextEmbeddingExtractor(device=base_config.device)
80
+ modality_extractors = {
81
+ "video": vlm_feature_extractor,
82
+ "text": text_feature_extractor,
83
+ }
84
+ ablation_config = {}
85
+ if not base_config.single_task:
86
+ modality_combinations = [
87
+ [], # 0 use all modalities
88
+
89
+ # Single modalities
90
+ ["text"], # 1
91
+ ["video"] # 2
92
+ ]
93
+
94
+ components = [
95
+ -1,
96
+ "disable_graph_attn",
97
+ "disable_cross_attn",
98
+ "disable_emo_logit_proj",
99
+ "disable_pkl_logit_proj",
100
+ "disable_guide_emo",
101
+ "disable_guide_pkl",
102
+ ]
103
+ ablation_config = (
104
+ {
105
+ "disabled_modalities": modality_combinations[base_config.id_ablation_type_by_modality],
106
+ components[base_config.id_ablation_type_by_component]: True
107
+ }
108
+ if components[base_config.id_ablation_type_by_component] != -1
109
+ else {"disabled_modalities": modality_combinations[base_config.id_ablation_type_by_modality]}
110
+ )
111
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
112
+ model = MultiModalFusionModelWithAblation(
113
+ hidden_dim=base_config.hidden_dim,
114
+ num_heads=base_config.num_transformer_heads,
115
+ dropout=base_config.dropout,
116
+ emo_out_dim=7,
117
+ pkl_out_dim=5,
118
+ device=device,
119
+ ablation_config=ablation_config,
120
+ attention=base_config.attention
121
+ ).to(device)
122
+ checkpoint = torch.load("multimodal_model.pt", map_location=device)
123
+ state_dict = checkpoint["model_state_dict"] if "model_state_dict" in checkpoint else checkpoint
124
+ model.load_state_dict(state_dict)
125
+ model.eval()
126
+ entry = {
127
+ "video_path": video_path,
128
+ "features": {},
129
+ }
130
+ try:
131
+ video_feats = modality_extractors["video"].extract(video_path=video_path, saved=False)
132
+ entry["features"]["video"] = aggregate(video_feats, True)
133
+ except Exception as e:
134
+ logging.warning(f"Video extract error {video_path}: {e}")
135
+ entry["features"]["video"] = None
136
+ try:
137
+ txt_raw = transcribe_audio(video_path)
138
+ text_feats = modality_extractors["text"].extract(txt_raw)
139
+ entry["features"]["text"] = aggregate(text_feats, True)
140
+ except Exception as e:
141
+ logging.warning(f"Text extract error {txt_raw}: {e}")
142
+ entry["features"]["text"] = None
143
+ outputs = model([entry])
144
+
145
+
146
+ if outputs.get('emotion_logits') is not None:
147
+ preds_emo = process_predictions(outputs['emotion_logits'])
148
+ if outputs.get('personality_scores') is not None:
149
+ preds_per = outputs['personality_scores']
150
+ return preds_emo, preds_per
151
+
multimodal_model.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d8a07a479cb927e51105a5d32989f8c7c38bdc9606f378fe34af6098d79d6fcb
3
+ size 39054457
requirements.txt ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ accelerate==1.12.0
2
+ annotated-doc==0.0.4
3
+ anyio==4.12.1
4
+ av==16.1.0
5
+ certifi==2026.2.25
6
+ charset-normalizer==3.4.4
7
+ click==8.3.1
8
+ colorlog==6.10.1
9
+ cuda-bindings==12.9.4
10
+ cuda-pathfinder==1.4.0
11
+ einops==0.8.2
12
+ filelock==3.24.3
13
+ fsspec==2026.2.0
14
+ h11==0.16.0
15
+ hf-xet==1.3.2
16
+ httpcore==1.0.9
17
+ httpx==0.28.1
18
+ huggingface_hub==0.36.2
19
+ idna==3.11
20
+ Jinja2==3.1.6
21
+ joblib==1.5.3
22
+ markdown-it-py==4.0.0
23
+ MarkupSafe==3.0.3
24
+ mdurl==0.1.2
25
+ mpmath==1.3.0
26
+ networkx==3.6.1
27
+ numpy==2.4.2
28
+ nvidia-cublas==13.0.0.19
29
+ nvidia-cublas-cu12==12.6.4.1
30
+ nvidia-cuda-cupti==13.0.48
31
+ nvidia-cuda-cupti-cu12==12.6.80
32
+ nvidia-cuda-nvrtc==13.0.48
33
+ nvidia-cuda-nvrtc-cu12==12.6.77
34
+ nvidia-cuda-runtime==13.0.48
35
+ nvidia-cuda-runtime-cu12==12.6.77
36
+ nvidia-cudnn-cu12==9.10.2.21
37
+ nvidia-cudnn-cu13==9.13.0.50
38
+ nvidia-cufft==12.0.0.15
39
+ nvidia-cufft-cu12==11.3.0.4
40
+ nvidia-cufile==1.15.0.42
41
+ nvidia-cufile-cu12==1.11.1.6
42
+ nvidia-curand==10.4.0.35
43
+ nvidia-curand-cu12==10.3.7.77
44
+ nvidia-cusolver==12.0.3.29
45
+ nvidia-cusolver-cu12==11.7.1.2
46
+ nvidia-cusparse==12.6.2.49
47
+ nvidia-cusparse-cu12==12.5.4.2
48
+ nvidia-cusparselt-cu12==0.7.1
49
+ nvidia-cusparselt-cu13==0.8.0
50
+ nvidia-nccl-cu12==2.27.5
51
+ nvidia-nccl-cu13==2.27.7
52
+ nvidia-nvjitlink==13.0.39
53
+ nvidia-nvjitlink-cu12==12.6.85
54
+ nvidia-nvshmem-cu12==3.3.20
55
+ nvidia-nvshmem-cu13==3.3.24
56
+ nvidia-nvtx==13.0.39
57
+ nvidia-nvtx-cu12==12.6.77
58
+ packaging==26.0
59
+ pandas==3.0.1
60
+ pillow==12.1.1
61
+ psutil==7.2.2
62
+ Pygments==2.19.2
63
+ python-dateutil==2.9.0.post0
64
+ PyYAML==6.0.3
65
+ qwen-vl-utils==0.0.14
66
+ regex==2026.2.28
67
+ requests==2.32.5
68
+ rich==14.3.3
69
+ safetensors==0.7.0
70
+ scikit-learn==1.8.0
71
+ scipy==1.17.1
72
+ setuptools==82.0.0
73
+ shellingham==1.5.4
74
+ six==1.17.0
75
+ sympy==1.14.0
76
+ threadpoolctl==3.6.0
77
+ tokenizers==0.22.2
78
+ toml==0.10.2
79
+ torchaudio==2.9.1+cu126
80
+ torchvision==0.24.1+cu126
81
+ tqdm==4.67.3
82
+ triton==3.5.1
83
+ typer==0.24.1
84
+ typer-slim==0.24.0
85
+ typing_extensions==4.15.0
86
+ urllib3==2.6.3