q-wang commited on
Commit
0e37ffd
·
verified ·
1 Parent(s): 55ac446

Upload folder using huggingface_hub

Browse files
MeralionForGender.py ADDED
@@ -0,0 +1,327 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # models/meralion_encoder.py
2
+ import torch
3
+ import torch.nn as nn
4
+ import torch.nn.functional as F
5
+ from typing import Tuple, Optional
6
+
7
+ from transformers import AutoModel,PretrainedConfig, PreTrainedModel, AutoConfig
8
+ from peft import get_peft_model, LoraConfig
9
+ from omegaconf import DictConfig
10
+
11
+ # 1. Define a Config class that holds all your YAML settings
12
+ class MeralionGenderConfig(PretrainedConfig):
13
+ model_type = "meralion_gender"
14
+ def __init__(
15
+ self,
16
+ meralion_name="",
17
+ num_classes=2,
18
+ aggregator="attention",
19
+ downstream_params=None,
20
+ **kwargs
21
+ ):
22
+ # Pass all basic types (strings, ints, dicts) to super
23
+ super().__init__(
24
+ meralion_name=meralion_name,
25
+ num_classes=num_classes,
26
+ aggregator=aggregator,
27
+ downstream_params=downstream_params or {},
28
+ **kwargs
29
+ )
30
+
31
+ class SE1d(nn.Module):
32
+ """Squeeze-and-Excitation block for 1D convolutions"""
33
+ def __init__(self, channels: int, reduction: int = 8):
34
+ super().__init__()
35
+ hidden = max(8, channels // reduction)
36
+ self.avg = nn.AdaptiveAvgPool1d(1)
37
+ self.fc = nn.Sequential(
38
+ nn.Conv1d(channels, hidden, 1, bias=False),
39
+ nn.ReLU(inplace=True),
40
+ nn.Conv1d(hidden, channels, 1, bias=False),
41
+ nn.Sigmoid(),
42
+ )
43
+
44
+ def forward(self, x: torch.Tensor):
45
+ w = self.fc(self.avg(x))
46
+ return x * w
47
+
48
+
49
+ class Res2Block1d(nn.Module):
50
+ """Res2Net block adapted for 1D convolutions - BatchNorm free"""
51
+ def __init__(self, channels: int, scale: int = 4, kernel_size: int = 3, dilation: int = 1):
52
+ super().__init__()
53
+ assert channels % scale == 0, f"channels ({channels}) must be divisible by scale ({scale})"
54
+ self.scale = scale
55
+ self.width = channels // scale
56
+ pad = (kernel_size // 2) * dilation
57
+
58
+ self.convs = nn.ModuleList([
59
+ nn.Conv1d(self.width, self.width, kernel_size, padding=pad, dilation=dilation, bias=True)
60
+ for _ in range(scale - 1)
61
+ ])
62
+ self.norm = nn.GroupNorm(num_groups=min(32, channels), num_channels=channels)
63
+ self.act = nn.ReLU(inplace=True)
64
+
65
+ def forward(self, x: torch.Tensor):
66
+ xs = torch.split(x, self.width, dim=1)
67
+ out = [xs[0]]
68
+ for i, conv in enumerate(self.convs, start=1):
69
+ if i == 1:
70
+ s = xs[i]
71
+ else:
72
+ s = xs[i] + out[-1] # Fixed: proper residual connection
73
+ out.append(conv(s))
74
+ y = torch.cat(out, dim=1)
75
+ return self.act(self.norm(y))
76
+
77
+ class ECAPABlock(nn.Module):
78
+ """Enhanced ECAPA block with proper residual connections - BatchNorm free"""
79
+ def __init__(self, channels: int, scale: int = 4, kernel_size: int = 3, dilation: int = 1):
80
+ super().__init__()
81
+ self.conv1 = nn.Conv1d(channels, channels, 1, bias=True)
82
+ self.norm1 = nn.GroupNorm(num_groups=min(32, channels), num_channels=channels)
83
+ self.act1 = nn.ReLU(inplace=True)
84
+
85
+ self.res2 = Res2Block1d(channels, scale=scale, kernel_size=kernel_size, dilation=dilation)
86
+ self.se = SE1d(channels)
87
+
88
+ self.conv2 = nn.Conv1d(channels, channels, 1, bias=True)
89
+ self.norm2 = nn.GroupNorm(num_groups=min(32, channels), num_channels=channels)
90
+ self.act2 = nn.ReLU(inplace=True)
91
+
92
+ def forward(self, x: torch.Tensor):
93
+ residual = x
94
+
95
+ y = self.act1(self.norm1(self.conv1(x)))
96
+ y = self.res2(y)
97
+ y = self.se(y)
98
+ y = self.norm2(self.conv2(y))
99
+
100
+ return self.act2(y + residual)
101
+
102
+
103
+ class EmotionECAPATDNN(nn.Module):
104
+ """ECAPA-TDNN optimized for emotion recognition with hierarchical attention"""
105
+ def __init__(
106
+ self,
107
+ input_dim: int,
108
+ channels: int = 512,
109
+ output_dim: int = 256,
110
+ num_blocks: int = 3,
111
+ dilations: tuple = (1, 2, 3),
112
+ embed_dim: int = 512,
113
+ num_emotions: int = 8, # Common emotion categories
114
+ dropout: float = 0.2,
115
+ pooling_type="attention"
116
+ ):
117
+ super().__init__()
118
+ self.output_dim = output_dim
119
+ # Input projection with layer norm for stability
120
+ self.proj_in = nn.Sequential(
121
+ nn.Linear(input_dim, channels),
122
+ nn.LayerNorm(channels),
123
+ nn.GELU(),
124
+ nn.Dropout(0.2),
125
+ )
126
+
127
+ # ECAPA blocks with different dilations
128
+ self.blocks = nn.ModuleList([
129
+ ECAPABlock(channels, scale=4, kernel_size=3, dilation=d)
130
+ for d in dilations
131
+ ])
132
+
133
+ # Multi-scale feature aggregation
134
+ self.mfa = nn.Sequential(
135
+ nn.Conv1d(channels * (len(dilations) + 1), channels, 1, bias=True),
136
+ nn.GroupNorm(num_groups=min(32, channels), num_channels=channels),
137
+ nn.GELU()
138
+ #nn.ReLU(inplace=True)
139
+ )
140
+
141
+ self.pooling = AttentionPooling(channels)
142
+
143
+ # Final embedding layers
144
+ self.embed = nn.Sequential(
145
+ nn.Linear(channels, channels),
146
+ nn.LayerNorm(channels),
147
+ nn.GELU(),
148
+ nn.Dropout(0.3),
149
+ nn.Linear(channels, channels // 2),
150
+ nn.LayerNorm(channels // 2),
151
+ nn.GELU(),
152
+ nn.Dropout(dropout)
153
+ )
154
+
155
+ # Initialize weights
156
+ self._init_weights()
157
+
158
+ def _init_weights(self):
159
+ """Initialize model weights"""
160
+ for m in self.modules():
161
+ if isinstance(m, nn.Linear):
162
+ nn.init.trunc_normal_(m.weight, std=0.02)
163
+ if m.bias is not None:
164
+ nn.init.constant_(m.bias, 0)
165
+ elif isinstance(m, nn.Conv1d):
166
+ nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
167
+ if m.bias is not None:
168
+ nn.init.constant_(m.bias, 0)
169
+
170
+ def forward(self, x: torch.Tensor, attention_mask: Optional[torch.Tensor] = None, return_embeddings: bool = False):
171
+ """
172
+ Forward pass
173
+
174
+ Args:
175
+ x: Input tensor (batch, time, features) - from Whisper encoder
176
+ attention_mask: Attention mask (batch, time)
177
+ return_embeddings: Whether to return embeddings instead of logits
178
+
179
+ Returns:
180
+ If return_embeddings=False: emotion logits (batch, num_emotions)
181
+ If return_embeddings=True: feature embeddings (batch, embed_dim // 2)
182
+ """
183
+ # Project input and transpose for conv1d
184
+ x = self.proj_in(x) # (batch, time, channels)
185
+ x_conv = x.transpose(1, 2) # (batch, channels, time)
186
+
187
+ # Apply ECAPA blocks and collect multi-scale features
188
+ features = [x_conv]
189
+ for block in self.blocks:
190
+ x_conv = block(x_conv)
191
+ features.append(x_conv)
192
+
193
+ # Multi-scale feature aggregation
194
+ y = torch.cat(features, dim=1) # (batch, channels * (n_blocks + 1), time)
195
+ y = self.mfa(y) # (batch, channels, time)
196
+ y = y.transpose(1, 2) # (batch, time, channels)
197
+
198
+ # Hierarchical attention pooling
199
+ #pooled = self.norm_layer(self.pooling(y)) # (batch, channels)
200
+ pooled = self.pooling(y) # (batch, channels)
201
+
202
+ # Generate embeddings
203
+ embeddings = self.embed(pooled) # (batch, embed_dim // 2)
204
+
205
+ return embeddings
206
+
207
+
208
+ class LayerAttentiveAggregation(nn.Module):
209
+ """
210
+ Smart Layer Aggregation:
211
+ Instead of a static weighted sum, this computes attention weights
212
+ based on the hidden states themselves.
213
+ """
214
+ def __init__(self, hidden_size: int, num_layers: int):
215
+ super().__init__()
216
+ # Transformation to compute score per layer
217
+ self.query_proj = nn.Linear(hidden_size, 1)
218
+ self.num_layers = num_layers
219
+
220
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
221
+ # hidden_states: (L, B, T, D)
222
+ # We want to learn which layer 'L' is most important per timestep or globally.
223
+ # Let's do global (per sample) importance to save compute.
224
+
225
+ # Mean pool over time for the scoring mechanism: (L, B, D)
226
+ # Using mean helps avoid noise from silence frames
227
+ global_repr = hidden_states.mean(dim=2)
228
+
229
+ # Compute scores: (L, B, 1)
230
+ scores = self.query_proj(global_repr)
231
+
232
+ # Softmax over layers (dim=0) -> (L, B, 1)
233
+ attn_weights = F.softmax(scores, dim=0)
234
+
235
+ # Reshape for broadcasting: (L, B, 1, 1)
236
+ attn_weights = attn_weights.unsqueeze(-1)
237
+
238
+ # Weighted sum: sum((L, B, T, D) * (L, B, 1, 1)) -> (B, T, D)
239
+ aggregated = (hidden_states * attn_weights).sum(dim=0)
240
+ #print(f"DEBUG: Number of hidden states provided to aggregator: {len(hidden_states)}")
241
+ #exit()
242
+ return aggregated
243
+
244
+ class MeralionForGenderClassification(PreTrainedModel):
245
+ config_class = MeralionGenderConfig
246
+
247
+ def __init__(self, config: MeralionGenderConfig):
248
+ super().__init__(config)
249
+
250
+
251
+ # 1. Load Backbone
252
+ backbone_config = AutoConfig.from_pretrained(config.meralion_name, trust_remote_code=True)
253
+ self.backbone = AutoModel.from_config(backbone_config, trust_remote_code=True)
254
+
255
+ hidden_size = getattr(self.backbone.config, "hidden_size", None) \
256
+ or getattr(self.backbone.config, "d_model", None)
257
+ if hidden_size is None:
258
+ raise ValueError("Cannot infer hidden size from MERaLiON config.")
259
+
260
+ num_layers = getattr(self.backbone.config, "num_hidden_layers", 0) + 1 # +1 for embeddings
261
+
262
+ # Reset all to requires_grad=True first
263
+ for p in self.backbone.parameters():
264
+ p.requires_grad = False
265
+
266
+ # 3. Layer Aggregation
267
+ self.backbone.config.output_hidden_states = True
268
+ self.layer_aggregator = LayerAttentiveAggregation(hidden_size, num_layers)
269
+
270
+ # 4. Downstream Head
271
+ self.downstream = EmotionECAPATDNN(
272
+ input_dim=hidden_size,
273
+ )
274
+ d_out = self.downstream.output_dim
275
+
276
+ # 5. Gender Heads
277
+ self.gender_proj = nn.Linear(d_out, 256)
278
+ self.gender_head = nn.Sequential(
279
+ nn.RMSNorm(256), nn.GELU(), nn.Linear(256, config.num_classes)
280
+ )
281
+
282
+ def _forward_backbone(self, inputs: torch.Tensor, attention_mask: torch.Tensor):
283
+ # During inference, we always want hidden states for the aggregator
284
+ outputs = self.backbone(
285
+ input_values=inputs,
286
+ attention_mask=attention_mask,
287
+ output_hidden_states=True
288
+ )
289
+
290
+ # Your Aggregation Logic
291
+ hs = torch.stack(outputs.hidden_states, dim=0) # (L, B, T, D)
292
+ return self.layer_aggregator(hs)
293
+
294
+ def forward(
295
+ self,
296
+ input_values: torch.Tensor,
297
+ attention_mask: torch.Tensor,
298
+ **kwargs
299
+ ):
300
+
301
+ inputs = input_values
302
+ x = self._forward_backbone(inputs, attention_mask) # (B, T, D)
303
+ feats = self.downstream(x) # Do Not Pass mask to downstream
304
+
305
+ pre_final = self.gender_proj(feats)
306
+ logits = self.gender_head(pre_final)
307
+ return pre_final, logits
308
+
309
+
310
+ class AttentionPooling(nn.Module):
311
+ """
312
+ Attention-based pooling over the sequence dimension.
313
+ Input: (batch, seq_len, embed_dim)
314
+ Output: (batch, embed_dim)
315
+ """
316
+ def __init__(self, embed_dim):
317
+ super().__init__()
318
+ self.attention = nn.Linear(embed_dim, 1)
319
+
320
+ def forward(self, x, mask=None):
321
+ # x: (batch, seq_len, embed_dim)
322
+ attn_scores = self.attention(x).squeeze(-1) # (batch, seq_len)
323
+ if mask is not None:
324
+ attn_scores = attn_scores.masked_fill(mask == 0, float('-inf'))
325
+ attn_weights = torch.softmax(attn_scores, dim=1) # (batch, seq_len)
326
+ pooled = torch.sum(x * attn_weights.unsqueeze(-1), dim=1) # (batch, embed_dim)
327
+ return pooled
__pycache__/MeralionForGender.cpython-313.pyc ADDED
Binary file (17.3 kB). View file
 
config.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "MeralionForGenderClassification"
4
+ ],
5
+ "auto_map": {
6
+ "AutoConfig": "MeralionForGender.MeralionGenderConfig",
7
+ "AutoModel": "MeralionForGender.MeralionForGenderClassification"
8
+ },
9
+ "dtype": "float32",
10
+ "meralion_name": "MERaLiON/MERaLiON-SpeechEncoder-2",
11
+ "model_type": "meralion_gender",
12
+ "num_classes": 2,
13
+ "transformers_version": "4.57.1"
14
+ }
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6e154bbf19106aeaa6a6e3481457dc5b993cb65420c63da510e0931de8cc566a
3
+ size 2554030272
preprocessor_config.json ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "auto_map": {
3
+ "AutoFeatureExtractor": "processing_bestrq_conformer.ModifiedWhisperFeatureExtractor",
4
+ "AutoProcessor": "processing_bestrq_conformer.ModifiedWhisperFeatureExtractor"
5
+ },
6
+ "chunk_length": 120,
7
+ "feature_extractor_type": "ModifiedWhisperFeatureExtractor",
8
+ "feature_size": 80,
9
+ "hop_length": 160,
10
+ "n_fft": 400,
11
+ "n_samples": 1920000,
12
+ "nb_max_frames": 12000,
13
+ "padding_side": "right",
14
+ "padding_value": 0.0,
15
+ "return_attention_mask": true,
16
+ "sampling_rate": 16000
17
+ }
processing_bestrq_conformer.py ADDED
@@ -0,0 +1,554 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding=utf-8
2
+ # Copyright 2022 The HuggingFace Inc. team.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ """
16
+ Feature extractor class for MERaLiON-SpeechEncoder, modified from original WhisperFeatureExtractor
17
+ """
18
+
19
+ import itertools
20
+ import os
21
+ from shutil import copyfile
22
+ from typing import Dict, List, Optional, Tuple, Union
23
+
24
+ import numpy as np
25
+
26
+ from transformers import is_torch_available, AutoFeatureExtractor, AutoTokenizer
27
+ from transformers.audio_utils import mel_filter_bank, spectrogram, window_function
28
+ from transformers.feature_extraction_sequence_utils import SequenceFeatureExtractor
29
+ from transformers.feature_extraction_utils import BatchFeature
30
+ from transformers.processing_utils import ProcessorMixin
31
+ from transformers.tokenization_utils import PreTrainedTokenizer
32
+ from transformers.utils import TensorType, logging
33
+
34
+
35
+ if is_torch_available():
36
+ import torch
37
+
38
+ logger = logging.get_logger(__name__)
39
+
40
+ VOCAB_FILES_NAMES = {"vocab_file": "sentencepiece.model"}
41
+
42
+ class ModifiedWhisperFeatureExtractor(SequenceFeatureExtractor):
43
+ r"""
44
+ Constructs a modified Whisper feature extractor.
45
+
46
+ This feature extractor inherits from [`~feature_extraction_sequence_utils.SequenceFeatureExtractor`] which contains
47
+ most of the main methods. Users should refer to this superclass for more information regarding those methods.
48
+
49
+ This class extracts mel-filter bank features from raw speech using a custom numpy implementation of the `Short Time
50
+ Fourier Transform` which should match pytorch's `torch.stft` equivalent.
51
+
52
+ Differences from WhisperFeatureExtractor:
53
+ - mel_filter_bank
54
+ - norm: "slaney" -> None
55
+ - mel_scale: "slaney" -> "htk"
56
+ - still uses log scaling and clamp but removes additional min-max/mean normalization
57
+
58
+ Args:
59
+ feature_size (`int`, *optional*, defaults to 80):
60
+ The feature dimension of the extracted features.
61
+ sampling_rate (`int`, *optional*, defaults to 16000):
62
+ The sampling rate at which the audio files should be digitalized expressed in hertz (Hz).
63
+ hop_length (`int`, *optional*, defaults to 160):
64
+ Length of the overlapping windows for the STFT used to obtain the Mel Frequency coefficients.
65
+ chunk_length (`int`, *optional*, defaults to 30):
66
+ The maximum number of chunks of `sampling_rate` samples used to trim and pad longer or shorter audio
67
+ sequences.
68
+ n_fft (`int`, *optional*, defaults to 400):
69
+ Size of the Fourier transform.
70
+ padding_value (`float`, *optional*, defaults to 0.0):
71
+ Padding value used to pad the audio. Should correspond to silences.
72
+ """
73
+
74
+ model_input_names = ["input_values"]
75
+
76
+ def __init__(
77
+ self,
78
+ feature_size=80,
79
+ sampling_rate=16000,
80
+ hop_length=160,
81
+ chunk_length=120,
82
+ n_fft=400,
83
+ padding_value=0.0,
84
+ return_attention_mask=True, # pad inputs to max length with silence token (zero) and no attention mask
85
+ **kwargs,
86
+ ):
87
+ super().__init__(
88
+ feature_size=feature_size,
89
+ sampling_rate=sampling_rate,
90
+ padding_value=padding_value,
91
+ return_attention_mask=return_attention_mask,
92
+ **kwargs,
93
+ )
94
+ self.n_fft = n_fft
95
+ self.hop_length = hop_length
96
+ self.chunk_length = chunk_length
97
+ self.n_samples = chunk_length * sampling_rate
98
+ self.nb_max_frames = self.n_samples // hop_length
99
+ self.sampling_rate = sampling_rate
100
+ self.mel_filters = mel_filter_bank(
101
+ num_frequency_bins=1 + n_fft // 2,
102
+ num_mel_filters=feature_size,
103
+ min_frequency=0.0,
104
+ max_frequency=8000.0,
105
+ sampling_rate=sampling_rate,
106
+ norm=None,
107
+ mel_scale="htk",
108
+ )
109
+
110
+ def _np_extract_fbank_features(self, waveform_batch: np.array, device: str) -> np.ndarray:
111
+ """
112
+ Compute the log-mel spectrogram of the provided audio, gives similar results to Whisper's original torch
113
+ implementation with 1e-5 tolerance.
114
+ """
115
+ if device != "cpu":
116
+ raise ValueError(
117
+ f"Got device `{device}` for feature extraction, but feature extraction on CUDA accelerator "
118
+ "devices requires torch, which is not installed. Either set `device='cpu'`, or "
119
+ "install torch according to the official instructions: https://pytorch.org/get-started/locally/"
120
+ )
121
+ log_spec_batch = []
122
+ for waveform in waveform_batch:
123
+ log_spec = spectrogram(
124
+ waveform,
125
+ window_function(self.n_fft, "hann"),
126
+ frame_length=self.n_fft,
127
+ hop_length=self.hop_length,
128
+ power=2.0,
129
+ mel_filters=self.mel_filters,
130
+ log_mel="log10",
131
+ )
132
+ log_spec = log_spec[:, :-1]
133
+
134
+ log_spec_batch.append(log_spec)
135
+ log_spec_batch = np.array(log_spec_batch)
136
+ return log_spec_batch
137
+
138
+ def _torch_extract_fbank_features(self, waveform: np.array, device: str = "cpu") -> np.ndarray:
139
+ """
140
+ Compute the log-mel spectrogram of the audio using PyTorch's GPU-accelerated STFT implementation with batching,
141
+ yielding results similar to cpu computing with 1e-5 tolerance.
142
+ """
143
+ waveform = torch.from_numpy(waveform).type(torch.float32)
144
+
145
+ window = torch.hann_window(self.n_fft)
146
+ if device != "cpu":
147
+ waveform = waveform.to(device)
148
+ window = window.to(device)
149
+ stft = torch.stft(waveform, self.n_fft, self.hop_length, window=window, return_complex=True)
150
+ magnitudes = stft[..., :-1].abs() ** 2
151
+
152
+ mel_filters = torch.from_numpy(self.mel_filters).type(torch.float32)
153
+ if device != "cpu":
154
+ mel_filters = mel_filters.to(device)
155
+ mel_spec = mel_filters.T @ magnitudes
156
+
157
+ log_spec = torch.clamp(mel_spec, min=1e-10).log10()
158
+
159
+ if device != "cpu":
160
+ log_spec = log_spec.detach().cpu()
161
+ return log_spec.numpy()
162
+
163
+ @staticmethod
164
+ # Copied from transformers.models.wav2vec2.feature_extraction_wav2vec2.Wav2Vec2FeatureExtractor.zero_mean_unit_var_norm
165
+ def zero_mean_unit_var_norm(
166
+ input_values: List[np.ndarray], attention_mask: List[np.ndarray], padding_value: float = 0.0
167
+ ) -> List[np.ndarray]:
168
+ """
169
+ Every array in the list is normalized to have zero mean and unit variance
170
+ """
171
+ if attention_mask is not None:
172
+ attention_mask = np.array(attention_mask, np.int32)
173
+ normed_input_values = []
174
+
175
+ for vector, length in zip(input_values, attention_mask.sum(-1)):
176
+ normed_slice = (vector - vector[:length].mean()) / np.sqrt(vector[:length].var() + 1e-7)
177
+ if length < normed_slice.shape[0]:
178
+ normed_slice[length:] = padding_value
179
+
180
+ normed_input_values.append(normed_slice)
181
+ else:
182
+ normed_input_values = [(x - x.mean()) / np.sqrt(x.var() + 1e-7) for x in input_values]
183
+
184
+ return normed_input_values
185
+
186
+ def __call__(
187
+ self,
188
+ raw_speech: Union[np.ndarray, List[float], List[np.ndarray], List[List[float]]],
189
+ truncation: bool = True,
190
+ pad_to_multiple_of: Optional[int] = None,
191
+ return_tensors: Optional[Union[str, TensorType]] = None,
192
+ return_attention_mask: Optional[bool] = True,
193
+ padding: Optional[Union[bool, str]] = True,
194
+ max_length: Optional[int] = None,
195
+ sampling_rate: Optional[int] = None,
196
+ do_normalize: Optional[bool] = None,
197
+ device: Optional[str] = "cpu",
198
+ return_token_timestamps: Optional[bool] = None,
199
+ **kwargs,
200
+ ) -> BatchFeature:
201
+ """
202
+ Main method to featurize and prepare for the model one or several sequence(s). Implementation uses PyTorch for
203
+ the STFT computation if available, otherwise a slower NumPy based one.
204
+
205
+ Args:
206
+ raw_speech (`np.ndarray`, `List[float]`, `List[np.ndarray]`, `List[List[float]]`):
207
+ The sequence or batch of sequences to be padded. Each sequence can be a numpy array, a list of float
208
+ values, a list of numpy arrays or a list of list of float values. Must be mono channel audio, not
209
+ stereo, i.e. single float per timestep.
210
+ truncation (`bool`, *optional*, default to `True`):
211
+ Activates truncation to cut input sequences longer than *max_length* to *max_length*.
212
+ pad_to_multiple_of (`int`, *optional*, defaults to None):
213
+ If set will pad the sequence to a multiple of the provided value.
214
+
215
+ This is especially useful to enable the use of Tensor Cores on NVIDIA hardware with compute capability
216
+ `>= 7.5` (Volta), or on TPUs which benefit from having sequence lengths be a multiple of 128.
217
+ return_attention_mask (`bool`, *optional*):
218
+ Whether to return the attention mask. If left to the default, will return the attention mask according
219
+ to the specific feature_extractor's default.
220
+
221
+ [What are attention masks?](../glossary#attention-mask)
222
+
223
+ <Tip>
224
+
225
+ For Whisper models, `attention_mask` should always be passed for batched inference, to avoid subtle
226
+ bugs.
227
+
228
+ </Tip>
229
+
230
+ return_tensors (`str` or [`~utils.TensorType`], *optional*):
231
+ If set, will return tensors instead of list of python integers. Acceptable values are:
232
+
233
+ - `'tf'`: Return TensorFlow `tf.constant` objects.
234
+ - `'pt'`: Return PyTorch `torch.Tensor` objects.
235
+ - `'np'`: Return Numpy `np.ndarray` objects.
236
+ sampling_rate (`int`, *optional*):
237
+ The sampling rate at which the `raw_speech` input was sampled. It is strongly recommended to pass
238
+ `sampling_rate` at the forward call to prevent silent errors and allow automatic speech recognition
239
+ pipeline.
240
+ padding_value (`float`, *optional*, defaults to 0.0):
241
+ The value that is used to fill the padding values / vectors.
242
+ do_normalize (`bool`, *optional*, defaults to `False`):
243
+ Whether or not to zero-mean unit-variance normalize the input. Normalizing can help to significantly
244
+ improve the performance of the model.
245
+ device (`str`, *optional*, defaults to `'cpu'`):
246
+ Specifies the device for computation of the log-mel spectrogram of audio signals in the
247
+ `_torch_extract_fbank_features` method. (e.g., "cpu", "cuda")
248
+ return_token_timestamps (`bool`, *optional*, defaults to `None`):
249
+ Whether or not to return the number of frames of the input raw_speech.
250
+ These num_frames can be used by the model to compute word level timestamps.
251
+ """
252
+
253
+ if sampling_rate is not None:
254
+ if sampling_rate != self.sampling_rate:
255
+ raise ValueError(
256
+ f"The model corresponding to this feature extractor: {self.__class__.__name__} was trained using a"
257
+ f" sampling rate of {self.sampling_rate}. Please make sure that the provided `raw_speech` input"
258
+ f" was sampled with {self.sampling_rate} and not {sampling_rate}."
259
+ )
260
+ else:
261
+ logger.warning(
262
+ "It is strongly recommended to pass the `sampling_rate` argument to this function. "
263
+ "Failing to do so can result in silent errors that might be hard to debug."
264
+ )
265
+
266
+ is_batched_numpy = isinstance(raw_speech, np.ndarray) and len(raw_speech.shape) > 1
267
+ if is_batched_numpy and len(raw_speech.shape) > 2:
268
+ raise ValueError(f"Only mono-channel audio is supported for input to {self}")
269
+ is_batched = is_batched_numpy or (
270
+ isinstance(raw_speech, (list, tuple)) and (isinstance(raw_speech[0], (np.ndarray, tuple, list)))
271
+ )
272
+
273
+ if is_batched:
274
+ raw_speech = [np.asarray([speech], dtype=np.float32).T for speech in raw_speech]
275
+ elif not is_batched and not isinstance(raw_speech, np.ndarray):
276
+ raw_speech = np.asarray(raw_speech, dtype=np.float32)
277
+ elif isinstance(raw_speech, np.ndarray) and raw_speech.dtype is np.dtype(np.float64):
278
+ raw_speech = raw_speech.astype(np.float32)
279
+
280
+ # always return batch
281
+ if not is_batched:
282
+ raw_speech = [np.asarray([raw_speech]).T]
283
+
284
+ batched_speech = BatchFeature({"input_values": raw_speech})
285
+
286
+ # convert into correct format for padding
287
+
288
+ padded_inputs = self.pad( #whisper pads first then transform, while we do the reverse
289
+ batched_speech,
290
+ padding=padding,
291
+ max_length=max_length if max_length else self.n_samples,
292
+ truncation=truncation,
293
+ pad_to_multiple_of=pad_to_multiple_of,
294
+ return_attention_mask=return_attention_mask or do_normalize,
295
+ )
296
+
297
+ # zero-mean and unit-variance normalization
298
+ if do_normalize:
299
+ padded_inputs["input_values"] = self.zero_mean_unit_var_norm(
300
+ padded_inputs["input_values"],
301
+ attention_mask=padded_inputs["attention_mask"],
302
+ padding_value=self.padding_value,
303
+ )
304
+ padded_inputs["input_values"] = np.stack(padded_inputs["input_values"], axis=0)
305
+
306
+ # make sure list is in array format
307
+ input_values = padded_inputs.get("input_values").transpose(2, 0, 1)
308
+
309
+ extract_fbank_features = (
310
+ self._torch_extract_fbank_features if is_torch_available() else self._np_extract_fbank_features
311
+ )
312
+ input_values = extract_fbank_features(input_values[0], device)
313
+
314
+ if isinstance(input_values[0], List):
315
+ padded_inputs["input_values"] = [np.asarray(feature, dtype=np.float32) for feature in input_values]
316
+
317
+ else:
318
+ padded_inputs["input_values"] = input_values
319
+
320
+ if return_attention_mask:
321
+ # rescale from sample (48000) to feature (3000)
322
+ padded_inputs["attention_mask"] = padded_inputs["attention_mask"][:, :: self.hop_length]
323
+
324
+ if return_token_timestamps is not None:
325
+ padded_inputs["num_frames"] = [len(raw_speech_i) // self.hop_length for raw_speech_i in raw_speech]
326
+
327
+ if return_tensors is not None:
328
+ padded_inputs = padded_inputs.convert_to_tensors(return_tensors)
329
+
330
+ return padded_inputs
331
+
332
+
333
+ class MeralionBestRqConformerTokenizer(PreTrainedTokenizer):
334
+ """
335
+ Constructs a MeralionBestRqConformer tokenizer. Based on `SentencePiece`.
336
+
337
+ Args:
338
+ vocab_file (`str`):
339
+ Path to the vocabulary file.
340
+ unk_token (`str`, *optional*, defaults to "<unk>"):
341
+ The unknown token. A token that is not in the vocabulary cannot be converted to an ID and is set to be this
342
+ token.
343
+ pad_token (`str`, *optional*, defaults to "<pad>"):
344
+ The token used for padding, for example when batching sequences of different lengths.
345
+ bos_token (`str`, *optional*, defaults to "<s>"):
346
+ The beginning of sequence token that was used during pretraining. Can be used a sequence classifier token.
347
+ eos_token (`str`, *optional*, defaults to "</s>"):
348
+ The end of sequence token.
349
+ **kwargs
350
+ Additional keyword arguments passed along to
351
+ [`PreTrainedTokenizer.__init__`](https://huggingface.co/docs/transformers/main_classes/tokenizer#transformers.PreTrainedTokenizer.__init__).
352
+ """
353
+
354
+ vocab_files_names = VOCAB_FILES_NAMES
355
+
356
+ def __init__(
357
+ self,
358
+ vocab_file,
359
+ unk_token="<unk>",
360
+ pad_token="<pad>",
361
+ bos_token="<s>",
362
+ eos_token="</s>",
363
+ blank_token="<blk>",
364
+ **kwargs
365
+ ):
366
+ import sentencepiece as spm
367
+
368
+ self.vocab_file = vocab_file
369
+ self.sp_model = spm.SentencePieceProcessor()
370
+ self.sp_model.Load(vocab_file)
371
+
372
+ super().__init__(
373
+ unk_token=unk_token,
374
+ pad_token=pad_token,
375
+ bos_token=bos_token,
376
+ eos_token=eos_token,
377
+ blank_token=blank_token,
378
+ **kwargs,
379
+ )
380
+
381
+ self.blank_token_id = self.sp_model.piece_to_id(blank_token)
382
+
383
+ def get_special_tokens_mask(
384
+ self,
385
+ token_ids_0: List[int],
386
+ token_ids_1: Optional[List[int]] = None,
387
+ already_has_special_tokens: bool = False,
388
+ ) -> List[int]:
389
+ """
390
+ Retrieves sequence of 0s and 1s specifying if corresponding token ID is a special token.
391
+ """
392
+ if already_has_special_tokens:
393
+ return super().get_special_tokens_mask(
394
+ token_ids_0=token_ids_0, token_ids_1=token_ids_1, already_has_special_tokens=True
395
+ )
396
+
397
+ if token_ids_1 is None:
398
+ return [0] * len(token_ids_0)
399
+ return ([0] * len(token_ids_0)) + ([0] * len(token_ids_1))
400
+
401
+ def _tokenize(self, text: str) -> List[str]:
402
+ """
403
+ Converts a string in a sequence of tokens (string), using the `sp_model` tokenizer.
404
+ """
405
+ # SentencePiece doesn't like empty strings
406
+ if not text:
407
+ return []
408
+ return self.sp_model.encode(text, out_type=str)
409
+
410
+ def _convert_token_to_id(self, token: str) -> int:
411
+ """
412
+ Converts a token (str) in an id (integer) using the vocab.
413
+ """
414
+ return self.sp_model.piece_to_id(token)
415
+
416
+ def _convert_id_to_token(self, index: int) -> str:
417
+ """
418
+ Converts an id (integer) in a token (str) using the vocab.
419
+ """
420
+ return self.sp_model.id_to_piece(index)
421
+
422
+ def convert_tokens_to_string(self, tokens: list[str]) -> str:
423
+ return self.sp_model.decode(tokens)
424
+
425
+ def decode(
426
+ self,
427
+ token_ids: Union[List[int], np.ndarray, torch.Tensor],
428
+ skip_special_tokens: bool = False,
429
+ clean_up_tokenization_spaces: bool = None,
430
+ group_tokens: bool = True,
431
+ **kwargs,
432
+ ) -> str:
433
+ """
434
+ Converts a sequence of ids in a string, using the tokenizer and vocabulary with CTC decoding logic.
435
+ """
436
+ if isinstance(token_ids, (np.ndarray, torch.Tensor)):
437
+ token_ids = token_ids.tolist()
438
+
439
+ # CTC decoding
440
+ if group_tokens:
441
+ token_ids = [token_id for token_id, _ in itertools.groupby(token_ids)]
442
+
443
+ # Remove blank tokens
444
+ token_ids = [token_id for token_id in token_ids if token_id != self.blank_token_id]
445
+
446
+ return super().decode(
447
+ token_ids,
448
+ skip_special_tokens=skip_special_tokens,
449
+ clean_up_tokenization_spaces=clean_up_tokenization_spaces,
450
+ )
451
+
452
+ def batch_decode(
453
+ self,
454
+ sequences: Union[List[int], List[List[int]], np.ndarray, torch.Tensor],
455
+ skip_special_tokens: bool = False,
456
+ clean_up_tokenization_spaces: Optional[bool] = None,
457
+ **kwargs,
458
+ ) -> List[str]:
459
+ """
460
+ Convert a list of lists of token ids into a list of strings by calling decode.
461
+ """
462
+ batch_decoded = [
463
+ self.decode(
464
+ seq,
465
+ skip_special_tokens=skip_special_tokens,
466
+ clean_up_tokenization_spaces=clean_up_tokenization_spaces,
467
+ **kwargs,
468
+ )
469
+ for seq in sequences
470
+ ]
471
+ return batch_decoded
472
+
473
+ def get_vocab(self) -> Dict[str, int]:
474
+ """
475
+ Returns the vocabulary as a dictionary of token to index.
476
+ """
477
+ vocab = {self.sp_model.IdToPiece(i): i for i in range(self.sp_model.GetPieceSize())}
478
+ vocab.update(self.added_tokens_encoder)
479
+ return vocab
480
+
481
+ def save_vocabulary(self, save_directory: str, filename_prefix: Optional[str] = None) -> Tuple[str]:
482
+ """
483
+ Save the vocabulary and special tokens file to a directory.
484
+ """
485
+ if not os.path.isdir(save_directory):
486
+ os.makedirs(save_directory)
487
+
488
+ vocab_file = os.path.join(
489
+ save_directory, (filename_prefix + "-" if filename_prefix else "") + VOCAB_FILES_NAMES["vocab_file"]
490
+ )
491
+
492
+ copyfile(self.vocab_file, vocab_file)
493
+
494
+ return (vocab_file,)
495
+
496
+ @property
497
+ def vocab_size(self) -> int:
498
+ return self.sp_model.get_piece_size()
499
+
500
+
501
+ class MeralionBestRqConformerProcessor(ProcessorMixin):
502
+ r"""
503
+ Constructs a Wav2Vec2 like processor which wraps a ModifiedWhisperFeatureExtractor feature extractor and a
504
+ MeralionBestRqConformerTokenizer sentencepiece tokenizer into a single
505
+ processor.
506
+
507
+ [`MeralionBestRqConformerProcessor`] offers all the functionalities of [`ModifiedWhisperFeatureExtractor`] and
508
+ [`MeralionBestRqConformerTokenizer`].
509
+ See the docstring of [`~MeralionBestRqConformerProcessor.__call__`] and [`~MeralionBestRqConformerProcessor.decode`]
510
+ for more information.
511
+
512
+ Args:
513
+ feature_extractor (`ModifiedWhisperFeatureExtractor`):
514
+ An instance of [`ModifiedWhisperFeatureExtractor`]. The feature extractor is a required input.
515
+ tokenizer ([`MeralionBestRqConformerTokenizer`]):
516
+ An instance of [`MeralionBestRqConformerTokenizer`]. The tokenizer is a required input.
517
+ """
518
+
519
+ feature_extractor_class = "ModifiedWhisperFeatureExtractor"
520
+ tokenizer_class = "MeralionBestRqConformerTokenizer"
521
+
522
+ def __init__(self, feature_extractor, tokenizer):
523
+ self.feature_extractor = feature_extractor
524
+ self.tokenizer = tokenizer
525
+ self.chat_template = None
526
+
527
+ @classmethod
528
+ def from_pretrained(cls, pretrained_model_name_or_path, **kwargs):
529
+ feature_extractor = ModifiedWhisperFeatureExtractor.from_pretrained(pretrained_model_name_or_path, **kwargs)
530
+ tokenizer = MeralionBestRqConformerTokenizer.from_pretrained(pretrained_model_name_or_path, **kwargs)
531
+
532
+ return cls(feature_extractor=feature_extractor, tokenizer=tokenizer)
533
+
534
+ def __call__(
535
+ self,
536
+ audio: Union[np.ndarray, List[float], List[np.ndarray], List[List[float]]],
537
+ *args,
538
+ **kwds,
539
+ ):
540
+ return self.feature_extractor(audio, *args, **kwds)
541
+
542
+ def batch_decode(self, *args, **kwargs):
543
+ """
544
+ This method forwards all its arguments to MeralionBestRqConformerTokenizer's [`~MeralionBestRqConformerTokenizer.batch_decode`].
545
+ Please refer to the docstring of this method for more information.
546
+ """
547
+ return self.tokenizer.batch_decode(*args, **kwargs)
548
+
549
+ def decode(self, *args, **kwargs):
550
+ """
551
+ This method forwards all its arguments to MeralionBestRqConformerTokenizer's [`~MeralionBestRqConformerTokenizer.decode`].
552
+ Please refer to the docstring of this method for more information.
553
+ """
554
+ return self.tokenizer.decode(*args, **kwargs)