|
@@ -0,0 +1,248 @@
|
|
|
|
|
+#!/usr/bin/env python3
|
|
|
|
|
+"""编译单日课程(周六 10:00 批处理)"""
|
|
|
|
|
+
|
|
|
|
|
+import argparse
|
|
|
|
|
+import os
|
|
|
|
|
+import sys
|
|
|
|
|
+from pathlib import Path
|
|
|
|
|
+from jinja2 import Template
|
|
|
|
|
+import yaml
|
|
|
|
|
+
|
|
|
|
|
+# 添加项目根目录到路径
|
|
|
|
|
+PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent.parent
|
|
|
|
|
+sys.path.insert(0, str(PROJECT_ROOT))
|
|
|
|
|
+
|
|
|
|
|
+def load_config() -> dict:
|
|
|
|
|
+ """加载配置"""
|
|
|
|
|
+ config_path = PROJECT_ROOT / "skills" / "mathlab" / "config.yaml"
|
|
|
|
|
+ with open(config_path, "r", encoding="utf-8") as f:
|
|
|
|
|
+ return yaml.safe_load(f)
|
|
|
|
|
+
|
|
|
|
|
+def generate_symbol_decoder(topic: str) -> str:
|
|
|
|
|
+ """生成符号解码字典(示例:感知机)"""
|
|
|
|
|
+ return r"""
|
|
|
|
|
+ <div class="symbol-map">
|
|
|
|
|
+ <strong>$w$</strong> → <code>self.weights</code> (权重向量,shape: [d])
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="symbol-map">
|
|
|
|
|
+ <strong>$b$</strong> → <code>self.bias</code> (偏置标量,shape: [])
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="symbol-map">
|
|
|
|
|
+ <strong>$x$</strong> → <code>input_tensor</code> (输入样本,shape: [d])
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="symbol-map">
|
|
|
|
|
+ <strong>$y$</strong> → <code>label</code> (标签,值域:{-1, +1})
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="symbol-map">
|
|
|
|
|
+ <strong>$\sign(\cdot)$</strong> → <code>np.sign()</code> (符号函数)
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="symbol-map">
|
|
|
|
|
+ <strong>$\xi$</strong> → <code>margin</code> (间隔,用于更新步长)
|
|
|
|
|
+ </div>
|
|
|
|
|
+"""
|
|
|
|
|
+
|
|
|
|
|
+def generate_math_derivation(topic: str) -> str:
|
|
|
|
|
+ """生成数学推导(示例:感知机)"""
|
|
|
|
|
+ return r"""
|
|
|
|
|
+### 感知机预测函数
|
|
|
|
|
+
|
|
|
|
|
+$$f(x) = w \cdot x + b$$
|
|
|
|
|
+
|
|
|
|
|
+其中 $\cdot$ 表示向量点积。
|
|
|
|
|
+
|
|
|
|
|
+### 损失函数( hinge loss 的简化形式)
|
|
|
|
|
+
|
|
|
|
|
+$$L(w, b) = -\sum_{x_i \in M} y_i (w \cdot x_i + b)$$
|
|
|
|
|
+
|
|
|
|
|
+$M$ 是误分类点集合。
|
|
|
|
|
+
|
|
|
|
|
+### 梯度下降更新规则
|
|
|
|
|
+
|
|
|
|
|
+$$w \leftarrow w + \eta \cdot y_i \cdot x_i$$
|
|
|
|
|
+
|
|
|
|
|
+$$b \leftarrow b + \eta \cdot y_i$$
|
|
|
|
|
+
|
|
|
|
|
+其中 $\eta$ 是学习率。
|
|
|
|
|
+"""
|
|
|
|
|
+
|
|
|
|
|
+def generate_html(day: int, topic: str, config: dict) -> str:
|
|
|
|
|
+ """生成课程 HTML"""
|
|
|
|
|
+ template_path = PROJECT_ROOT / "skills" / "mathlab" / "templates" / "course_template.html"
|
|
|
|
|
+
|
|
|
|
|
+ with open(template_path, "r", encoding="utf-8") as f:
|
|
|
|
|
+ template_content = f.read()
|
|
|
|
|
+
|
|
|
|
|
+ template = Template(template_content)
|
|
|
|
|
+
|
|
|
|
|
+ html_content = template.render(
|
|
|
|
|
+ day=day,
|
|
|
|
|
+ topic=topic,
|
|
|
|
|
+ technical_debt="之前的算法无法处理线性不可分数据,导致泛化能力差。感知机通过引入决策边界,将分类问题转化为优化问题。",
|
|
|
|
|
+ visual_intuition="想象一条直线(或超平面)将两类点分开。权重向量 $w$ 决定直线的方向,偏置 $b$ 决定直线的位置。",
|
|
|
|
|
+ bilibili_keyword="感知机 直观解释",
|
|
|
|
|
+ symbol_decoder=generate_symbol_decoder(topic),
|
|
|
|
|
+ math_derivation=generate_math_derivation(topic),
|
|
|
|
|
+ optimization_bottleneck=r"全量梯度下降需要遍历所有样本,复杂度 $O(N \cdot d)$。现代框架使用 mini-batch SGD 加速。",
|
|
|
|
|
+ oj_mission="实现感知机的 forward 和 update 函数,在 XOR 数据集上验证无法收敛(线性不可分)。",
|
|
|
|
|
+ katex_css="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css",
|
|
|
|
|
+ katex_js="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ return html_content
|
|
|
|
|
+
|
|
|
|
|
+def generate_exercise(day: int, topic: str) -> str:
|
|
|
|
|
+ """生成练习题"""
|
|
|
|
|
+ return f'''"""
|
|
|
|
|
+Day {day} - {topic} 练习
|
|
|
|
|
+
|
|
|
|
|
+任务:实现 {topic} 的核心算法
|
|
|
|
|
+"""
|
|
|
|
|
+
|
|
|
|
|
+import numpy as np
|
|
|
|
|
+import matplotlib.pyplot as plt
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+class {topic.replace(" ", "_").lower()}:
|
|
|
|
|
+ """{topic} 类"""
|
|
|
|
|
+
|
|
|
|
|
+ def __init__(self, learning_rate: float = 0.01):
|
|
|
|
|
+ self.learning_rate = learning_rate
|
|
|
|
|
+ self.weights = None
|
|
|
|
|
+ self.bias = None
|
|
|
|
|
+
|
|
|
|
|
+ def forward(self, X: np.ndarray) -> np.ndarray:
|
|
|
|
|
+ """前向传播
|
|
|
|
|
+
|
|
|
|
|
+ Args:
|
|
|
|
|
+ X: 输入数据,shape: [n_samples, n_features]
|
|
|
|
|
+
|
|
|
|
|
+ Returns:
|
|
|
|
|
+ 预测结果,shape: [n_samples]
|
|
|
|
|
+ """
|
|
|
|
|
+ # TODO: 实现 f(x) = sign(w · x + b)
|
|
|
|
|
+ raise NotImplementedError
|
|
|
|
|
+
|
|
|
|
|
+ def compute_loss(self, X: np.ndarray, y: np.ndarray) -> float:
|
|
|
|
|
+ """计算损失"""
|
|
|
|
|
+ # TODO: 实现损失函数
|
|
|
|
|
+ raise NotImplementedError
|
|
|
|
|
+
|
|
|
|
|
+ def update(self, X_i: np.ndarray, y_i: int):
|
|
|
|
|
+ """更新参数
|
|
|
|
|
+
|
|
|
|
|
+ Args:
|
|
|
|
|
+ X_i: 单个样本,shape: [n_features]
|
|
|
|
|
+ y_i: 标签,值域 {-1, +1}
|
|
|
|
|
+ """
|
|
|
|
|
+ # TODO: 实现梯度下降更新
|
|
|
|
|
+ raise NotImplementedError
|
|
|
|
|
+
|
|
|
|
|
+ def fit(self, X: np.ndarray, y: np.ndarray, max_iter: int = 100):
|
|
|
|
|
+ """训练模型"""
|
|
|
|
|
+ # TODO: 实现训练循环
|
|
|
|
|
+ raise NotImplementedError
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def plot_concept():
|
|
|
|
|
+ """可视化概念"""
|
|
|
|
|
+ # 生成二维数据
|
|
|
|
|
+ np.random.seed(42)
|
|
|
|
|
+ X = np.random.randn(100, 2)
|
|
|
|
|
+ y = np.sign(X[:, 0] + X[:, 1] - 0.5) * 1
|
|
|
|
|
+
|
|
|
|
|
+ # 绘制散点图
|
|
|
|
|
+ plt.figure(figsize=(8, 6))
|
|
|
|
|
+ scatter = plt.scatter(X[:, 0], X[:, 1], c=y, cmap="bwr", s=100, edgecolors="black")
|
|
|
|
|
+ plt.xlabel("x1")
|
|
|
|
|
+ plt.ylabel("x2")
|
|
|
|
|
+ plt.title("Day {day} - {topic} 可视化")
|
|
|
|
|
+ plt.colorbar(scatter)
|
|
|
|
|
+ plt.grid(True, alpha=0.3)
|
|
|
|
|
+ plt.savefig("./plots/day{day}_concept.png", dpi=150)
|
|
|
|
|
+ print(f"✅ 可视化已保存:plots/day{day}_concept.png")
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+if __name__ == "__main__":
|
|
|
|
|
+ plot_concept()
|
|
|
|
|
+'''
|
|
|
|
|
+
|
|
|
|
|
+def generate_test(day: int, topic: str) -> str:
|
|
|
|
|
+ """生成测试用例"""
|
|
|
|
|
+ return f'''"""
|
|
|
|
|
+Day {day} - {topic} 测试用例
|
|
|
|
|
+"""
|
|
|
|
|
+
|
|
|
|
|
+import numpy as np
|
|
|
|
|
+import sys
|
|
|
|
|
+sys.path.append("../exercises")
|
|
|
|
|
+
|
|
|
|
|
+# TODO: 导入对应的类
|
|
|
|
|
+# from day{day}_task import *
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def test_forward_shape():
|
|
|
|
|
+ """测试前向传播输出形状"""
|
|
|
|
|
+ # TODO: 实现测试
|
|
|
|
|
+ assert True
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def test_loss_computation():
|
|
|
|
|
+ """测试损失计算"""
|
|
|
|
|
+ # TODO: 实现测试
|
|
|
|
|
+ assert True
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def test_update_rule():
|
|
|
|
|
+ """测试参数更新规则"""
|
|
|
|
|
+ # TODO: 实现测试
|
|
|
|
|
+ assert True
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def test_convergence():
|
|
|
|
|
+ """测试收敛性"""
|
|
|
|
|
+ # TODO: 实现测试
|
|
|
|
|
+ assert True
|
|
|
|
|
+'''
|
|
|
|
|
+
|
|
|
|
|
+def main():
|
|
|
|
|
+ parser = argparse.ArgumentParser(description="编译单日课程")
|
|
|
|
|
+ parser.add_argument("--day", type=int, required=True, help="天数 (1-7)")
|
|
|
|
|
+ parser.add_argument("--topic", type=str, required=True, help="主题名称")
|
|
|
|
|
+ args = parser.parse_args()
|
|
|
|
|
+
|
|
|
|
|
+ config = load_config()
|
|
|
|
|
+
|
|
|
|
|
+ # 创建 staging 目录
|
|
|
|
|
+ staging_dir = PROJECT_ROOT / config["output"]["staging"]
|
|
|
|
|
+ staging_dir.mkdir(exist_ok=True)
|
|
|
|
|
+
|
|
|
|
|
+ exercises_dir = staging_dir / "exercises"
|
|
|
|
|
+ tests_dir = staging_dir / "tests"
|
|
|
|
|
+ exercises_dir.mkdir(exist_ok=True)
|
|
|
|
|
+ tests_dir.mkdir(exist_ok=True)
|
|
|
|
|
+
|
|
|
|
|
+ # 生成 HTML
|
|
|
|
|
+ html_content = generate_html(args.day, args.topic, config)
|
|
|
|
|
+ html_path = staging_dir / f"course_day{args.day}.html"
|
|
|
|
|
+ with open(html_path, "w", encoding="utf-8") as f:
|
|
|
|
|
+ f.write(html_content)
|
|
|
|
|
+ print(f"✅ 生成课程 HTML: {html_path}")
|
|
|
|
|
+
|
|
|
|
|
+ # 生成练习题
|
|
|
|
|
+ exercise_content = generate_exercise(args.day, args.topic)
|
|
|
|
|
+ exercise_path = exercises_dir / f"day{args.day}_task.py"
|
|
|
|
|
+ with open(exercise_path, "w", encoding="utf-8") as f:
|
|
|
|
|
+ f.write(exercise_content)
|
|
|
|
|
+ print(f"✅ 生成练习题:{exercise_path}")
|
|
|
|
|
+
|
|
|
|
|
+ # 生成测试
|
|
|
|
|
+ test_content = generate_test(args.day, args.topic)
|
|
|
|
|
+ test_path = tests_dir / f"test_day{args.day}.py"
|
|
|
|
|
+ with open(test_path, "w", encoding="utf-8") as f:
|
|
|
|
|
+ f.write(test_content)
|
|
|
|
|
+ print(f"✅ 生成测试用例:{test_path}")
|
|
|
|
|
+
|
|
|
|
|
+ print(f"\n🎉 Day {args.day} 编译完成!文件已保存到 staging/")
|
|
|
|
|
+
|
|
|
|
|
+if __name__ == "__main__":
|
|
|
|
|
+ main()
|