summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--slides/SDIL_project_intro.pptxbin0 -> 519129 bytes
-rw-r--r--slides/SDIL_project_intro.py633
-rw-r--r--slides/assets/.gitignore1
-rw-r--r--slides/qa-ledger.md8
-rw-r--r--slides/rendered/.gitignore2
-rw-r--r--slides/rendered/SDIL_project_intro.pdfbin0 -> 843127 bytes
-rw-r--r--slides/rendered/contact_sheet.pngbin0 -> 761169 bytes
-rw-r--r--slides/visual-contract.md14
8 files changed, 658 insertions, 0 deletions
diff --git a/slides/SDIL_project_intro.pptx b/slides/SDIL_project_intro.pptx
new file mode 100644
index 0000000..5efde76
--- /dev/null
+++ b/slides/SDIL_project_intro.pptx
Binary files differ
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()
diff --git a/slides/assets/.gitignore b/slides/assets/.gitignore
new file mode 100644
index 0000000..e33609d
--- /dev/null
+++ b/slides/assets/.gitignore
@@ -0,0 +1 @@
+*.png
diff --git a/slides/qa-ledger.md b/slides/qa-ledger.md
new file mode 100644
index 0000000..3375b35
--- /dev/null
+++ b/slides/qa-ledger.md
@@ -0,0 +1,8 @@
+# SDIL project introduction deck: render QA ledger
+
+| Issue | Artifact | Slide | Severity | Fix | Status |
+| --- | --- | ---: | --- | --- | --- |
+| Bottom text exceeded its cards | PPTX/PDF | 3 | medium | Shortened the attribution copy and increased both card heights | resolved |
+| Method title and three-stage visual used inconsistent step counts | PPTX/PDF | 3 | medium | Removed the numeric step count from the title | resolved |
+| Chinese font, formula glyph, chart and image rendering | PPTX/PDF | all | high | Exported through LibreOffice and inspected all seven rendered slides | resolved |
+| Claim attribution | PPTX/PDF | 5 and 7 | high | States that clean scaling comes from reciprocal KP and SDIL-specific evidence is traffic robustness | resolved |
diff --git a/slides/rendered/.gitignore b/slides/rendered/.gitignore
new file mode 100644
index 0000000..82ae779
--- /dev/null
+++ b/slides/rendered/.gitignore
@@ -0,0 +1,2 @@
+slide-[0-9]*.png
+slide3_check.png
diff --git a/slides/rendered/SDIL_project_intro.pdf b/slides/rendered/SDIL_project_intro.pdf
new file mode 100644
index 0000000..0378cdb
--- /dev/null
+++ b/slides/rendered/SDIL_project_intro.pdf
Binary files differ
diff --git a/slides/rendered/contact_sheet.png b/slides/rendered/contact_sheet.png
new file mode 100644
index 0000000..ad0ddc2
--- /dev/null
+++ b/slides/rendered/contact_sheet.png
Binary files differ
diff --git a/slides/visual-contract.md b/slides/visual-contract.md
new file mode 100644
index 0000000..86a7ffc
--- /dev/null
+++ b/slides/visual-contract.md
@@ -0,0 +1,14 @@
+# SDIL project introduction deck: visual contract
+
+- **Artifact:** Seven-slide, 16:9 project introduction deck.
+- **Audience:** Machine-learning researchers with no prior SDIL context.
+- **Core claim:** A local learner should use the soma-unpredicted component of a mixed apical signal; this residualization protects an inherited scalable credit path from predictable traffic.
+- **Reader questions:** Why is raw feedback insufficient? What exactly is new? Is the update BP-free? Is residualization necessary? Does the combined method scale? Which evidence is controlled or synthetic?
+- **Evidence layers:** problem and mechanism (slides 2–3), causal ablation (slide 4), standard-depth scaling and cost (slide 5), dynamical-task evidence (slide 6), attribution and boundary (slide 7).
+- **Source data:** `results/figs/figure3_innovation.png`, `results/figs/figure5_bci_v2.png`, `results/oral_a_dynamic_scaling_v2_gate.json`, and the evidence-bound manuscript.
+- **Statistics:** Five seeds for the controlled traffic and ResNet panels; six task clusters by five model seeds for the synthetic BCI panel. Values shown are audited means or paired outcomes already reported in the manuscript.
+- **Visual grammar:** Direct mechanism diagrams, two result figures, and editable line charts. SDIL is blue, reciprocal KP is green, DFA is orange, BP is dark gray.
+- **Exact method labels:** SDIL, raw apical signal, somatic prediction, innovation, reciprocal KP, BP, DFA, local update, neutral observation.
+- **Output:** Editable PPTX, PDF export, generation source, and rendered QA contact sheet.
+- **Claim boundary:** Clean scaling is attributed to reciprocal KP; SDIL-specific evidence is robustness under predictable mixed traffic and the synthetic BCI mechanism test.
+