| |
| """ |
| Tier 2: Core Biology Embedding Analysis |
| Analyzes whether GlycanBERT understands glycan biology at a structural level. |
| |
| Analyses: |
| 1. Glycan structural type clustering (N-glycan, O-glycan, GAG, etc.) |
| 2. Monosaccharide composition vs embedding distance correlation |
| 3. Branching complexity analysis |
| 4. Linkage chemistry sensitivity (alpha vs beta) |
| 5. Decorations (fucosylation, sialylation) |
| 6. Plantae / Phytomining investigation |
| |
| Usage: |
| python3 analyze_core_biology.py --model v6 |
| python3 analyze_core_biology.py --model v5 |
| """ |
|
|
| import os, sys, json, re, pickle, argparse |
| import numpy as np |
| from collections import Counter |
| from pathlib import Path |
|
|
| try: |
| import matplotlib |
| matplotlib.use('Agg') |
| import matplotlib.pyplot as plt |
| except ImportError: |
| print("ERROR: matplotlib required"); sys.exit(1) |
|
|
| try: |
| import umap |
| def compute_umap(data, n=2): |
| return umap.UMAP(n_components=n, n_neighbors=30, min_dist=0.3, |
| random_state=42, n_jobs=1).fit_transform(data) |
| except ImportError: |
| from sklearn.manifold import TSNE |
| def compute_umap(data, n=2): |
| return TSNE(n_components=n, perplexity=30, random_state=42).fit_transform(data) |
|
|
| from scipy.stats import spearmanr |
|
|
| |
|
|
| def classify_glycan_type(iupac): |
| """Classify a glycan by structural type from its IUPAC name.""" |
| if not iupac or iupac == 'nan': |
| return 'Unknown' |
| iupac_upper = iupac.upper() |
| if 'GLCNAC(B1-4)GLCNAC' in iupac_upper and 'MAN' in iupac_upper: |
| return 'N-glycan' |
| if iupac_upper.count('MAN') >= 5 and 'GLCNAC' in iupac_upper: |
| return 'N-glycan (high-mannose)' |
| if 'GALNAC(A1-' in iupac_upper: |
| return 'O-glycan' |
| if any(x in iupac_upper for x in ['GLCA', 'IDOA', 'HEPARAN', 'CHONDROITIN']): |
| return 'GAG' |
| if iupac_upper.startswith('GLC(B1-') or iupac_upper.startswith('GLCCER'): |
| return 'Glycolipid' |
| if 'LAC' in iupac_upper and len(iupac) < 60: |
| return 'Milk OS' |
| return 'Other' |
|
|
| |
|
|
| MONOSACCHARIDES = ['Glc', 'GlcNAc', 'Man', 'Gal', 'GalNAc', 'Fuc', |
| 'Neu5Ac', 'Xyl', 'Rha', 'GlcA', 'IdoA', 'Ara'] |
|
|
| def get_composition(iupac): |
| """Count monosaccharides in an IUPAC string.""" |
| if not iupac: |
| return np.zeros(len(MONOSACCHARIDES)) |
| vec = [] |
| for mono in MONOSACCHARIDES: |
| count = len(re.findall(re.escape(mono) + r'[\(\[]', iupac, re.IGNORECASE)) |
| if count == 0: |
| count = iupac.upper().count(mono.upper()) |
| vec.append(count) |
| return np.array(vec, dtype=float) |
|
|
| |
|
|
| def analysis_1_structural_types(embs, iupacs, output_dir, name): |
| """UMAP colored by glycan structural type.""" |
| print(f"\n=== Tier 2 Analysis 1: Structural Type Clustering ({name}) ===") |
| types = [classify_glycan_type(s) for s in iupacs] |
| type_counts = Counter(types) |
| print(f" Type distribution: {dict(type_counts)}") |
| valid_types = {t for t, c in type_counts.items() if c >= 5 and t != 'Unknown'} |
| mask = np.array([t in valid_types for t in types]) |
| if mask.sum() < 20: |
| print(f" Only {mask.sum()} samples with known type, skipping UMAP.") |
| return {'structural_type_counts': dict(type_counts)} |
| embs_f = embs[mask] |
| types_f = [t for t, m in zip(types, mask) if m] |
| proj = compute_umap(embs_f) |
| colors = ['#E53935', '#1E88E5', '#43A047', '#FB8C00', '#8E24AA', |
| '#00ACC1', '#6D4C41', '#546E7A'] |
| unique_types = sorted(set(types_f)) |
| fig, ax = plt.subplots(figsize=(14, 10)) |
| for i, gtype in enumerate(unique_types): |
| m = np.array([t == gtype for t in types_f]) |
| ax.scatter(proj[m, 0], proj[m, 1], c=colors[i % len(colors)], |
| s=15, alpha=0.6, label=f'{gtype} (n={m.sum()})', rasterized=True) |
| ax.set_title(f'{name}: Glycan Structural Types in Embedding Space', fontsize=16, fontweight='bold') |
| ax.legend(fontsize=10, markerscale=3, loc='best') |
| ax.set_xlabel('UMAP-1', fontsize=12); ax.set_ylabel('UMAP-2', fontsize=12) |
| plt.tight_layout() |
| plt.savefig(os.path.join(output_dir, f'structural_types_{name}.png'), dpi=200, bbox_inches='tight') |
| plt.close() |
| print(f" Saved structural_types_{name}.png") |
| try: |
| from sklearn.metrics import silhouette_score |
| labels_num = [unique_types.index(t) for t in types_f] |
| if len(set(labels_num)) >= 2: |
| sil = silhouette_score(embs_f, labels_num, metric='cosine', sample_size=min(5000, len(embs_f))) |
| print(f" Silhouette (structural type): {sil:.4f}") |
| return {'structural_type_counts': dict(type_counts), 'silhouette_structural_type': round(sil, 4)} |
| except: pass |
| return {'structural_type_counts': dict(type_counts)} |
|
|
|
|
| def analysis_2_composition(embs, iupacs, output_dir, name, n_sample=2000): |
| """Monosaccharide composition vs embedding distance correlation.""" |
| print(f"\n=== Tier 2 Analysis 2: Composition Correlation ({name}) ===") |
| n = min(n_sample, len(embs)) |
| idx = np.random.RandomState(42).choice(len(embs), n, replace=False) |
| comp_vecs = np.array([get_composition(iupacs[i]) for i in idx]) |
| emb_subset = embs[idx] |
| norms = np.linalg.norm(comp_vecs, axis=1) |
| valid = norms > 0 |
| if valid.sum() < 50: |
| print(f" Only {valid.sum()} samples with parseable composition, skipping.") |
| return {} |
| comp_vecs = comp_vecs[valid] |
| emb_subset = emb_subset[valid] |
| print(f" {len(comp_vecs)} samples with valid composition") |
| emb_n = emb_subset / (np.linalg.norm(emb_subset, axis=1, keepdims=True) + 1e-8) |
| comp_n = comp_vecs / (np.linalg.norm(comp_vecs, axis=1, keepdims=True) + 1e-8) |
| emb_sim = emb_n @ emb_n.T |
| comp_sim = comp_n @ comp_n.T |
| mask = np.triu(np.ones_like(emb_sim, dtype=bool), k=1) |
| emb_flat = emb_sim[mask] |
| comp_flat = comp_sim[mask] |
| if len(emb_flat) > 50000: |
| sample_idx = np.random.RandomState(42).choice(len(emb_flat), 50000, replace=False) |
| emb_flat = emb_flat[sample_idx] |
| comp_flat = comp_flat[sample_idx] |
| rho, pval = spearmanr(comp_flat, emb_flat) |
| print(f" Spearman rho = {rho:.4f} (p = {pval:.2e})") |
| fig, ax = plt.subplots(figsize=(10, 8)) |
| sample = np.random.RandomState(42).choice(len(emb_flat), min(5000, len(emb_flat)), replace=False) |
| ax.scatter(comp_flat[sample], emb_flat[sample], s=1, alpha=0.15, c='#1565C0', rasterized=True) |
| ax.set_xlabel('Compositional Similarity (cosine)', fontsize=14) |
| ax.set_ylabel('Embedding Similarity (cosine)', fontsize=14) |
| ax.set_title(f'{name}: Composition vs Embedding Similarity\nSpearman rho = {rho:.4f}', fontsize=14, fontweight='bold') |
| plt.tight_layout() |
| plt.savefig(os.path.join(output_dir, f'composition_correlation_{name}.png'), dpi=200, bbox_inches='tight') |
| plt.close() |
| print(f" Saved composition_correlation_{name}.png") |
| return {'composition_spearman_rho': round(rho, 4), 'composition_p_value': float(f'{pval:.2e}'), |
| 'n_composition_samples': int(len(comp_vecs))} |
|
|
|
|
| def analysis_3_branching(embs, branch_depths_list, output_dir, name): |
| """Branching complexity analysis.""" |
| print(f"\n=== Tier 2 Analysis 3: Branching Complexity ({name}) ===") |
| max_depths = [] |
| for bd in branch_depths_list: |
| if isinstance(bd, (list, np.ndarray)) and len(bd) > 0: |
| max_depths.append(int(max(bd))) |
| else: |
| max_depths.append(0) |
| max_depths = np.array(max_depths) |
| depth_counts = Counter(max_depths) |
| print(f" Depth distribution: {dict(sorted(depth_counts.items()))}") |
| def depth_category(d): |
| if d <= 1: return 'Linear (depth<=1)' |
| elif d == 2: return 'Branched (depth=2)' |
| elif d == 3: return 'Complex (depth=3)' |
| else: return 'Hyper-branched (depth>=4)' |
| categories = [depth_category(d) for d in max_depths] |
| cat_counts = Counter(categories) |
| valid_cats = {c for c, n in cat_counts.items() if n >= 10} |
| mask = np.array([c in valid_cats for c in categories]) |
| if mask.sum() < 30: |
| print(f" Insufficient category diversity ({mask.sum()} valid), skipping UMAP.") |
| return {'branching_distribution': dict(depth_counts)} |
| embs_f = embs[mask] |
| cats_f = [c for c, m in zip(categories, mask) if m] |
| proj = compute_umap(embs_f) |
| colors = {'Linear (depth<=1)': '#42A5F5', 'Branched (depth=2)': '#66BB6A', |
| 'Complex (depth=3)': '#FFA726', 'Hyper-branched (depth>=4)': '#EF5350'} |
| unique_cats = sorted(set(cats_f)) |
| fig, ax = plt.subplots(figsize=(14, 10)) |
| for cat in unique_cats: |
| m = np.array([c == cat for c in cats_f]) |
| ax.scatter(proj[m, 0], proj[m, 1], c=colors.get(cat, '#999'), |
| s=15, alpha=0.5, label=f'{cat} (n={m.sum()})', rasterized=True) |
| ax.set_title(f'{name}: Branching Complexity in Embedding Space', fontsize=16, fontweight='bold') |
| ax.legend(fontsize=11, markerscale=3) |
| ax.set_xlabel('UMAP-1', fontsize=12); ax.set_ylabel('UMAP-2', fontsize=12) |
| plt.tight_layout() |
| plt.savefig(os.path.join(output_dir, f'branching_complexity_{name}.png'), dpi=200, bbox_inches='tight') |
| plt.close() |
| print(f" Saved branching_complexity_{name}.png") |
| try: |
| from sklearn.metrics import silhouette_score |
| labels_num = [unique_cats.index(c) for c in cats_f] |
| if len(set(labels_num)) >= 2: |
| sil = silhouette_score(embs_f, labels_num, metric='cosine', sample_size=min(5000, len(embs_f))) |
| print(f" Silhouette (branching): {sil:.4f}") |
| return {'branching_distribution': dict(depth_counts), 'silhouette_branching': round(sil, 4)} |
| except: pass |
| return {'branching_distribution': dict(depth_counts)} |
|
|
|
|
| def analysis_4_linkage(embs, linkage_types_list, output_dir, name): |
| """Linkage chemistry analysis: alpha vs beta distribution in embedding space.""" |
| print(f"\n=== Tier 2 Analysis 4: Linkage Chemistry ({name}) ===") |
| alpha_fracs = [] |
| for lt in linkage_types_list: |
| if isinstance(lt, (list, np.ndarray)) and len(lt) > 0: |
| arr = np.array(lt) |
| n_valid = np.sum(arr > 0) |
| if n_valid > 0: |
| n_alpha = np.sum(arr == 1) |
| alpha_fracs.append(n_alpha / n_valid) |
| else: |
| alpha_fracs.append(0.5) |
| else: |
| alpha_fracs.append(0.5) |
| alpha_fracs = np.array(alpha_fracs) |
| def linkage_category(frac): |
| if frac >= 0.8: return 'Mostly alpha' |
| elif frac <= 0.2: return 'Mostly beta' |
| else: return 'Mixed alpha/beta' |
| categories = [linkage_category(f) for f in alpha_fracs] |
| cat_counts = Counter(categories) |
| print(f" Linkage distribution: {dict(cat_counts)}") |
| valid_cats = {c for c, n in cat_counts.items() if n >= 10} |
| mask = np.array([c in valid_cats for c in categories]) |
| if mask.sum() < 30: |
| print(f" Insufficient diversity, skipping UMAP.") |
| return {'linkage_distribution': dict(cat_counts)} |
| embs_f = embs[mask] |
| cats_f = [c for c, m in zip(categories, mask) if m] |
| proj = compute_umap(embs_f) |
| colors = {'Mostly alpha': '#E53935', 'Mixed alpha/beta': '#FDD835', 'Mostly beta': '#1E88E5'} |
| unique_cats = sorted(set(cats_f)) |
| fig, ax = plt.subplots(figsize=(14, 10)) |
| for cat in unique_cats: |
| m = np.array([c == cat for c in cats_f]) |
| ax.scatter(proj[m, 0], proj[m, 1], c=colors.get(cat, '#999'), |
| s=15, alpha=0.5, label=f'{cat} (n={m.sum()})', rasterized=True) |
| ax.set_title(f'{name}: Linkage Chemistry (alpha vs beta) in Embedding Space', fontsize=16, fontweight='bold') |
| ax.legend(fontsize=12, markerscale=3) |
| ax.set_xlabel('UMAP-1', fontsize=12); ax.set_ylabel('UMAP-2', fontsize=12) |
| plt.tight_layout() |
| plt.savefig(os.path.join(output_dir, f'linkage_chemistry_{name}.png'), dpi=200, bbox_inches='tight') |
| plt.close() |
| print(f" Saved linkage_chemistry_{name}.png") |
| alpha_mask = np.array([c == 'Mostly alpha' for c in categories]) |
| beta_mask = np.array([c == 'Mostly beta' for c in categories]) |
| if alpha_mask.sum() >= 10 and beta_mask.sum() >= 10: |
| a_embs = embs[alpha_mask][:500] |
| b_embs = embs[beta_mask][:500] |
| a_n = a_embs / (np.linalg.norm(a_embs, axis=1, keepdims=True) + 1e-8) |
| b_n = b_embs / (np.linalg.norm(b_embs, axis=1, keepdims=True) + 1e-8) |
| cross_sim = np.mean(a_n @ b_n.T) |
| within_a = np.mean(a_n @ a_n.T) |
| within_b = np.mean(b_n @ b_n.T) |
| print(f" a-a sim: {within_a:.4f}, b-b sim: {within_b:.4f}, a-b sim: {cross_sim:.4f}") |
| return {'linkage_distribution': dict(cat_counts), |
| 'alpha_alpha_sim': round(float(within_a), 4), |
| 'beta_beta_sim': round(float(within_b), 4), |
| 'alpha_beta_cross_sim': round(float(cross_sim), 4)} |
| return {'linkage_distribution': dict(cat_counts)} |
|
|
|
|
| def analysis_5_decorations(embs, iupacs, output_dir, name): |
| """Fucosylation / sialylation decoration analysis.""" |
| print(f"\n=== Tier 2 Analysis 5: Decorations ({name}) ===") |
| fuc_mask = np.array(['FUC' in (s or '').upper() for s in iupacs]) |
| sia_mask = np.array([any(x in (s or '').upper() for x in ['NEU5AC','NEU5GC','SIA']) for s in iupacs]) |
| print(f" Fucosylated: {fuc_mask.sum()}/{len(fuc_mask)}") |
| print(f" Sialylated: {sia_mask.sum()}/{len(sia_mask)}") |
| n = min(5000, len(embs)) |
| idx = np.random.RandomState(42).choice(len(embs), n, replace=False) |
| proj = compute_umap(embs[idx]) |
| fig, axes = plt.subplots(1, 2, figsize=(22, 9)) |
| for ax, (dec_name, mask_full, color) in zip(axes, [ |
| ('Fucosylated', fuc_mask, '#E53935'), |
| ('Sialylated', sia_mask, '#7B1FA2') |
| ]): |
| m = mask_full[idx] |
| ax.scatter(proj[~m, 0], proj[~m, 1], c='#BDBDBD', s=5, alpha=0.2, |
| label=f'No (n={int((~m).sum())})', rasterized=True) |
| ax.scatter(proj[m, 0], proj[m, 1], c=color, s=15, alpha=0.6, |
| label=f'{dec_name} (n={int(m.sum())})', rasterized=True) |
| ax.set_title(f'{name}: {dec_name}', fontsize=16, fontweight='bold') |
| ax.legend(fontsize=12, markerscale=3) |
| ax.set_xlabel('UMAP-1'); ax.set_ylabel('UMAP-2') |
| plt.tight_layout() |
| plt.savefig(os.path.join(output_dir, f'decorations_{name}.png'), dpi=200, bbox_inches='tight') |
| plt.close() |
| print(f" Saved decorations_{name}.png") |
| metrics = { |
| 'n_fucosylated': int(fuc_mask.sum()), |
| 'n_sialylated': int(sia_mask.sum()), |
| 'n_total': int(len(iupacs)) |
| } |
| if fuc_mask.sum() >= 20 and (~fuc_mask).sum() >= 20: |
| f_embs = embs[fuc_mask][:500] |
| nf_embs = embs[~fuc_mask][:500] |
| f_n = f_embs / (np.linalg.norm(f_embs, axis=1, keepdims=True) + 1e-8) |
| nf_n = nf_embs / (np.linalg.norm(nf_embs, axis=1, keepdims=True) + 1e-8) |
| cross = np.mean(f_n @ nf_n.T) |
| within_f = np.mean(f_n @ f_n.T) |
| print(f" Fuc-Fuc sim: {within_f:.4f}, Fuc-NonFuc sim: {cross:.4f}") |
| metrics['fuc_fuc_sim'] = round(float(within_f), 4) |
| metrics['fuc_nonfuc_sim'] = round(float(cross), 4) |
| return metrics |
|
|
|
|
| def analysis_6_phytomining_plantae(benchmark_embs, benchmark_kingdoms, output_dir, name): |
| """Plantae glycan cluster analysis (phytomining proxy). |
| Literature basis: Plant polysaccharides (cellulose, EPS, sulfated sugars) |
| bind metals via hydroxyl groups and 1,2-diolato coordination. Plantae glycans |
| with GlcA, Xyl, Rha, Ara monosaccharides are characteristic of plant cell wall |
| components relevant to phytomining/phytoremediation. |
| """ |
| print(f"\n=== Tier 2 Analysis 6: Plantae / Phytomining ({name}) ===") |
| plant_mask = np.array([k == 'Plantae' for k in benchmark_kingdoms]) |
| animal_mask = np.array([k == 'Animalia' for k in benchmark_kingdoms]) |
| bacteria_mask = np.array([k == 'Bacteria' for k in benchmark_kingdoms]) |
| fungi_mask = np.array([k == 'Fungi' for k in benchmark_kingdoms]) |
| print(f" Plantae: {plant_mask.sum()}, Animalia: {animal_mask.sum()}, Bacteria: {bacteria_mask.sum()}, Fungi: {fungi_mask.sum()}") |
| if plant_mask.sum() < 10: |
| print(" Not enough Plantae samples.") |
| return {} |
| proj = compute_umap(benchmark_embs) |
| fig, ax = plt.subplots(figsize=(14, 10)) |
| other = ~(plant_mask | animal_mask | bacteria_mask | fungi_mask) |
| if other.sum() > 0: |
| ax.scatter(proj[other, 0], proj[other, 1], c='#BDBDBD', s=8, alpha=0.3, |
| label=f'Other (n={other.sum()})', rasterized=True) |
| if fungi_mask.sum() > 0: |
| ax.scatter(proj[fungi_mask, 0], proj[fungi_mask, 1], c='#AB47BC', s=20, alpha=0.6, |
| label=f'Fungi (n={fungi_mask.sum()})', rasterized=True) |
| if bacteria_mask.sum() > 0: |
| ax.scatter(proj[bacteria_mask, 0], proj[bacteria_mask, 1], c='#42A5F5', s=20, alpha=0.6, |
| label=f'Bacteria (n={bacteria_mask.sum()})', rasterized=True) |
| if animal_mask.sum() > 0: |
| ax.scatter(proj[animal_mask, 0], proj[animal_mask, 1], c='#FFA726', s=20, alpha=0.6, |
| label=f'Animalia (n={animal_mask.sum()})', rasterized=True) |
| ax.scatter(proj[plant_mask, 0], proj[plant_mask, 1], c='#2E7D32', s=30, alpha=0.8, |
| edgecolors='black', linewidths=0.5, |
| label=f'Plantae (n={plant_mask.sum()})', rasterized=True) |
| ax.set_title(f'{name}: Plantae Glycans in Embedding Space (Phytomining Proxy)', |
| fontsize=16, fontweight='bold') |
| ax.legend(fontsize=12, markerscale=2.5) |
| ax.set_xlabel('UMAP-1', fontsize=12); ax.set_ylabel('UMAP-2', fontsize=12) |
| plt.tight_layout() |
| plt.savefig(os.path.join(output_dir, f'plantae_phytomining_{name}.png'), dpi=200, bbox_inches='tight') |
| plt.close() |
| print(f" Saved plantae_phytomining_{name}.png") |
| metrics = {} |
| for k_name, k_mask in [('Plantae', plant_mask), ('Animalia', animal_mask), ('Bacteria', bacteria_mask), ('Fungi', fungi_mask)]: |
| if k_mask.sum() >= 10: |
| e = benchmark_embs[k_mask][:300] |
| e_n = e / (np.linalg.norm(e, axis=1, keepdims=True) + 1e-8) |
| within = np.triu(e_n @ e_n.T, k=1) |
| mask_tri = np.triu(np.ones_like(within, dtype=bool), k=1) |
| if mask_tri.sum() > 0: |
| metrics[f'{k_name.lower()}_within_sim'] = round(float(np.mean(within[mask_tri])), 4) |
| if plant_mask.sum() >= 10 and animal_mask.sum() >= 10: |
| p_n = benchmark_embs[plant_mask][:200] |
| p_n = p_n / (np.linalg.norm(p_n, axis=1, keepdims=True) + 1e-8) |
| a_n = benchmark_embs[animal_mask][:200] |
| a_n = a_n / (np.linalg.norm(a_n, axis=1, keepdims=True) + 1e-8) |
| metrics['plant_animal_cross_sim'] = round(float(np.mean(p_n @ a_n.T)), 4) |
| if plant_mask.sum() >= 10 and bacteria_mask.sum() >= 10: |
| p_n = benchmark_embs[plant_mask][:200] |
| p_n = p_n / (np.linalg.norm(p_n, axis=1, keepdims=True) + 1e-8) |
| b_n = benchmark_embs[bacteria_mask][:200] |
| b_n = b_n / (np.linalg.norm(b_n, axis=1, keepdims=True) + 1e-8) |
| metrics['plant_bacteria_cross_sim'] = round(float(np.mean(p_n @ b_n.T)), 4) |
| for k, v in metrics.items(): |
| print(f" {k}: {v}") |
| return metrics |
|
|
|
|
| |
|
|
| def main(): |
| parser = argparse.ArgumentParser() |
| parser.add_argument('--model', required=True, choices=['v5', 'v6']) |
| parser.add_argument('--output-dir', default='bert_v6_contrastive/analysis') |
| args = parser.parse_args() |
|
|
| root = Path('.') |
| output_dir = args.output_dir |
| os.makedirs(output_dir, exist_ok=True) |
|
|
| |
| emb_path = os.path.join(output_dir, f'embeddings_{args.model}.npz') |
| print(f"Loading embeddings from {emb_path}...") |
| data = np.load(emb_path, allow_pickle=True) |
| train_embs = data['train_embs'] |
| benchmark_embs = data['benchmark_embs'] |
| benchmark_kingdoms = data['benchmark_kingdom'] |
|
|
| |
| data_path = root / 'bert_v5_bpe_topo' / 'data' / 'sequences_bpe_expanded.pkl' |
| print(f"Loading training data from {data_path}...") |
| with open(data_path, 'rb') as f: |
| all_samples = pickle.load(f) |
|
|
| |
| |
| import random |
| random.seed(42) |
| n_train = min(10000, len(all_samples)) |
| sampled = random.sample(all_samples, n_train) |
|
|
| iupacs = [s.get('iupac_name', '') or '' for s in sampled] |
| branch_depths = [s.get('branch_depths', []) for s in sampled] |
| linkage_types = [s.get('linkage_types', []) for s in sampled] |
|
|
| print(f" {len(iupacs)} training samples with metadata") |
| print(f" IUPAC example: {iupacs[0][:80]}") |
|
|
| |
| metrics = {'model': args.model, 'tier': 2} |
|
|
| m = analysis_1_structural_types(train_embs, iupacs, output_dir, args.model) |
| metrics.update(m) |
|
|
| m = analysis_2_composition(train_embs, iupacs, output_dir, args.model) |
| metrics.update(m) |
|
|
| m = analysis_3_branching(train_embs, branch_depths, output_dir, args.model) |
| metrics.update(m) |
|
|
| m = analysis_4_linkage(train_embs, linkage_types, output_dir, args.model) |
| metrics.update(m) |
|
|
| m = analysis_5_decorations(train_embs, iupacs, output_dir, args.model) |
| metrics.update(m) |
|
|
| m = analysis_6_phytomining_plantae(benchmark_embs, benchmark_kingdoms, output_dir, args.model) |
| metrics.update(m) |
|
|
| |
| out_path = os.path.join(output_dir, f'tier2_metrics_{args.model}.json') |
| def convert(o): |
| if isinstance(o, (np.integer,)): return int(o) |
| if isinstance(o, (np.floating,)): return float(o) |
| if isinstance(o, np.ndarray): return o.tolist() |
| return o |
| with open(out_path, 'w') as f: |
| json.dump(metrics, f, indent=2, default=convert) |
| print(f"\nAll Tier 2 metrics saved to {out_path}") |
|
|
| if __name__ == '__main__': |
| main() |
|
|