Image Classification
Transformers
Safetensors
English
age-estimation
age-prediction
gender-classification
race-classification
ethnicity-classification
face-analysis
demographics
facial-attributes
fairness
bias-evaluation
convnext
Instructions to use TimmaJ/age-gender-race-prediction with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use TimmaJ/age-gender-race-prediction with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("image-classification", model="TimmaJ/age-gender-race-prediction") pipe("https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/hub/parrots.png")# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("TimmaJ/age-gender-race-prediction", device_map="auto") - Notebooks
- Google Colab
- Kaggle
| """ | |
| Correct aggregate group shares for classifier error. | |
| If you classify a corpus and report the share of each group, those shares are | |
| biased by classifier error, and the bias is largest exactly where accuracy is | |
| lowest. With a confusion matrix M where | |
| M[i, j] = P(predicted = i | true = j) | |
| observed and true shares are related by p_obs = M @ p_true, so | |
| p_true ~= pinv(M) @ p_obs | |
| This is the standard quantification / prior-correction estimator. Its bias goes | |
| to zero as M approaches the identity, and it is base-rate invariant, so a | |
| confusion matrix estimated on a stratified validation sample is valid here. | |
| Two cautions: | |
| * Estimate M with the SAME pipeline that produced the counts you are | |
| correcting - same detector, same crop margin, same checkpoints. A confusion | |
| matrix from a different preprocessing is the wrong matrix. | |
| * When a class has very low recall the correction is poorly conditioned and | |
| can return a near-zero or clipped estimate. Report those classes as bounds. | |
| On our data the unweighted correction sent Hispanic to 0.0, while the | |
| inverse-probability-weighted version recovered 2.6%. Prefer the weighted | |
| matrix and say which you used. | |
| Licence: MIT. | |
| """ | |
| from __future__ import annotations | |
| import numpy as np | |
| __all__ = [ | |
| "confusion_matrix_from_labels", | |
| "correct_proportions", | |
| "correct_counts", | |
| ] | |
| def confusion_matrix_from_labels(y_true, y_pred, labels, weights=None) -> np.ndarray: | |
| """ | |
| Column-stochastic confusion matrix M[i, j] = P(pred = labels[i] | true = labels[j]). | |
| weights : optional per-sample weights. Pass inverse selection probabilities | |
| when the validation sample was stratified on predicted labels, | |
| otherwise M is conditioned on the predictions rather than the | |
| population. | |
| """ | |
| labels = list(labels) | |
| idx = {l: i for i, l in enumerate(labels)} | |
| n = len(labels) | |
| if weights is None: | |
| weights = np.ones(len(y_true), dtype=float) | |
| weights = np.asarray(weights, dtype=float) | |
| M = np.zeros((n, n), dtype=float) | |
| for t, p, w in zip(y_true, y_pred, weights): | |
| if t in idx and p in idx: | |
| M[idx[p], idx[t]] += w | |
| for j in range(n): | |
| col = M[:, j].sum() | |
| if col > 0: | |
| M[:, j] /= col | |
| else: | |
| M[j, j] = 1.0 # unobserved class: assume no confusion | |
| return M | |
| def correct_proportions(p_obs, M, clip_negative: bool = True) -> np.ndarray: | |
| """ | |
| Recover true class proportions from observed ones. | |
| p_obs : observed proportions, same order as the confusion matrix labels | |
| M : column-stochastic confusion matrix | |
| """ | |
| p_obs = np.asarray(p_obs, dtype=float) | |
| q = np.linalg.pinv(M) @ p_obs | |
| if clip_negative: | |
| q = np.clip(q, 0.0, None) | |
| total = q.sum() | |
| return q / total if total > 0 else p_obs | |
| def correct_counts(counts, M) -> np.ndarray: | |
| """Same correction expressed in counts rather than proportions.""" | |
| counts = np.asarray(counts, dtype=float) | |
| total = counts.sum() | |
| if total <= 0: | |
| return counts | |
| return correct_proportions(counts / total, M) * total | |
| if __name__ == "__main__": | |
| # Worked example: race shares of real advertisements, using the published | |
| # real-domain IPW matrix from benchmark/confusion_race_real_ipw.csv | |
| labels = ["white", "black", "asian", "hispanic"] | |
| p_obs = [0.6494, 0.1889, 0.1552, 0.0065] | |
| census = [0.6090, 0.1329, 0.0697, 0.1884] | |
| M = np.array([ | |
| [0.9603, 0.1138, 0.2073, 0.1376], | |
| [0.0057, 0.8383, 0.0106, 0.2646], | |
| [0.0327, 0.0448, 0.7804, 0.4092], | |
| [0.0013, 0.0031, 0.0017, 0.1886], | |
| ]) | |
| p_cor = correct_proportions(p_obs, M) | |
| print(f"{'class':10s} {'observed':>9s} {'corrected':>10s} {'census':>8s} " | |
| f"{'dev obs':>8s} {'dev cor':>8s}") | |
| for l, o, c, r in zip(labels, p_obs, p_cor, census): | |
| print(f"{l:10s} {o*100:8.2f}% {c*100:9.2f}% {r*100:7.2f}% " | |
| f"{(o-r)*100:+7.2f} {(c-r)*100:+7.2f}") | |
| print("\nWhite over-representation: +4.04 pp observed -> +0.67 pp corrected.") | |