#!/usr/bin/env python3 # -*- coding: utf-8 -*- """exposure_analysis.py — «تعرّض فعلي» لا «توافر»: يعيد تشغيل build_examples بنفس البذرة (13) وعبر قائمة المجمّع الأصلية (pairs.jsonl من مخرجات النواة) لتحديد الأزواج التي دخلت أمثلة التدريب فعلًا، ثم يقارنها بعناصر التقييم. تحقق سلامة إعادة البناء: عدد النصوص الفريدة (md5) يجب أن يطابق feat_cache_300000.npz المُنتَج وقت التدريب نفسه (إن تطابق ⇒ إعادة البناء مطابقة حرفيًا).""" import os, sys, json, time, random, hashlib import numpy as np sys.path.insert(0, "/home/user/lhc/code"); sys.path.insert(0, "/home/user/lhc/recon") T0=time.time() def log(*a): print(f"[{time.time()-T0:6.1f}s]", *a, flush=True) W4="/var/tmp/lhc_w4" N_EXAMPLES=300000; SEED=13 # --- نفس الاستيرادات التي يستخدمها build_examples الأصلي --- from phase_c_augment import augment from phase_p2_slot import make_sibling, novel_view from phase_s_scale import make_scaled_corpus kb=json.load(open("/home/user/lhc/code/knowledge_bank_superbrain.json", encoding="utf-8")) kb_prompts=[e["prompt"] for e in kb] synth=kb_prompts+[e["prompt"] for e in make_scaled_corpus(3000, seed=0)] NS=len(synth) # عدّ أسطر pairs.jsonl with open(f"{W4}/pairs.jsonl","rb") as f: n_pairs=sum(1 for _ in f) log(f"pairs.jsonl: {n_pairs:,} زوجًا | len(synth)={NS}") # --- (أ) إعادة تشغيل RNG فقط لتحديد الفهارس --- rng=random.Random(SEED) par_idx=[]; mono_idx=[]; branch_counts={"par":0,"mono_pair":0,"mono_synth":0,"slot":0,"prot":0} for i in range(N_EXAMPLES): r=rng.random() if r<0.40: p_i=rng.randrange(n_pairs); par_idx.append(p_i); branch_counts["par"]+=1 elif r<0.65: if rng.random()<0.5: p_i=rng.randrange(n_pairs); rng.choice(("en","ar")); mono_idx.append(p_i); branch_counts["mono_pair"]+=1 else: rng.randrange(NS); branch_counts["mono_synth"]+=1 elif r<0.85: rng.randrange(NS); branch_counts["slot"]+=1 else: rng.randrange(NS); branch_counts["prot"]+=1 log(f"الفروع: {branch_counts} | فهارس الأزواج: {len(par_idx):,} + {len(mono_idx):,}") # --- (ب) استخراج نصوص الفهارس المطلوبة من pairs.jsonl (جولة واحدة) --- need=set(par_idx)|set(mono_idx) texts={} with open(f"{W4}/pairs.jsonl", encoding="utf-8") as f: for i,line in enumerate(f): if i in need: o=json.loads(line); texts[i]=(o.get("ar",""),o.get("en","")) log(f"استُخرج {len(texts):,} زوجًا نصيًا") # --- (ج) بناء مجموعات نصوص التعرّض --- def h(s): return hashlib.md5(s.encode()).hexdigest() exact_ar=set(); exact_en=set(); mono_ar=set(); mono_en=set() for i in par_idx: a,e=texts[i]; exact_ar.add(h(a)); exact_en.add(h(e)) # للفرع الأحادي: النص الخام يُغذّى إلى augment() — تعرّض «مشتق» (نحتفظ بالنص الخام للمقارنة) for i in mono_idx: a,e=texts[i] mono_ar.add(h(a)); mono_en.add(h(e)) log(f"نصوص تعرّض صريح: ar={len(exact_ar):,} en={len(exact_en):,} | مشتق: +ar={len(mono_ar-exact_ar):,} +en={len(mono_en-exact_en):,}") # --- (د) العناصر المُقيَّمة: كم منها مُتعرَّض فعلًا؟ --- sys.path.insert(0,"/home/user/lhc/recon") exec(open('/home/user/lhc/recon/build_masks_stream.py').read().split('# ---------------- مجموعات التقييم')[0]) from phase_w1_data import download_corpus DD="/home/user/lhc/data/opus" OPUS=[("Tanzil","https://object.pouta.csc.fi/OPUS-Tanzil/v1/moses/ar-en.txt.zip",20000), ("Bible","https://object.pouta.csc.fi/OPUS-bible-uedin/v1/moses/ar-en.txt.zip",20000), ("NeuLab-TED","https://object.pouta.csc.fi/OPUS-NeuLab-TedTalks/v1/moses/ar-en.txt.zip",20000)] pairs={} for name,url,lim in OPUS: pairs[name]=download_corpus(name,url,lim,DD) DEV="/home/user/lhc/data/flores101_dataset/devtest" ld=lambda p:[l.strip() for l in open(p,encoding="utf-8") if l.strip()] pairs["FLORES"]=[{"ar":a,"en":e} for a,e in zip(ld(f"{DEV}/ara.devtest"),ld(f"{DEV}/eng.devtest"))] M=json.load(open("/home/user/lhc/results/masks_v2.json")) def norm_h(side, s): return h(norm_ar(s) if side=="ar" else norm_en(s)) # مجموعات مُطبَّعة للتعرّض (بمقارنة مُطبَّعة — أدق للاختلافات الشكلية) nsexact_ar={norm_h("ar",texts[i][0]) for i in par_idx} nsexact_en={norm_h("en",texts[i][1]) for i in par_idx} nsmono_ar={norm_h("ar",texts[i][0]) for i in mono_idx} nsmono_en={norm_h("en",texts[i][1]) for i in mono_idx} report={"pairs_in_pool":n_pairs,"n_examples":N_EXAMPLES,"branch_counts":branch_counts, "n_par_pairs":len(par_idx),"n_mono_pairs":len(mono_idx), "sets":{}} for cn,ps in pairs.items(): n=len(ps) avail=set(M["masks_strong"][cn]) ex=0; ex_ar=0; ex_en=0; dv=0 for i,p in enumerate(ps): ha=norm_h("ar",p["ar"]); he=norm_h("en",p["en"]) a_in = ha in nsexact_ar; e_in = he in nsexact_en a_m = ha in nsmono_ar; e_m = he in nsmono_en hit_exact = a_in or e_in hit_deriv = (a_m or e_m) if hit_exact: ex+=1 if a_in: ex_ar+=1 if e_in: ex_en+=1 if hit_exact or hit_deriv: dv+=1 report["sets"][cn]={"n":n,"available_mask":len(avail), "exposed_exact":ex,"exposed_exact_ar":ex_ar,"exposed_exact_en":ex_en, "exposed_exact_or_derived":dv, "exposure_rate_pct":round(100*ex/n,3), "availability_rate_pct":round(100*len(avail)/n,3), "exposure_over_availability":round(ex/max(1,len(avail)),3) if avail else None} log(f"{cn:12s} توافر={len(avail):>5} ({100*len(avail)/n:.2f}%) → تعرّض صريح={ex:>5} ({100*ex/n:.3f}%) | +مشتق={dv:>5}") # --- (هـ) تحقق feat_cache (عدد النصوص الفريدة) --- fc=np.load(f"{W4}/feat_cache.npz") report["feat_cache"]={"n_texts":int(fc["n_texts"]),"rows_shape":list(fc["rows"].shape), "example_texts_slots":int(fc["rows"].shape[0])} log(f"feat_cache: n_texts={int(fc['n_texts']):,} rows={fc['rows'].shape}") json.dump(report, open("/home/user/lhc/results/exposure_analysis.json","w"), ensure_ascii=False, indent=1) log("SAVED results/exposure_analysis.json")