NikiPshg commited on
Commit
6b0fab3
·
verified ·
1 Parent(s): 2a52c76

Upload folder using huggingface_hub

Browse files
Files changed (6) hide show
  1. .gitattributes +0 -34
  2. LICENSE +13 -0
  3. README.md +133 -0
  4. inference.py +159 -0
  5. model.onnx +3 -0
  6. requirements.txt +4 -0
.gitattributes CHANGED
@@ -1,35 +1 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
  *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  *.onnx filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
LICENSE ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Copyright 2025 (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
README.md CHANGED
@@ -1,3 +1,136 @@
1
  ---
 
 
 
 
 
 
 
 
 
 
 
2
  license: apache-2.0
3
  ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ language:
3
+ - multilingual
4
+ library_name: onnxruntime
5
+ pipeline_tag: audio-classification
6
+ tags:
7
+ - audio
8
+ - speech
9
+ - tts
10
+ - quality-classification
11
+ - wav2vec2
12
+ - onnx
13
  license: apache-2.0
14
  ---
15
+
16
+ # TTS Suitability Classifier
17
+
18
+ ONNX audio classifier that estimates whether a speech segment is suitable for
19
+ TTS training.
20
+
21
+ The model is a binary classifier based on the 300M wav2vec2 encoder from
22
+ [facebook/omniASR](https://github.com/facebookresearch/omnilingual-asr).
23
+ The ONNX file is self-contained and does not require `fairseq2`, PyTorch, or the
24
+ original omnilingual-asr repository for inference.
25
+
26
+ ## Labels
27
+
28
+ | Class | Label | Meaning |
29
+ |---:|---|---|
30
+ | 0 | `not_tts` | Audio is not suitable for TTS training |
31
+ | 1 | `tts` | Audio is suitable for TTS training |
32
+
33
+ `p_tts` is the softmax probability of class 1. The default decision threshold
34
+ is `0.5`. For dataset filtering, choose the threshold on a manually labeled
35
+ validation set.
36
+
37
+ ## Installation
38
+
39
+ ```bash
40
+ pip install -r requirements.txt
41
+ ```
42
+
43
+ For CUDA inference, replace `onnxruntime` with a compatible
44
+ `onnxruntime-gpu` build.
45
+
46
+ ## Command-line inference
47
+
48
+ ```bash
49
+ python inference.py sample.mp3
50
+ python inference.py /path/to/audio-directory --provider cpu
51
+ python inference.py sample.wav --provider cuda --cuda-device-id 0
52
+ ```
53
+
54
+ Each result is printed as one JSON object:
55
+
56
+ ```json
57
+ {
58
+ "label": "tts",
59
+ "predicted_class": 1,
60
+ "p_not_tts": 0.02,
61
+ "p_tts": 0.98,
62
+ "logits": [-2.2, 1.5]
63
+ }
64
+ ```
65
+
66
+ ## Python API
67
+
68
+ ```python
69
+ from inference import TTSSuitabilityClassifier
70
+
71
+ classifier = TTSSuitabilityClassifier(provider="auto")
72
+ result = classifier.predict("sample.mp3")
73
+
74
+ print(result["label"])
75
+ print(result["p_tts"])
76
+ ```
77
+
78
+ ## Input preprocessing
79
+
80
+ The included inference code applies the same preprocessing as the training and
81
+ export recipe:
82
+
83
+ 1. Decode WAV, FLAC, MP3, OGG, or M4A.
84
+ 2. Mix channels to mono.
85
+ 3. Resample to 16 kHz.
86
+ 4. Apply waveform layer normalization.
87
+ 5. Split long audio into 10-second chunks.
88
+ 6. Average chunk logits and apply softmax.
89
+
90
+ The ONNX input is a float32 tensor named `waveforms` with shape
91
+ `[batch_size, num_frames]`. The output is `logits` with shape
92
+ `[batch_size, 2]`. Both input axes are dynamic; ONNX opset 17 is used.
93
+
94
+ ## Files
95
+
96
+ - `model.onnx`: self-contained FP32 ONNX model.
97
+ - `inference.py`: standalone ONNX Runtime inference.
98
+ - `requirements.txt`: CPU inference dependencies.
99
+
100
+ ## Upload to Hugging Face
101
+
102
+ Create an empty model repository, then run from this directory:
103
+
104
+ ```bash
105
+ hf upload-large-folder <username>/<repo-name> . --repo-type model
106
+ ```
107
+
108
+ `model.onnx` is configured for Git LFS in `.gitattributes`.
109
+
110
+ ## Training and export
111
+
112
+ The released model corresponds to training checkpoint step 94,000. It was
113
+ exported using the repository recipes:
114
+
115
+ - `workflows/recipes/wav2vec2/binary_classification/export_onnx.py`
116
+ - `workflows/recipes/wav2vec2/binary_classification/run_onnx.py`
117
+
118
+ Architecture: `wav2vec2_asr 300m`
119
+ Sample rate: `16000 Hz`
120
+ Training maximum audio length: `160000` samples
121
+ Classes: `not_tts`, `tts`
122
+
123
+ ## Limitations
124
+
125
+ - The score measures similarity to the training definition of TTS-suitable
126
+ audio; it is not a general-purpose MOS score.
127
+ - Music, noise, clipping, overlapping speakers, and unusual recording
128
+ conditions may affect predictions.
129
+ - Probabilities are not guaranteed to be calibrated.
130
+ - Validate the threshold on data from the intended domain before filtering a
131
+ large dataset.
132
+
133
+ ## License
134
+
135
+ Apache 2.0. The base architecture and code originate from the
136
+ omnilingual-asr project.
inference.py ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import json
5
+ from pathlib import Path
6
+
7
+ import numpy as np
8
+ import onnxruntime as ort
9
+ import soundfile as sf
10
+ from scipy.signal import resample_poly
11
+
12
+
13
+ MODEL_PATH = Path(__file__).with_name("model.onnx")
14
+ SAMPLE_RATE = 16_000
15
+ CHUNK_FRAMES = 160_000
16
+ SUPPORTED_EXTENSIONS = {".wav", ".flac", ".mp3", ".ogg", ".m4a"}
17
+
18
+
19
+ def load_audio(path: Path) -> tuple[np.ndarray, int]:
20
+ audio, sample_rate = sf.read(path, dtype="float32", always_2d=True)
21
+ waveform = np.ascontiguousarray(audio.mean(axis=1), dtype=np.float32)
22
+ return waveform, int(sample_rate)
23
+
24
+
25
+ def resample_audio(
26
+ waveform: np.ndarray, source_rate: int, target_rate: int
27
+ ) -> np.ndarray:
28
+ gcd = np.gcd(source_rate, target_rate)
29
+ waveform = resample_poly(
30
+ waveform, target_rate // gcd, source_rate // gcd
31
+ ).astype(np.float32)
32
+ return np.ascontiguousarray(waveform)
33
+
34
+
35
+ def layer_norm(waveform: np.ndarray, eps: float = 1e-5) -> np.ndarray:
36
+ mean = waveform.mean(dtype=np.float64)
37
+ variance = waveform.var(dtype=np.float64)
38
+ return ((waveform - mean) / np.sqrt(variance + eps)).astype(np.float32)
39
+
40
+
41
+ def chunk_waveform(waveform: np.ndarray, chunk_frames: int) -> np.ndarray:
42
+ if chunk_frames <= 0 or waveform.size <= chunk_frames:
43
+ return waveform[None, :]
44
+
45
+ chunks = [
46
+ waveform[start : start + chunk_frames]
47
+ for start in range(0, waveform.size, chunk_frames)
48
+ ]
49
+ max_length = max(chunk.size for chunk in chunks)
50
+ batch = np.zeros((len(chunks), max_length), dtype=np.float32)
51
+
52
+ for index, chunk in enumerate(chunks):
53
+ batch[index, : chunk.size] = chunk
54
+
55
+ return batch
56
+
57
+
58
+ def softmax(logits: np.ndarray) -> np.ndarray:
59
+ logits = logits.astype(np.float64)
60
+ probabilities = np.exp(logits - logits.max())
61
+ return probabilities / probabilities.sum()
62
+
63
+
64
+ class TTSSuitabilityClassifier:
65
+ def __init__(
66
+ self,
67
+ model_path: str | Path = MODEL_PATH,
68
+ provider: str = "auto",
69
+ cuda_device_id: int = 0,
70
+ ) -> None:
71
+ available = set(ort.get_available_providers())
72
+
73
+ if provider == "auto":
74
+ provider = "cuda" if "CUDAExecutionProvider" in available else "cpu"
75
+
76
+ if provider == "cuda":
77
+ if "CUDAExecutionProvider" not in available:
78
+ raise RuntimeError(
79
+ "CUDAExecutionProvider is unavailable. Install onnxruntime-gpu "
80
+ "or use provider='cpu'."
81
+ )
82
+ providers = [
83
+ ("CUDAExecutionProvider", {"device_id": cuda_device_id}),
84
+ "CPUExecutionProvider",
85
+ ]
86
+ elif provider == "cpu":
87
+ providers = ["CPUExecutionProvider"]
88
+ else:
89
+ raise ValueError("provider must be one of: auto, cpu, cuda")
90
+
91
+ self.session = ort.InferenceSession(str(model_path), providers=providers)
92
+ self.input_name = self.session.get_inputs()[0].name
93
+ self.output_names = [output.name for output in self.session.get_outputs()]
94
+
95
+ def predict(self, audio_path: str | Path) -> dict[str, object]:
96
+ path = Path(audio_path).expanduser().resolve()
97
+ waveform, sample_rate = load_audio(path)
98
+
99
+ if sample_rate != SAMPLE_RATE:
100
+ waveform = resample_audio(waveform, sample_rate, SAMPLE_RATE)
101
+
102
+ waveform = layer_norm(waveform)
103
+ batch = chunk_waveform(waveform, CHUNK_FRAMES)
104
+ logits = self.session.run(
105
+ self.output_names, {self.input_name: batch}
106
+ )[0].mean(axis=0)
107
+ probabilities = softmax(logits)
108
+ predicted_class = int(probabilities.argmax())
109
+
110
+ return {
111
+ "path": str(path),
112
+ "label": "tts" if predicted_class == 1 else "not_tts",
113
+ "predicted_class": predicted_class,
114
+ "p_not_tts": float(probabilities[0]),
115
+ "p_tts": float(probabilities[1]),
116
+ "logits": [float(value) for value in logits],
117
+ }
118
+
119
+
120
+ def collect_audio_paths(path: Path) -> list[Path]:
121
+ path = path.expanduser().resolve()
122
+ if path.is_file():
123
+ return [path]
124
+
125
+ return sorted(
126
+ child
127
+ for child in path.rglob("*")
128
+ if child.is_file() and child.suffix.lower() in SUPPORTED_EXTENSIONS
129
+ )
130
+
131
+
132
+ def main() -> None:
133
+ parser = argparse.ArgumentParser(
134
+ description="ONNX inference for the TTS suitability classifier."
135
+ )
136
+ parser.add_argument("audio", type=Path, help="Audio file or directory.")
137
+ parser.add_argument(
138
+ "--model", type=Path, default=MODEL_PATH, help="Path to model.onnx."
139
+ )
140
+ parser.add_argument(
141
+ "--provider", choices=("auto", "cpu", "cuda"), default="auto"
142
+ )
143
+ parser.add_argument("--cuda-device-id", type=int, default=0)
144
+ args = parser.parse_args()
145
+
146
+ classifier = TTSSuitabilityClassifier(
147
+ args.model, args.provider, args.cuda_device_id
148
+ )
149
+ paths = collect_audio_paths(args.audio)
150
+
151
+ if not paths:
152
+ raise RuntimeError(f"No supported audio files found at '{args.audio}'.")
153
+
154
+ for path in paths:
155
+ print(json.dumps(classifier.predict(path), ensure_ascii=False))
156
+
157
+
158
+ if __name__ == "__main__":
159
+ main()
model.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b885c900b93c4698407a3187baefbb701866cd980db8782b7f40539cf101b221
3
+ size 1262092745
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ numpy>=1.24
2
+ onnxruntime>=1.17
3
+ scipy>=1.10
4
+ soundfile>=0.12