diff options
| author | YurenHao0426 <Blackhao0426@gmail.com> | 2026-08-10 11:04:06 -0500 |
|---|---|---|
| committer | YurenHao0426 <Blackhao0426@gmail.com> | 2026-08-10 11:04:06 -0500 |
| commit | 52a734aa8736cd8edf95f3fbf595014f7559e6f5 (patch) | |
| tree | 58b1cd238e8da50752813425c6159a27050a73c5 /slides/SDIL_project_intro.py | |
| parent | 0d167497b75031fae67b71fa1e7ab12e57c761bb (diff) | |
docs: add context-free SDIL project deck
Diffstat (limited to 'slides/SDIL_project_intro.py')
| -rw-r--r-- | slides/SDIL_project_intro.py | 633 |
1 files changed, 633 insertions, 0 deletions
diff --git a/slides/SDIL_project_intro.py b/slides/SDIL_project_intro.py new file mode 100644 index 0000000..19e49c5 --- /dev/null +++ b/slides/SDIL_project_intro.py @@ -0,0 +1,633 @@ +#!/usr/bin/env python3 +"""Build the context-free SDIL project introduction deck. + +All quantitative claims come from tracked project result files. The deck uses +only completed full-method experiments: the controlled traffic panel, the +standard ResNet-20/32/56 panel, and the calibrated synthetic BCI confirmation. +""" + +from pathlib import Path +from typing import Iterable, Sequence + +from PIL import Image +from pptx import Presentation +from pptx.dml.color import RGBColor +from pptx.enum.shapes import MSO_CONNECTOR, MSO_SHAPE +from pptx.enum.text import MSO_ANCHOR, PP_ALIGN +from pptx.util import Inches, Pt + + +ROOT = Path(__file__).resolve().parents[1] +OUT_DIR = ROOT / "slides" +ASSET_DIR = OUT_DIR / "assets" +OUT_PATH = OUT_DIR / "SDIL_project_intro.pptx" + +FIG3 = ROOT / "results/figs/figure3_innovation.png" +FIG5 = ROOT / "results/figs/figure5_bci_v2.png" + +SLIDE_W = 13.333 +SLIDE_H = 7.5 + +FONT = "Noto Sans CJK SC" +FONT_LATIN = "DejaVu Sans" + +BG = "F7F9FB" +PAPER = "FFFFFF" +INK = "17212B" +MUTED = "5D6975" +LIGHT = "DDE4EA" +GRID = "D9E0E6" +BLUE = "0072B2" +BLUE_LIGHT = "DCEFF9" +GREEN = "009E73" +GREEN_LIGHT = "DDF3EC" +ORANGE = "E69F00" +ORANGE_LIGHT = "FBEBC9" +RED = "D55E00" +GRAY = "777777" +DARK_GRAY = "3F4850" + + +def rgb(hex_color: str) -> RGBColor: + return RGBColor.from_string(hex_color) + + +def add_text( + slide, + text: str, + x: float, + y: float, + w: float, + h: float, + *, + size: float = 18, + color: str = INK, + bold: bool = False, + align=PP_ALIGN.LEFT, + valign=MSO_ANCHOR.TOP, + margin: float = 0.03, + font: str = FONT, + line_spacing: float = 1.0, +): + box = slide.shapes.add_textbox(Inches(x), Inches(y), Inches(w), Inches(h)) + tf = box.text_frame + tf.clear() + tf.word_wrap = True + tf.margin_left = Inches(margin) + tf.margin_right = Inches(margin) + tf.margin_top = Inches(margin) + tf.margin_bottom = Inches(margin) + tf.vertical_anchor = valign + p = tf.paragraphs[0] + p.alignment = align + p.line_spacing = line_spacing + run = p.add_run() + run.text = text + run.font.name = font + run.font.size = Pt(size) + run.font.bold = bold + run.font.color.rgb = rgb(color) + return box + + +def add_rich_text( + slide, + runs: Sequence[tuple[str, float, str, bool]], + x: float, + y: float, + w: float, + h: float, + *, + align=PP_ALIGN.LEFT, + valign=MSO_ANCHOR.TOP, + margin: float = 0.03, +): + box = slide.shapes.add_textbox(Inches(x), Inches(y), Inches(w), Inches(h)) + tf = box.text_frame + tf.clear() + tf.word_wrap = True + tf.margin_left = Inches(margin) + tf.margin_right = Inches(margin) + tf.margin_top = Inches(margin) + tf.margin_bottom = Inches(margin) + tf.vertical_anchor = valign + p = tf.paragraphs[0] + p.alignment = align + for text, size, color, bold in runs: + run = p.add_run() + run.text = text + run.font.name = FONT + run.font.size = Pt(size) + run.font.bold = bold + run.font.color.rgb = rgb(color) + return box + + +def add_bullets( + slide, + items: Iterable[str], + x: float, + y: float, + w: float, + h: float, + *, + size: float = 17, + color: str = INK, + bullet_color: str = BLUE, + gap: float = 7, +): + box = slide.shapes.add_textbox(Inches(x), Inches(y), Inches(w), Inches(h)) + tf = box.text_frame + tf.clear() + tf.word_wrap = True + tf.margin_left = Inches(0.03) + tf.margin_right = Inches(0.03) + tf.margin_top = Inches(0.02) + tf.margin_bottom = Inches(0.02) + for idx, item in enumerate(items): + p = tf.paragraphs[0] if idx == 0 else tf.add_paragraph() + p.level = 0 + p.space_after = Pt(gap) + p.line_spacing = 1.05 + p.text = "" + marker = p.add_run() + marker.text = "● " + marker.font.name = FONT + marker.font.size = Pt(max(size - 4, 10)) + marker.font.color.rgb = rgb(bullet_color) + body = p.add_run() + body.text = item + body.font.name = FONT + body.font.size = Pt(size) + body.font.color.rgb = rgb(color) + return box + + +def add_rect( + slide, + x: float, + y: float, + w: float, + h: float, + *, + fill: str = PAPER, + line: str = LIGHT, + radius: bool = True, + line_width: float = 1.0, +): + kind = MSO_SHAPE.ROUNDED_RECTANGLE if radius else MSO_SHAPE.RECTANGLE + shape = slide.shapes.add_shape(kind, Inches(x), Inches(y), Inches(w), Inches(h)) + shape.fill.solid() + shape.fill.fore_color.rgb = rgb(fill) + shape.line.color.rgb = rgb(line) + shape.line.width = Pt(line_width) + return shape + + +def add_line( + slide, + x1: float, + y1: float, + x2: float, + y2: float, + *, + color: str = MUTED, + width: float = 1.5, + dash=None, +): + line = slide.shapes.add_connector( + MSO_CONNECTOR.STRAIGHT, + Inches(x1), + Inches(y1), + Inches(x2), + Inches(y2), + ) + line.line.color.rgb = rgb(color) + line.line.width = Pt(width) + if dash is not None: + line.line.dash_style = dash + return line + + +def add_arrow(slide, x1, y1, x2, y2, *, color=MUTED, width=2.0): + line = add_line(slide, x1, y1, x2, y2, color=color, width=width) + line.line.end_arrowhead = True + return line + + +def add_circle(slide, cx, cy, r, *, fill=BLUE, line=PAPER, line_width=1.0): + shape = slide.shapes.add_shape( + MSO_SHAPE.OVAL, + Inches(cx - r), + Inches(cy - r), + Inches(2 * r), + Inches(2 * r), + ) + shape.fill.solid() + shape.fill.fore_color.rgb = rgb(fill) + shape.line.color.rgb = rgb(line) + shape.line.width = Pt(line_width) + return shape + + +def add_title(slide, title: str, number: int, subtitle: str | None = None): + add_text(slide, title, 0.55, 0.28, 11.9, 0.54, size=26, bold=True) + add_text(slide, f"{number:02d}", 12.35, 0.34, 0.45, 0.3, size=11, color=MUTED, align=PP_ALIGN.RIGHT) + add_line(slide, 0.55, 0.91, 12.78, 0.91, color=LIGHT, width=1.0) + if subtitle: + add_text(slide, subtitle, 0.58, 1.00, 12.0, 0.35, size=13.5, color=MUTED) + + +def add_footer(slide, text: str): + add_text(slide, text, 0.58, 7.18, 12.15, 0.2, size=8.5, color=MUTED, valign=MSO_ANCHOR.MIDDLE) + + +def add_badge(slide, text, x, y, w, *, fill=BLUE_LIGHT, color=BLUE, size=13): + add_rect(slide, x, y, w, 0.43, fill=fill, line=fill, radius=True) + add_text(slide, text, x + 0.05, y + 0.02, w - 0.1, 0.35, size=size, color=color, bold=True, align=PP_ALIGN.CENTER, valign=MSO_ANCHOR.MIDDLE) + + +def prepare_crops(): + ASSET_DIR.mkdir(parents=True, exist_ok=True) + img = Image.open(FIG5) + w, h = img.size + crops = { + "figure5a_actor_critic.png": (0, 0, w // 2, h // 2), + "figure5d_outcome.png": (w // 2, h // 2, w, h), + } + for name, box in crops.items(): + img.crop(box).save(ASSET_DIR / name) + + +def add_custom_line_chart( + slide, + x: float, + y: float, + w: float, + h: float, + categories: Sequence[str], + series: Sequence[tuple[str, Sequence[float], str]], + *, + ymin: float, + ymax: float, + yticks: Sequence[float], + title: str, + show_legend: bool = True, +): + add_rect(slide, x, y, w, h, fill=PAPER, line=LIGHT, radius=True) + add_text(slide, title, x + 0.25, y + 0.12, w - 0.5, 0.35, size=15, bold=True) + left = x + 0.60 + right = x + w - 0.25 + top = y + 0.72 + bottom = y + h - 0.65 + + def px(i): + if len(categories) == 1: + return (left + right) / 2 + return left + i * (right - left) / (len(categories) - 1) + + def py(v): + return bottom - (v - ymin) * (bottom - top) / (ymax - ymin) + + for tick in yticks: + yy = py(tick) + add_line(slide, left, yy, right, yy, color=GRID, width=0.8) + add_text(slide, f"{tick:g}", x + 0.08, yy - 0.12, 0.42, 0.24, size=9.5, color=MUTED, align=PP_ALIGN.RIGHT, valign=MSO_ANCHOR.MIDDLE) + + add_line(slide, left, top, left, bottom, color=DARK_GRAY, width=1.1) + add_line(slide, left, bottom, right, bottom, color=DARK_GRAY, width=1.1) + for i, cat in enumerate(categories): + xx = px(i) + add_line(slide, xx, bottom, xx, bottom + 0.07, color=DARK_GRAY, width=1.0) + add_text(slide, cat, xx - 0.38, bottom + 0.10, 0.76, 0.28, size=9.5, align=PP_ALIGN.CENTER) + + for name, values, color in series: + points = [(px(i), py(v)) for i, v in enumerate(values)] + for (x1, y1), (x2, y2) in zip(points, points[1:]): + add_line(slide, x1, y1, x2, y2, color=color, width=2.3) + for xx, yy in points: + add_circle(slide, xx, yy, 0.065, fill=color, line=PAPER, line_width=0.8) + + if show_legend: + legend_y = y + h - 0.27 + total = len(series) + slot = (w - 0.55) / total + for i, (name, _, color) in enumerate(series): + lx = x + 0.30 + i * slot + add_line(slide, lx, legend_y + 0.08, lx + 0.26, legend_y + 0.08, color=color, width=2.3) + add_circle(slide, lx + 0.13, legend_y + 0.08, 0.04, fill=color, line=color) + add_text(slide, name, lx + 0.32, legend_y - 0.04, slot - 0.34, 0.25, size=9.2, color=INK, valign=MSO_ANCHOR.MIDDLE) + + +def build_deck(): + prepare_crops() + prs = Presentation() + prs.slide_width = Inches(SLIDE_W) + prs.slide_height = Inches(SLIDE_H) + blank = prs.slide_layouts[6] + + def new_slide(): + slide = prs.slides.add_slide(blank) + background = slide.background + background.fill.solid() + background.fill.fore_color.rgb = rgb(BG) + return slide + + # Slide 1: title and one-minute summary. + slide = new_slide() + add_text(slide, "SDIL", 0.62, 0.60, 3.0, 0.72, size=38, color=BLUE, bold=True) + add_text(slide, "让局部学习使用意外的反馈", 0.62, 1.28, 7.1, 0.70, size=29, bold=True) + add_text( + slide, + "Somato-Dendritic Innovation Learning\n受 Francioni et al.(Harnett lab)Nature 2026 启发", + 0.65, + 2.10, + 6.5, + 0.88, + size=16, + color=MUTED, + line_spacing=1.08, + ) + + add_rect(slide, 7.55, 0.72, 5.05, 2.42, fill=PAPER, line=LIGHT, radius=True, line_width=1.2) + add_text(slide, "核心操作", 7.90, 1.02, 1.4, 0.35, size=14, color=MUTED, bold=True) + add_text(slide, "teaching signal", 7.92, 1.48, 1.75, 0.34, size=16, color=DARK_GRAY, align=PP_ALIGN.RIGHT) + add_text(slide, "=", 9.76, 1.45, 0.35, 0.40, size=22, color=MUTED, bold=True, align=PP_ALIGN.CENTER) + add_text(slide, "apical feedback", 10.12, 1.31, 2.05, 0.33, size=15.5, color=INK, bold=True, align=PP_ALIGN.CENTER) + add_text(slide, "− expected from soma", 10.12, 1.75, 2.05, 0.33, size=15.5, color=BLUE, bold=True, align=PP_ALIGN.CENTER) + add_text(slide, "每个神经元各自减去正常 soma–dendrite 耦合", 7.90, 2.43, 4.25, 0.38, size=13.5, color=MUTED, align=PP_ALIGN.CENTER) + + cards = [ + ("BP-free", "学习器内无反向计算图", BLUE_LIGHT, BLUE), + ("92.76%", "ResNet-56 + 4× predictable traffic", GREEN_LIGHT, GREEN), + ("1.33×", "BP MAC estimate", ORANGE_LIGHT, RED), + ] + for i, (big, small, fill, color) in enumerate(cards): + x = 0.65 + i * 4.12 + add_rect(slide, x, 4.17, 3.75, 1.47, fill=fill, line=fill, radius=True) + add_text(slide, big, x + 0.18, 4.36, 3.39, 0.48, size=25, color=color, bold=True, align=PP_ALIGN.CENTER) + add_text(slide, small, x + 0.18, 4.91, 3.39, 0.38, size=12.3, color=INK, align=PP_ALIGN.CENTER) + add_text( + slide, + "一句话:可扩展的局部信用路径负责把方向送到各层;SDIL 负责从混合反馈中提取可用于学习的部分。", + 0.82, + 6.30, + 11.7, + 0.50, + size=18, + color=INK, + bold=True, + align=PP_ALIGN.CENTER, + ) + add_footer(slide, "Project introduction · ICLR 2027 work in progress · headline values use completed full experiments") + + # Slide 2: scientific origin and problem. + slide = new_slide() + add_title(slide, "问题:反馈通道不只传教学信号", 2, "把全部 apical activity 当作 error,会把正常状态与上下文一起写入权重。") + + add_rect(slide, 0.62, 1.52, 5.70, 4.88, fill=PAPER, line=LIGHT) + add_text(slide, "局部学习常见假设", 0.95, 1.78, 2.6, 0.38, size=17, bold=True) + add_rect(slide, 0.98, 2.42, 1.55, 0.76, fill=BLUE_LIGHT, line=BLUE) + add_text(slide, "teaching\nsignal", 1.10, 2.52, 1.31, 0.50, size=14, color=BLUE, bold=True, align=PP_ALIGN.CENTER, valign=MSO_ANCHOR.MIDDLE) + add_rect(slide, 0.98, 3.62, 1.55, 0.76, fill="EFF1F3", line=GRAY) + add_text(slide, "ordinary\ntraffic", 1.10, 3.72, 1.31, 0.50, size=14, color=DARK_GRAY, bold=True, align=PP_ALIGN.CENTER, valign=MSO_ANCHOR.MIDDLE) + add_arrow(slide, 2.55, 2.80, 3.55, 3.25, color=BLUE, width=2.1) + add_arrow(slide, 2.55, 4.00, 3.55, 3.55, color=GRAY, width=2.1) + add_rect(slide, 3.58, 2.72, 2.10, 1.36, fill="F3F5F6", line=DARK_GRAY) + add_text(slide, "raw apical\nfeedback", 3.76, 2.93, 1.74, 0.72, size=18, bold=True, align=PP_ALIGN.CENTER, valign=MSO_ANCHOR.MIDDLE) + add_arrow(slide, 4.63, 4.10, 4.63, 4.85, color=RED, width=2.4) + add_rect(slide, 3.48, 4.90, 2.30, 0.84, fill="F8E6DE", line=RED) + add_text(slide, "update direction\nmay rotate", 3.65, 5.04, 1.96, 0.52, size=14.2, color=RED, bold=True, align=PP_ALIGN.CENTER, valign=MSO_ANCHOR.MIDDLE) + + add_rect(slide, 6.58, 1.52, 6.12, 4.88, fill=PAPER, line=LIGHT) + add_text(slide, "Nature 2026 提供了更具体的对象", 6.92, 1.78, 4.8, 0.38, size=17, bold=True) + # Simple soma-dendrite regression sketch. + plot_x, plot_y, plot_w, plot_h = 7.08, 2.60, 2.35, 2.30 + add_line(slide, plot_x, plot_y + plot_h, plot_x + plot_w, plot_y + plot_h, color=DARK_GRAY, width=1.2) + add_line(slide, plot_x, plot_y + plot_h, plot_x, plot_y, color=DARK_GRAY, width=1.2) + add_line(slide, plot_x + 0.20, plot_y + 1.97, plot_x + 2.12, plot_y + 0.35, color=GRAY, width=2.0) + points = [(0.35, 1.74), (0.72, 1.41), (1.03, 1.29), (1.42, 0.88), (1.82, 0.61)] + for dx, dy in points: + add_circle(slide, plot_x + dx, plot_y + dy, 0.055, fill=GRAY, line=PAPER) + expected_x = plot_x + 1.45 + expected_y = plot_y + 0.90 + observed_y = plot_y + 0.35 + add_circle(slide, expected_x, observed_y, 0.075, fill=BLUE, line=PAPER) + add_line(slide, expected_x, expected_y, expected_x, observed_y, color=BLUE, width=2.8) + add_text(slide, "residual", expected_x + 0.12, observed_y + 0.10, 0.80, 0.25, size=11.5, color=BLUE, bold=True) + add_text(slide, "soma", plot_x + 0.88, plot_y + plot_h + 0.18, 0.80, 0.25, size=10.5, color=MUTED, align=PP_ALIGN.CENTER) + add_text(slide, "dendrite", plot_x - 0.68, plot_y + 0.93, 0.70, 0.25, size=10.5, color=MUTED, align=PP_ALIGN.CENTER) + + add_bullets( + slide, + [ + "先拟合每个神经元正常的 soma–dendrite 关系", + "残差与 soma 明显去相关", + "残差携带 outcome 与神经元特异的有符号任务信息", + ], + 9.75, + 2.56, + 2.62, + 2.60, + size=14.3, + gap=8, + ) + add_rect(slide, 6.93, 5.34, 5.42, 0.68, fill=BLUE_LIGHT, line=BLUE_LIGHT) + add_text(slide, "算法问题:学习是否也应使用 residual,而不是 raw activity?", 7.10, 5.50, 5.08, 0.33, size=15.2, color=BLUE, bold=True, align=PP_ALIGN.CENTER) + add_footer(slide, "Source: Francioni et al., “Vectorized instructive signals in cortical dendrites,” Nature (2026).") + + # Slide 3: method. + slide = new_slide() + add_title(slide, "方法:预测正常耦合,再用残差完成局部更新", 3, "SDIL 改变教学变量;它不要求重新发明整条反馈传播路径。") + + steps = [ + ("1", "Neutral observation", "âₗ = Pₗ(hₗ)", "估计同一神经元的正常耦合", "EFF1F3", DARK_GRAY), + ("2", "Innovation", "rₗ = aₗ − âₗ", "只保留 soma 无法预测的部分", BLUE_LIGHT, BLUE), + ("3", "Local plasticity", "ΔWₗ = η(rₗ ⊙ φ′(uₗ))hₗ₋₁ᵀ", "pre × post gain × dendritic residual", GREEN_LIGHT, GREEN), + ] + for i, (num, label, eq, desc, fill, color) in enumerate(steps): + x = 0.67 + i * 4.18 + add_rect(slide, x, 1.70, 3.76, 2.38, fill=fill, line=color, radius=True, line_width=1.4) + add_circle(slide, x + 0.35, 2.04, 0.20, fill=color, line=color) + add_text(slide, num, x + 0.23, 1.90, 0.24, 0.28, size=13, color=PAPER, bold=True, align=PP_ALIGN.CENTER, valign=MSO_ANCHOR.MIDDLE) + add_text(slide, label, x + 0.68, 1.83, 2.72, 0.36, size=16, color=color, bold=True) + add_text(slide, eq, x + 0.22, 2.48, 3.32, 0.53, size=19, color=INK, bold=True, align=PP_ALIGN.CENTER, valign=MSO_ANCHOR.MIDDLE, font=FONT_LATIN) + add_text(slide, desc, x + 0.30, 3.34, 3.16, 0.43, size=13.5, color=MUTED, align=PP_ALIGN.CENTER) + if i < 2: + add_arrow(slide, x + 3.82, 2.86, x + 4.10, 2.86, color=MUTED, width=2.0) + + add_rect(slide, 0.67, 4.42, 5.96, 2.02, fill=PAPER, line=LIGHT) + add_text(slide, "学习器内部为什么是 BP-free", 0.98, 4.68, 3.2, 0.35, size=17, bold=True) + add_bullets( + slide, + [ + "更新只读取 pre-synaptic activity、local gain 和本细胞 residual", + "无 reverse-mode autograd;不复制 forward-weight transpose", + ], + 0.98, + 5.12, + 5.25, + 1.12, + size=13.5, + gap=5, + ) + + add_rect(slide, 6.88, 4.42, 5.78, 2.02, fill=PAPER, line=LIGHT) + add_text(slide, "信用路径是可替换的已有组件", 7.19, 4.68, 3.8, 0.35, size=17, bold=True) + add_bullets( + slide, + [ + "Small nets: node-perturbation feedback vectorizer", + "ResNet: reciprocal KP plasticity", + "两者均为已有组件;SDIL 的贡献是 residualization", + ], + 7.19, + 5.10, + 5.06, + 1.18, + size=13.2, + bullet_color=GREEN, + gap=3, + ) + add_footer(slide, "Credit substrates: Lansdell et al. (ICLR 2020); reciprocal plasticity: Akrout et al. (NeurIPS 2019).") + + # Slide 4: controlled causal evidence. + slide = new_slide() + add_title(slide, "关键消融:减法改变了方向,不只是信号大小", 4, "强 soma-predictable traffic 下,raw 与 norm-matched raw 都失效;innovation 保留学习方向。") + slide.shapes.add_picture(str(FIG3), Inches(0.40), Inches(1.38), width=Inches(12.52), height=Inches(4.89)) + add_rect(slide, 1.02, 6.36, 11.25, 0.57, fill=BLUE_LIGHT, line=BLUE_LIGHT) + add_rich_text( + slide, + [ + ("ρ = 0.5:", 15, INK, True), + ("raw 10.38%", 15, DARK_GRAY, True), + (" · norm-matched raw 10.31%", 15, ORANGE, True), + (" · innovation 97.35%", 15, BLUE, True), + (" (5 seeds)", 13, MUTED, False), + ], + 1.18, + 6.47, + 10.92, + 0.33, + align=PP_ALIGN.CENTER, + valign=MSO_ANCHOR.MIDDLE, + ) + add_footer(slide, "Controlled MNIST traffic intervention. Norm matching uses the same per-example update magnitude as innovation.") + + # Slide 5: standard-scale evidence. + slide = new_slide() + add_title(slide, "标准 ResNet:扩展性来自 KP,抗混合流量来自 SDIL", 5, "60 个 CIFAR-10 endpoints;每个深度 5 seeds,200 epochs,无 depth-specific tuning。") + + categories = ["R20", "R32", "R56"] + bp = [91.624, 92.302, 92.632] + dfa = [31.878, 32.684, 30.850] + kp = [91.388, 92.332, 92.670] + sdil = [91.584, 92.254, 92.760] + add_custom_line_chart( + slide, + 0.62, + 1.48, + 5.92, + 4.82, + categories, + [("BP", bp, DARK_GRAY), ("DFA", dfa, ORANGE), ("clean KP", kp, GREEN), ("SDIL + traffic", sdil, BLUE)], + ymin=25, + ymax=95, + yticks=[30, 50, 70, 90], + title="完整尺度:固定 DFA 随深度仍处于约 31%", + ) + add_custom_line_chart( + slide, + 6.80, + 1.48, + 5.92, + 4.82, + categories, + [("BP", bp, DARK_GRAY), ("clean KP", kp, GREEN), ("SDIL + 4× traffic", sdil, BLUE)], + ymin=91.0, + ymax=93.0, + yticks=[91.0, 91.5, 92.0, 92.5, 93.0], + title="近 BP 放大:SDIL 在 4× traffic 下跟随 clean KP", + ) + add_badge(slide, "SDIL: 91.58 → 92.76%", 0.88, 6.45, 3.42, fill=BLUE_LIGHT, color=BLUE) + add_badge(slide, "1.31–1.33× BP MAC", 4.48, 6.45, 3.42, fill=ORANGE_LIGHT, color=RED) + add_badge(slide, "0 loss queries · 1 neutral obs/example", 8.08, 6.45, 4.25, fill=GREEN_LIGHT, color=GREEN, size=12.2) + add_footer(slide, "Source: complete 60-record ResNet panel. SDIL-specific claim is robustness under traffic, not clean superiority over KP.") + + # Slide 6: dynamical task evidence. + slide = new_slide() + add_title(slide, "动态任务:residual 携带 outcome surprise,而不只是分类误差", 6, "独立的 synthetic BCI actor–critic:6 task clusters × 5 models,所有学习更新均为手写局部规则。") + p_a = ASSET_DIR / "figure5a_actor_critic.png" + p_d = ASSET_DIR / "figure5d_outcome.png" + slide.shapes.add_picture(str(p_a), Inches(0.57), Inches(1.46), width=Inches(5.95), height=Inches(4.42)) + slide.shapes.add_picture(str(p_d), Inches(6.80), Inches(1.46), width=Inches(5.95), height=Inches(4.42)) + add_badge(slide, "100% final task success", 0.93, 6.07, 3.44, fill=BLUE_LIGHT, color=BLUE) + add_badge(slide, "99.83% terminal outcome decoding", 4.73, 6.07, 3.82, fill=GREEN_LIGHT, color=GREEN, size=12.4) + add_badge(slide, "outcome lesion: −0.400 separation", 8.92, 6.07, 3.50, fill=ORANGE_LIGHT, color=RED, size=12.0) + add_text(slide, "范围:这是机制验证用的合成任务;terminal reward 被直接提供,不是皮层数据。", 0.82, 6.66, 11.70, 0.32, size=13.5, color=MUTED, align=PP_ALIGN.CENTER) + add_footer(slide, "Source: complete untouched calibrated BCI confirmation; outcome labels and exact roles are diagnostic-only.") + + # Slide 7: positioning and next decisive evidence. + slide = new_slide() + add_title(slide, "当前最准确的定位:可扩展 local credit 的混合信号分离模块", 7) + + # Pipeline strip. + pipeline = [ + ("existing credit path", "KP / learned feedback", "EFF1F3", DARK_GRAY, 0.66, 2.28), + ("mixed apical channel", "instruction + ordinary traffic", ORANGE_LIGHT, RED, 3.33, 2.76), + ("SDIL", "subtract soma-predictable part", BLUE_LIGHT, BLUE, 6.52, 2.52), + ("local update", "eligibility × innovation", GREEN_LIGHT, GREEN, 9.48, 2.80), + ] + for i, (head, sub, fill, color, x, w) in enumerate(pipeline): + add_rect(slide, x, 1.35, w, 0.98, fill=fill, line=color, radius=True, line_width=1.2) + add_text(slide, head, x + 0.12, 1.51, w - 0.24, 0.28, size=14.2, color=color, bold=True, align=PP_ALIGN.CENTER) + add_text(slide, sub, x + 0.12, 1.87, w - 0.24, 0.24, size=10.8, color=INK, align=PP_ALIGN.CENTER) + if i < len(pipeline) - 1: + next_x = pipeline[i + 1][4] + add_arrow(slide, x + w + 0.06, 1.84, next_x - 0.07, 1.84, color=MUTED, width=1.8) + + columns = [ + (0.66, 3.05, 3.80, "已经建立", BLUE, BLUE_LIGHT, [ + "可预测 traffic 会旋转 raw local update", + "per-neuron residualization 在受控干扰下是 load-bearing 的", + "组合方法在 ResNet-20/32/56 上保持近 BP accuracy", + ]), + (4.76, 3.05, 3.80, "真正的新东西", GREEN, GREEN_LIGHT, [ + "把 Harnett residual 直接定义成 teaching variable", + "neutral-period predictor + dynamic local projection", + "机制、方向、成本和动态 outcome 的联合证据", + ]), + (8.86, 3.05, 3.80, "仍然缺少", RED, ORANGE_LIGHT, [ + "自然任务中不可预先测量的 mixed feedback", + "真实硬件或真实神经数据上的必要性", + "证明 SDIL 的价值不能由 clean KP 单独解释", + ]), + ] + for x, y, w, head, color, fill, items in columns: + add_rect(slide, x, y, w, 2.68, fill=PAPER, line=LIGHT) + add_rect(slide, x, y, w, 0.56, fill=fill, line=fill) + add_text(slide, head, x + 0.18, y + 0.10, w - 0.36, 0.32, size=16, color=color, bold=True, align=PP_ALIGN.CENTER) + add_bullets(slide, items, x + 0.25, y + 0.78, w - 0.50, 1.70, size=13.2, bullet_color=color, gap=7) + + add_rect(slide, 1.06, 6.10, 11.20, 0.67, fill=BLUE_LIGHT, line=BLUE_LIGHT) + add_text( + slide, + "下一项决定性证据:在自然或硬件产生的 state-dependent mixed traffic 中,raw credit 失败,而 SDIL 无需 oracle calibration 即可恢复。", + 1.27, + 6.25, + 10.78, + 0.34, + size=14.6, + color=BLUE, + bold=True, + align=PP_ALIGN.CENTER, + ) + add_footer(slide, "Bounded conclusion: strong controlled mechanism + standard-scale compatibility; natural mixed-traffic necessity remains open.") + + OUT_DIR.mkdir(parents=True, exist_ok=True) + prs.save(OUT_PATH) + print(OUT_PATH) + + +if __name__ == "__main__": + build_deck() |
