返回 build

nb-fallback.mjs

build/nb-fallback.mjs · 3.5 KB · 81 行
build/nb-fallback.mjs 3.5 KB · 81 行 GitHub
// Lemma fallback for Numberbatch misses: try morphological candidates// (distributions->distribution, scowled->scowl, impressionistically->impressionistic, ...)// copying vectors from vocab rows when the lemma is already in GWD, else rescanning the gz.// Patches build/nb-vecs.f32 in place; writes build/nb-report2.json (recovered + dropped).import fs from "node:fs";import zlib from "node:zlib";import readline from "node:readline";import path from "node:path";import { fileURLToPath } from "node:url";const ROOT = path.dirname(fileURLToPath(import.meta.url));const GZ = "D:\\gw-dl\\numberbatch-en-19.08.txt.gz";const D = 300;const words = JSON.parse(fs.readFileSync(path.join(ROOT, "words.json"), "utf8"));const N = words.length;const idx = new Map(words.map((w, i) => [w, i]));const rep = JSON.parse(fs.readFileSync(path.join(ROOT, "nb-report.json"), "utf8"));const f32buf = fs.readFileSync(path.join(ROOT, "nb-vecs.f32"));const f32 = new Float32Array(f32buf.buffer, f32buf.byteOffset, f32buf.length / 4);function* candidates(w) {  const has = s => w.endsWith(s);  if (has("ically")) yield w.slice(0, -6) + "ic";  if (has("ies")) yield w.slice(0, -3) + "y";  if (has("ing")) { yield w.slice(0, -3); yield w.slice(0, -3) + "e"; }  if (has("ed")) { yield w.slice(0, -2); yield w.slice(0, -2) + "e"; yield w.slice(0, -3); }  if (has("ly")) yield w.slice(0, -2);  if (has("ae")) yield w.slice(0, -2) + "a";  if (has("ia")) yield w.slice(0, -2) + "ium";  if (has("i")) yield w.slice(0, -1) + "us";  if (has("es")) { yield w.slice(0, -2); yield w.slice(0, -2) + "is"; yield w.slice(0, -1); }  if (has("s")) yield w.slice(0, -1);}const recovered = {}, needScan = new Map();   // lemma -> [miss words]const isZero = i => { const o = i * D; for (let d = 0; d < D; d++) if (f32[o + d] !== 0) return false; return true; };for (const m of rep.misses) {  for (const c of candidates(m)) {    if (recovered[m]) break;    if (idx.has(c) && !isZero(idx.get(c))) {          // lemma already has a vector in vocab      f32.set(f32.subarray(idx.get(c) * D, idx.get(c) * D + D), idx.get(m) * D);      recovered[m] = c + " (vocab)";    } else if (!idx.has(c)) {      if (!needScan.has(c)) needScan.set(c, []);      needScan.get(c).push(m);    }  }}if (needScan.size) {  const lemmaSet = new Set(needScan.keys());  const rl = readline.createInterface({ input: fs.createReadStream(GZ).pipe(zlib.createGunzip()), crlfDelay: Infinity });  let n = 0;  for await (const line of rl) {    if (++n === 1) continue;    const sp = line.indexOf(" ");    if (sp < 1) continue;    const term = line.slice(0, sp);    if (!lemmaSet.has(term)) continue;    const parts = line.slice(sp + 1).trim().split(/\s+/);    if (parts.length !== D) continue;    const row = new Float32Array(D);    for (let d = 0; d < D; d++) row[d] = +parts[d];    for (const m of needScan.get(term)) {      f32.set(row, idx.get(m) * D);      recovered[m] = term;    }    lemmaSet.delete(term);    if (!lemmaSet.size) break;  }}fs.writeFileSync(path.join(ROOT, "nb-vecs.f32"), Buffer.from(f32.buffer, f32.byteOffset, f32.byteLength));const dropped = rep.misses.filter(m => !recovered[m]);fs.writeFileSync(path.join(ROOT, "nb-report2.json"), JSON.stringify({ recovered, dropped }, null, 1));console.log(`recovered ${Object.keys(recovered).length}/${rep.misses.length}, dropped ${dropped.length}`);console.log("sample recovered:", Object.entries(recovered).slice(0, 12).map(([m, l]) => `${m}->${l}`).join(", "));console.log("dropped:", dropped.join(" "));