Skip to content

About

Do face clusters and voice clusters describe the same people? Cross-modal clustering with a permutation test, plus the LTC audio model behind it.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

5 Commits

Folders and files

Repository files navigation

AudioFacialMatrix

The reproducible parts of the AudioFacialMatrix experiments (Section IV of R. Singh and R. Singh, "AudioFacialMatrix: Dataset for Voice and Face AI"): cross-modal clustering of facial landmark ratios (IV-A), the liquid time-constant network (LTNN) run on the audio front end (IV-B), and BYO-media preprocessing utilities for the cleaning steps described in Section III-B.

It does not implement the data collection in Section III. See Scope for why, and for exactly where the line is drawn.

What it does

Clustering (Section IV-A)

Landmark ratio features. The paper notes that 68 landmarks give 2278 pairwise distances and about 2.6 million ratios of those distances, then fixes the nose length as a common numerator. Fixing the denominator is what makes the space tractable: 2.6 million candidates become 2277, each scale-invariant against a single reference length that is roughly stable under head pose.

from afm import ratio_features
from afm.landmarks import select_by_variance

features = ratio_features(landmarks)                   # (n_faces, 2278)
features = features[:, select_by_variance(features, 26)]

Ratios are invariant to scale and translation and not to rotation, which is a frontal-crop assumption worth stating out loud. There is a test for each of those three properties.

Faces with a degenerate nose length raise rather than divide. A near-zero denominator turns a bad crop into an outlier large enough to drag a k-means centroid across the space unaided, and the paper reports exactly this class of noise surviving the automated filters.

Choosing k when the criteria disagree. The paper reports that silhouette peaked at k = 5 and fell almost monotonically after, but that the k = 5 clusters were unevenly distributed and the best trade-off was at k = 8. That reasoning is sound and it is usually done by eye. sweep_k scores each k on separation and on balance and combines them with a weight the caller sets:

from afm import sweep_k, select_k

rows = sweep_k(features, k_values=range(2, 13), balance_weight=0.5)
select_k(rows, by="silhouette")   # what separation alone prefers
select_k(rows, by="combined")     # what separation and balance prefer together

Balance is the normalised entropy of the cluster size distribution, in [0, 1], rather than a min/max size ratio, so it responds to the whole distribution and not only its extremes.

The two criteria disagreeing is a finding, not a bug, and select_k is built to let you report both rather than resolve it silently.

One thing the sweep cannot do. Balance rises with k almost monotonically, so no choice of balance_weight pins down a particular k on its own. Push the weight up and the winner walks off the end of the range. The paper's k = 8 comes from knowing there were eight source countries, which is external structure the sweep did not recover. The sweep's job is to show what that prior costs in separation, not to replace it.

Cross-modal agreement. The second AFM hypothesis is that individuals grouped into a voice subset belong to the corresponding face subset. Two independent clusterings of the same people, with arbitrary integer labels and no correspondence between them, so the measure is partition agreement and not accuracy.

from afm import cross_modal_agreement

print(cross_modal_agreement(face_clusters, voice_clusters, n_permutations=1000))
n=720  ARI=+0.0680  NMI=0.1945  AMI=+0.1814
permutation null: mean=+0.0001 sd=0.0035  p=0.0020
agreement exceeds the independent null at the 5% level

The permutation test is not optional here. Adjusted Rand Index has an expected value near zero under independence, but its sampling variance at realistic cluster counts is wide enough that a positive ARI on its own says very little. The null shuffles which face cluster belongs to which individual while leaving both marginal size distributions intact, so it isolates the pairing from the shapes of the two partitions. The p-value carries an add-one correction and never reports zero off a finite number of draws.

LTNN: liquid time-constant network on audio (Section IV-B)

ltnn is a liquid time-constant (LTC) recurrent network in PyTorch, plus the MFCC front end it was used with in the AFM audio experiment. An LTC unit is a continuous-time state whose time constant is a function of its input: a gated RNN decides whether to update its state, an LTC decides how fast the state evolves. That buys two concrete things: sequences sampled at uneven intervals can be consumed directly without resampling, and different units can settle on different timescales in the same layer.

import torch
from ltnn import LTCClassifier

model = LTCClassifier(input_size=39, num_classes=8, hidden_size=128, tau_init=8.0)
x = torch.randn(16, 300, 39)          # (batch, frames, mfcc features)
logits = model(x)                     # (16, 8)

Defaults follow the paper's Section IV-B configuration: a 128-unit recurrent layer into a linear head, trained with Adam (lr=0.001) on categorical cross-entropy against the face-side cluster assignments, batch size 32, early stopping patience 10.

ltnn.features implements the AFM preprocessing with numpy and scipy only: resample to 16 kHz, pre-emphasis, 25 ms windows at a 10 ms hop, a 40-band mel filterbank, 13 MFCCs, first/second-order deltas, for 39 features per frame. The log floor is relative to each clip's own peak rather than absolute, since an absolute floor clips more mel bins in a quiet recording than a loud one and lets recording level leak into the shape coefficients (test_mfcc_is_invariant_to_recording_level covers this across a 400x gain range).

Two implementation details matter and are not obvious from the Hasani et al. paper: the dynamics need a signed, input-dependent target (not a per-unit constant reversal potential, which collapses every sequence to the same attractor), and the conductance bias should start negative (gate_bias_init = -2.0) so units begin in the long-memory regime. Both are guarded by tests (test_final_state_actually_depends_on_the_sequence, test_solver_is_stable_on_large_inputs). examples/ltnn_irregular_sampling_benchmark.py demonstrates the mechanism against a GRU baseline on a synthetic uneven-sampling task (LTC 0.743 accuracy at 2340 params vs. GRU 0.728 at 3588 params, one seed, illustrative not a benchmark result).

Preprocessing: BYO-media cleaning utilities (Section III-B)

afm.preprocessing implements the cleaning steps Section III-B describes, as generic functions over media you already have -- no search, no scraping, no download, no identity or nationality targeting anywhere in this module. See Scope.

from afm.preprocessing import (
    deduplicate_frames, select_single_subject_frames,
    trim_silence, has_sufficient_speech_energy, fixed_duration_clip,
)

frames = deduplicate_frames(frames)                          # drop near-duplicate frames (dHash)
frames = select_single_subject_frames(frames, my_detector)   # keep only single-detection frames
clip = trim_silence(audio, sample_rate)                       # drop leading/trailing near-silence
ok = has_sufficient_speech_energy(clip, sample_rate)          # coarse "is this mostly dead air" gate
clip = fixed_duration_clip(clip, sample_rate, start_s=0.0, duration_s=10.0)

select_single_subject_frames and has_sufficient_speech_energy are deliberately thin: no face detector or speech/music classifier is bundled. Bring your own via the detect_fn callback (for frames) or substitute a real VAD (for audio) if your use case needs to tell speech apart from music or count speakers -- the bundled energy heuristic only flags near-silent or empty clips.

Install

git clone https://github.com/rahulrikisinghcode/AudioFacialMatrix.git
cd AudioFacialMatrix
pip install -e ".[dev]"
pytest

ltnn pulls in torch, so this install is the slow one; afm (clustering and preprocessing) is numpy, scipy and scikit-learn.

Examples

examples/k_selection_and_agreement.py builds synthetic landmark data with the structure the paper describes: eight groups, four close together and four far apart, so that separation and balance pull in different directions. It runs the sweep, shows the two criteria disagreeing, and runs the agreement test against both a voice partition that tracks the groups and one that does not.

examples/ltnn_irregular_sampling_benchmark.py builds a task where the elapsed time between observations carries the signal (sinusoids at four frequencies, sampled at random uneven intervals) and compares the LTC against a GRU baseline given the same delta_t information.

The data in both scripts is synthetic. Nothing in their output is a result about faces, voices, or nationality; they exist so the procedures can be run and argued with without the dataset.

What the sweep and the agreement test actually look like

Why silhouette-only and balance-aware k selection disagree, what the resulting cluster-size distributions look like, and the cross-modal agreement's observed ARI against its permutation null

All three panels come from a live run of examples/k_selection_and_agreement.py; see scripts/render_visuals.py, which reuses that script's own module-level state rather than recomputing anything, so the numbers here match its printed output exactly. On this run, silhouette alone peaks at k=2 while the balance-aware combined score peaks at k=5, the same disagreement the paper reports at k=5 versus k=8 (left panel). The middle panel shows why it matters: even the balance-aware pick still leaves 600 of 720 faces in one dominant cluster, because 5 clusters isn't enough to both separate the four close-together core groups and keep the four outlier groups apart. Right: the observed cross-modal ARI (+0.068) sits well outside its 800-draw permutation null, which is what "exceeds the independent null at the 5% level" looks like as a picture instead of a p-value.

Regenerate with:

pip install -e ".[viz]"
PYTHONPATH=src:examples python scripts/render_visuals.py

The LTNN visuals (accuracy curves, learned timescales, MFCC gain invariance) live in the standalone liquid-time-constant-networks repo linked in the Citation section below, rather than duplicated here.

Scope

This repository implements the analysis (Section IV) and the generic media cleaning a pipeline like this needs (Section III-B), not the collection (Section III-A). There is no web crawler, no video downloader, no search-by-region or search-by-language logic, and no pipeline that assembles face/voice pairs from public video on its own.

That is a deliberate omission, not an oversight. The collection method gathers biometric identifiers, faces and voiceprints, from identifiable people who did not consent, labelled by nationality. The paper is candid that named public figures ended up in the set. Describing that methodology in a paper and shipping a one-command tool that reproduces it are different acts with different consequences, and only the first is what a reader of the paper needs. The preprocessing utilities above stop at "clean media I already have" and do not cross into "find more of it."

Two further limits are worth stating plainly, because code in this area gets reused past its warrant.

The labels in AFM are source language and search region, not nationality and certainly not ancestry. The paper itself flags the confound: the Swedish subset contains Swedish-born Asian reporters. Cluster structure recovered from these features is structure in a corpus of news broadcasts, and a corpus of news broadcasts is not a population.

The paper's suggested applications include narrowing suspects in criminal investigation. Nothing here is fit for that. Cluster agreement above an independent null is a statement about a partition, not a statement about any individual, and it carries no per-person error rate that anyone could be held to.

Citation

@misc{singh_afm,
  author = {Singh, Rahul and Singh, Rita},
  title  = {AudioFacialMatrix: Dataset for Voice and Face AI},
  note   = {Carnegie Mellon University, Language Technologies Institute}
}

@inproceedings{hasani2021liquid,
  author    = {Hasani, Ramin and Lechner, Mathias and Amini, Alexander
               and Rus, Daniela and Grosu, Radu},
  title     = {Liquid Time-constant Networks},
  booktitle = {Proceedings of the AAAI Conference on Artificial Intelligence},
  volume    = {35},
  number    = {9},
  pages     = {7657--7666},
  year      = {2021}
}

A standalone, general-purpose version of the LTC implementation (not tied to AFM) also lives at liquid-time-constant-networks.

License

MIT. See LICENSE.

About

Do face clusters and voice clusters describe the same people? Cross-modal clustering with a permutation test, plus the LTC audio model behind it.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages