File size: 3,351 Bytes
0c025bc 6be2d5b 0c025bc 56325ab 0c025bc | 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 | """Re-ranker v2: candidate-EC features from the merged hit list (train + UniProt references).
The same function builds the training rows (official-label proxy, see experiments/external) and the
inference rows, so features cannot drift. Candidates are the top 10 ECs of the weighted vote.
"""
import math
FEATURES = ['v12w', 'v12', 'v8', 'v4', 'v1', 'rank', 's_tr', 's_sp', 's_un', 's_u2', 'best_bits_rel', 'best_pid',
'best_rank', 'best_src', 'qcov', 'scov', 'n10', 'n50', 'l3', 'l2', 'lfreq', 'top_pid', 'top_bits',
'top_src', 'n_hits', 'n_cand', 'margin', 'pid_gap', 'nec_best']
SRC = {'sp_': 1, 'cp_': 1, 'br_': 1, 'dk_': 1, 'bo_': 1, 'pd_': 1, 'un_': 2, 'es_': 2, 'e2_': 2, 'u2_': 3} # curated -> 1, unreviewed -> 2/3
def source(s):
"""0 = training protein (ids tr_<digits>), 1 = curated external (Swiss-Prot, BRENDA, CatPred, DLKcat),
2 = unreviewed with protein-level evidence or ESP, 3 = TrEMBL transcript-level."""
return 0 if (s.startswith('tr_') and s[3:].isdigit()) else SRC.get(s[:3], 1)
def candidate_features(hits, labels, freq, weights, k=50, power=12):
"""hits: (sseqid, pid, qlen, slen, evalue, bits, qstart, qend, sstart, send) tuples, any order.
Returns (candidate ECs ordered by weighted vote, feature rows)."""
h = sorted(hits, key=lambda x: -x[5])[:k]
if not h:
return [], []
top = h[0][5]
st, l3, l2 = {}, {}, {}
tot = {'w': 0.0, '12': 0.0, '8': 0.0, '4': 0.0, '1': 0.0}
for r, (s, pid, ql, sl, ev, b, qs, qe, ss, se) in enumerate(h):
ecs = labels[s]
n = len(ecs)
rb = b / top
src = source(s)
w = rb ** power * (1.0 if src == 0 else weights.get(s[:3], 1.0))
tot['w'] += w
tot['12'] += rb ** 12
tot['8'] += rb ** 8
tot['4'] += rb ** 4
tot['1'] += rb
for e in ecs:
d = st.get(e)
if d is None:
d = st[e] = dict(w=0.0, p12=0.0, p8=0.0, p4=0.0, p1=0.0, src=[0.0, 0.0, 0.0, 0.0], bb=b, bp=pid, br=r,
bs=src, qc=(qe - qs + 1) / ql, sc=(se - ss + 1) / sl, n10=0, n50=0, nec=n)
d['w'] += w / n
d['p12'] += rb ** 12 / n
d['p8'] += rb ** 8 / n
d['p4'] += rb ** 4 / n
d['p1'] += rb / n
d['src'][src] += w / n
d['n10'] += r < 10
d['n50'] += 1
p3 = e.rsplit('.', 1)[0]
p2 = '.'.join(e.split('.')[:2])
l3[p3] = l3.get(p3, 0.0) + w / n
l2[p2] = l2.get(p2, 0.0) + w / n
cands = sorted(st, key=lambda e: (-st[e]['w'], -freq.get(e, 0), e))[:10]
ws = [st[e]['w'] for e in cands]
margin = (ws[0] - (ws[1] if len(ws) > 1 else 0.0)) / tot['w']
rows = []
for i, e in enumerate(cands):
d = st[e]
rows.append([d['w'] / tot['w'], d['p12'] / tot['12'], d['p8'] / tot['8'], d['p4'] / tot['4'], d['p1'] / tot['1'], i,
*[x / tot['w'] for x in d['src']], d['bb'] / top, d['bp'], d['br'], d['bs'], d['qc'], d['sc'],
d['n10'], d['n50'], l3[e.rsplit('.', 1)[0]] / tot['w'], l2['.'.join(e.split('.')[:2])] / tot['w'],
math.log1p(freq.get(e, 0)), h[0][1], top, source(h[0][0]), len(h), len(st), margin,
h[0][1] - d['bp'], d['nec']])
return cands, rows
|