EXPERIMENT / 配套代码

main.py

← 返回文章
下载代码

在项目根目录执行;下载包解压后,在解压目录执行 python3 main.py。

cd 'content/posts/AI-深度学习/嵌入式视觉模型进阶/29-量化、蒸馏与剪枝的实际取舍/code' && python3 main.py
"""运行逐通道量化误差、教师学生训练和通道裁剪对照,理解三种方法的不同作用。"""

import copy
import torch
from torch import nn
import torch.nn.functional as F


def main():
    torch.manual_seed(7)
    torch.set_num_threads(2)
    w = torch.randn(4, 8)
    scale = w.abs().amax(1, keepdim=True).clamp_min(1e-8) / 127
    q = torch.round(w / scale).clamp(-127, 127)
    restored = q * scale
    assert bool(((w - restored).abs() <= scale / 2 + 1e-6).all())
    print("per-channel simulated weight max error", float((w - restored).abs().max()))
    x = torch.randn(128, 4)
    y = (x[:, 0] + x[:, 1] > 0).long()
    teacher = nn.Sequential(nn.Linear(4, 16), nn.ReLU(), nn.Linear(16, 2))
    opt = torch.optim.Adam(teacher.parameters(), lr=0.03)
    for _ in range(60):
        opt.zero_grad()
        loss = F.cross_entropy(teacher(x), y)
        loss.backward()
        opt.step()
    teacher.eval()
    with torch.no_grad():
        soft = (teacher(x) / 2).softmax(1)
    student = nn.Sequential(nn.Linear(4, 4), nn.ReLU(), nn.Linear(4, 2))
    baseline = copy.deepcopy(student)
    opt = torch.optim.Adam(student.parameters(), lr=0.03)
    plain = torch.optim.Adam(baseline.parameters(), lr=0.03)
    initial = float(F.cross_entropy(student(x), y).detach())
    for _ in range(80):
        logits = student(x)
        loss = 0.5 * F.cross_entropy(logits, y) + 0.5 * 4 * F.kl_div(
            (logits / 2).log_softmax(1), soft, reduction="batchmean"
        )
        opt.zero_grad()
        loss.backward()
        opt.step()
        plain.zero_grad()
        F.cross_entropy(baseline(x), y).backward()
        plain.step()
    final = float(F.cross_entropy(student(x), y).detach())
    assert final < initial
    test = torch.randn(256, 4)
    truth = (test[:, 0] + test[:, 1] > 0).long()
    with torch.no_grad():
        print(
            "synthetic held-out accuracy teacher/student/hard-only",
            *[
                float((m(test).argmax(1) == truth).float().mean())
                for m in [teacher, student, baseline]
            ]
        )
    first = nn.Linear(4, 6)
    last = nn.Linear(6, 2)
    keep = torch.tensor([0, 2, 5])
    small_first = nn.Linear(4, 3)
    small_last = nn.Linear(3, 2)
    with torch.no_grad():
        small_first.weight.copy_(first.weight[keep])
        small_first.bias.copy_(first.bias[keep])
        small_last.weight.copy_(last.weight[:, keep])
        small_last.bias.copy_(last.bias)
        hidden = torch.relu(first(x))
        mask = torch.zeros(6)
        mask[keep] = 1
        reference = last(hidden * mask)
        actual = small_last(torch.relu(small_first(x)))
        torch.testing.assert_close(actual, reference, atol=1e-6, rtol=1e-5)
    print(
        "distillation train CE",
        initial,
        final,
        "channel-pruning index equivalence passed",
    )


if __name__ == "__main__":
    main()