{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "1d1f4b5b-4e05-4957-b92f-4ecee448c3cd",
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.metrics.pairwise import cosine_similarity, manhattan_distances\n",
    "from matplotlib.backends.backend_pdf import PdfPages\n",
    "from sklearn.metrics import roc_curve\n",
    "from joblib import Parallel, delayed\n",
    "from tqdm.notebook import tqdm\n",
    "from random import shuffle"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "263621fb-e477-42ee-a743-4b4c18ef9c79",
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "import networkx as nx\n",
    "import seaborn as sns\n",
    "import numpy as np\n",
    "\n",
    "sns.set_theme()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d800b686-5c68-4643-9b1e-f081a91a44d3",
   "metadata": {
    "tags": []
   },
   "source": [
    "#### FUNCTIONS to load data "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "cc29b147-3e95-455d-ade5-8bec76b5866a",
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_embeddings(model):\n",
    "    return np.load(f'../RFW/embd_{model}.npy')\n",
    "def get_names():\n",
    "    return np.load('../RFW/x_names.npy')\n",
    "def get_ids():\n",
    "    return np.load('../RFW/x_ids.npy')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e0592605-e959-48d5-be25-66680eaedd6d",
   "metadata": {
    "tags": []
   },
   "source": [
    "#### FUNCTIONS of score comparisons"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "7883570f-a209-446e-b0f4-6504847241fc",
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_impostors(id_label):\n",
    "    # Generate the index matrix of same-id comparisons\n",
    "    comparison_matrix = (id_label[:, None] == id_label[None, :]).astype(int)\n",
    "    genuines_matrix = comparison_matrix.copy()\n",
    "    genuines_matrix[np.tril_indices(genuines_matrix.shape[0])] = 0\n",
    "    # Invert index matrix to get diff-id comparisons matrix\n",
    "    impostors_matrix = (1 - comparison_matrix)\n",
    "    impostors_matrix[np.tril_indices(impostors_matrix.shape[0])] = 0\n",
    "    return impostors_matrix, genuines_matrix"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "19fb83be-d8ca-497b-b1c8-1c6c3a0a4e48",
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_group_scores(scores, index_matrix, img_ids):\n",
    "    A = scores[img_ids][:,img_ids]\n",
    "    B = index_matrix[img_ids][:,img_ids]\n",
    "    return A[B == 1]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "a57cc324-b7a2-4dde-8355-e744855e729f",
   "metadata": {},
   "outputs": [],
   "source": [
    "def compute_eer_and_fnmr(genuine_scores, impostor_scores, fmr_targets=[0.01, 0.001]):\n",
    "    y_true = np.concatenate([np.ones_like(genuine_scores), np.zeros_like(impostor_scores)])\n",
    "    y_scores = np.concatenate([genuine_scores, impostor_scores])\n",
    "\n",
    "    fpr, tpr, thresholds = roc_curve(y_true, y_scores)\n",
    "\n",
    "    # Compute EER (where FPR ~= 1 - TPR)\n",
    "    fnr = 1 - tpr\n",
    "    eer_idx = np.nanargmin(np.abs(fpr - fnr))\n",
    "    eer = (fpr[eer_idx] + fnr[eer_idx]) / 2\n",
    "    eer_threshold = thresholds[eer_idx]\n",
    "\n",
    "    print(f\"\\n=== EER: {eer:.4f} at threshold {eer_threshold:.4f} ===\")\n",
    "\n",
    "    # Compute FNMR @ target FMRs\n",
    "    for target in fmr_targets:\n",
    "        idx = np.searchsorted(fpr, target)\n",
    "        if idx >= len(fnr):\n",
    "            print(f\"FMR {target:.4f} is out of range.\")\n",
    "            continue\n",
    "        print(f\"FNMR @ FMR={target:.4f}: {fnr[idx]:.4f} (threshold={thresholds[idx]:.4f})\")\n",
    "\n",
    "    return eer, eer_threshold"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "08132cbb-1d4b-47fc-87cb-c2362532fb38",
   "metadata": {
    "tags": []
   },
   "source": [
    "### ALGORITHM"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "3aaffe49-47ea-4e52-9b6c-a58a7203f094",
   "metadata": {},
   "outputs": [],
   "source": [
    "def inital_clusters(vectors, threshold=0.5):\n",
    "    sims = cosine_similarity(vectors)\n",
    "    np.fill_diagonal(sims, 0)\n",
    "    \n",
    "    locations = np.where(sims > threshold)\n",
    "    \n",
    "    leafs = set(range(len(vectors))) - set(np.unique(locations))\n",
    "    leafs = [[num] for num in list(leafs)]\n",
    "    \n",
    "    G = nx.Graph()\n",
    "    for a, b in zip(locations[0], locations[1]):\n",
    "        G.add_edge(a, b)\n",
    "    \n",
    "    clusters = [list(component) for component in nx.connected_components(G)]\n",
    "    \n",
    "    return clusters + leafs"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "8f6c591d-3b57-41d4-8e73-a80875144341",
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_direction(id_labels, embeddings):\n",
    "    # Map labels to integers and get counts in one pass.\n",
    "    unique_labels, integer_labels, counts = np.unique(id_labels, return_inverse=True, return_counts=True)\n",
    "    # Compute per-sample weights: each sample gets weight = 1 / (number of times its label occurs).\n",
    "    weights = 1 / counts[integer_labels]\n",
    "    # Compute the weighted sum using @\n",
    "    v = weights.astype(np.float32) @ embeddings\n",
    "    # The sum of weights is equal to the number of unique labels, so we normalize by that\n",
    "    v /= unique_labels.size\n",
    "    # Normalize the vector v\n",
    "    return v / np.linalg.norm(v)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "630fa970-ffd3-4699-9899-7a5a0f790e25",
   "metadata": {},
   "outputs": [],
   "source": [
    "def grow(seed, threshold = 0.3):\n",
    "    elements = seed\n",
    "    for _ in range(1000): # 1000 means it did not converge \n",
    "        v = get_direction(ids[elements], embeddings[elements])\n",
    "        \n",
    "        proj = embeddings @ v\n",
    "\n",
    "        for i in np.argsort(proj)[::-1]:\n",
    "            if (i not in elements):\n",
    "                if proj[i] < threshold:\n",
    "                    return elements\n",
    "                elements = elements + [i]\n",
    "                break\n",
    "            \n",
    "    return elements"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e35f47a7-3548-4725-8b8d-5ea857d0151a",
   "metadata": {},
   "source": [
    "### CLUSTER FILTERING"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "aa31a44c-c3e3-4729-9e63-c4af5d6f263d",
   "metadata": {},
   "outputs": [],
   "source": [
    "def biased_clusters(clusters, scores, impostors_matrix, label_id, threshold=0.1):\n",
    "    filtered = []\n",
    "    indices = []\n",
    "    for i, val in enumerate(clusters):\n",
    "        B = impostors_matrix[val][:,val]\n",
    "        if np.any(B != 0) & (np.unique(label_id[val]).shape[0] >= 20):\n",
    "            A = scores[val][:,val]\n",
    "            B = impostors_matrix[val][:,val]\n",
    "            a = np.mean(A[B == 1])\n",
    "            if (a > threshold):\n",
    "                filtered.append(val)\n",
    "                indices.append(i)\n",
    "                # print(f\"{i} CLUSTER AVG {a:.2f} |  {len(A[B == 1])} comparisons\")\n",
    "    return filtered, indices"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6e5223f1-9141-4d4e-b702-2d9ae6694148",
   "metadata": {},
   "source": [
    "### Load Data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "dae6189b-4b9e-481e-9e7a-90631bcab077",
   "metadata": {},
   "outputs": [],
   "source": [
    "model_name = \"partialFC\"\n",
    "# model_name = \"elasticface\"\n",
    "# model_name = \"cosface\"\n",
    "# model_name = \"arcface\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "b4bfacf6-1794-4a87-81f1-5af338cc8db6",
   "metadata": {},
   "outputs": [],
   "source": [
    "embeddings = get_embeddings(model_name)\n",
    "names = get_names()\n",
    "ids = get_ids()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4c9978a3-8eab-4b82-9408-fcb0aef22c0e",
   "metadata": {},
   "source": [
    "### Compute scores"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "40aedec4-d7aa-4a99-a84a-7f7515eeab02",
   "metadata": {},
   "outputs": [],
   "source": [
    "scores = cosine_similarity(embeddings)\n",
    "impostors_matrix, genuines_matrix = get_impostors(ids)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b63d7816-527e-4b35-b27c-b4b461471f65",
   "metadata": {},
   "source": [
    "### Compute initial clusters"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "8dd874a6-67c6-4cd8-9f20-30884d8dbe3d",
   "metadata": {},
   "outputs": [],
   "source": [
    "clusters = inital_clusters(embeddings)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "91e42d43-8ee2-428b-9685-f3c1d20a22b7",
   "metadata": {},
   "source": [
    "# LABELS"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "533fe159-63d9-4d16-9af6-e9a056e312c0",
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "df = pd.read_csv('labels_RFW.csv')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "id": "86d1166e-bf70-4df6-8db1-93ca8283d656",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>File Name</th>\n",
       "      <th>Gender</th>\n",
       "      <th>Age</th>\n",
       "      <th>Skin Color</th>\n",
       "      <th>Ancestry</th>\n",
       "      <th>Hair Color</th>\n",
       "      <th>Bangs</th>\n",
       "      <th>Bald</th>\n",
       "      <th>Beard</th>\n",
       "      <th>Glasses</th>\n",
       "      <th>Headwear</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>African/m.03c31xs/m.03c31xs_0001.jpg</td>\n",
       "      <td>male</td>\n",
       "      <td>young</td>\n",
       "      <td>dark</td>\n",
       "      <td>black</td>\n",
       "      <td>blonde</td>\n",
       "      <td>no</td>\n",
       "      <td>no</td>\n",
       "      <td>stubble</td>\n",
       "      <td>no</td>\n",
       "      <td>no</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>African/m.03c31xs/m.03c31xs_0004.jpg</td>\n",
       "      <td>male</td>\n",
       "      <td>young</td>\n",
       "      <td>dark</td>\n",
       "      <td>black</td>\n",
       "      <td>black</td>\n",
       "      <td>no</td>\n",
       "      <td>no</td>\n",
       "      <td>stubble</td>\n",
       "      <td>no</td>\n",
       "      <td>no</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>African/m.03c31xs/m.03c31xs_0003.jpg</td>\n",
       "      <td>male</td>\n",
       "      <td>young</td>\n",
       "      <td>dark</td>\n",
       "      <td>black</td>\n",
       "      <td>black</td>\n",
       "      <td>no</td>\n",
       "      <td>no</td>\n",
       "      <td>no</td>\n",
       "      <td>no</td>\n",
       "      <td>no</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>African/m.03c31xs/m.03c31xs_0002.jpg</td>\n",
       "      <td>male</td>\n",
       "      <td>young</td>\n",
       "      <td>dark</td>\n",
       "      <td>black</td>\n",
       "      <td>black</td>\n",
       "      <td>no</td>\n",
       "      <td>no</td>\n",
       "      <td>full</td>\n",
       "      <td>no</td>\n",
       "      <td>no</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>African/m.01ng51t/m.01ng51t_0002.jpg</td>\n",
       "      <td>male</td>\n",
       "      <td>senior</td>\n",
       "      <td>dark</td>\n",
       "      <td>black</td>\n",
       "      <td>black</td>\n",
       "      <td>no</td>\n",
       "      <td>no</td>\n",
       "      <td>mustache</td>\n",
       "      <td>no</td>\n",
       "      <td>hat</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>...</th>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>40602</th>\n",
       "      <td>Indian/m.0fgsc6/m.0fgsc6_0002.jpg</td>\n",
       "      <td>male</td>\n",
       "      <td>middle-aged</td>\n",
       "      <td>medium</td>\n",
       "      <td>south_asian</td>\n",
       "      <td>black</td>\n",
       "      <td>no</td>\n",
       "      <td>no</td>\n",
       "      <td>no</td>\n",
       "      <td>no</td>\n",
       "      <td>no</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>40603</th>\n",
       "      <td>Indian/m.0h06p_/m.0h06p__0001.jpg</td>\n",
       "      <td>male</td>\n",
       "      <td>senior</td>\n",
       "      <td>medium</td>\n",
       "      <td>south_asian</td>\n",
       "      <td>gray</td>\n",
       "      <td>no</td>\n",
       "      <td>no</td>\n",
       "      <td>no</td>\n",
       "      <td>no</td>\n",
       "      <td>no</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>40604</th>\n",
       "      <td>Indian/m.0h06p_/m.0h06p__0003.jpg</td>\n",
       "      <td>male</td>\n",
       "      <td>senior</td>\n",
       "      <td>medium</td>\n",
       "      <td>south_asian</td>\n",
       "      <td>gray</td>\n",
       "      <td>no</td>\n",
       "      <td>no</td>\n",
       "      <td>no</td>\n",
       "      <td>no</td>\n",
       "      <td>no</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>40605</th>\n",
       "      <td>Indian/m.0h06p_/m.0h06p__0002.jpg</td>\n",
       "      <td>male</td>\n",
       "      <td>senior</td>\n",
       "      <td>medium</td>\n",
       "      <td>south_asian</td>\n",
       "      <td>gray</td>\n",
       "      <td>no</td>\n",
       "      <td>no</td>\n",
       "      <td>no</td>\n",
       "      <td>no</td>\n",
       "      <td>no</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>40606</th>\n",
       "      <td>Indian/m.0h06p_/m.0h06p__0004.jpg</td>\n",
       "      <td>male</td>\n",
       "      <td>middle-aged</td>\n",
       "      <td>medium</td>\n",
       "      <td>south_asian</td>\n",
       "      <td>gray</td>\n",
       "      <td>no</td>\n",
       "      <td>no</td>\n",
       "      <td>no</td>\n",
       "      <td>no</td>\n",
       "      <td>no</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>40607 rows × 11 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "                                  File Name Gender          Age Skin Color  \\\n",
       "0      African/m.03c31xs/m.03c31xs_0001.jpg   male        young       dark   \n",
       "1      African/m.03c31xs/m.03c31xs_0004.jpg   male        young       dark   \n",
       "2      African/m.03c31xs/m.03c31xs_0003.jpg   male        young       dark   \n",
       "3      African/m.03c31xs/m.03c31xs_0002.jpg   male        young       dark   \n",
       "4      African/m.01ng51t/m.01ng51t_0002.jpg   male       senior       dark   \n",
       "...                                     ...    ...          ...        ...   \n",
       "40602     Indian/m.0fgsc6/m.0fgsc6_0002.jpg   male  middle-aged     medium   \n",
       "40603     Indian/m.0h06p_/m.0h06p__0001.jpg   male       senior     medium   \n",
       "40604     Indian/m.0h06p_/m.0h06p__0003.jpg   male       senior     medium   \n",
       "40605     Indian/m.0h06p_/m.0h06p__0002.jpg   male       senior     medium   \n",
       "40606     Indian/m.0h06p_/m.0h06p__0004.jpg   male  middle-aged     medium   \n",
       "\n",
       "          Ancestry Hair Color Bangs Bald     Beard Glasses Headwear  \n",
       "0            black     blonde    no   no   stubble      no       no  \n",
       "1            black      black    no   no   stubble      no       no  \n",
       "2            black      black    no   no        no      no       no  \n",
       "3            black      black    no   no      full      no       no  \n",
       "4            black      black    no   no  mustache      no      hat  \n",
       "...            ...        ...   ...  ...       ...     ...      ...  \n",
       "40602  south_asian      black    no   no        no      no       no  \n",
       "40603  south_asian       gray    no   no        no      no       no  \n",
       "40604  south_asian       gray    no   no        no      no       no  \n",
       "40605  south_asian       gray    no   no        no      no       no  \n",
       "40606  south_asian       gray    no   no        no      no       no  \n",
       "\n",
       "[40607 rows x 11 columns]"
      ]
     },
     "execution_count": 30,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "a62b8e14-399e-4b9e-9ef1-c444d6edf1fc",
   "metadata": {},
   "outputs": [],
   "source": [
    "names = np.array([path.replace('RFW/data/', '') for path in names])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "a9c6b6e8-4141-4ae3-b26a-802c3b71a00b",
   "metadata": {},
   "outputs": [],
   "source": [
    "from scipy.spatial.distance import hamming"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "id": "939ca1eb-ce71-44c1-8bbe-3769fcb999cb",
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.preprocessing import OneHotEncoder"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "id": "3eaa9148-0f3c-4fb0-87fa-28aece4ac9dc",
   "metadata": {},
   "outputs": [],
   "source": [
    "attributes = [\n",
    "    \"Gender\",\"Age\",\"Skin Color\",\"Ancestry\",\"Hair Color\",\n",
    "    \"Bangs\",\"Bald\",\"Beard\",\"Glasses\",\"Headwear\"\n",
    "]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "id": "afe49a03-3bb0-43ae-b9a0-ecb28d707c94",
   "metadata": {},
   "outputs": [],
   "source": [
    "encoder = OneHotEncoder(sparse=False, handle_unknown=\"ignore\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "id": "5675f055-bf4a-4d36-b85f-b9b7a23f7be7",
   "metadata": {},
   "outputs": [],
   "source": [
    "file_set = set(names)\n",
    "\n",
    "# Filter the DataFrame\n",
    "df = df[df['File Name'].isin(file_set)]\n",
    "df = df.reset_index(drop=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "id": "72c20c27-06d6-429d-ae79-59ef86c3a5c5",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/mpcdf/soft/SLE_15/packages/x86_64/anaconda/3/2023.03/lib/python3.10/site-packages/sklearn/preprocessing/_encoders.py:828: FutureWarning: `sparse` was renamed to `sparse_output` in version 1.2 and will be removed in 1.4. `sparse_output` is ignored unless you leave `sparse` to its default value.\n",
      "  warnings.warn(\n"
     ]
    }
   ],
   "source": [
    "X_encoded = encoder.fit_transform(df[attributes])  # shape: (n_samples, n_features_encoded)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "id": "0004efee-9123-47b6-89b4-09b447eac502",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(40602, 50)"
      ]
     },
     "execution_count": 38,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "X_encoded.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3399e656-23eb-4b7b-8571-16dfe2605fbc",
   "metadata": {},
   "source": [
    "# EVALUATION"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "id": "86f98a84-2a75-4ffd-9dff-028b6f38df96",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<style>#sk-container-id-2 {color: black;background-color: white;}#sk-container-id-2 pre{padding: 0;}#sk-container-id-2 div.sk-toggleable {background-color: white;}#sk-container-id-2 label.sk-toggleable__label {cursor: pointer;display: block;width: 100%;margin-bottom: 0;padding: 0.3em;box-sizing: border-box;text-align: center;}#sk-container-id-2 label.sk-toggleable__label-arrow:before {content: \"▸\";float: left;margin-right: 0.25em;color: #696969;}#sk-container-id-2 label.sk-toggleable__label-arrow:hover:before {color: black;}#sk-container-id-2 div.sk-estimator:hover label.sk-toggleable__label-arrow:before {color: black;}#sk-container-id-2 div.sk-toggleable__content {max-height: 0;max-width: 0;overflow: hidden;text-align: left;background-color: #f0f8ff;}#sk-container-id-2 div.sk-toggleable__content pre {margin: 0.2em;color: black;border-radius: 0.25em;background-color: #f0f8ff;}#sk-container-id-2 input.sk-toggleable__control:checked~div.sk-toggleable__content {max-height: 200px;max-width: 100%;overflow: auto;}#sk-container-id-2 input.sk-toggleable__control:checked~label.sk-toggleable__label-arrow:before {content: \"▾\";}#sk-container-id-2 div.sk-estimator input.sk-toggleable__control:checked~label.sk-toggleable__label {background-color: #d4ebff;}#sk-container-id-2 div.sk-label input.sk-toggleable__control:checked~label.sk-toggleable__label {background-color: #d4ebff;}#sk-container-id-2 input.sk-hidden--visually {border: 0;clip: rect(1px 1px 1px 1px);clip: rect(1px, 1px, 1px, 1px);height: 1px;margin: -1px;overflow: hidden;padding: 0;position: absolute;width: 1px;}#sk-container-id-2 div.sk-estimator {font-family: monospace;background-color: #f0f8ff;border: 1px dotted black;border-radius: 0.25em;box-sizing: border-box;margin-bottom: 0.5em;}#sk-container-id-2 div.sk-estimator:hover {background-color: #d4ebff;}#sk-container-id-2 div.sk-parallel-item::after {content: \"\";width: 100%;border-bottom: 1px solid gray;flex-grow: 1;}#sk-container-id-2 div.sk-label:hover label.sk-toggleable__label {background-color: #d4ebff;}#sk-container-id-2 div.sk-serial::before {content: \"\";position: absolute;border-left: 1px solid gray;box-sizing: border-box;top: 0;bottom: 0;left: 50%;z-index: 0;}#sk-container-id-2 div.sk-serial {display: flex;flex-direction: column;align-items: center;background-color: white;padding-right: 0.2em;padding-left: 0.2em;position: relative;}#sk-container-id-2 div.sk-item {position: relative;z-index: 1;}#sk-container-id-2 div.sk-parallel {display: flex;align-items: stretch;justify-content: center;background-color: white;position: relative;}#sk-container-id-2 div.sk-item::before, #sk-container-id-2 div.sk-parallel-item::before {content: \"\";position: absolute;border-left: 1px solid gray;box-sizing: border-box;top: 0;bottom: 0;left: 50%;z-index: -1;}#sk-container-id-2 div.sk-parallel-item {display: flex;flex-direction: column;z-index: 1;position: relative;background-color: white;}#sk-container-id-2 div.sk-parallel-item:first-child::after {align-self: flex-end;width: 50%;}#sk-container-id-2 div.sk-parallel-item:last-child::after {align-self: flex-start;width: 50%;}#sk-container-id-2 div.sk-parallel-item:only-child::after {width: 0;}#sk-container-id-2 div.sk-dashed-wrapped {border: 1px dashed gray;margin: 0 0.4em 0.5em 0.4em;box-sizing: border-box;padding-bottom: 0.4em;background-color: white;}#sk-container-id-2 div.sk-label label {font-family: monospace;font-weight: bold;display: inline-block;line-height: 1.2em;}#sk-container-id-2 div.sk-label-container {text-align: center;}#sk-container-id-2 div.sk-container {/* jupyter's `normalize.less` sets `[hidden] { display: none; }` but bootstrap.min.css set `[hidden] { display: none !important; }` so we also need the `!important` here to be able to override the default hidden behavior on the sphinx rendered scikit-learn.org. See: https://github.com/scikit-learn/scikit-learn/issues/21755 */display: inline-block !important;position: relative;}#sk-container-id-2 div.sk-text-repr-fallback {display: none;}</style><div id=\"sk-container-id-2\" class=\"sk-top-container\"><div class=\"sk-text-repr-fallback\"><pre>NearestNeighbors(n_neighbors=100)</pre><b>In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. <br />On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.</b></div><div class=\"sk-container\" hidden><div class=\"sk-item\"><div class=\"sk-estimator sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"sk-estimator-id-2\" type=\"checkbox\" checked><label for=\"sk-estimator-id-2\" class=\"sk-toggleable__label sk-toggleable__label-arrow\">NearestNeighbors</label><div class=\"sk-toggleable__content\"><pre>NearestNeighbors(n_neighbors=100)</pre></div></div></div></div></div>"
      ],
      "text/plain": [
       "NearestNeighbors(n_neighbors=100)"
      ]
     },
     "execution_count": 39,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from sklearn.neighbors import NearestNeighbors\n",
    "# Fit the nearest neighbors model to your embeddings\n",
    "neigh = NearestNeighbors(n_neighbors=100)\n",
    "neigh.fit(embeddings)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "92a4b48b-8320-4f4c-b0a4-7fdd70727ac4",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.35 1.3296099290780141 0.19988679457971142 0.9219858156028369 0.0 48.0\n",
      "0.3 2.3551756013660796 0.6357207753469776 1.6026932463455281 0.5319644716046119 95.38068844807468\n"
     ]
    }
   ],
   "source": [
    "groups = clusters.copy()\n",
    "for threshold in [0.4, 0.35, 0.3, 0.25, 0.2]:\n",
    "    groups = Parallel(n_jobs=-1, backend=\"loky\")(\n",
    "            delayed(grow)(g, threshold)\n",
    "            for g in groups\n",
    "        )\n",
    "    biased_groups, indices = biased_clusters(groups, scores, impostors_matrix, ids, threshold=0.1)\n",
    "    \n",
    "    aggr = []\n",
    "    ksearch = []\n",
    "    avg_size = []\n",
    "    for i, cl in enumerate(biased_groups):\n",
    "        aggr.append(np.mean(manhattan_distances(X_encoded[cl])[np.triu_indices(len(X_encoded[cl]), k = 1)])/2)\n",
    "        distances, indices = neigh.kneighbors([embeddings[cl[0]]], n_neighbors=len(cl))\n",
    "        ksearch.append(np.mean(manhattan_distances(X_encoded[indices[0]])[np.triu_indices(len(cl), k = 1)])/2)\n",
    "        avg_size.append(len(X_encoded[cl]))\n",
    "    print(threshold, np.mean(ksearch), np.std(ksearch), np.mean(aggr), np.std(aggr), np.mean(avg_size))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "fc83729f-f28a-4073-bc3c-5b125f4c85a4",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 42360 characters]\n",
      "text/plain": [
       "<Figure size 1200x400 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "import seaborn as sns\n",
    "import pandas as pd\n",
    "import numpy as np\n",
    "\n",
    "# Set the Seaborn style\n",
    "sns.set(style=\"darkgrid\")\n",
    "sns.set_style(\"darkgrid\", {\"axes.facecolor\": \".93\"})\n",
    "\n",
    "\n",
    "# Example data\n",
    "data = {\n",
    "    'Dataset': ['Database: RFW'] * 8 + ['Database: CelebA'] * 8,\n",
    "    'Algorithm': ['ArcFace', 'ArcFace', 'CosFace', 'CosFace', 'ElasticFace', 'ElasticFace', 'PartialFC', 'PartialFC'] * 2,\n",
    "    'Method': ['Baseline', 'Ours'] * 4 * 2,\n",
    "    'Mean': [\n",
    "        2.99, 2.11, 2.82, 1.84, 2.97, 2.10, 2.41, 1.76,\n",
    "        8.14, 7.19, 7.97, 6.88, 7.63, 6.89, 7.87, 6.96\n",
    "    ],\n",
    "    'Std': [\n",
    "        0.61, 0.66, 0.62, 0.63, 0.63, 0.66, 0.68, 0.60,\n",
    "        1.27, 1.31, 1.37, 1.34, 1.47, 1.31, 1.39, 1.33\n",
    "    ]\n",
    "}\n",
    "\n",
    "df = pd.DataFrame(data)\n",
    "\n",
    "# Pivot the data for easier plotting\n",
    "pivot_mean = df.pivot(index=['Dataset', 'Algorithm'], columns='Method', values='Mean').reset_index()\n",
    "pivot_std = df.pivot(index=['Dataset', 'Algorithm'], columns='Method', values='Std').reset_index()\n",
    "\n",
    "# Plotting parameters\n",
    "methods = ['Baseline', 'Ours']\n",
    "algorithms = ['ArcFace', 'CosFace', 'ElasticFace', 'PartialFC']\n",
    "datasets = ['Database: RFW', 'Database: CelebA']\n",
    "bar_width = 0.3\n",
    "x = np.arange(len(algorithms))\n",
    "with plt.rc_context(rc={\"font.family\": \"DejaVu Sans Mono\"}):\n",
    "    # your plotting code here\n",
    "\n",
    "    # Set up the figure\n",
    "    fig, axes = plt.subplots(1, 2, figsize=(12, 4), sharey=True)\n",
    "\n",
    "    for i, dataset in enumerate(datasets):\n",
    "        ax = axes[i]\n",
    "        \n",
    "        mean_subset = pivot_mean[pivot_mean['Dataset'] == dataset]\n",
    "        std_subset = pivot_std[pivot_std['Dataset'] == dataset]\n",
    "\n",
    "        baseline_scores = mean_subset['Baseline'].values\n",
    "        ours_scores = mean_subset['Ours'].values\n",
    "\n",
    "        baseline_err = std_subset['Baseline'].values\n",
    "        ours_err = std_subset['Ours'].values\n",
    "\n",
    "        # Plot bars\n",
    "        ax.bar(x - bar_width/2, baseline_scores, yerr=baseline_err, width=bar_width, label='Baseline', color='skyblue', capsize=3,)\n",
    "        ax.bar(x + bar_width/2, ours_scores, yerr=ours_err, width=bar_width, label='LFA (Ours)', color='mediumseagreen', capsize=3)\n",
    "\n",
    "        # Annotate above the top of the error bars\n",
    "        offset = .1  # vertical space above the error bar\n",
    "        x_offset = .11  # vertical space above the error bar\n",
    "        \n",
    "        \n",
    "        # Annotate\n",
    "        for j in range(len(x)):\n",
    "            # ax.text(x[j] - bar_width/2, baseline_scores[j] + baseline_err[j] + offset, f'{baseline_scores[j]:.1f}', ha='center', fontsize=9)\n",
    "            # ax.text(x[j] + bar_width/2, ours_scores[j] + ours_err[j] + offset, f'{ours_scores[j]:.1f}', ha='center', fontsize=9)\n",
    "            \n",
    "            ax.text(x[j] - bar_width/2 - x_offset, baseline_scores[j] + offset, f'{baseline_scores[j]:.1f}', ha='center', fontsize=9)\n",
    "            ax.text(x[j] + bar_width/2 + x_offset, ours_scores[j] + offset, f'{ours_scores[j]:.1f}', ha='center', fontsize=9)\n",
    "\n",
    "        ax.set_title(dataset)\n",
    "        ax.set_xticks(x)\n",
    "        ax.set_xticklabels(algorithms)\n",
    "        ax.set_xlabel(\"Face Rec. Model\")\n",
    "        if i == 0:\n",
    "            ax.set_ylabel(\"Avg Intra-Group Distance\")\n",
    "        # ax.set_ylim(0, 1)\n",
    "\n",
    "    for ax in axes:\n",
    "        ax.xaxis.grid(False)  # Just remove horizontal lines\n",
    "\n",
    "\n",
    "    # Legend only on one plot\n",
    "    axes[1].legend(loc='lower right')\n",
    "\n",
    "    plt.tight_layout()\n",
    "    plt.show()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "032a5063-ed18-40a8-8001-c1f9ca528e00",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
