"""【已验证 · 小 MLP 档】图像分类 + G 几何分区函数化。
一句话结论:
在下采样 MNIST(7×7)上训练的小 MLP,对其首层做 G 几何分区函数化(1 阶
低秩),还原后测试准确率**几乎零损失**——证明「小 MLP + G 几何」这一档的
函数化真实可用(本项目 P0 已实证「决策误差 <5%」)。
诚实说明(很重要):
- 本例验证的是「函数化不损精度」,不是「压缩」。小网络输出单元 n=16 太小,
函数化本身压缩不了多少(甚至 order-1 低秩项会反超原始参数量)。
- 0 阶质心虽能 n/K 压缩,但在「特征多样的首层」会灾难性掉点(本例实测
88%→27%)——因为质心是「训练后」才叠加的误差,没被训练吸收。这正是
后训练压缩(本库)与训练期压缩(QAT/蒸馏)的本质区别。
- 真正的大压缩要到 n 大的层(见 mnist_mlp_classification.py / CNN / Transformer)。
对应分型表第一行:小 MLP = 几何分区 G(本项目已实证「不损精度」)。
"""
from _common import (accuracy, device, downsample_images, load_mnist,
make_drop_guard, show_artifacts, show_value,
train_model)
import numpy as np
import torch
import torch.nn as nn
import nrfunc
class SmallMLP(nn.Module):
def __init__(self, d_in=49, h=16, d_out=10):
super().__init__()
self.fc1 = nn.Linear(d_in, h)
self.fc2 = nn.Linear(h, d_out)
def forward(self, x):
x = torch.relu(self.fc1(x))
return self.fc2(x)
def main():
dev = device()
print(f'设备:{dev}')
train_x, train_y, test_x, test_y = load_mnist()
train_x = downsample_images(train_x, size=7)
test_x = downsample_images(test_x, size=7)
train_x, train_y = train_x[:30000], train_y[:30000]
print('训练小 MLP(49→16→10):')
model = train_model(SmallMLP(), train_x, train_y, epochs=15, seed=0, dev=dev)
acc_before = accuracy(model, test_x, test_y, dev)
print(f'函数化前 · 测试准确率 = {acc_before * 100:.2f}%')
W = model.fc1.weight.detach().cpu().numpy().astype(np.float64)
guard = make_drop_guard(model, model.fc1, W.shape, test_x, test_y,
dev, acc_before, tol_pt=3.0)
alloc = nrfunc.auto_alloc(W, signal='G', eval_fn=guard, seed=0)
print(f"\n函数化(auto_alloc 自动选):decision={alloc['decision']} "
f"order={alloc['order']} K={alloc['K']} r={alloc['r']},"
f"内存省 {alloc['saving']*100:.1f}%"
f"(通过候选 {alloc['n_candidates']}/{alloc['n_tried']})")
if alloc['decision'] == 'skip':
print(f' → 该层被判跳过:{alloc["skipped_reason"]}')
return
res = alloc['result']
print('\n生成物(量化打包 + 分片存储):')
show_artifacts(res, bits=8, n_shards=4)
recon = nrfunc.reconstruct(res)
with torch.no_grad():
model.fc1.weight.copy_(torch.as_tensor(recon, dtype=torch.float32))
acc_after = accuracy(model, test_x, test_y, dev)
print(f'\n部署后 · 测试准确率 = {acc_after * 100:.2f}% '
f'(掉 {acc_before * 100 - acc_after * 100:.2f}pt)')
print('\n价值对比(fc1 层 16×49,函数化前 vs 后):')
show_value(W, res, test_x[:256])
print('注:此层 n=16 太小,函数化本身无参数压缩,节省主要来自 32→8bit 量化;'
'大压缩见 MNIST MLP / CNN / Transformer 档。')
if __name__ == '__main__':
main()