pylexnet / tools /score.py
Pranavz's picture
pylexnet: 827-number Python highlighter network
beaedc8 verified
Raw History Blame Contribute Delete
4.73 kB
"""Score ./pylexnet against Pygments' PythonLexer, per byte, on the dev split of the stdlib. PC only, one process.
Pygments labels are cached in dev_cache/ after the first run. usage: score.py [dev] [report file]"""
import hashlib
import subprocess
import sys
from collections import Counter
from pathlib import Path
import numpy as np
ROOT = Path("/usr/lib/python3.12")
HERE = Path(__file__).resolve().parent
CACHE = HERE / "dev_cache"
NAMES = ["text", "keyword", "builtin", "string", "comment", "number", "func", "class", "decorator"]
def files(split):
out = []
for p in sorted(ROOT.rglob("*.py")):
if "site-packages" in p.parts or "dist-packages" in p.parts or "lib2to3" in p.parts:
continue
if p.stat().st_size > 300_000:
continue
h = hashlib.sha256(str(p).encode()).digest()[0]
if (h < 128) == (split == "dev"):
out.append(p)
return out
def oracle(text):
from pygments.lexers import PythonLexer
from pygments.token import Token as T
lex = PythonLexer(stripnl=False, ensurenl=False, stripall=False)
out = bytearray()
for _, tok, val in lex.get_tokens_unprocessed(text):
if tok in T.Comment: c = 4
elif tok in T.Literal.String: c = 3
elif tok in T.Literal.Number: c = 5
elif tok in T.Keyword or tok in T.Operator.Word: c = 1
elif tok in T.Name.Decorator: c = 8
elif tok in T.Name.Function: c = 6
elif tok in T.Name.Class: c = 7
elif tok in T.Name.Builtin or tok in T.Name.Exception: c = 2
else: c = 0
out += bytes([c]) * len(val.encode("utf-8", "surrogateescape"))
return out
def build_cache(split):
CACHE.mkdir(exist_ok=True)
idx, data, gold = [], bytearray(), bytearray()
for p in files(split):
try:
text = p.read_text("utf-8")
except UnicodeDecodeError:
continue
text = text.replace("\r\n", "\n")
d = text.encode()
g = oracle(text)
if len(g) != len(d):
continue
idx.append(f"{p}\t{len(data)}\t{len(d)}\n")
data += d; gold += g
(CACHE / f"{split}_data.bin").write_bytes(data)
(CACHE / f"{split}_gold.bin").write_bytes(gold)
(CACHE / f"{split}_index.tsv").write_text("".join(idx))
def main():
split = sys.argv[1] if len(sys.argv) > 1 else "dev"
rep = open(sys.argv[2], "w") if len(sys.argv) > 2 else None
if not (CACHE / f"{split}_index.tsv").exists():
build_cache(split)
data = np.memmap(CACHE / f"{split}_data.bin", dtype=np.uint8, mode="r")
gold = np.memmap(CACHE / f"{split}_gold.bin", dtype=np.uint8, mode="r")
conf = np.zeros((9, 9), dtype=np.int64)
exact = nfiles = 0
bytext = Counter(); sample = {}
for line in open(CACHE / f"{split}_index.tsv"):
path, off, n = line.rstrip("\n").split("\t")
off, n = int(off), int(n)
d = data[off:off + n].tobytes(); g = gold[off:off + n]
out = subprocess.run([str(HERE / "pylexnet"), "--classes"], input=d, capture_output=True).stdout
p = np.frombuffer(out, dtype=np.uint8)
assert len(p) == n, (path, len(p), n)
nfiles += 1
np.add.at(conf, (g, p), 1)
bad = np.nonzero(g != p)[0]
if not len(bad):
exact += 1
continue
s = e = int(bad[0]); runs = []
for i in map(int, bad[1:]):
if i == e + 1 and g[i] == g[e] and p[i] == p[e]:
e = i
else:
runs.append((s, e)); s = e = i
runs.append((s, e))
for s, e in runs:
k = (f"{NAMES[g[s]]}>{NAMES[p[s]]}", d[s:e + 1][:18].decode("utf-8", "replace"))
bytext[k] += e - s + 1
if k not in sample:
a = d.rfind(b"\n", 0, s) + 1; z = d.find(b"\n", e); z = n if z < 0 else z
sample[k] = (Path(path).name, d[a:z][:110].decode("utf-8", "replace"))
tot = conf.sum()
print(f"{split}: {nfiles} files, {tot / 1e6:.1f} MB")
print(f"byte accuracy {np.trace(conf) / tot:.5f}, exact files {exact}/{nfiles}, wrong bytes {tot - np.trace(conf)}")
for k in range(9):
pr = conf[k, k] / max(1, conf[:, k].sum()); rc = conf[k, k] / max(1, conf[k].sum())
print(f" {NAMES[k]:10} P {pr:.5f} R {rc:.5f} n {conf[k].sum()}")
off = conf.copy(); np.fill_diagonal(off, 0)
print("top confusions:", [(f"{NAMES[i // 9]}>{NAMES[i % 9]}", int(off.ravel()[i])) for i in np.argsort(off.ravel())[::-1][:8] if off.ravel()[i]])
for k, v in bytext.most_common(25 if rep is None else 80):
print(f"{v:7} {k[0]} {k[1]!r} [{sample[k][0]}] {sample[k][1]!r}", file=rep or sys.stdout)
if __name__ == "__main__":
main()