master-thesis / tools /ext_parser_acceptance.py
MinhNH232331M's picture
Code, references, slides, pretrained backbone
76285c7 verified
Raw History Blame Contribute Delete
6.37 kB
"""Tier 2: acceptance check for the filename-derived session/modality parser.
Produces three things:
1. A stratified sample of >=200 images with every parse field, written for
human review. Human sign-off is NOT performed here and remains outstanding.
2. An independent-reimplementation agreement rate. A second parser written
with different logic (separator splitting, not the production regexes) must
agree on session identity.
3. A per-image behavioural check: is an image's nearest same-identity
neighbour in embedding space also in its parsed session? Real capture
sessions should dominate the nearest-neighbour relation; this gives a
falsifiable error rate rather than an assertion.
"""
from __future__ import annotations
import json, re, sys
from pathlib import Path
import numpy as np, pandas as pd
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT)); sys.path.insert(0, str(ROOT / 'tools'))
from ext_sessions import COPY_PREFIX, load as load_sessions
from face_research.prepare import load_prepared
OUT = ROOT / 'artifacts' / 'extensions' / 'parser_acceptance_v1'
A = ROOT / 'artifacts' / 'thesis_study_v1'
PER_STRATUM = 40
def independent_session(filename: str, collection: str) -> str:
"""Deliberately different implementation: split on separators, no shared regex."""
name = COPY_PREFIX.sub('', filename)
stem = name.rsplit('.', 1)[0]
if collection == 'HUST_Face_data':
if '_frame' in stem:
return 'clip' + stem.split('_frame')[0]
parts = stem.split('_')
if parts[0] == 'IMG' and len(parts) >= 3 and len(parts[1]) == 8 and parts[1].isdigit():
return 'ts' + parts[1] + parts[2][:4]
if len(parts) >= 2 and len(parts[0]) == 8 and parts[0].isdigit() and parts[1].isdigit():
return 'ts' + parts[0] + parts[1][:4]
if parts[0] == 'IMG' and len(parts) == 2 and parts[1].isdigit():
return 'img' + parts[1][:4]
return 'unparsed'
if collection == 'ProcessedData2324':
parts = stem.split('_')
return '_'.join(parts[:2]) if len(parts) >= 3 and parts[0] == 'IMG' else 'unparsed'
if collection == 'ProcessedData20241':
parts = stem.split('-')
return '-'.join(parts[:5]) if len(parts) >= 6 else 'unparsed'
if collection == 'ProcessedData20242':
parts = stem.rsplit('_', 1)
return parts[0] if len(parts) == 2 and parts[1].isdigit() else 'unparsed'
return 'unparsed'
def main():
OUT.mkdir(parents=True, exist_ok=True)
df = load_sessions(ROOT / 'artifacts' / 'data_audit_v1')
df['bare_session'] = df.session.str.split('|').str[1:].str.join('|')
# (2) agreement of an independently written parser, on the FULL inventory
indep = [independent_session(f, c) for f, c in zip(df.filename, df.collection)]
prod = [s if r != 'none' else 'unparsed' for s, r in
zip(df.bare_session.str.replace(r'\|.*$', '', regex=True), df.session_rule)]
agree = np.mean([a == b for a, b in zip(indep, prod)])
per_coll = {}
for coll in sorted(df.collection.unique()):
m = df.collection == coll
per_coll[coll] = float(np.mean([a == b for a, b, k in zip(indep, prod, m) if k]))
print(f"independent-reimplementation agreement: {agree:.4f} overall")
for k, v in per_coll.items():
print(f" {k:<20} {v:.4f}")
# (1) stratified reviewable sample
rng = np.random.default_rng(20260908)
parts = []
for (coll, rule), g in df.groupby(['collection', 'session_rule']):
take = min(PER_STRATUM, len(g))
parts.append(g.iloc[rng.choice(len(g), take, replace=False)])
sample = pd.concat(parts)[['path', 'filename', 'collection', 'session_rule',
'modality', 'bare_session', 'person_id']]
sample['independent_session'] = [independent_session(f, c) for f, c in
zip(sample.filename, sample.collection)]
sample.to_csv(OUT / 'review_sample.csv', index=False)
print(f"\nstratified review sample: {len(sample)} images across "
f"{sample.groupby(['collection','session_rule']).ngroups} strata -> review_sample.csv")
# (3) behavioural check on the sample, using untouched M0 embeddings
prepared = load_prepared(A)
emb_by_id, order = {}, {}
for fold in range(3):
meta = json.loads((A / 'runs' / f'fold_{fold}' / 'M0' / 'seed_42' / 'scores_test.json').read_text())
cache = A / 'cache' / 'embeddings' / meta['cache_key']
e = np.load(cache / 'embeddings.npy', allow_pickle=False)
ids = pd.read_csv(cache / 'images.csv', dtype=str).image_id.tolist()
for i, k in enumerate(ids):
emb_by_id[k] = e[i]
sess_of = df.set_index('image_id').session.to_dict()
person_of = df.set_index('image_id').person_id.to_dict()
by_person = {}
for k in emb_by_id:
by_person.setdefault(person_of[k], []).append(k)
hits = tot = 0
rows = []
for k in df[df.image_id.isin(emb_by_id)].image_id:
peers = [q for q in by_person[person_of[k]] if q != k]
if len(peers) < 2 or 'unparsed' in sess_of[k]:
continue
same = [q for q in peers if sess_of[q] == sess_of[k]]
if not same or len(same) == len(peers):
continue
v = emb_by_id[k]
sims = np.array([float(v @ emb_by_id[q]) for q in peers])
nn = peers[int(np.argmax(sims))]
ok = sess_of[nn] == sess_of[k]
hits += ok; tot += 1
rows.append({'image_id': k, 'nearest_in_same_session': bool(ok)})
print(f"\nbehavioural check: nearest same-identity neighbour lies in the parsed session "
f"for {hits}/{tot} images ({100*hits/tot:.1f}%)")
pd.DataFrame(rows).to_csv(OUT / 'nearest_neighbour_check.csv', index=False)
json.dump({'independent_agreement_overall': float(agree),
'independent_agreement_by_collection': per_coll,
'review_sample_size': int(len(sample)),
'nearest_neighbour_same_session_rate': hits / tot,
'nearest_neighbour_evaluated_images': int(tot),
'human_signoff': 'OUTSTANDING - sample written for manual review, not yet reviewed'},
open(OUT / 'acceptance.json', 'w'), indent=2)
print('wrote', OUT)
if __name__ == '__main__':
main()