bookworm-smart-assistant/scripts/patches/patch-w1-weight-decay.js

132 lines
5.3 KiB
JavaScript
Raw Normal View History

#!/usr/bin/env node
/**
* W1 补丁 2026-04-16 audit-2026-04-16 WARNING
*
* 问题: simulateSignals context/project/workflow 三维梯度恒为 0
* bm25/semantic 单向累积导致权重持续漂移 (当前 bm25=0.598, DEFAULT 0.40 漂移 +0.198)
* 虽有 cooldown reset 抖动但根因未修长期仍会趋近 MAX_WEIGHT 边界
*
* 修复方案: 在两处梯度更新后追加 L2 权重衰减项 (正则化拉回 DEFAULT_WEIGHTS)
* delta_new = LR * gradient + DECAY * (DEFAULT - current)
* current 远离 DEFAULT 反向拉力加大
* current 等于 DEFAULT 无影响
* 稳态: 梯度力 = 衰减力 spread 有界
*
* 常量: WEIGHT_DECAY = 0.02 ( LEARNING_RATE 同量级)
* 数学估算: 当前 bm25=0.598 每批衰减 0.02*(0.598-0.4)=0.004
* 足以抵消 simulateSignals bm25 的单向梯度 (~0.02 量级)
*
* 两处更新点:
* - L283 learnFusionWeights 主路径
* - L541 applyImplicitWeights 隐式反馈路径
*
* 幂等: sentinel = 'WEIGHT_DECAY'
*/
'use strict';
const fs = require('fs');
const path = require('path');
const TARGET = path.join(__dirname, '..', 'fusion-weight-learner.js');
const SENTINEL = 'WEIGHT_DECAY';
function main() {
if (!fs.existsSync(TARGET)) {
console.error('[patch-w1] 目标文件不存在');
process.exit(1);
}
const before = fs.readFileSync(TARGET, 'utf8');
if (before.includes(SENTINEL)) {
console.log('[patch-w1] 已打过补丁 (发现 ' + SENTINEL + '),跳过');
process.exit(0);
}
// ==== 注入 1: 在 LEARNING_RATE 后追加 WEIGHT_DECAY 常量 ====
const constAnchor = 'const LEARNING_RATE = 0.02;';
const constInjection =
'const LEARNING_RATE = 0.02;\n' +
'// W1 修复 (patch-w1-weight-decay, 2026-04-16): L2 正则化强度\n' +
'// 每批次将权重按比例拉回 DEFAULT_WEIGHTS防止 simulateSignals 三维=0 导致的单向漂移\n' +
'const WEIGHT_DECAY = 0.02;';
if (!before.includes(constAnchor)) {
console.error('[patch-w1] 找不到 LEARNING_RATE 常量锚点');
process.exit(2);
}
let after = before.replace(constAnchor, constInjection);
// ==== 注入 2: learnFusionWeights L283 梯度更新循环 ====
const mainAnchor =
' // 梯度更新\n' +
' const newWeights = { ...weights };\n' +
' for (const key of Object.keys(newWeights)) {\n' +
' newWeights[key] += LEARNING_RATE * totalGradient[key];\n' +
' }';
const mainReplacement =
' // 梯度更新 + W1 权重衰减 (L2 正则化拉回 DEFAULT_WEIGHTS)\n' +
' const newWeights = { ...weights };\n' +
' for (const key of Object.keys(newWeights)) {\n' +
' newWeights[key] += LEARNING_RATE * totalGradient[key];\n' +
' newWeights[key] += WEIGHT_DECAY * (DEFAULT_WEIGHTS[key] - newWeights[key]);\n' +
' }';
if (!after.includes(mainAnchor)) {
console.error('[patch-w1] 找不到 learnFusionWeights 梯度更新锚点');
process.exit(3);
}
after = after.replace(mainAnchor, mainReplacement);
// ==== 注入 3: applyImplicitWeights L541 梯度更新循环 ====
const implicitAnchor =
' // 应用梯度更新(保守学习率,使用 LEARNING_RATE 的一半避免与 learnFusionWeights 叠加)\n' +
' const implicitLR = LEARNING_RATE * 0.5;\n' +
' const newWeights = { ...currentWeights };\n' +
' for (const key of Object.keys(newWeights)) {\n' +
' newWeights[key] += implicitLR * (totalGradient[key] || 0);\n' +
' }';
const implicitReplacement =
' // 应用梯度更新(保守学习率,使用 LEARNING_RATE 的一半避免与 learnFusionWeights 叠加)\n' +
' // W1: 同步使用 WEIGHT_DECAY 的一半,与 implicitLR 成比例\n' +
' const implicitLR = LEARNING_RATE * 0.5;\n' +
' const implicitDecay = WEIGHT_DECAY * 0.5;\n' +
' const newWeights = { ...currentWeights };\n' +
' for (const key of Object.keys(newWeights)) {\n' +
' newWeights[key] += implicitLR * (totalGradient[key] || 0);\n' +
' newWeights[key] += implicitDecay * (DEFAULT_WEIGHTS[key] - newWeights[key]);\n' +
' }';
if (!after.includes(implicitAnchor)) {
console.error('[patch-w1] 找不到 applyImplicitWeights 梯度更新锚点');
process.exit(4);
}
after = after.replace(implicitAnchor, implicitReplacement);
// ==== 校验 ====
if (after === before) {
console.error('[patch-w1] 替换无效果');
process.exit(5);
}
const decayCount = (after.match(/WEIGHT_DECAY/g) || []).length;
if (decayCount < 4) {
console.error('[patch-w1] WEIGHT_DECAY 引用数异常:', decayCount, '(期望 >= 4)');
process.exit(6);
}
// ==== 备份并原子写回 ====
const backup = TARGET + '.bak.w1.' + Date.now();
fs.writeFileSync(backup, before);
const _tmp = TARGET + '.tmp.' + process.pid;
fs.writeFileSync(_tmp, after);
fs.renameSync(_tmp, TARGET);
console.log('[patch-w1] ✓ 补丁应用成功');
console.log('[patch-w1] 备份: ' + path.basename(backup));
console.log('[patch-w1] 变更: 新增 WEIGHT_DECAY=0.02 + 两处梯度循环加衰减项');
console.log('[patch-w1] WEIGHT_DECAY 引用数: ' + decayCount);
}
try {
main();
} catch (e) {
console.error('[patch-w1] 异常:', e.message);
process.exit(99);
}