ssl-kaldi-feats
Kaldi ark/scp features from any HuggingFace speech encoder. No Kaldi
installation required - archives are written in pure Python, so the output is
consumable by Kaldi, ESPnet, or anything else that reads the format, on a
machine that has never compiled Kaldi.
pip install ssl-kaldi-feats
Use
# single layer, from a Kaldi wav.scp (the "cmd |" form works too)
ssl-kaldi-feats --model facebook/hubert-base-ls960 --layer 9 \
--wav-scp data/train/wav.scp --output-dir feats/train
# or from a plain list of audio files
ssl-kaldi-feats --model microsoft/wavlm-large --layer 16 \
--file-list files.txt --output-dir feats/train
Output is a directory Kaldi will accept as-is:
feats/train/feats.scp feats.ark utt2num_frames utt2dur frame_shift
As a library:
from ssl_kaldi_feats import SSLExtractor
ex = SSLExtractor("facebook/mms-300m", layers=14)
feats = ex.extract_one(waveform) # (frames, 1024) float32, 20 ms shift
Sweeping layers costs one forward pass, not one per layer
ssl-kaldi-feats --model facebook/mms-300m --layers 10,12,14,16,18 \
--wav-scp data/train/wav.scp --output-dir feats/sweep
# -> feats/sweep/layer10/ layer12/ layer14/ layer16/ layer18/
Probing those five layers separately costs 10+12+14+16+18 = 70 layer-evaluations. Together it costs 18, plus one model load and one pass over the audio instead of five. Measured at 3.9x faster on a 3696-utterance set.
The layer_norm gotcha
Taking layer k by truncating the encoder and reading the last hidden state is
not the same as indexing hidden_states[k] on the full model.
Wav2Vec2EncoderStableLayerNorm (the large models: mms-300m, mms-1b,
wavlm-large, wav2vec2-large, xls-r) and WhisperEncoder apply
encoder.layer_norm after the layer loop, so truncation moves a norm trained
for the top layer onto layer k. Base-sized models (hubert-base, wavlm-base,
mHuBERT-147) norm before the loop and are unaffected.
Verified exactly on mms-300m: cosine against layer_norm(L_k) is 1.000000 at
layers 8, 14 and 20, while cosine against the raw layer is 0.67 to 0.87.
--final-layer-norm keep # default: layer_norm(layer_k)
--final-layer-norm strip # hidden_states[k], comparable with the literature
keep is the default because it measures better as a front end: on TIMIT with
mms-300m layer 14 it wins at every HMM-GMM stage by 0.67 to 2.21 PER. The norm
acts as a free per-frame normalisation, which a per-speaker CMVN cannot
substitute for. It also changes which layer wins (14 with the norm, 16
without), so a layer study run through a misplaced norm is not measuring the
layer alone. Use strip when you need numbers comparable with published layer
studies.
Notes
- Frame rate is measured, not assumed. A short probe pass at load time
derives seconds-per-frame and writes it to
frame_shift. Every encoder supported here is 20 ms, but hardcoding that would be silently wrong for anything else, and a wrongframe_shiftmisaligns everything downstream without erroring. Measured rates within 5% of 10, 20 or 40 ms snap to the clean value (models typically emit one frame fewer than duration/shift, from the convolutional receptive field); anything else is reported with a warning. Override withSSLExtractor(..., frame_shift=0.04)if you disagree. - Whisper is handled: the decoder is dropped, and because its feature extractor pads every utterance to 30 s the frames beyond the real audio are discarded. Utterances over 30 s raise rather than being silently truncated.
- Multi-channel input is rejected, not mangled. A stereo
(n, 2)array survivesnp.squeezeand the HF feature extractor then reads it as a batch of two, returning wrong features with no error. - Failures abort by default. A tolerated failure silently shrinks the
output, which later looks like a smaller test set rather than an error. Raise
--max-failuresdeliberately. soundfileis only needed for non-wav or non-PCM16 audio:pip install 'ssl-kaldi-feats[audio]'.
Where this comes from
Extracted from ssl-kaldi, a set of
Kaldi recipes that put frozen self-supervised features in front of a classic
HMM-GMM and LF-MMI back end. The numbers quoted above come from its TIMIT
recipe; per-recipe results are in the READMEs under egs/.
Apache-2.0.
Metadata
Release files for ssl-kaldi-feats 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| ssl_kaldi_feats-0.1.1.tar.gz | 596.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| ssl_kaldi_feats-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 612.3 kB
Release files / ssl_kaldi_feats-0.1.1.tar.gz
| Download URL | ssl_kaldi_feats-0.1.1.tar.gz |
|---|---|
| Size | 596.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8d33fd988e94e10da4bf5bd45f0b5f985a4c1553a1ff5ecf63c413f789d18504
|
|
BLAKE2b-256 checksum How to use checksums |
0641d095dcf55ce7b68edb151e4f5fb71de77dce57a203187772e839976defc3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.8.20
|
Release files / ssl_kaldi_feats-0.1.1-py3-none-any.whl
| Download URL | ssl_kaldi_feats-0.1.1-py3-none-any.whl |
|---|---|
| Size | 16.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
230bc056949f5b70db8a450a0bc931da2ac20de4a852e011ab65c3916d41002d
|
|
BLAKE2b-256 checksum How to use checksums |
432ed870f25f3e2a7aaba325bf6dfb7f07201dac8d262729b7a8dc29e14bff96
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.8.20
|