#!/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"""
$w$ → self.weights (权重向量,shape: [d])
$b$ → self.bias (偏置标量,shape: [])
$x$ → input_tensor (输入样本,shape: [d])
$y$ → label (标签,值域:{-1, +1})
$\sign(\cdot)$ → np.sign() (符号函数)
$\xi$ → margin (间隔,用于更新步长)
"""
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()