File size: 16,367 Bytes
76285c7 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 | """Build the defence deck as .pptx from the same content as the Beamer source.
Beamer is the primary deliverable; this exists so the deck can be handed to
someone who needs to edit it in PowerPoint. Numbers come from
slides/numbers.json (generated), figures from slides/figures/ (generated), so
the two decks cannot disagree with each other or with the thesis.
"""
from __future__ import annotations
import json
from pathlib import Path
from pptx import Presentation
from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN
from pptx.util import Emu, Inches, Pt
ROOT = Path(__file__).resolve().parents[1]
SLIDES = ROOT / 'slides'
FIGS = SLIDES / 'figures'
INK = RGBColor(0x0B, 0x0B, 0x0B)
INK2 = RGBColor(0x52, 0x51, 0x4E)
MUTED = RGBColor(0x89, 0x87, 0x81)
MINORITY = RGBColor(0x2A, 0x78, 0xD6)
SURFACE = RGBColor(0xFC, 0xFC, 0xFB)
N = json.loads((SLIDES / 'numbers.json').read_text())
W, H = Inches(13.333), Inches(7.5)
def background(slide):
fill = slide.background.fill
fill.solid()
fill.fore_color.rgb = SURFACE
def textbox(slide, left, top, width, height, text, size=18, color=INK,
bold=False, align=PP_ALIGN.LEFT, spacing=1.15):
box = slide.shapes.add_textbox(left, top, width, height)
frame = box.text_frame
frame.word_wrap = True
for i, line in enumerate(text.split('\n')):
para = frame.paragraphs[0] if i == 0 else frame.add_paragraph()
para.alignment = align
para.line_spacing = spacing
run = para.add_run()
run.text = line
run.font.size = Pt(size)
run.font.color.rgb = color
run.font.bold = bold
run.font.name = 'Calibri'
return box
def blank(prs):
slide = prs.slides.add_slide(prs.slide_layouts[6])
background(slide)
return slide
def titled(prs, title):
slide = blank(prs)
textbox(slide, Inches(0.7), Inches(0.4), Inches(12.0), Inches(0.9),
title, size=30, color=INK, bold=True)
return slide
def picture(slide, name, top=Inches(1.45), height=Inches(4.6)):
path = FIGS / f'{name}.png'
pic = slide.shapes.add_picture(str(path), Inches(0), top, height=height)
pic.left = Emu(int((W - pic.width) / 2))
return pic
def takeaway(slide, text, size=16):
textbox(slide, Inches(0.7), Inches(6.25), Inches(12.0), Inches(1.0),
text, size=size, color=INK2)
def bullets(slide, items, top=Inches(1.6), size=20, gap=0.78):
for i, item in enumerate(items):
textbox(slide, Inches(1.0), top + Inches(gap * i), Inches(11.4),
Inches(0.9), f'• {item}', size=size, color=INK)
def build():
prs = Presentation()
prs.slide_width, prs.slide_height = W, H
s = blank(prs)
textbox(s, Inches(1.0), Inches(2.5), Inches(11.3), Inches(2.0),
'Majority Accumulation and Minority False Matches\nin Locally Adapted Face Verification',
size=34, color=INK, bold=True, align=PP_ALIGN.CENTER)
textbox(s, Inches(1.0), Inches(4.6), Inches(11.3), Inches(0.6),
'MSc thesis defence', size=18, color=MUTED, align=PP_ALIGN.CENTER)
s = titled(prs, 'Institutions adapt recognisers. They do not train them.')
bullets(s, ['Take a model pretrained on a very large public corpus.',
'Fine-tune it on the identities you have already enrolled.',
'Cheap, needs no new data, and reliably improves measured accuracy.'])
textbox(s, Inches(0.7), Inches(4.5), Inches(12.0), Inches(1.4),
f"But you do not choose who is in that set. In our corpus, "
f"{N['pctMale']}% of {N['Nidentities']} enrolled identities are male — "
f"not by decision, but because the cohort comes from engineering programmes.",
size=19, color=INK)
takeaway(s, 'The practitioner faces a fork that does not look like a fork: '
'use the data as it arrives, or curate it.')
s = titled(prs, 'The question')
textbox(s, Inches(1.0), Inches(2.1), Inches(11.3), Inches(1.4),
'What does the natural choice cost,\nand who pays for it?',
size=32, color=MINORITY, bold=True, align=PP_ALIGN.CENTER)
bullets(s, ['RQ1 How does adaptation-set composition affect subgroup performance?',
'RQ2 Do protocol choices change what we observe?',
'RQ3 What in the representation explains it?'], top=Inches(4.3), size=18, gap=0.62)
s = titled(prs, 'Seven models. The metric everybody reports.')
picture(s, 's_hook_genuine')
takeaway(s, 'Flat. Overall accuracy spans 0.025 across every condition.')
s = titled(prs, 'The same seven models. The metric almost nobody reports.')
picture(s, 's_hook_impostor')
takeaway(s, f"The rate at which two different minority users are confused spans "
f"{N['FPRFFspan']}×, from {N['FPRFFbest']} to {N['FPRFFworst']}.")
s = titled(prs, 'Three things worth noticing')
bullets(s, ['The two impostor rates move in opposite directions. '
'A merely worse model would degrade both.',
'The harm is entirely on the impostor side — minority users are not locked out, '
'they are let into each other’s accounts.',
f"The minority true-accept rate is highest in the worst condition ({N['TPRFworst']})."],
size=19, gap=1.05)
takeaway(s, 'An auditor checking whether minority users are wrongly rejected '
'would see nothing wrong at all.')
s = titled(prs, 'Why prior work cannot answer this')
bullets(s, ['The female share falls from 50% to 22%, and',
'the female count falls from 87 to 39 — at the same time.'])
textbox(s, Inches(0.7), Inches(3.5), Inches(12.0), Inches(1.6),
'Those imply opposite remedies: rebalance what you have, or go and collect more '
'minority data. Varying a ratio at fixed total size cannot separate them.',
size=19, color=INK)
takeaway(s, 'So we vary the two counts independently.')
s = titled(prs, 'The factorial design')
pic = s.shapes.add_picture(str(FIGS / 's_design.png'), Inches(0.6), Inches(1.5), height=Inches(4.9))
textbox(s, Inches(7.6), Inches(1.8), Inches(5.2), Inches(4.0),
'Four cells cross minority count with majority count.\n\n'
'Across a row: adding majority identities.\n\n'
'Down a column: adding minority identities.\n\n'
'Fixed throughout: optimiser budget, identity splits, comparison manifests, '
'and the pretrained checkpoint.', size=17, color=INK)
s = titled(prs, 'Protocol discipline')
bullets(s, [f"{N['Nidentities']} audited identities ({N['Nmale']} male, {N['Nfemale']} female), "
f"{N['Nimages']} images.",
'Four disjoint roles per fold: fit, select, operate (calibration only), test (reporting only).',
'No calibration or test identity ever receives a gradient update.',
'Thresholds fitted on operate, applied unchanged to test.'], size=18, gap=0.72)
takeaway(s, 'The central quantities are differences of a few thousandths. '
'Minor leakage would not support them.')
s = titled(prs, 'Both levers are real')
picture(s, 's_forest')
takeaway(s, f"Per identity added, the minority lever is {N['leverRatio']}× stronger "
f"than the majority one.")
s = titled(prs, 'And they interact')
s.shapes.add_picture(str(FIGS / 's_interaction.png'), Inches(0.6), Inches(1.5), height=Inches(4.9))
textbox(s, Inches(7.6), Inches(2.0), Inches(5.2), Inches(3.6),
'Majority accumulation is 2.2× more harmful when the minority is scarce.\n\n'
'Minority collection is 2.4× more beneficial when the majority is plentiful.\n\n'
'Each remedy is worth most exactly where the other is worth least.',
size=17, color=INK)
s = titled(prs, 'A result we had to withdraw')
picture(s, 's_seeds')
takeaway(s, f"At three seeds the accuracy gain read +0.0124 [+0.0051, +0.0187] — an interval "
f"excluding zero. At eight seeds it is {N['seedTPR']} (t = {N['seedTPRt']}). "
f"The minority harm survives at {N['seedFPR']} (t = {N['seedFPRt']}).", size=15)
s = titled(prs, 'What that changed')
textbox(s, Inches(0.7), Inches(1.7), Inches(12.0), Inches(1.6),
'The earlier interval was honest but conditional: it described variation over '
'comparisons, not over retraining. Adding the omitted variance component decided '
'the contrast.', size=20, color=INK)
textbox(s, Inches(1.0), Inches(3.7), Inches(11.3), Inches(1.2),
'There is no reliable accuracy to gain.\nOnly minority security to lose.',
size=28, color=MINORITY, bold=True, align=PP_ALIGN.CENTER)
takeaway(s, 'Skewed adaptation is also less predictable: 4.5× less stable in minority '
'false matches across seeds.')
s = titled(prs, 'Why: the embedding space')
picture(s, 's_geometry')
takeaway(s, f"Crowding between identity centroids predicts the error (r = {N['rCrowding']}). "
f"Compactness within clusters does not (r = {N['rCompactness']}) — and a false "
f"match is an event between two different identities, so it could not.", size=15)
s = titled(prs, 'Why: the threshold follows the majority')
picture(s, 's_threshold', height=Inches(4.5))
takeaway(s, f"Female–female pairs are only {N['calibFFshare']}% of the calibration pool, so the "
f"threshold tracks the male distribution and leaves the minority tail above it.", size=15)
s = titled(prs, 'Can threshold policy fix it?')
picture(s, 's_policy')
takeaway(s, 'Correction moves every skewed model left and down. Balanced adaptation under a '
'plain global threshold beats all of them on both axes at once.', size=15)
s = titled(prs, 'Two things the policy analysis reveals')
bullets(s, [f"Unconstrained per-subgroup thresholds create a second harm: {N['mixedHarm']} "
f"mixed-gender false matches. A tighten-only constraint gets identical gap closure "
f"with a cross-group effect of exactly +0.0000.",
f"The required offset is not a model constant: {N['offsetBal']} under balanced "
f"adaptation (t = {N['offsetBalT']}, indistinguishable from zero) rising to "
f"{N['offsetNat']} under the natural composition."], size=18, gap=1.5)
takeaway(s, 'An operator who measures an offset once and reuses it after retraining holds one '
'of the wrong magnitude, possibly the wrong sign.')
s = titled(prs, 'Who pays for equalisation')
rows = [('Aggregate true-accept cost', N['policyAggCost']),
('Cost to the protected group', N['policyFemCost']),
('Understatement factor', f"{N['policyRatio']}×")]
for i, (label, value) in enumerate(rows):
top = Inches(1.9 + 0.75 * i)
bold = i == 1
textbox(s, Inches(2.2), top, Inches(6.4), Inches(0.6), label, size=21,
color=INK if not bold else MINORITY, bold=bold)
textbox(s, Inches(8.6), top, Inches(2.4), Inches(0.6), value, size=21,
color=INK if not bold else MINORITY, bold=bold, align=PP_ALIGN.RIGHT)
textbox(s, Inches(0.7), Inches(4.6), Inches(12.0), Inches(1.2),
'The protected group carries only 22% of the population weight, so the aggregate '
'hides most of what the intervention charges them.', size=19, color=INK)
takeaway(s, 'That may be the right trade — a false match is unauthorised access, a false '
'rejection an inconvenience. But it is a trade, and it should be stated as one.')
s = titled(prs, 'The comparison that should govern practice')
hdr = [('', 'Minority TPR', 'Gap'),
('Balanced adaptation, no intervention', N['balancedTPRF'], N['balancedGap']),
('Skewed adaptation, best intervention', N['skewedTPRF'], N['skewedGap'])]
for i, (a, b, c) in enumerate(hdr):
top = Inches(1.8 + 0.7 * i)
bold = i == 1
col = MINORITY if bold else (MUTED if i == 0 else INK)
textbox(s, Inches(1.4), top, Inches(7.0), Inches(0.6), a, size=19, color=col, bold=bold)
textbox(s, Inches(8.4), top, Inches(2.0), Inches(0.6), b, size=19, color=col,
bold=bold, align=PP_ALIGN.RIGHT)
textbox(s, Inches(10.5), top, Inches(1.8), Inches(0.6), c, size=19, color=col,
bold=bold, align=PP_ALIGN.RIGHT)
textbox(s, Inches(0.7), Inches(4.6), Inches(12.0), Inches(1.2),
f"Minority users are {N['pointsBetter']} points better off under balanced adaptation "
f"with no fairness intervention.", size=23, color=MINORITY, bold=True,
align=PP_ALIGN.CENTER)
takeaway(s, 'Threshold policy is a mitigation for a model you are stuck with. It is not a '
'substitute for the composition decision.')
s = titled(prs, 'Recommendations')
bullets(s, ['Do not add majority identities just because you have them. '
'The accuracy benefit is not reliable; the minority cost is.',
'Treat curation as dominating threshold correction.',
'If you use per-subgroup thresholds, constrain them to tighten-only.',
'Report within-group impostor rates — nothing else reveals this harm.'],
size=19, gap=1.0)
s = titled(prs, 'Limitations')
bullets(s, ['One checkpoint, one architecture, one recipe, one institution.',
f"{N['Nfemale']} female identities bound the precision of every minority estimate.",
'Session and modality labels come from filenames; manual validation outstanding.',
'Binary recorded gender labels — nothing establishes a causal effect of gender.'],
size=19, gap=0.95)
s = titled(prs, 'Conclusion')
textbox(s, Inches(0.9), Inches(1.7), Inches(11.5), Inches(1.2),
'Using the data as it arrives costs nothing measurable\non the metrics these systems '
'are judged by.', size=26, color=INK, bold=True, align=PP_ALIGN.CENTER)
textbox(s, Inches(0.9), Inches(3.5), Inches(11.5), Inches(1.6),
'Underneath them, minority users are confused with one another 2.5 to 2.7× more '
'often — through a mechanism we can measure in the embedding space, predict from '
'calibration data, and trace to a representational change meeting a calibration '
'convention.', size=19, color=INK, align=PP_ALIGN.CENTER)
textbox(s, Inches(0.9), Inches(5.5), Inches(11.5), Inches(0.8),
'The unfairness is purchased for nothing.', size=26, color=MINORITY, bold=True,
align=PP_ALIGN.CENTER)
s = blank(prs)
textbox(s, Inches(0.9), Inches(3.2), Inches(11.5), Inches(0.9), 'Backup slides',
size=32, color=MUTED, bold=True, align=PP_ALIGN.CENTER)
s = titled(prs, 'Backup: adaptation specialises to capture modality')
picture(s, 's_protocol')
takeaway(s, f"Roughly two thirds ({N['leakLo']}–{N['leakHi']}% by fold) of genuine test "
f"comparisons come from within one capture session. Adaptation helps within a "
f"capture mode and hurts across one.", size=15)
s = titled(prs, 'Backup: the offset across conditions')
picture(s, 's_offset')
s = titled(prs, f"Backup: is r = {N['rCrowding']} pseudo-replicated?")
bullets(s, ['Partly, and we say so.',
f"{N['Nruns']} runs, but only four conditions.",
f"Between conditions: r = {N['rBetween']} (4 points).",
'Pooled within condition: r = +0.268, not significant.',
'Leave-one-condition-out MAE: 0.0066 for crowding against 0.0105 for a '
'two-moment score summary.'], size=18, gap=0.8)
takeaway(s, 'Claimed as a composition-level diagnostic — not as a run-level predictor '
'at fixed composition.')
out = SLIDES / 'defense.pptx'
prs.save(str(out))
print(f'wrote {out} ({len(prs.slides.__iter__.__self__._sldIdLst)} slides)')
if __name__ == '__main__':
build()
|