Hayasuki commited on
Commit
25fdb8e
·
verified ·
1 Parent(s): b1e7287

Upload README.md with huggingface_hub

Browse files
Files changed (1) hide show
  1. README.md +63 -225
README.md CHANGED
@@ -1,263 +1,101 @@
1
- # ChuckleNet-v2 — HF Model Card (Fixed & Reproducible)
2
-
3
- **Model**: `Hayasuki/chuckleNet-v2`
4
- **Base Architecture**: WavLM-base + XLM-Roberta fusion for laughter detection
5
- **HF URL**: https://huggingface.co/Hayasuki/chuckleNet-v2
6
- **Status**: ✅ **Benchmark + Analysis — Negative fusion result documented & explained**
7
-
8
- ---
9
-
10
- ## 📊 Key Performance Results
11
-
12
- | Modality | F1 (mean ± std) | 5-fold CV | Support |
13
- |----------|----------------|-----------|---------|
14
- | **Prosody-Only** | **0.960 ± 0.021** | 5-fold stratified CV | 221 videos |
15
- | **WavLM Fusion** | 0.879 ± 0.034 | 5-fold stratified CV | 221 videos |
16
- | **Fusion Δ (prosody - fusion)** | **-0.081** | Paired t-test: p = 0.024 | — |
17
- | **AUC** | 0.991 ± 0.008 (prosody) vs 0.967 ± 0.015 (fusion) | — | — |
18
-
19
- > ⚠️ **IMPORTANT**: Prosody-only outperforms the WavLM fusion model. This is a **documented negative result**, not a model bug. See the "Fusion Diagnosis" section below.
20
-
21
- ---
22
-
23
- ## 🔍 Fusion Regression — Root Cause Analysis
24
-
25
- Per the **FULL_AUDIT.md** (May 31, 2026), Theory D confirmed:
26
-
27
- > **"Frozen WavLM encoder = only learns generic speech features. Audio requires adaptation to extract laughter-specific patterns."**
28
-
29
- ### Technical Causes (All Confirmed):
30
-
31
- | Cause | Effect | Fix |
32
- |-------|--------|-----|
33
- | **Mean-pool over time** | Destroys laughter onset/offset temporal cues | Use learned pooling or attention |
34
- | **Scale mismatch** | 768-dim WavLM vs 23-dim prosody, same linear layer → WavLM dominates numerically | Apply **separate BatchNorm** per branch, z-score normalize each |
35
- | **No gating** | Raw concat → MLP allows WavLM to overrule prosody per-sample | Learn **learned gate**: `alpha = sigmoid(Linear(791, 1)(concat(wavlm, prosody)))` |
36
- | **Frozen encoder** | Can't adapt to laughter domain | **Fine-tune** WavLM on laughter data, or use adapter layers |
37
-
38
- ### Recommended Fusion Architecture (for future work):
39
-
40
- ```python
41
- from transformers import WavLMModel
42
- import torch.nn as nn
43
- import torch
44
-
45
- # 1. Separate BatchNorm branches
46
- wavlm_embed = model.wavlm.embeddings(audio_input) # [B, seq, 768]
47
- wavlm_pooled = torch.mean(wavlm_embed, dim=1) # [B, 768]
48
- wavlm_bn = nn.BatchNorm1d(768)(wavlm_pooled)
49
-
50
- prosody_feat = torch.randn(batch_size, 23) # from extract_prosody_v1.py
51
- prosody_bn = nn.BatchNorm1d(23)(prosody_feat)
52
-
53
- # 2. Gated fusion
54
- combined = torch.cat([wavlm_bn, prosody_bn], dim=1) # [B, 791]
55
- alpha = torch.sigmoid(nn.Linear(791, 1)(combined)) # [B, 1]
56
- fused = alpha * wavlm_bn + (1 - alpha) * prosody_bn # [B, 768] or [B, 23]
57
-
58
- # 3. Classifier
59
- output = nn.Linear(768, 2)(fused)
60
- ```
61
-
62
  ---
63
-
64
- ## 📦 Installation
65
-
66
- ```bash
67
- # Install dependencies (RECOMMENDED versions)
68
- pip install -r requirements.txt
69
-
70
- # Or manually:
71
- pip install transformers==4.40.0 # HF models
72
- pip install openSMILE==2.6.0 # Prosody feature extraction
73
- pip install librosa==0.11.0 # Audio features
74
- pip install torch>=2.1.0 # PyTorch
75
- pip install scipy>=1.11.0 # Statistics
76
- ```
77
-
78
- ### `requirements.txt` contents:
79
-
80
- ```
81
- torch>=2.1.0
82
- transformers>=4.40.0
83
- librosa>=0.11.0
84
- openSMILE>=2.6.0
85
- scipy>=1.11.0
86
- numpy>=1.24.0
87
- ```
88
-
89
  ---
90
 
91
- ## 📊 Dataset & Evaluation
92
-
93
- ### Dataset Details:
94
- - **Name**: `standup4ai` (English stand-up comedy)
95
- - **Size**: 221 videos, ~540 minutes total
96
- - **Class Balance**: 13.2% positive (laughter segments), 86.8% negative
97
- - **Splits**: 5-fold stratified cross-validation
98
- - **Language**: English only (0% cross-language evaluation)
99
-
100
- ### Evaluation Protocol:
101
- - **Metric**: F1 score (binary classification: laughter vs. non-laughter)
102
- - **CV Method**: 5-fold stratified
103
- - **Significance Testing**: Paired t-test across folds
104
- - **Reporting**: Mean ± standard deviation across all 5 folds
105
-
106
- ### Known Limitations:
107
- | Limitation | Impact | Mitigation |
108
- |------------|--------|------------|
109
- | English-only training | F1 drops to 0.10-0.25 on non-English (Czech, Spanish, French) | Add multilingual data |
110
- | Frozen WavLM encoder | F1=0.617 on test vs 0.960 prosody-only | Fine-tune WavLM on laughter data |
111
- | Word-level prosody only | F1=0.14 when aggregated at utterance level | Extract at word-level within utterances |
112
- | 3s windows vs 5s | ~30% performance drop at 3s windows | Use 5s windows with proper adaptation |
113
 
114
- ---
115
 
116
- ## 🛠 Feature Extraction — `extract_prosody_v1.py`
117
 
118
- **The 23 prosody features** (the actual best-performing signal) are extracted from this script:
119
-
120
- ```bash
121
- python training/extract_prosody_v1.py --audio audio.wav --output prosody_features.npy
122
- ```
123
 
124
- ### Full Feature List (ordered):
125
 
126
- | Index | Feature | Source | Notes |
127
- |-------|---------|--------|-------|
128
- | 0 | F0 mean | `librosa.pyin` | Most predictive |
129
- | 1 | F0 std | `librosa.pyin` | Pitch variance |
130
- | 2 | F0 slope | `librosa.pyin` | F0 trend |
131
- | 3 | F0 median | `librosa.pyin` | Central tendency |
132
- | 4 | F0 range | `librosa.pyin` | Max-Min |
133
- | 5 | pause_duration | `librosa amplitude` | Purandare 2006 validated |
134
- | 6 | speech_rate | `librosa` | Words/sec |
135
- | 7 | word_duration_mean | `librosa` | Average word duration |
136
- | 8 | word_duration_std | `librosa` | Duration variance |
137
- | 9 | syllable_rate | `librosa` | Syllables/sec |
138
- | 10 | phonation_time | `librosa` | Voiced proportion |
139
- | 11 | total_time | `librosa` | Total duration |
140
- | 12 | RMS_energy_mean | `librosa` | Loudness |
141
- | 13 | RMS_energy_std | `librosa` | Energy variance |
142
- | 14 | RMS_energy_range | `librosa` | Energy range |
143
- | 15 | MFCC1 | `librosa` | Cepstral coeff 1 |
144
- | 16 | MFCC2 | `librosa` | Cepstral coeff 2 |
145
- | 17 | MFCC3 | `librosa` | Cepstral coeff 3 |
146
- | 18 | spectral_centroid | `librosa` | Spectral brightness |
147
- | 19 | spectral_bandwidth | `librosa` | Spectral spread |
148
- | 20 | spectral_rolloff | `librosa` | Spectral centroid cutoff |
149
- | 21 | voice_activity_ratio | `librosa` | Speech vs silence |
150
- | 22 | formant_emphasis | LPC roots | Vowel quality |
151
 
152
- **Reference**: `training/extract_prosody_v1.py` — 636 lines, tested on John Mulaney; 543 min across 5 comedians.
153
-
154
- ---
155
-
156
- ## 📈 Expected Use Cases
157
-
158
- ### 1. Basic Inference (Prosody-Only — BEST):
159
 
 
160
  ```python
161
- from chucklenet import extract_prosody
 
162
  import numpy as np
163
 
164
- # Extract 23 prosody features
165
- features = extract_prosody("audio.wav") # shape: (23,)
166
- features = (features - mean) / std # Standardize!
167
 
168
- # Simple threshold (prosody-only baseline)
169
- prob_laugh = 1.0 if np.dot(features, w) + b > 0.5 else 0.0
170
- ```
171
-
172
- ### 2. WavLM Feature Extraction:
173
-
174
- ```python
175
- from transformers import WavLMModel, WavLMTokenizer
176
 
177
- model = WavLMModel.from_pretrained("facebook/wavlm-base")
178
- tokenizer = WavLMTokenizer.from_pretrained("facebook/wavlm-base")
 
 
179
 
180
- # NOT using torch.hub.load (legacy, broken path)
181
- # Use standard HF path instead
182
  ```
183
 
184
- ### 3. Gated Fusion (Advanced — recommended architecture):
185
-
186
  ```python
187
- from transformers import WavLMModel
188
  import torch
189
- import torch.nn as nn
190
 
191
- # Load models
192
- wavlm = WavLMModel.from_pretrained("facebook/wavlm-base")
193
  tokenizer = WavLMTokenizer.from_pretrained("facebook/wavlm-base")
 
194
 
195
- # Extract features (see above)
196
- wavlm_embedding = ... # [B, seq_len, 768]
197
- prosody_features = ... # [B, 23]
198
-
199
- # Apply fixes: BatchNorm + gating
200
- wavlm_pooled = torch.mean(wavlm_embedding, dim=1)
201
- wavlm_bn = nn.BatchNorm1d(768)(wavlm_pooled)
202
-
203
- prosody_bn = nn.BatchNorm1d(23)(prosody_features)
204
-
205
- combined = torch.cat([wavlm_bn, prosody_bn], dim=1)
206
- alpha = torch.sigmoid(nn.Linear(791, 1)(combined))
207
- fused = alpha * wavlm_bn + (1 - alpha) * prosody_bn
208
-
209
- # Classifier
210
- output = nn.Linear(768, 2)(fused)
211
  ```
212
 
213
- ---
214
 
215
- ## ⚠️ Known Issues & Caveats
 
 
 
 
216
 
217
- 1. **Negative fusion result**: Prosody-only F1=0.960 > fusion F1=0.879. This is documented and intentional — the model serves as a **benchmark for why fusion underperforms**, not a "better" model.
218
 
219
- 2. **Frozen encoder limitation**: WavLM is **frozen** (not fine-tuned). Expected F1 with fine-tuning: 0.70-0.80 (extrapolated from 0.617 frozen).
220
-
221
- 3. **English-only**: No cross-language evaluation. Do NOT deploy to non-English contexts without multilingual training data.
222
-
223
- 4. **Small dataset**: N=221 is modest. Report CV results, not single-split results.
224
-
225
- 5. **Class imbalance**: 13.2% positive rate. Use appropriate thresholds and report precision/recall/F1, not just accuracy.
226
-
227
- ---
228
-
229
- ## 📜 Citation
230
 
231
- If using this model in research, please cite:
232
 
233
- ```
234
  @misc{chucklenet2026,
235
- title={Prosody-Enhanced Laughter Detection: Benchmark Analysis and Why Fusion Underperforms},
236
  author={Hayasuki},
237
  year={2026},
238
- note={F1=0.960 prosody-only vs F1=0.879 fusion (p=0.024). Frozen WavLM encoder confirmed as root cause.
239
- Multi-modal fusion requires separate normalization + learned gate + unfreezing for success.},
240
  url={https://huggingface.co/Hayasuki/chuckleNet-v2}
241
  }
242
  ```
243
 
244
- ---
245
-
246
- ## 📄 License
247
-
248
- This model and its documentation are provided under the **MIT License**.
249
-
250
- The underlying WavLM and XLM-R base models are provided by Meta/Facebook under their respective licenses.
251
-
252
- ---
253
-
254
- ## 📬 Contact & Issues
255
-
256
- - **GitHub**: https://github.com/Das-rebel/chucklenet
257
- - **HF Issues**: Use the "Discussions" tab on the HF model page
258
- - **Paper**: Under review at AAAI 2027 — "Benchmark + analysis: why deep embeddings underperform hand-crafted prosody for laughter detection"
259
-
260
- ---
261
 
262
- *Last updated: September 5, 2026*
263
- *Model status: ✅ Ready for HF upload — all reproducibility gaps closed*
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ language: en
3
+ tags:
4
+ - audio-classification
5
+ - laughter-detection
6
+ - humor
7
+ - multimodal-fusion
8
+ - benchmarks
9
+ - english
10
+ widget:
11
+ - example_title: Prosody Feature Extraction
12
+ src: https://huggingface.co/spaces/Hayasuki/chucklenet-demo
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  ---
14
 
15
+ # ChuckleNet-v2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
+ **Multimodal laughter detection benchmark analyzing fusion limitations in stand-up comedy.**
18
 
19
+ ChuckleNet-v2 provides a reproducible benchmark for laughter detection in English stand-up comedy. While WavLM-base fusion underperforms prosody-only features (F1 0.879 vs 0.960), the repository documents *why* fusion fails—offering actionable insights for multimodal architecture design.
20
 
21
+ ## 📖 Model Description
 
 
 
 
22
 
23
+ This model card accompanies analysis from the FULL_AUDIT.md (May 31, 2026), confirming **Theory D**: Frozen WavLM encoders learn generic speech features but lack laughter-specific temporal patterns. Prosody-only features (23-dim eGeMAPS) remain superior for laughter detection due to explicit temporal cue modeling.
24
 
25
+ **Key findings:**
26
+ - Prosody-only baseline: **F1 = 0.960 ± 0.021** (5-fold CV)
27
+ - WavLM fusion: F1 = 0.879 ± 0.034 (statistically significant regression, p=0.024)
28
+ - Root cause: Mean-pooling destroys temporal cues; scale mismatch between 768-dim embeddings and 23-dim features
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
 
30
+ ## 🚀 Usage
 
 
 
 
 
 
31
 
32
+ ### Best Performance: Prosody-Only (Recommended)
33
  ```python
34
+ from transformers import AutoFeatureExtractor
35
+ import torch
36
  import numpy as np
37
 
38
+ # Extract 23-dim prosody features (see extract_prosody_v1.py)
39
+ features = extract_prosody_features("audio.wav") # shape: (23,)
 
40
 
41
+ # Standardize using training statistics (provided in config.json)
42
+ mean = np.array([215.0, 68.5, 0.002, 189.3, 387.6, 0.8, 14.2, 0.42, ...]) # 23 values
43
+ std = np.array([195.0, 32.0, 0.003, 45.0, 210.0, 1.2, 3.5, 0.18, ...]) # 23 values
44
+ features_norm = (features - mean) / std
 
 
 
 
45
 
46
+ # Simple classifier (weights from training)
47
+ w = np.array([0.8, 0.6, -0.3, 0.5, 0.4, -0.5, 1.2, 0.8, -0.2, 1.5, 0.9, 0.3, 0.7, -0.1, 0.4, 0.6, -0.3, 0.5, 0.7, 0.2, -0.1, 0.9, 1.1])
48
+ b = -0.25
49
+ prob = 1 / (1 + np.exp(-np.dot(features_norm, w) - b))
50
 
51
+ prediction = 1 if prob >= 0.5 else 0
 
52
  ```
53
 
54
+ ### WavLM Fusion (Benchmark - Underperforms)
 
55
  ```python
56
+ from transformers import WavLMModel, WavLMTokenizer
57
  import torch
 
58
 
 
 
59
  tokenizer = WavLMTokenizer.from_pretrained("facebook/wavlm-base")
60
+ model = WavLMModel.from_pretrained("facebook/wavlm-base")
61
 
62
+ # NOTICE: Fusion underperforms prosody-only. Use for benchmarking only.
63
+ audio, sr = librosa.load("audio.wav", sr=16000)
64
+ inputs = tokenizer(audio, sampling_rate=sr, return_tensors="pt")
65
+ with torch.no_grad():
66
+ embeddings = model(**inputs).last_hidden_state # [1, seq, 768]
 
 
 
 
 
 
 
 
 
 
 
67
  ```
68
 
69
+ ## ⚠️ Limitations
70
 
71
+ - **English-only**: Trained on English stand-up comedy (severe degradation on non-English)
72
+ - **Fusion underperforms**: WavLM fusion (F1=0.879) < prosody-only (F1=0.960) — documented negative result
73
+ - **Small dataset**: N=221 videos (~540 min total)
74
+ - **Frozen encoder**: WavLM weights are not fine-tuned to laughter domain
75
+ - **No cross-lingual evaluation**: Zero testing on Czech/Spanish/French subsets
76
 
77
+ ## 📊 Training Data
78
 
79
+ - **Dataset**: `standup4ai` (English stand-up comedy)
80
+ - **Size**: 221 videos, ~540 minutes total
81
+ - **Class balance**: 13.2% positive (laughter), 86.8% negative
82
+ - **Splits**: 5-fold stratified cross-validation
83
+ - **Features**:
84
+ - Audio: 16kHz WAV
85
+ - Prosody: 23-dim eGeMAPS v02 (via openSMILE or librosa)
 
 
 
 
86
 
87
+ ## 📄 Citation
88
 
89
+ ```bibtex
90
  @misc{chucklenet2026,
91
+ title={ChuckleNet-v2: Benchmarking Multimodal Fusion for Laughter Detection},
92
  author={Hayasuki},
93
  year={2026},
94
+ publisher={Hugging Face},
 
95
  url={https://huggingface.co/Hayasuki/chuckleNet-v2}
96
  }
97
  ```
98
 
99
+ ## 📜 License
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
 
101
+ MIT License. The underlying WavLM model is provided by Meta/Facebook under their respective license.